audacia/src/toolbars/ToolDock.cpp

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 &rect;
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();
}