/**********************************************************************

  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"

// 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;

      SetMinSize(mCurrentTotalSize);
      SetMaxSize(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

      SetMinSize(mCurrentTotalSize);
      SetMaxSize(mCurrentTotalSize);
      SetSize(mCurrentTotalSize);

      GetParent()->Fit();
   }

   if (mDialogParent) {
      // If we're in a dialog

      SetMinSize(mCurrentTotalSize);
      SetMaxSize(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->SetMinSize(mCurrentDrawerSize);
      mExtraPanel->SetMaxSize(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);

   SetMinSize(mImageRoll[0].GetMinSize());
   SetMaxSize(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 TranslatableString& 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;
      SetMinSize(mMinSize);
      SetMaxSize(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;
}
