1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-07-13 23:27:43 +02:00
audacity/src/widgets/ExpandingToolBar.cpp
Paul Licameli 5e7d41ec07 Each .cpp/.mm file includes corresponding header before any other...
... except Audacity.h

This forces us to make each header contain all forward declarations or nested
headers that it requires, rather than depend on context.
2019-03-17 22:54:52 -04:00

1285 lines
33 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
ExpandingToolBar.cpp
Dominic Mazzoni
*******************************************************************//**
\class ExpandingToolBar
\brief A smart ToolBar class that has a "MainPanel" which is always
displayed, and an "ExtraPanel" that can be hidden to save space.
Can be docked into a ToolBarArea or floated in an ToolBarFrame;
If auto-expanding is off, behavior is very simple: clicking the
toggle button expands, clicking it again collapses.
If auto-expanding is on, behavior is a little more complicated.
When the mouse movers over a toolbar and it is collapsed, it gets
auto-expanded, and it gets auto-collapsed as soon as the mouse
leaves. However, if they manually toggle it collapsed
while it was auto-expanded, it will stay collapsed until you move
the mouse completely away and then back again later. If you
manually expand it, it will stay manually expanded until you
manually collapse it.
*//****************************************************************//**
\class ExpandingToolBarEvtHandler
\brief A custom event handler for ExpandingToolBar.
*//****************************************************************//**
\class ToolBarGrabber
\brief Draws the grabber for an ExpandingToolBar.
*//****************************************************************//**
\class ToolBarDialog
\brief A dialog based container for ExpandingToolBars providing modal
based operations.
*//****************************************************************//**
\class ToolBarFrame
\brief A miniframe based container for ExpandingToolBars providing
modeless presentation.
*//****************************************************************//**
\class ToolBarArea
\brief An alterantive to ToolBarFrame which can contain an
ExpandingToolBar. ToolBarArea is used for a 'docked' ToolBar,
ToolBarFrame for a floating one.
*//****************************************************************//**
\class ToolBarArrangement
\brief Small class that holds some layout information for an
ExpandingToolBar.
*//*******************************************************************/
#include "ExpandingToolBar.h"
#include "../Experimental.h"
#include "../Theme.h"
// For compilers that support precompilation, includes "wx/wx.h".
#include <wx/wxprec.h>
#ifndef WX_PRECOMP
#include <wx/window.h>
#endif
#include <wx/wx.h>
#include <wx/dcmemory.h>
#include <wx/log.h>
#include <wx/dialog.h>
#include "AButton.h"
#include "../AllThemeResources.h"
const int kToggleButtonHeight = 8;
const int kTimerInterval = 50; // every 50 ms -> ~20 updates per second
const wxRect kDummyRect = wxRect(-9999, -9999, 0, 0);
enum {
kToggleButtonID = 5000,
kTimerID
};
class ToolBarArrangement
{
public:
std::vector<ExpandingToolBar*> childArray;
std::vector<wxRect> rectArray;
std::vector<int> rowArray;
};
//
// ExpandingToolBar
//
BEGIN_EVENT_TABLE(ExpandingToolBar, wxPanelWrapper)
EVT_SIZE(ExpandingToolBar::OnSize)
EVT_TIMER(kTimerID, ExpandingToolBar::OnTimer)
EVT_BUTTON(kToggleButtonID, ExpandingToolBar::OnToggle)
END_EVENT_TABLE()
IMPLEMENT_CLASS(ExpandingToolBar, wxPanelWrapper)
//static
int ExpandingToolBar::msNoAutoExpandStack = 0;
ExpandingToolBar::ExpandingToolBar(wxWindow* parent,
wxWindowID id,
const wxPoint& pos,
const wxSize& size):
wxPanelWrapper(parent, id, pos, size),
mIsAutoExpanded(false),
mIsManualExpanded(false),
mIsExpanded(false),
mAutoExpand(true),
mFirstTime(true),
mFrameParent(NULL),
mDialogParent(NULL),
mAreaParent(NULL),
mSavedArrangement{},
mTopLevelParent(NULL)
{
mMainPanel = safenew wxPanelWrapper(this, -1,
wxDefaultPosition, wxSize(1, 1));
mExtraPanel = safenew wxPanelWrapper(this, -1,
wxDefaultPosition, wxSize(1, 1));
mGrabber = NULL;
ToolBarArea *toolBarParent =
dynamic_cast<ToolBarArea *>(GetParent());
if (toolBarParent)
mGrabber = safenew ToolBarGrabber(this, -1, this);
/// \todo check whether this is a memory leak (and check similar code)
//wxImage hbar = theTheme.Image(bmpToolBarToggle);
//wxColour magicColor = wxColour(0, 255, 255);
//ImageArray fourStates = ImageRoll::SplitV(hbar, magicColor);
/*
mToggleButton = safenew AButton(this, kToggleButtonID,
wxDefaultPosition, wxDefaultSize,
ImageRoll(ImageRoll::HorizontalRoll,
fourStates[0], magicColor),
ImageRoll(ImageRoll::HorizontalRoll,
fourStates[1], magicColor),
ImageRoll(ImageRoll::HorizontalRoll,
fourStates[2], magicColor),
ImageRoll(ImageRoll::HorizontalRoll,
fourStates[3], magicColor),
true);
mToggleButton->UseDisabledAsDownHiliteImage(true);
*/
SetAutoLayout(true);
mTimer.SetOwner(this, kTimerID);
}
ExpandingToolBar::~ExpandingToolBar()
{
}
void ExpandingToolBar::OnSize(wxSizeEvent & WXUNUSED(event))
{
if (mFrameParent || mDialogParent || mAreaParent)
return;
// At the time of construction, it wasn't "safe" to tell
// our parent that we've just joined the window, so we check
// for it during our first OnSize event.
if (!mFrameParent) {
ToolBarFrame *toolBarParent =
dynamic_cast<ToolBarFrame *>(GetParent());
if (toolBarParent) {
// We were placed into a floating window
mFrameParent = toolBarParent;
toolBarParent->SetChild(this);
}
}
if (!mDialogParent) {
ToolBarDialog *toolBarParent =
dynamic_cast<ToolBarDialog *>(GetParent());
if (toolBarParent) {
// We were placed into a dialog
mDialogParent = toolBarParent;
toolBarParent->SetChild(this);
}
}
if (!mAreaParent) {
ToolBarArea *toolBarParent =
dynamic_cast<ToolBarArea *>(GetParent());
if (toolBarParent) {
// We were placed into an area full of other toolbars
mAreaParent = toolBarParent;
toolBarParent->AddChild(this);
}
}
}
void ExpandingToolBar::OnToggle(wxCommandEvent & WXUNUSED(event))
{
if (mIsExpanded)
Collapse();
else
Expand();
}
void ExpandingToolBar::Expand()
{
// We set both mIsManualExpanded and mIsAutoExpanded to true;
// that way if the user manually collapses the toolbar we set
// mIsManualExpanded to false but keep mIsAutoExpanded to true
// to prevent it from being auto-expanded again until the user
// actually moves the mouse completely away and back again later.
mToggleButton->PushDown();
mIsManualExpanded = true;
mIsAutoExpanded = true;
Fit();
}
void ExpandingToolBar::Collapse(bool now /* = false */)
{
// After being manually collapsed, we set mIsAutoExpanded back to
// true, which prevents it from being immediately auto-expanded
// again until after the mouse actually moves away and then
// back again later.
mToggleButton->PopUp();
mIsManualExpanded = false;
mIsAutoExpanded = false;
Fit();
mIsAutoExpanded = true;
if (now) {
mCurrentDrawerSize = mTargetDrawerSize;
MoveDrawer(wxSize(0, 0));
}
}
void ExpandingToolBar::TryAutoExpand()
{
if (mAutoExpand && msNoAutoExpandStack==0 &&
mIsManualExpanded == false && mIsAutoExpanded == false) {
mToggleButton->PushDown();
mIsAutoExpanded = true;
Fit();
}
}
void ExpandingToolBar::TryAutoCollapse()
{
#ifdef EXPERIMENTAL_ROLL_UP_DIALOG
if (mIsAutoExpanded == true && mIsManualExpanded == false) {
mToggleButton->PopUp();
mIsAutoExpanded = false;
Fit();
}
#endif
}
class ExpandingToolBarEvtHandler final : public wxEvtHandler
{
public:
ExpandingToolBarEvtHandler(ExpandingToolBar *toolbar,
wxWindow *window,
wxEvtHandler *inheritedEvtHandler)
{
mToolBar = toolbar;
mWindow = window;
mInheritedEvtHandler = inheritedEvtHandler;
window->PushEventHandler(this);
}
bool ProcessEvent(wxEvent& evt) override
{
// if (mToolBar->IsCursorInWindow())
mToolBar->TryAutoExpand();
// else
// mToolBar->TryAutoExpand();
return mInheritedEvtHandler->ProcessEvent(evt);
}
~ExpandingToolBarEvtHandler()
{
mWindow->RemoveEventHandler(this);
}
protected:
ExpandingToolBar *mToolBar;
wxWindow *mWindow;
wxEvtHandler *mInheritedEvtHandler;
DECLARE_NO_COPY_CLASS(ExpandingToolBarEvtHandler)
};
void ExpandingToolBar::RecursivelyPushEventHandlers(wxWindow *win)
{
if (!mWindowHash[win]) {
mHandlers.push_back(std::make_unique<ExpandingToolBarEvtHandler>
(this, win, win->GetEventHandler()));
mWindowHash[win] = 1;
}
wxWindowList children = win->GetChildren();
for(auto child : children)
RecursivelyPushEventHandlers(child);
}
bool ExpandingToolBar::Layout()
{
mMainSize = mMainPanel->GetBestSize();
mExtraSize = mExtraPanel->GetBestSize();
mButtonSize = wxSize(wxMax(mMainSize.x, mExtraSize.x),
kToggleButtonHeight);
int left = 0;
if (mGrabber) {
mGrabberSize = mGrabber->GetMinSize();
left += mGrabberSize.x;
}
else
mGrabberSize = wxSize(0, 0);
mMainPanel->SetSize(left, 0, mMainSize.x, mMainSize.y);
mToggleButton->SetSize(left, mMainSize.y, mButtonSize.x, mButtonSize.y);
mExtraPanel->SetSize(left, mMainSize.y + mButtonSize.y,
mExtraSize.x, mExtraSize.y);
if (mGrabber)
mGrabber->SetSize(0, 0, left, mMainSize.y + mButtonSize.y);
// Add event handlers to all children
//RecursivelyPushEventHandlers(this);
return true;
}
void ExpandingToolBar::Fit()
{
#ifdef EXPERIMENTAL_ROLL_UP_DIALOG
mIsExpanded = (mIsAutoExpanded || mIsManualExpanded);
#else
mIsExpanded = true;// JKC - Wedge it open at all times.
#endif
int width = mButtonSize.x + mGrabberSize.x;
wxSize baseWindowSize = wxSize(width,
mMainSize.y + mButtonSize.y);
mTargetDrawerSize = wxSize(mButtonSize.x, 0);
if (mIsExpanded)
mTargetDrawerSize.y += mExtraSize.y;
mCurrentDrawerSize.x = mTargetDrawerSize.x;
// The first time, we always update the size. Otherwise, we set
// a target size, and the actual size changes during a timer
// event.
if (mFirstTime) {
mFirstTime = false;
mCurrentDrawerSize = wxSize(mExtraSize.x, 0);
mCurrentTotalSize = baseWindowSize;
SetSizeHints(mCurrentTotalSize, mCurrentTotalSize);
SetSize(mCurrentTotalSize);
}
// wxTimers seem to be a little unreliable - sometimes they stop for
// no good reason, so this "primes" it every now and then...
mTimer.Stop();
mTimer.Start(kTimerInterval);
}
bool ExpandingToolBar::IsCursorInWindow()
{
wxPoint globalMouse = ::wxGetMousePosition();
wxPoint localMouse = ScreenToClient(globalMouse);
bool result = (localMouse.x >= 0 && localMouse.y >= 0 &&
localMouse.x < mCurrentTotalSize.x &&
localMouse.y < mCurrentTotalSize.y);
// The grabber doesn't count!
if (mGrabber && mGrabber->GetRect().Contains(localMouse))
result = false;
return result;
}
void ExpandingToolBar::ReparentExtraPanel()
{
// This is how we make sure the extra panel, which slides out
// like a drawer, appears on top of everything else in the window...
wxPoint pos;
pos.x = mGrabberSize.x;
pos.y = mMainSize.y + mButtonSize.y;
wxWindow *frame = this;
while(!frame->IsTopLevel()) {
pos += frame->GetPosition();
frame = frame->GetParent();
}
mExtraPanel->Reparent(frame);
mExtraPanel->SetPosition(pos);
}
void ExpandingToolBar::MoveDrawer(wxSize prevSize)
{
mCurrentTotalSize = wxSize(mButtonSize.x,
mMainSize.y +
mButtonSize.y +
mCurrentDrawerSize.y);
if (mFrameParent) {
// If we're in a tool window
SetSizeHints(mCurrentTotalSize, mCurrentTotalSize);
SetSize(mCurrentTotalSize);
GetParent()->Fit();
}
if (mDialogParent) {
// If we're in a dialog
SetSizeHints(mCurrentTotalSize, mCurrentTotalSize);
SetSize(mCurrentTotalSize);
GetParent()->Fit();
}
if (mAreaParent) {
// If we're in a tool area
if (mCurrentDrawerSize.y > 0 && prevSize.y == 0) {
ReparentExtraPanel();
mExtraPanel->Show();
}
mExtraPanel->SetSizeHints(mCurrentDrawerSize, mCurrentDrawerSize);
mExtraPanel->SetSize(mCurrentDrawerSize);
if (mCurrentDrawerSize.y == 0)
mExtraPanel->Hide();
}
}
void ExpandingToolBar::OnTimer(wxTimerEvent & WXUNUSED(event))
{
if (mAutoExpand && msNoAutoExpandStack==0 &&
IsCursorInWindow())
TryAutoExpand();
else if (!IsCursorInWindow())
TryAutoCollapse();
if (mCurrentDrawerSize == mTargetDrawerSize)
return;
// This accelerates the current size towards the target size;
// it's a neat way for the window to roll open, but in such a
// way that it
wxSize prevSize = mCurrentDrawerSize;
mCurrentDrawerSize = (mCurrentDrawerSize*2 + mTargetDrawerSize) / 3;
if (abs((mCurrentDrawerSize-mTargetDrawerSize).x)<2 &&
abs((mCurrentDrawerSize-mTargetDrawerSize).y)<2)
mCurrentDrawerSize = mTargetDrawerSize;
MoveDrawer(prevSize);
}
wxBitmap ExpandingToolBar::GetToolbarBitmap()
{
wxSize size = GetClientSize();
wxBitmap bitmap(size.x, size.y);
wxClientDC winDC(this);
wxMemoryDC memDC;
memDC.SelectObject(bitmap);
memDC.Blit(0, 0, size.x, size.y,
&winDC, 0, 0);
return bitmap;
}
void ExpandingToolBar::StartMoving()
{
if (!mAreaParent)
return;
int j;
mAreaParent->CollapseAll(true);
mTimer.Stop();
// This gives time for wx to finish redrawing the window that way.
// HACK: why do we need to do it so many times???
for(j=0; j<500; j++)
::wxSafeYield();
wxBitmap toolbarBitmap = GetToolbarBitmap();
msNoAutoExpandStack++;
mSavedArrangement = mAreaParent->SaveArrangement();
mAreaParent->RemoveChild(this);
mAreaParent->Refresh(true);
mTopLevelParent = this;
while(!mTopLevelParent->IsTopLevel())
mTopLevelParent = mTopLevelParent->GetParent();
wxPoint hotSpot = ScreenToClient(wxGetMousePosition());
hotSpot -= (ClientToScreen(wxPoint(0, 0)) -
mAreaParent->ClientToScreen(wxPoint(0, 0)));
mDropTargets = mAreaParent->GetDropTargets();
mDropTarget = kDummyRect;
wxColour magicColor = wxColour(0, 255, 255);
// wxImage tgtImage = theTheme.Image(bmpToolBarTarget);
// ImageRoll tgtImageRoll = ImageRoll(ImageRoll::VerticalRoll,
// tgtImage,
// magicColor);
mTargetPanel = safenew ImageRollPanel(mAreaParent, -1, //tgtImageRoll,
wxDefaultPosition,
wxDefaultSize,
wxTRANSPARENT_WINDOW);
mTargetPanel->SetLogicalFunction(wxXOR);
mTargetPanel->SetSize(mDropTarget);
// This gives time for wx to finish redrawing the window that way.
// HACK: why do we need to do it several times???
for(j=0; j<500; j++)
::wxSafeYield();
mAreaParent->SetCapturedChild(this);
mDragImage = std::make_unique<wxDragImage>(toolbarBitmap);
mDragImage->BeginDrag(hotSpot, mAreaParent, mTopLevelParent);
mDragImage->Show();
mDragImage->Move(ScreenToClient(wxGetMousePosition()));
}
void ExpandingToolBar::UpdateMoving()
{
if (!mAreaParent || !mSavedArrangement || !mDragImage)
return;
wxPoint cursorPos = mAreaParent->ScreenToClient(wxGetMousePosition());
wxRect prevTarget = mDropTarget;
int best_dist_sq = 99999;
int i;
for(i = 0; i < (int)mDropTargets.size(); i++) {
int x = (mDropTargets[i].x + (mDropTargets[i].width/2))-cursorPos.x;
int y = (mDropTargets[i].y + (mDropTargets[i].height/2))-cursorPos.y;
int dist_sq = (x*x) + (y*y);
if (dist_sq < best_dist_sq) {
best_dist_sq = dist_sq;
mDropTarget = mDropTargets[i];
}
}
if (!mAreaParent->GetRect().Contains(cursorPos))
mDropTarget = kDummyRect;
if (mDropTarget != prevTarget) {
mDragImage->Hide();
wxRect r = mDropTarget;
r.Inflate(4, 4);
mTargetPanel->SetSize(r);
#if 0
wxClientDC dc(mAreaParent);
dc.DestroyClippingRegion();
dc.SetLogicalFunction(wxINVERT);
wxRect r = prevTarget;
r.Inflate(4, 4);
dc.DrawRectangle(r);
r = mDropTarget;
r.Inflate(4, 4);
dc.DrawRectangle(r);
#endif
// This gives time for wx to finish redrawing the window that way.
// HACK: why do we need to do it so many times???
for(i=0; i<500; i++)
::wxSafeYield();
mDragImage->Show();
mDragImage->Move(ScreenToClient(wxGetMousePosition()));
}
else
mDragImage->Move(ScreenToClient(wxGetMousePosition()));
}
void ExpandingToolBar::FinishMoving()
{
if (!mAreaParent || !mSavedArrangement)
return;
// DELETE mTargetPanel; // I think this is not needed, but unreachable anyway -- PRL
mAreaParent->SetCapturedChild(NULL);
mDragImage->Hide();
mDragImage->EndDrag();
msNoAutoExpandStack--;
if (mDropTarget == kDummyRect) {
mAreaParent->RestoreArrangement(std::move(mSavedArrangement));
}
else {
mSavedArrangement.reset();
mAreaParent->MoveChild(this, mDropTarget);
}
// Keep all drawers closed until the user moves specifically to a
// different window
mAreaParent->CollapseAll();
mTopLevelParent->Refresh(true);
mTimer.Start(kTimerInterval);
}
//
// ToolBarGrabber
//
BEGIN_EVENT_TABLE(ToolBarGrabber, wxPanelWrapper)
EVT_PAINT(ToolBarGrabber::OnPaint)
EVT_SIZE(ToolBarGrabber::OnSize)
EVT_MOUSE_EVENTS(ToolBarGrabber::OnMouse)
END_EVENT_TABLE()
IMPLEMENT_CLASS(ToolBarGrabber, wxPanelWrapper)
ToolBarGrabber::ToolBarGrabber(wxWindow *parent,
wxWindowID id,
ExpandingToolBar *ownerToolbar,
const wxPoint& pos,
const wxSize& size):
wxPanelWrapper(parent, id, pos, size),
mOwnerToolBar(ownerToolbar)
{
#if 0
wxImage grabberImages = theTheme.Image(bmpToolBarGrabber);
wxColour magicColor = wxColour(0, 255, 255);
ImageArray images = ImageRoll::SplitH(grabberImages, magicColor);
mImageRoll[0] = ImageRoll(ImageRoll::VerticalRoll,
images[0],
magicColor);
mImageRoll[1] = ImageRoll(ImageRoll::VerticalRoll,
images[1],
magicColor);
SetSizeHints(mImageRoll[0].GetMinSize(),
mImageRoll[1].GetMaxSize());
#endif
mState = 0;
}
void ToolBarGrabber::OnMouse(wxMouseEvent &event)
{
int prevState = mState;
// Handle hilighting the image if the mouse is over it
if (event.Entering())
mState = 1;
else if (event.Leaving())
mState = 0;
else {
wxSize clientSize = GetClientSize();
if (event.m_x >= 0 && event.m_y >= 0 &&
event.m_x < clientSize.x && event.m_y < clientSize.y)
mState = 1;
else
mState = 0;
}
if (event.ButtonDown())
mOwnerToolBar->StartMoving();
if (mState != prevState)
Refresh(false);
}
void ToolBarGrabber::OnPaint(wxPaintEvent & WXUNUSED(event))
{
wxPaintDC dc(this);
// mImageRoll[mState].Draw(dc, GetClientRect());
}
void ToolBarGrabber::OnSize(wxSizeEvent & WXUNUSED(event))
{
Refresh(false);
}
//
// ToolBarDialog
//
BEGIN_EVENT_TABLE(ToolBarDialog, wxDialogWrapper)
END_EVENT_TABLE()
IMPLEMENT_CLASS(ToolBarDialog, wxDialogWrapper)
ToolBarDialog::ToolBarDialog(wxWindow* parent,
wxWindowID id,
const wxString& name,
const wxPoint& pos):
wxDialogWrapper(parent, id, name, pos, wxSize(1, 1),
// Workaround for bug in __WXMSW__. No close box on a wxDialog unless wxSYSTEM_MENU is used.
#ifdef __WXMSW__
wxSYSTEM_MENU |
#endif
wxCAPTION|wxCLOSE_BOX),
mChild(NULL)
{
}
ToolBarDialog::~ToolBarDialog()
{
}
void ToolBarDialog::SetChild(ExpandingToolBar *child)
{
mChild = child;
if (mChild && mChild->GetParent() != this)
mChild->Reparent(this);
Fit();
}
void ToolBarDialog::Fit()
{
if (mChild) {
wxSize childSize = mChild->GetBestSize();
// Take into account the difference between the content
// size and the frame size
wxSize curContentSize = GetClientSize();
wxSize curFrameSize = GetSize();
wxSize newFrameSize = childSize + (curFrameSize - curContentSize);
SetSizeHints(newFrameSize, newFrameSize);
SetSize(newFrameSize);
}
}
//
// ToolBarFrame
//
BEGIN_EVENT_TABLE(ToolBarFrame, wxMiniFrame)
END_EVENT_TABLE()
IMPLEMENT_CLASS(ToolBarFrame, wxMiniFrame)
ToolBarFrame::ToolBarFrame(wxWindow* parent,
wxWindowID id,
const wxString& name,
const wxPoint& pos):
wxMiniFrame(parent, id, name, pos, wxSize(1, 1),
// Workaround for bug in __WXMSW__. No close box on a miniframe unless wxSYSTEM_MENU is used.
#ifdef __WXMSW__
wxSYSTEM_MENU |
#endif
wxCAPTION|wxCLOSE_BOX),
mChild(NULL)
{
}
ToolBarFrame::~ToolBarFrame()
{
}
void ToolBarFrame::SetChild(ExpandingToolBar *child)
{
mChild = child;
if (mChild && mChild->GetParent() != this)
mChild->Reparent(this);
Fit();
}
void ToolBarFrame::Fit()
{
if (mChild) {
wxSize childSize = mChild->GetBestSize();
// Take into account the difference between the content
// size and the frame size
wxSize curContentSize = GetClientSize();
wxSize curFrameSize = GetSize();
wxSize newFrameSize = childSize + (curFrameSize - curContentSize);
SetSizeHints(newFrameSize, newFrameSize);
SetSize(newFrameSize);
}
}
//
// ToolBarArea
//
BEGIN_EVENT_TABLE(ToolBarArea, wxPanelWrapper)
EVT_SIZE(ToolBarArea::OnSize)
EVT_MOUSE_EVENTS(ToolBarArea::OnMouse)
END_EVENT_TABLE()
IMPLEMENT_CLASS(ToolBarArea, wxPanelWrapper)
ToolBarArea::ToolBarArea(wxWindow* parent,
wxWindowID id,
const wxPoint& pos,
const wxSize& size):
wxPanelWrapper(parent, id, pos, size),
mInOnSize(false),
mCapturedChild(NULL)
{
}
ToolBarArea::~ToolBarArea()
{
}
void ToolBarArea::ContractRow(int rowIndex)
{
// Contract all of the toolbars in a given row to their
// minimum size. This is an intermediate step in layout.
int i;
int x = 0;
for(i = 0; i < (int)mChildArray.size(); i++)
if (mRowArray[i] == rowIndex) {
wxPoint childPos = mChildArray[i]->GetPosition();
wxSize childMin = mChildArray[i]->GetMinSize();
mChildArray[i]->SetSize(x, childPos.y,
childMin.x, childMin.y);
x += childMin.x;
}
}
bool ToolBarArea::ExpandRow(int rowIndex)
{
// Expand all of the toolbars in a given row so that the
// whole width is filled, if possible. This is the last
// step after laying out as many toolbars as possible in
// that row. Returns false if it's not possible to fit
// all of these toolbars in one row anymore.
wxSize area = GetClientSize();
int i, j, x;
int minWidth = 0;
int leftoverSpace = 0;
int expandableCount = 0;
int toolbarCount = 0;
for(i = 0; i < (int)mChildArray.size(); i++)
if (mRowArray[i] == rowIndex) {
ExpandingToolBar *child = mChildArray[i];
wxSize childMin = child->GetMinSize();
wxSize childMax = child->GetMaxSize();
minWidth += childMin.x;
toolbarCount++;
if (childMax.x > childMin.x)
expandableCount++;
}
leftoverSpace = area.x - minWidth;
if (leftoverSpace <= 0) {
if (toolbarCount > 1)
return false; // not possible to fit all in one row
else
return true; // there's only one, so it doesn't matter
}
j = 0;
x = 0;
for(i = 0; i < (int)mChildArray.size(); i++)
if (mRowArray[i] == rowIndex) {
ExpandingToolBar *child = mChildArray[i];
wxPoint childPos = child->GetPosition();
wxSize childMin = child->GetMinSize();
wxSize childMax = child->GetMaxSize();
int width = childMin.x;
if (childMax.x > childMin.x)
width +=
(leftoverSpace * (j+1) / expandableCount) -
(leftoverSpace * (j) / expandableCount);
mChildArray[i]->SetSize(x, childPos.y,
width, childMin.y);
x += width;
j++;
}
return true; // success
}
void ToolBarArea::LayoutOne(int childIndex)
{
wxSize area = GetClientSize();
ExpandingToolBar *child = mChildArray[childIndex];
wxSize childMin = child->GetMinSize();
if (childIndex == 0) {
mRowArray[childIndex] = 0;
mChildArray[childIndex]->SetSize(0, 0, childMin.x, childMin.y);
ExpandRow(0);
#if 0
wxPoint p = mChildArray[childIndex]->GetPosition();
wxSize s = mChildArray[childIndex]->GetSize();
wxPrintf("ToolBar %d moved to row %d at (%d, %d), size (%d x %d)\n",
childIndex, mRowArray[childIndex],
p.x, p.y, s.x, s.y);
#endif
mLastLayoutSize = area;
return;
}
int prevRow = mRowArray[childIndex-1];
ContractRow(prevRow);
wxPoint prevPos = mChildArray[childIndex-1]->GetPosition();
wxSize prevSize = mChildArray[childIndex-1]->GetSize();
int prevX = prevPos.x + prevSize.x;
int availableWidth = area.x - prevX;
if (childMin.x <= availableWidth) {
// It fits into the same row
mRowArray[childIndex] = prevRow;
mChildArray[childIndex]->SetSize(prevX, prevPos.y,
childMin.x, childMin.y);
ExpandRow(prevRow);
}
else {
// Go to the next row
ExpandRow(prevRow);
mRowArray[childIndex] = prevRow + 1;
int i;
int maxRowHeight = 0;
for(i=0; i<childIndex; i++)
if (mRowArray[i] == prevRow &&
mChildArray[i]->GetSize().y > maxRowHeight)
maxRowHeight = mChildArray[i]->GetSize().y;
mChildArray[childIndex]->SetSize(0, prevPos.y + maxRowHeight,
childMin.x, childMin.y);
ExpandRow(prevRow+1);
}
// Save the size of the window the last time we moved one of the
// toolbars around. If the user does a minor resize, we try to
// preserve the layout. If the user does a major resize, we're
// allowed to redo the layout.
mLastLayoutSize = area;
#if 0
wxPoint p = mChildArray[childIndex]->GetPosition();
wxSize s = mChildArray[childIndex]->GetSize();
wxPrintf("ToolBar %d moved to row %d at (%d, %d), size (%d x %d)\n",
childIndex, mRowArray[childIndex],
p.x, p.y, s.x, s.y);
#endif
}
bool ToolBarArea::Layout()
{
// Redo the layout from scratch, preserving only the order of
// the children
int i;
for(i = 0; i < (int)mChildArray.size(); i++)
mRowArray[i] = -1;
for(i = 0; i < (int)mChildArray.size(); i++)
LayoutOne(i);
Refresh(true);
return true;
}
void ToolBarArea::AdjustLayout()
{
// Try to modify the layout as little as possible - but if that's
// impossible, redo the layout as necessary.
int row = -1;
int i, j;
for(i = 0; i < (int)mChildArray.size(); i++) {
if (mRowArray[i] > row) {
row = mRowArray[i];
bool success = ExpandRow(row);
if (!success) {
// Re-layout all toolbars from this row on
for(j = i; j < (int)mChildArray.size(); j++)
LayoutOne(j);
return;
}
}
}
}
void ToolBarArea::Fit()
{
Fit(true, true);
}
void ToolBarArea::Fit(bool horizontal, bool vertical)
{
wxSize clientSize = GetClientSize();
wxSize minSize;
wxSize maxSize;
wxSize actualSize;
int i;
minSize.x = 0;
minSize.y = 0;
maxSize.x = 9999;
maxSize.y = 0;
for(i = 0; i < (int)mChildArray.size(); i++) {
wxPoint childPos = mChildArray[i]->GetPosition();
wxSize childSize = mChildArray[i]->GetSize();
if (childPos.x + childSize.x > actualSize.x) {
actualSize.x = childPos.x + childSize.x;
}
if (childSize.x > minSize.x) {
minSize.x = childSize.x;
}
if (childPos.y + childSize.y > maxSize.y) {
maxSize.y = childPos.y + childSize.y;
minSize.y = maxSize.y;
actualSize.y = maxSize.y;
}
}
if (!horizontal && actualSize.x < clientSize.x)
actualSize.x = clientSize.x;
if (!vertical && actualSize.y < clientSize.y)
actualSize.y = clientSize.y;
if (minSize != mMinSize ||
maxSize != mMaxSize) {
mMinSize = minSize;
mMaxSize = maxSize;
SetSizeHints(mMinSize, mMaxSize);
}
if (actualSize != mActualSize) {
mActualSize = actualSize;
SetSize(mActualSize);
}
}
void ToolBarArea::OnSize(wxSizeEvent & WXUNUSED(event))
{
if (mInOnSize)
return;
mInOnSize = true;
wxSize currentSize = GetClientSize();
if (abs(currentSize.x - mLastLayoutSize.x) >= 100) {
// If they resize by more than 100 pixels (horizontally),
// we totally redo the layout, preserving the order of the
// toolbars but not the exact position.
Layout();
}
else {
// If it was a minor resize, we try to preserve the positions of
// the toolbars. If this is impossible, we still redo the layout,
// of course.
AdjustLayout();
}
Fit(false, true);
mInOnSize = false;
}
void ToolBarArea::OnMouse(wxMouseEvent &evt)
{
if (mCapturedChild) {
if (evt.ButtonUp())
mCapturedChild->FinishMoving();
else if (evt.Moving() || evt.Dragging())
mCapturedChild->UpdateMoving();
}
else {
evt.Skip();
}
}
void ToolBarArea::CollapseAll(bool now)
{
int i;
for(i = 0; i < (int)mChildArray.size(); i++)
mChildArray[i]->Collapse(now);
}
void ToolBarArea::AddChild(ExpandingToolBar *child)
{
mChildArray.push_back(child);
mRowArray.push_back(-1); // unknown row
LayoutOne(mChildArray.size() - 1);
Fit(false, true);
}
void ToolBarArea::RemoveChild(ExpandingToolBar *child)
{
int i, j;
for(i = 0; i < (int)mChildArray.size(); i++) {
if (mChildArray[i] == child) {
child->Hide();
mChildArray.erase(mChildArray.begin() + i);
mRowArray.erase(mRowArray.begin() + i);
for(j = i; j < (int)mChildArray.size(); j++)
mRowArray[j] = -1;
for(j = i; j < (int)mChildArray.size(); j++)
LayoutOne(j);
Fit(false, true);
}
}
}
std::unique_ptr<ToolBarArrangement> ToolBarArea::SaveArrangement()
{
auto arrangement = std::make_unique<ToolBarArrangement>();
int i;
arrangement->childArray = mChildArray;
arrangement->rowArray = mRowArray;
for(i = 0; i < (int)mChildArray.size(); i++)
arrangement->rectArray.push_back(mChildArray[i]->GetRect());
return arrangement;
}
void ToolBarArea::RestoreArrangement(std::unique_ptr<ToolBarArrangement>&& arrangement)
{
int i;
mChildArray = arrangement->childArray;
mRowArray = arrangement->rowArray;
for(i = 0; i < (int)mChildArray.size(); i++) {
mChildArray[i]->SetSize(arrangement->rectArray[i]);
mChildArray[i]->Show();
}
Fit(false, true);
arrangement.reset();
}
std::vector<wxRect> ToolBarArea::GetDropTargets()
{
mDropTargets.clear();
mDropTargetIndices.clear();
mDropTargetRows.clear();
int numChildren = (int)mChildArray.size();
int i;
int row = -1;
if (numChildren == 0)
return mDropTargets;
for(i=0; i<numChildren; i++) {
int childRow = mRowArray[i];
wxRect childRect = mChildArray[i]->GetRect();
if (childRow != row) {
// Add a target before this child (at beginning of row only)
row = childRow;
mDropTargetIndices.push_back(i);
mDropTargetRows.push_back(row);
mDropTargets.push_back(wxRect(childRect.x, childRect.y,
0, childRect.height));
}
// Add a target after this child (always)
mDropTargetIndices.push_back(i+1);
mDropTargetRows.push_back(row);
mDropTargets.push_back(wxRect(childRect.x+childRect.width, childRect.y,
0, childRect.height));
}
return mDropTargets;
}
void ToolBarArea::MoveChild(ExpandingToolBar *toolBar, wxRect dropTarget)
{
int i, j;
for(i = 0; i < (int)mDropTargets.size(); i++) {
if (dropTarget == mDropTargets[i]) {
int newIndex = mDropTargetIndices[i];
int newRow = mDropTargetRows[i];
mChildArray.insert(mChildArray.begin() + newIndex, toolBar);
mRowArray.insert(mRowArray.begin() + newIndex, newRow);
for(j = newIndex+1; j < (int)mChildArray.size(); j++)
mRowArray[j] = -1;
ContractRow(newRow);
mChildArray[newIndex]->Show();
for(j = newIndex; j < (int)mChildArray.size(); j++)
LayoutOne(j);
Fit(false, true);
return;
}
}
}
void ToolBarArea::SetCapturedChild(ExpandingToolBar *child)
{
mCapturedChild = child;
}