950 lines
26 KiB
C++
950 lines
26 KiB
C++
/**********************************************************************
|
|
|
|
Audacity: A Digital Audio Editor
|
|
|
|
ToolDock.cpp
|
|
|
|
Dominic Mazzoni
|
|
Shane T. Mueller
|
|
Leland Lucius
|
|
|
|
*******************************************************************//**
|
|
|
|
\file ToolDock.cpp
|
|
|
|
Implements ToolDock
|
|
|
|
*//*******************************************************************//**
|
|
|
|
\class ToolDock
|
|
\brief A dynamic panel where a ToolBar can be docked.
|
|
|
|
*//**********************************************************************/
|
|
|
|
|
|
#include "ToolDock.h"
|
|
|
|
#include <wx/tokenzr.h>
|
|
|
|
// For compilers that support precompilation, includes "wx/wx.h".
|
|
#include <wx/wxprec.h>
|
|
|
|
#ifndef WX_PRECOMP
|
|
#include <wx/dcclient.h>
|
|
#include <wx/defs.h>
|
|
#include <wx/event.h>
|
|
#include <wx/gdicmn.h>
|
|
#include <wx/intl.h>
|
|
#include <wx/panel.h>
|
|
#include <wx/settings.h>
|
|
#include <wx/window.h>
|
|
#endif /* */
|
|
|
|
#include <algorithm>
|
|
|
|
#include "../AColor.h"
|
|
#include "../AllThemeResources.h"
|
|
#include "../ImageManipulation.h"
|
|
#include "../Prefs.h"
|
|
#include "../widgets/Grabber.h"
|
|
|
|
const ToolBarConfiguration::Position
|
|
ToolBarConfiguration::UnspecifiedPosition { false };
|
|
|
|
auto ToolBarConfiguration::FindPlace(const ToolBar *bar) const
|
|
-> Iterator
|
|
{
|
|
auto This = const_cast<ToolBarConfiguration*>(this);
|
|
return std::find_if(This->begin(), This->end(),
|
|
[=](const Place &place){
|
|
return place.pTree->pBar == bar;
|
|
});
|
|
}
|
|
|
|
auto ToolBarConfiguration::FindPeers(const ToolBar *bar)
|
|
-> std::pair<Forest*, Forest::iterator>
|
|
{
|
|
auto findTree = [=](Forest &forest){
|
|
return std::find_if(forest.begin(), forest.end(),
|
|
[=](const Tree &tree){ return tree.pBar == bar; });
|
|
};
|
|
|
|
auto iter1 = findTree(mForest);
|
|
if (iter1 != mForest.end())
|
|
return { &mForest, iter1 };
|
|
|
|
Forest::iterator result;
|
|
auto iter = std::find_if(begin(), end(),
|
|
[&](const Place &place){
|
|
auto &children = place.pTree->children;
|
|
return (result = findTree(children)) != children.end();
|
|
}
|
|
);
|
|
if (iter != end())
|
|
return { &iter->pTree->children, result };
|
|
|
|
return { nullptr, Forest::iterator{} };
|
|
}
|
|
|
|
auto ToolBarConfiguration::Find(const ToolBar *bar) const -> Position
|
|
{
|
|
auto iter = FindPlace(bar);
|
|
if (iter == end())
|
|
return UnspecifiedPosition;
|
|
else
|
|
return iter->position;
|
|
}
|
|
|
|
void ToolBarConfiguration::Insert(ToolBar *bar, Position position)
|
|
{
|
|
if (position == UnspecifiedPosition) {
|
|
// Add at the "end" of the layout
|
|
// bottommost and rightmost
|
|
Forest *pForest = &mForest;
|
|
while (!pForest->empty())
|
|
pForest = &pForest->back().children;
|
|
pForest->push_back( Tree {} );
|
|
pForest->back().pBar = bar;
|
|
}
|
|
else {
|
|
// Insert at what depth?
|
|
auto pForest = &mForest;
|
|
if (position.rightOf) {
|
|
const auto parent = FindPlace(position.rightOf);
|
|
if (parent != end())
|
|
// Insert among children of some node
|
|
pForest = &parent->pTree->children;
|
|
}
|
|
else {
|
|
// Insert a new root in the top forest
|
|
}
|
|
|
|
// Insert at what breadth?
|
|
const auto begin = pForest->begin();
|
|
auto iter = begin;
|
|
const auto end = pForest->end();
|
|
bool adopt = false;
|
|
|
|
if (position.below) {
|
|
iter = std::find_if(begin, end,
|
|
[=](const Tree &tree){ return tree.pBar == position.below; }
|
|
);
|
|
if (iter != end) {
|
|
++iter;
|
|
if (iter != end)
|
|
adopt = true;
|
|
}
|
|
else
|
|
// Not found, default to topmost
|
|
iter = begin;
|
|
}
|
|
else
|
|
// No previous sibling specified, so insert as first
|
|
adopt = (iter != end);
|
|
|
|
// Insert as a leaf, or as an internal node?
|
|
if (adopt && position.adopt) {
|
|
// Existing children of parent become grandchildren
|
|
|
|
// Make NEW node
|
|
Tree tree;
|
|
tree.pBar = bar;
|
|
|
|
// Do adoption
|
|
const auto barHeight = bar->GetSize().GetY() + toolbarGap;
|
|
auto totalHeight = 0;
|
|
while (iter != pForest->end() &&
|
|
barHeight >=
|
|
(totalHeight += (iter->pBar->GetSize().GetY() + toolbarGap))) {
|
|
tree.children.push_back(Tree{});
|
|
auto &child = tree.children.back();
|
|
child.pBar = iter->pBar;
|
|
child.children.swap(iter->children);
|
|
iter = pForest->erase(iter);
|
|
}
|
|
|
|
// Put the node in the tree
|
|
iter = pForest->insert(iter, Tree{});
|
|
(*iter).swap(tree);
|
|
}
|
|
else
|
|
// Insert as a leaf
|
|
pForest->insert(iter, Tree {})->pBar = bar;
|
|
}
|
|
}
|
|
|
|
void ToolBarConfiguration::InsertAtPath
|
|
(ToolBar *bar, const std::vector<int> &path)
|
|
{
|
|
auto pForest = &mForest;
|
|
Tree *pTree {};
|
|
|
|
// Guarantee the existence of nodes
|
|
for (auto ii : path) {
|
|
Forest::size_type uu = std::max(0, ii);
|
|
// This may make more than one default-constructed tree, which we
|
|
// will fill in with some other call to InsertAtPath, or else cleanup
|
|
// with RemoveNulls
|
|
pForest->resize(std::max(uu + 1, pForest->size()));
|
|
pTree = &(*pForest)[uu];
|
|
pForest = &pTree->children;
|
|
}
|
|
|
|
if (pTree)
|
|
pTree->pBar = bar;
|
|
}
|
|
|
|
void ToolBarConfiguration::Remove(Forest &forest, Forest::iterator iter)
|
|
{
|
|
// Reparent all of the children of the deleted node
|
|
Tree tree;
|
|
tree.swap(*iter);
|
|
iter = forest.erase(iter);
|
|
auto &children = tree.children;
|
|
auto cIter = children.rbegin(), cEnd = children.rend();
|
|
while (cIter != cEnd) {
|
|
iter = forest.insert(iter, Tree{});
|
|
(*iter).swap(*cIter);
|
|
++cIter;
|
|
}
|
|
}
|
|
|
|
void ToolBarConfiguration::Remove(const ToolBar *bar)
|
|
{
|
|
auto results = FindPeers(bar);
|
|
auto pForest = results.first;
|
|
if (pForest) {
|
|
// Reparent all of the children of the deleted node
|
|
auto iter = results.second;
|
|
wxASSERT(iter->pBar == bar);
|
|
Remove(*pForest, iter);
|
|
}
|
|
}
|
|
|
|
void ToolBarConfiguration::Show(ToolBar *bar)
|
|
{
|
|
// Do not assume the bar is absent, though in practice that is always so
|
|
if (!Contains(bar))
|
|
Insert(bar);
|
|
}
|
|
|
|
void ToolBarConfiguration::Hide(ToolBar *bar)
|
|
{
|
|
// Future: might hide a bar without eliminating it from the configuration
|
|
Remove(bar);
|
|
}
|
|
|
|
bool ToolBarConfiguration::IsRightmost(const ToolBar *bar) const
|
|
{
|
|
auto iter = FindPlace(bar);
|
|
auto endit = end();
|
|
if (iter == endit)
|
|
// not present
|
|
return true;
|
|
if (++iter == endit)
|
|
// Last of all
|
|
return true;
|
|
if (bar->GetRect().y != iter->pTree->pBar->GetRect().y)
|
|
// Next step in preorder traversal is not rightward to a child drawn at
|
|
// the same height
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool ToolBarConfiguration::Read
|
|
(ToolBarConfiguration *pConfiguration,
|
|
Legacy *pLegacy,
|
|
ToolBar *bar, bool &visible, bool defaultVisible)
|
|
{
|
|
bool result = true;
|
|
|
|
// Future: might remember visibility in the configuration, not forgetting
|
|
// positions of hidden bars.
|
|
gPrefs->Read( wxT("Show"), &visible, defaultVisible);
|
|
|
|
if (pConfiguration && visible) {
|
|
int ord;
|
|
gPrefs->Read( wxT("Order"), &ord, -1 );
|
|
// Index was written 1-based
|
|
--ord;
|
|
if (ord >= ToolBarCount)
|
|
result = false;
|
|
else if (ord >= 0)
|
|
{
|
|
// Legacy preferences
|
|
while (pLegacy->bars.size() <= size_t(ord))
|
|
pLegacy->bars.push_back(nullptr);
|
|
pLegacy->bars[ord] = bar;
|
|
}
|
|
else {
|
|
wxString strPath;
|
|
gPrefs->Read( wxT("Path"), &strPath );
|
|
if (!strPath.empty()) {
|
|
wxStringTokenizer toker { strPath, wxT(",") };
|
|
std::vector<int> path;
|
|
while(toker.HasMoreTokens()) {
|
|
auto token = toker.GetNextToken();
|
|
auto ii = wxAtoi(token);
|
|
path.push_back(ii);
|
|
}
|
|
pConfiguration->InsertAtPath(bar, path);
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void ToolBarConfiguration::RemoveNulls(Forest &forest)
|
|
{
|
|
for (size_t ii = 0; ii < forest.size(); ++ii) {
|
|
if(forest[ii].pBar == nullptr)
|
|
Remove(forest, forest.begin() + ii--);
|
|
}
|
|
|
|
// Now do the same recursively
|
|
for (auto &tree : forest)
|
|
RemoveNulls(tree.children);
|
|
}
|
|
|
|
void ToolBarConfiguration::PostRead(Legacy &legacy)
|
|
{
|
|
// Be sure no nodes contain NULL,
|
|
// against the case of obsolete preferences, perhaps
|
|
RemoveNulls(mForest);
|
|
|
|
// Interpret what was saved in old .cfg files under "order"
|
|
// which specified toolbar configuration simply as a sequence, not a tree
|
|
ToolBar *prev {};
|
|
for (auto pBar : legacy.bars) {
|
|
if (!pBar)
|
|
continue;
|
|
|
|
Position position{ prev };
|
|
Insert(pBar, position);
|
|
|
|
prev = pBar;
|
|
}
|
|
}
|
|
|
|
void ToolBarConfiguration::Write
|
|
(const ToolBarConfiguration *pConfiguration, const ToolBar *bar)
|
|
{
|
|
// Assume a path has been set in gPrefs suitable for bar
|
|
if (pConfiguration) {
|
|
// Write comma-separated list of numbers specifying position in the tree
|
|
wxString strPath;
|
|
const auto cIter = pConfiguration->FindPlace(bar);
|
|
const auto path = cIter.GetPath();
|
|
if (!path.empty()) {
|
|
auto iter = path.begin(), end = path.end();
|
|
strPath += wxString::Format(wxT("%d"), *iter++);
|
|
while (iter != end)
|
|
strPath += wxString::Format(wxT(",%d"), *iter++);
|
|
}
|
|
gPrefs->Write(wxT("Path"), strPath);
|
|
|
|
// Remove any legacy configuration info.
|
|
// Note: this causes Audacity 2.1.2 and earlier to create toolbars
|
|
// always in default position when reading a .cfg saved by Audacity
|
|
// 2.1.3 or later
|
|
gPrefs->DeleteEntry(wxT("Order"));
|
|
}
|
|
gPrefs->Write( wxT("Show"), bar->IsVisible() );
|
|
}
|
|
|
|
IMPLEMENT_CLASS( ToolDock, wxPanelWrapper );
|
|
|
|
////////////////////////////////////////////////////////////
|
|
/// Methods for ToolDock
|
|
////////////////////////////////////////////////////////////
|
|
|
|
//
|
|
// Custom event
|
|
//
|
|
//DEFINE_EVENT_TYPE( EVT_TOOLBAR_FLOAT );
|
|
|
|
BEGIN_EVENT_TABLE( ToolDock, wxPanelWrapper )
|
|
EVT_GRABBER( wxID_ANY, ToolDock::OnGrabber )
|
|
EVT_ERASE_BACKGROUND( ToolDock::OnErase )
|
|
EVT_PAINT( ToolDock::OnPaint )
|
|
EVT_SIZE( ToolDock::OnSize )
|
|
EVT_MOUSE_EVENTS( ToolDock::OnMouseEvents )
|
|
END_EVENT_TABLE()
|
|
|
|
//
|
|
// Constructor
|
|
//
|
|
ToolDock::ToolDock( wxEvtHandler *manager, wxWindow *parent, int dockid ):
|
|
wxPanelWrapper( parent, dockid, wxDefaultPosition, parent->GetSize() )
|
|
{
|
|
SetLabel( XO( "ToolDock" ) );
|
|
SetName( XO( "ToolDock" ) );
|
|
|
|
// Init
|
|
mManager = manager;
|
|
memset(mBars, 0, sizeof(mBars)); // otherwise uninitialized
|
|
SetBackgroundColour(theTheme.Colour( clrMedium ));
|
|
SetLayoutDirection(wxLayout_LeftToRight);
|
|
// Use for testing gaps
|
|
// SetOwnBackgroundColour( wxColour( 255, 0, 0 ) );
|
|
}
|
|
|
|
//
|
|
// Destructor
|
|
//
|
|
ToolDock::~ToolDock()
|
|
{
|
|
}
|
|
|
|
//
|
|
// Remove the toolbar from our control
|
|
//
|
|
void ToolDock::Undock( ToolBar *bar )
|
|
{
|
|
if( mConfiguration.Contains( bar ) )
|
|
{
|
|
mConfiguration.Remove( bar );
|
|
}
|
|
mBars[ bar->GetId() ] = nullptr;
|
|
}
|
|
|
|
//
|
|
// Handle ToolDock events
|
|
//
|
|
void ToolDock::Dock( ToolBar *bar, bool deflate, ToolBarConfiguration::Position position )
|
|
{
|
|
#ifndef __WXMAC__
|
|
// Apply the deflate fix only on Mac, else you introduce the opposite bug on others
|
|
deflate = false;
|
|
#endif
|
|
|
|
// Adopt the toolbar into our family
|
|
bar->Reparent( this );
|
|
mBars[ bar->GetId() ] = bar;
|
|
|
|
// Reset size
|
|
bar->SetSize(
|
|
// Undo the expansion that was applied when un-docking
|
|
bar->GetSize().x - (deflate ? 2 * ToolBarFloatMargin : 0),
|
|
// Don't need to adjust y the same way.
|
|
bar->GetDockedSize().y
|
|
);
|
|
|
|
// Park the NEW bar in the correct berth
|
|
if (!mConfiguration.Contains(bar) && bar->IsVisible())
|
|
mConfiguration.Insert( bar, position );
|
|
|
|
// Inform toolbar of change
|
|
bar->SetDocked( this, false );
|
|
}
|
|
|
|
// Initial docking of bars
|
|
void ToolDock::LoadConfig()
|
|
{
|
|
// Add all ordered toolbars
|
|
for(const auto &place : GetConfiguration()) {
|
|
auto bar = place.pTree->pBar;
|
|
this->Dock(bar, false);
|
|
// Show it -- hidden bars are not (yet) ever saved as part of a
|
|
// configuration
|
|
Expose( bar->GetId(), true );
|
|
}
|
|
Updated();
|
|
}
|
|
|
|
// A policy object for the skeleton routine below
|
|
class ToolDock::LayoutVisitor
|
|
{
|
|
public:
|
|
virtual void ModifySize
|
|
(ToolBar *,
|
|
const wxRect &,
|
|
ToolBarConfiguration::Position,
|
|
ToolBarConfiguration::Position,
|
|
wxSize &)
|
|
{
|
|
}
|
|
|
|
virtual void Visit
|
|
(ToolBar *ct, wxPoint point) = 0;
|
|
|
|
virtual bool ShouldVisitSpaces() = 0;
|
|
|
|
virtual void FinalRect
|
|
(const wxRect &, ToolBarConfiguration::Position)
|
|
{
|
|
}
|
|
};
|
|
|
|
// Skeleton routine common to insertion of a toolbar, and figuring out
|
|
// width-constrained layout of toolbars
|
|
void ToolDock::VisitLayout(LayoutVisitor &visitor,
|
|
ToolBarConfiguration *pWrappedConfiguration)
|
|
{
|
|
if (pWrappedConfiguration)
|
|
pWrappedConfiguration->Clear();
|
|
|
|
// Get size of our parent since we haven't been sized yet
|
|
int width, height;
|
|
GetParent()->GetClientSize( &width, &height );
|
|
width -= toolbarGap;
|
|
height -= toolbarGap;
|
|
|
|
// Rectangle of space to allocate
|
|
wxRect main{ toolbarGap, toolbarGap,
|
|
// Allow limited width, but arbitrary height, for the root rectangle
|
|
width, std::numeric_limits<int>::max() };
|
|
|
|
// For recording the nested subdivisions of the rectangle
|
|
struct Item {
|
|
int myBarID { NoBarID };
|
|
int parentBarID { NoBarID };
|
|
ToolBar *lastSib {};
|
|
ToolBar *lastWrappedChild {};
|
|
wxRect rect;
|
|
} layout[ ToolBarCount ];
|
|
|
|
ToolBar *lastRoot {};
|
|
ToolBar *lastWrappedRoot {};
|
|
|
|
// Process all docked and visible toolbars
|
|
for ( const auto &place : this->GetConfiguration() )
|
|
{
|
|
// Cache toolbar pointer
|
|
const auto ct = place.pTree->pBar;
|
|
|
|
// set up the chain of ancestors.
|
|
const auto parent = place.position.rightOf;
|
|
const auto type = ct->GetType();
|
|
auto &newItem = layout[ type ];
|
|
newItem.parentBarID = parent ? parent->GetType() : NoBarID;
|
|
// Mark the slots that really were visited, for final pass through
|
|
// the spaces.
|
|
newItem.myBarID = type;
|
|
|
|
const auto parentItem = parent ? &layout[ parent->GetType() ] : nullptr;
|
|
ToolBar *prevSib;
|
|
if (!parent) {
|
|
prevSib = lastRoot;
|
|
lastRoot = ct;
|
|
}
|
|
else {
|
|
auto &sib = parentItem->lastSib;
|
|
prevSib = sib;
|
|
sib = ct;
|
|
}
|
|
auto prevPosition = ToolBarConfiguration::Position{ parent, prevSib };
|
|
|
|
// Determine the size of the toolbar to fit, with advice from
|
|
// the visitor object
|
|
wxSize sz = ct->GetSize();
|
|
{
|
|
wxRect temp;
|
|
temp.SetPosition(ct->GetParent()->ClientToScreen(ct->GetPosition()));
|
|
temp.SetSize(sz);
|
|
visitor.ModifySize(ct, temp, prevPosition, place.position, sz);
|
|
}
|
|
|
|
// Inflate the size to leave margins
|
|
int tw = sz.GetWidth() + toolbarGap;
|
|
int th = sz.GetHeight() + toolbarGap;
|
|
|
|
// Choose the rectangle to subdivide
|
|
// Find a box that we fit in by going up the tree as needed --
|
|
// thus when parent space is exhausted, fall back on ancestors --
|
|
// so if the tree has too much depth for the width of the
|
|
// window, the toolbars may "wrap."
|
|
// Can always fall back to the main rectangle even if the bar is too
|
|
// wide.
|
|
auto pItem = parentItem;
|
|
auto pRect = pItem ? &pItem->rect : &main;
|
|
while (pRect != &main)
|
|
{
|
|
// Get out if it will fit
|
|
bool bTooWide = tw > pRect->GetWidth();
|
|
// We'd like to be able to add a tall toolbar in at the start of a row,
|
|
// even if there isn't enough height for it.
|
|
// If so, we'd have to at least change how we calculate 'bTooHigh'.
|
|
bool bTooHigh = th > pRect->GetHeight();
|
|
//bTooHigh &= stack[stkcnt].GetWidth() < (width - toolbarGap);
|
|
//bTooHigh = false;
|
|
|
|
if (!bTooWide && !bTooHigh)
|
|
break;
|
|
|
|
if (pItem->parentBarID == NoBarID) {
|
|
pItem = nullptr;
|
|
pRect = &main;
|
|
}
|
|
else {
|
|
pItem = &layout[ pItem->parentBarID ];
|
|
pRect = &pItem->rect;
|
|
}
|
|
}
|
|
|
|
// Record where the toolbar wrapped
|
|
ToolBar *& sib = pItem ? pItem->lastWrappedChild : lastWrappedRoot;
|
|
ToolBarConfiguration::Position newPosition {
|
|
pItem ? this->mBars[ pItem->myBarID ] : nullptr,
|
|
sib
|
|
};
|
|
sib = ct;
|
|
if (pWrappedConfiguration)
|
|
pWrappedConfiguration->Insert(ct, newPosition);
|
|
|
|
// Place the toolbar at the upper left part of the rectangle.
|
|
const auto cpos = pRect->GetPosition();
|
|
visitor.Visit(ct, cpos);
|
|
|
|
// Allocate an upper portion of the rectangle to this bar.
|
|
pRect->y += th;
|
|
pRect->height -= th;
|
|
|
|
// A right portion of that upper portion remains available for
|
|
// descendant bars and is remembered in the layout array.
|
|
int x = cpos.x + tw;
|
|
newItem.rect = wxRect{ x, cpos.y, width - x, th };
|
|
}
|
|
|
|
if (visitor.ShouldVisitSpaces()) {
|
|
// Visit the fringe where NEW leaves of the tree could go
|
|
|
|
// Find the items with leftover spaces
|
|
const auto end = std::remove_if(layout, layout + ToolBarCount,
|
|
[](const Item &item){
|
|
return item.myBarID == NoBarID || item.rect.IsEmpty();
|
|
}
|
|
);
|
|
// Sort top to bottom for definiteness, though perhaps not really needed
|
|
std::sort(layout, end,
|
|
[](const Item &lhs, const Item &rhs){
|
|
return lhs.rect.y < rhs.rect.y;
|
|
}
|
|
);
|
|
for (auto iter = layout; iter != end; ++iter) {
|
|
const auto &item = *iter;
|
|
const auto &rect = item.rect;
|
|
|
|
auto globalRect = rect;
|
|
globalRect.SetPosition( this->ClientToScreen(rect.GetPosition()) );
|
|
|
|
// Let the visitor determine size
|
|
wxSize sz {};
|
|
ToolBarConfiguration::Position
|
|
position { this->mBars[ item.myBarID ], item.lastWrappedChild },
|
|
prevPosition {};
|
|
visitor.ModifySize(nullptr, globalRect, prevPosition, position, sz);
|
|
int tw = sz.GetWidth() + toolbarGap;
|
|
int th = sz.GetHeight() + toolbarGap;
|
|
|
|
// Test fit
|
|
bool bTooWide = tw > rect.GetWidth();
|
|
bool bTooHigh = th > rect.GetHeight();
|
|
if (!bTooWide && !bTooHigh) {
|
|
// Call visitor again to confirm the placement
|
|
const auto cpos = rect.GetPosition();
|
|
visitor.Visit(nullptr, cpos);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Report the final bounding box of all the bars, and a position where
|
|
// you can insert a NEW bar at bottom left.
|
|
ToolBarConfiguration::Position finalPosition { nullptr, lastRoot };
|
|
visitor.FinalRect(
|
|
wxRect { toolbarGap, toolbarGap, main.width, main.y }, finalPosition
|
|
);
|
|
}
|
|
|
|
//
|
|
// Layout the toolbars
|
|
//
|
|
void ToolDock::LayoutToolBars()
|
|
{
|
|
struct SizeSetter final : public LayoutVisitor
|
|
{
|
|
SizeSetter (ToolDock *d) : dock{ d } {}
|
|
|
|
void Visit
|
|
(ToolBar *bar, wxPoint point)
|
|
override
|
|
{
|
|
// Place the toolbar
|
|
if(bar)
|
|
bar->SetPosition( point );
|
|
}
|
|
|
|
bool ShouldVisitSpaces() override
|
|
{
|
|
return false;
|
|
}
|
|
|
|
virtual void FinalRect
|
|
(const wxRect &rect, ToolBarConfiguration::Position)
|
|
override
|
|
{
|
|
// Set the final size of the dock window
|
|
dock->SetMinSize( rect.GetSize() );
|
|
}
|
|
|
|
ToolDock *dock;
|
|
} sizeSetter {
|
|
this
|
|
};
|
|
VisitLayout(sizeSetter, &mWrappedConfiguration);
|
|
|
|
// Set tab order and layout internal controls.
|
|
{
|
|
ToolBar *lt{};
|
|
for ( const auto &place : GetConfiguration() ) {
|
|
auto ct = place.pTree->pBar;
|
|
if( lt ){
|
|
ct->MoveAfterInTabOrder( lt );
|
|
}
|
|
lt = ct;
|
|
// Bug 1371.
|
|
// After a dock size change, the toolbars may need relaying inside.
|
|
lt->Layout();
|
|
}
|
|
}
|
|
|
|
// Clean things up
|
|
Refresh( false );
|
|
}
|
|
|
|
// Determine the position where a NEW bar would be placed
|
|
//
|
|
// 'rect' will be the rectangle for the dock marker (black triangle)
|
|
ToolBarConfiguration::Position
|
|
ToolDock::PositionBar( ToolBar *t, const wxPoint & pos, wxRect & rect )
|
|
{
|
|
// Set width and size, but we must still find x and y.
|
|
rect = t->GetRect();
|
|
|
|
using Position = ToolBarConfiguration::Position;
|
|
Position result { ToolBarConfiguration::UnspecifiedPosition };
|
|
struct Inserter : public LayoutVisitor
|
|
{
|
|
struct Stop {};
|
|
|
|
Inserter(Position &p, wxRect &r, const wxPoint &pt, ToolBar *t)
|
|
: result(p), rect(r), point(pt), tb(t)
|
|
{}
|
|
|
|
void ModifySize
|
|
(ToolBar *ct,
|
|
const wxRect &rectIn,
|
|
ToolBarConfiguration::Position prevPosition,
|
|
ToolBarConfiguration::Position position,
|
|
wxSize &sz)
|
|
override
|
|
{
|
|
// Maybe insert the NEW bar if it hasn't already been done
|
|
// and is in the right place.
|
|
|
|
// Does the location fall within this bar?
|
|
if (rectIn.Contains(point))
|
|
{
|
|
sz = tb->GetDockedSize();
|
|
// Choose a position always, if there is a bar to displace.
|
|
// Else, only if the fit is possible.
|
|
if (ct || (sz.x <= rectIn.width && sz.y <= rectIn.height)) {
|
|
// May choose current or previous.
|
|
if (ct &&
|
|
(sz.y < rectIn.height ||
|
|
point.y < (rectIn.GetTop() + rectIn.GetBottom()) / 2))
|
|
// "Wedge" the bar into a crack alone, not adopting others,
|
|
// if either a short bar displaces a tall one, or else
|
|
// the displacing bar is at least at tall, but the pointer is
|
|
// in the upper half of the box.
|
|
usedPrev = true, result = prevPosition, result.adopt = false;
|
|
else
|
|
result = position;
|
|
}
|
|
// Now wait until the other callback below to discover x and y
|
|
}
|
|
}
|
|
|
|
void Visit
|
|
(ToolBar *, wxPoint pointIn)
|
|
override
|
|
{
|
|
if (result != ToolBarConfiguration::UnspecifiedPosition) {
|
|
// If we've placed it, we're done.
|
|
rect.x = pointIn.x;
|
|
rect.y = pointIn.y;
|
|
if (usedPrev)
|
|
rect.y -= tb->GetDockedSize().GetHeight() / 2;
|
|
|
|
throw Stop {};
|
|
}
|
|
}
|
|
|
|
bool ShouldVisitSpaces() override
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void FinalRect
|
|
(const wxRect &finalRect, ToolBarConfiguration::Position finalPosition)
|
|
override
|
|
{
|
|
if (result == ToolBarConfiguration::UnspecifiedPosition) {
|
|
// Default of all other placements.
|
|
result = finalPosition;
|
|
wxPoint point1 { finalRect.GetLeft(), finalRect.GetBottom() };
|
|
rect.SetPosition(point1);
|
|
}
|
|
}
|
|
|
|
|
|
Position &result;
|
|
wxRect ▭
|
|
const wxPoint point;
|
|
ToolBar *const tb;
|
|
bool usedPrev { false };
|
|
} inserter {
|
|
result, rect, pos, t
|
|
};
|
|
|
|
try { this->VisitLayout(inserter); } catch (const Inserter::Stop&) {}
|
|
|
|
// rect is decided
|
|
return result;
|
|
}
|
|
|
|
void ToolDock::WrapConfiguration(ToolBarConfiguration &backup)
|
|
{
|
|
backup.Clear();
|
|
backup.Swap(mConfiguration);
|
|
mConfiguration.Swap(mWrappedConfiguration);
|
|
}
|
|
|
|
void ToolDock::RestoreConfiguration(ToolBarConfiguration &backup)
|
|
{
|
|
mWrappedConfiguration.Clear();
|
|
mWrappedConfiguration.Swap(mConfiguration);
|
|
mConfiguration.Swap(backup);
|
|
}
|
|
|
|
//
|
|
// Set the visible/hidden state of a toolbar
|
|
//
|
|
void ToolDock::Expose( int type, bool show )
|
|
{
|
|
ToolBar *t = mBars[ type ];
|
|
|
|
// Maintain the docked array
|
|
const auto shown = mConfiguration.Shows( t );
|
|
if( show && !shown )
|
|
mConfiguration.Show( t );
|
|
else if( !show && shown )
|
|
mConfiguration.Hide( t );
|
|
|
|
// Make it (dis)appear
|
|
t->Expose( show );
|
|
}
|
|
|
|
//
|
|
// Queues an EVT_TOOLBAR_UPDATED command event to notify any
|
|
// interested parties of an updated toolbar or dock layout
|
|
//
|
|
void ToolDock::Updated()
|
|
{
|
|
// Queue an update event
|
|
wxCommandEvent e( EVT_TOOLBAR_UPDATED, GetId() );
|
|
GetParent()->GetEventHandler()->AddPendingEvent( e );
|
|
}
|
|
|
|
//
|
|
// Handle grabber clicking
|
|
//
|
|
void ToolDock::OnGrabber( GrabberEvent & event )
|
|
{
|
|
// auto pos = event.GetPosition();
|
|
if (!event.IsEscaping()) {
|
|
// Pass it on to the manager since it isn't in the handling hierarchy
|
|
mManager->ProcessEvent( event );
|
|
}
|
|
}
|
|
|
|
//
|
|
// Handle sizing
|
|
//
|
|
void ToolDock::OnSize( wxSizeEvent & WXUNUSED(event) )
|
|
{
|
|
// event.Skip();
|
|
}
|
|
|
|
//
|
|
// Prevent flicker
|
|
//
|
|
void ToolDock::OnErase( wxEraseEvent & WXUNUSED(event) )
|
|
{
|
|
// Ignore it to prevent flashing
|
|
}
|
|
|
|
//
|
|
// Repaint toolbar gap lines
|
|
//
|
|
void ToolDock::OnPaint( wxPaintEvent & WXUNUSED(event) )
|
|
{
|
|
// Don't use a wxBufferedPaintDC() here. It produces a bogus
|
|
// background on Windows and GTK.
|
|
wxPaintDC dc( this );
|
|
|
|
// Start with a clean background
|
|
//
|
|
// Under GTK, we don't set the toolbar background to the background
|
|
// colour in the system theme. Instead we use our own colour.
|
|
|
|
dc.SetBackground( wxBrush( theTheme.Colour( clrMedium )));
|
|
dc.Clear();
|
|
|
|
// Set the gap color
|
|
AColor::Dark( &dc, false );
|
|
|
|
// Draw the initial horizontal and vertical gaps
|
|
wxSize sz = GetClientSize();
|
|
|
|
AColor::Line(dc, 0, 0, sz.GetWidth(), 0 );
|
|
AColor::Line(dc, 0, 0, 0, sz.GetHeight() );
|
|
|
|
// Draw the gap between each bar
|
|
for (const auto &place : GetConfiguration())
|
|
{
|
|
auto toolbar = place.pTree->pBar;
|
|
if (!toolbar)
|
|
continue;
|
|
|
|
wxRect r = toolbar->GetRect();
|
|
|
|
// Draw a horizontal line under the bar extending to the right edge of
|
|
// the dock
|
|
AColor::Line( dc,
|
|
r.GetLeft(),
|
|
r.GetBottom() + 1,
|
|
sz.GetWidth(),
|
|
r.GetBottom() + 1 );
|
|
|
|
// For all bars but the last...
|
|
// ...and for bars that aren't the last in a row, draw a
|
|
// vertical gap line
|
|
if (!mConfiguration.IsRightmost(toolbar)) {
|
|
AColor::Line(dc,
|
|
r.GetRight() + 1,
|
|
r.GetTop(),
|
|
r.GetRight() + 1,
|
|
r.GetBottom() + 1 );
|
|
}
|
|
}
|
|
}
|
|
|
|
void ToolDock::OnMouseEvents(wxMouseEvent &event)
|
|
{
|
|
// Do this hack so scrubber can detect mouse drags anywhere
|
|
event.ResumePropagation(wxEVENT_PROPAGATE_MAX);
|
|
event.Skip();
|
|
}
|