1 /**********************************************************************
2 
3   Audacity: A Digital Audio Editor
4 
5   ToolManager.cpp
6 
7   Dominic Mazzoni
8   Shane T. Mueller
9   Leland Lucius
10 
11   See ToolManager.h for details.
12 
13 *******************************************************************//**
14 
15 \file ToolManager.cpp
16 
17   Implements ToolManager
18 
19 *//*******************************************************************//**
20 
21 \class ToolManager
22 \brief Manages the ToolDocks and handles the dragging, floating, and
23   docking of ToolBars.
24 
25 *//**********************************************************************/
26 
27 
28 #include "ToolManager.h"
29 
30 #include "../commands/CommandContext.h"
31 
32 // For compilers that support precompilation, includes "wx/wx.h".
33 #include <wx/wxprec.h>
34 
35 #ifndef WX_PRECOMP
36 #include <wx/app.h>
37 #include <wx/dcclient.h>
38 #include <wx/defs.h>
39 #include <wx/event.h>
40 #include <wx/frame.h>
41 #include <wx/gdicmn.h>
42 #include <wx/intl.h>
43 #include <wx/region.h>
44 #include <wx/settings.h>
45 #include <wx/sizer.h>
46 #include <wx/sysopt.h>
47 #include <wx/timer.h>
48 #include <wx/utils.h>
49 #include <wx/window.h>
50 #endif  /*  */
51 
52 #include <wx/minifram.h>
53 #include <wx/popupwin.h>
54 
55 #include "AColor.h"
56 #include "AllThemeResources.h"
57 #include "ImageManipulation.h"
58 #include "Prefs.h"
59 #include "Project.h"
60 #include "ProjectWindows.h"
61 #include "widgets/AButton.h"
62 #include "widgets/ASlider.h"
63 #include "widgets/MeterPanelBase.h"
64 #include "widgets/Grabber.h"
65 
66 ////////////////////////////////////////////////////////////
67 /// Methods for ToolFrame
68 ////////////////////////////////////////////////////////////
69 #define sizerW 11
70 
71 //
72 // Constructor
73 //
ToolFrame(AudacityProject * parent,ToolManager * manager,ToolBar * bar,wxPoint pos)74 ToolFrame::ToolFrame
75    ( AudacityProject *parent, ToolManager *manager, ToolBar *bar, wxPoint pos )
76    : wxFrame( FindProjectFrame( parent ),
77           bar->GetId(),
78           wxEmptyString,
79           pos,
80           wxDefaultSize,
81           wxNO_BORDER |
82           wxFRAME_NO_TASKBAR |
83 #if !defined(__WXMAC__) // bug1358
84           wxFRAME_TOOL_WINDOW |
85 #endif
86           wxFRAME_FLOAT_ON_PARENT )
87    , mParent{ parent }
88 {
89    int width = bar->GetSize().x;
90    int border = 1;
91 
92    // Save parameters
93    mManager = manager;
94    mBar = bar;
95 
96    // Transfer the bar to the ferry
97    bar->Reparent(this);
98 
99    // Bug 2120 (comment 6 residual): No need to set a minimum size
100    // if the toolbar is not resizable. On GTK, setting a minimum
101    // size will prevent the frame from shrinking if the toolbar gets
102    // reconfigured and needs to resize smaller.
103    if (bar->IsResizable())
104    {
105       SetMinSize(bar->GetDockedSize());
106    }
107 
108    {
109       // We use a sizer to maintain proper spacing
110       auto s = std::make_unique<wxBoxSizer>(wxHORIZONTAL);
111 
112       // Add the bar to the sizer
113       s->Add(bar, 1, wxEXPAND | wxALL, border);
114 
115       // Add space for the resize grabber
116       if (bar->IsResizable())
117       {
118          s->Add(sizerW, 1);
119          width += sizerW;
120       }
121 
122       SetSize(width + 2 * ToolBarFloatMargin,
123               bar->GetDockedSize().y + 2 * ToolBarFloatMargin);
124 
125       // Attach the sizer and resize the window to fit
126       SetSizer(s.release());
127    }
128 
129    Layout();
130 
131    // Inform toolbar of change
132    bar->SetDocked( NULL, true );
133 
134    // Make sure resizable floaters don't get any smaller than initial size
135    if( bar->IsResizable() )
136    {
137       // Calc the minimum size of the frame
138       mMinSize = bar->GetMinSize() + ( GetSize() - bar->GetSize() );
139    }
140 }
141 
~ToolFrame()142 ToolFrame::~ToolFrame()
143 {
144    if(HasCapture())
145       ReleaseMouse();
146 }
147 
OnGrabber(GrabberEvent & event)148 void ToolFrame::OnGrabber( GrabberEvent & event )
149 {
150    // Pass it on to the manager since it isn't in the handling hierarchy
151    mManager->ProcessEvent( event );
152 }
153 
154 // The current size determines the min size for resizing...
155 // the 'lock in' is at that aspect ratio.
LockInMinSize(ToolBar * pBar)156 void ToolFrame::LockInMinSize(ToolBar * pBar)
157 {
158    mBar = pBar;
159 
160    wxSize sz = mBar->GetSize();
161    SetClientSize( sz );
162    int yDesiredMin = 26;
163    int y = sz.GetHeight();
164    if (y > yDesiredMin) {
165       sz.SetWidth((sz.GetWidth() * yDesiredMin) / y);
166       sz.SetHeight( yDesiredMin );
167    }
168    mMinSize = sz -wxSize( 10, 0);
169 }
170 
OnToolBarUpdate(wxCommandEvent & event)171 void ToolFrame::OnToolBarUpdate( wxCommandEvent & event )
172 {
173    // Resize floater window to exactly contain toolbar
174    // use actual size rather than minimum size.
175    if (mBar)
176       mBar->GetParent()->SetClientSize( mBar->GetSize() );// ->GetMinSize() );
177 
178    // Allow it to propagate to our parent
179    event.Skip();
180 }
181 
OnPaint(wxPaintEvent & WXUNUSED (event))182 void ToolFrame::OnPaint( wxPaintEvent & WXUNUSED(event) )
183 {
184    wxPaintDC dc( this );
185    wxSize sz = GetSize();
186    wxRect r;
187 
188    dc.SetPen( theTheme.Colour( clrTrackPanelText ) );
189    dc.SetBackground( wxBrush( theTheme.Colour( clrMedium ) ) );
190    dc.Clear();
191    dc.SetBrush( *wxTRANSPARENT_BRUSH );
192    dc.DrawRectangle( 0, 0, sz.GetWidth(), sz.GetHeight() );
193 
194    if( mBar && mBar->IsResizable() )
195    {
196       r.x = sz.x - sizerW - 2,
197       r.y = sz.y - sizerW - 2;
198       r.width = sizerW + 2;
199       r.height = sizerW + 2;
200 
201       AColor::Line(dc, r.GetLeft(), r.GetBottom(), r.GetRight(), r.GetTop() );
202       AColor::Line(dc, r.GetLeft() + 3, r.GetBottom(), r.GetRight(), r.GetTop() + 3 );
203       AColor::Line(dc, r.GetLeft() + 6, r.GetBottom(), r.GetRight(), r.GetTop() + 6 );
204       AColor::Line(dc, r.GetLeft() + 9, r.GetBottom(), r.GetRight(), r.GetTop() + 9 );
205    }
206 
207 }
208 
OnMotion(wxMouseEvent & event)209 void ToolFrame::OnMotion( wxMouseEvent & event )
210 {
211    // Don't do anything if we're docked or not resizeable
212    if( !mBar || mBar->IsDocked() || !mBar->IsResizable() )
213    {
214       return;
215    }
216 
217    // Retrieve the mouse position
218    wxPoint pos = ClientToScreen( event.GetPosition() );
219    if( HasCapture() && event.Dragging() )
220    {
221       wxRect rect = GetRect();
222 
223       rect.SetBottomRight( pos );
224 
225       // Keep it within max size, if specified
226       wxSize maxsz = mBar->GetMaxSize();
227       if (maxsz != wxDefaultSize)
228       {
229          if (maxsz.x != wxDefaultCoord && rect.width > maxsz.x)
230          {
231             rect.width = maxsz.x;
232          }
233          if (maxsz.y != wxDefaultCoord && rect.height > maxsz.y)
234          {
235             rect.height = maxsz.y;
236          }
237       }
238 
239       if( rect.width < mMinSize.x )
240       {
241          rect.width = mMinSize.x;
242       }
243 
244       if( rect.height < mMinSize.y )
245       {
246          rect.height = mMinSize.y;
247       }
248 
249       Resize( rect.GetSize() );
250    }
251    else if( HasCapture() && event.LeftUp() )
252    {
253       ReleaseMouse();
254    }
255    else if( !HasCapture() )
256    {
257       wxRect rect = GetRect();
258       wxRect r;
259 
260       r.x = rect.GetRight() - sizerW - 2,
261       r.y = rect.GetBottom() - sizerW - 2;
262       r.width = sizerW + 2;
263       r.height = sizerW + 2;
264 
265       // Is left click within resize grabber?
266       if( r.Contains( pos ) && !event.Leaving() )
267       {
268          mOrigSize = GetSize();
269 
270          SetCursor( wxCURSOR_SIZENWSE );
271          if( event.LeftDown() )
272          {
273             CaptureMouse();
274          }
275       }
276       else
277       {
278          SetCursor( wxCURSOR_ARROW );
279       }
280    }
281 }
282 
OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED (event))283 void ToolFrame::OnCaptureLost( wxMouseCaptureLostEvent & WXUNUSED(event) )
284 {
285    if( HasCapture() )
286    {
287       ReleaseMouse();
288    }
289 }
290 
OnClose(wxCloseEvent & event)291 void ToolFrame::OnClose( wxCloseEvent & event )
292 {
293    event.Veto();
294 }
295 
OnKeyDown(wxKeyEvent & event)296 void ToolFrame::OnKeyDown( wxKeyEvent &event )
297 {
298    event.Skip();
299    if( HasCapture() && event.GetKeyCode() == WXK_ESCAPE ) {
300       Resize( mOrigSize );
301       ReleaseMouse();
302    }
303 }
304 
Resize(const wxSize & size)305 void ToolFrame::Resize( const wxSize &size )
306 {
307    SetMinSize( size );
308    SetSize( size );
309    Layout();
310    Refresh( false );
311 }
312 
313 IMPLEMENT_CLASS( ToolFrame, wxFrame );
314 
315 BEGIN_EVENT_TABLE( ToolFrame, wxFrame )
316    EVT_GRABBER( wxID_ANY, ToolFrame::OnGrabber )
317    EVT_PAINT( ToolFrame::OnPaint )
318    EVT_MOUSE_EVENTS( ToolFrame::OnMotion )
319    EVT_MOUSE_CAPTURE_LOST( ToolFrame::OnCaptureLost )
320    EVT_CLOSE( ToolFrame::OnClose )
321    EVT_COMMAND( wxID_ANY, EVT_TOOLBAR_UPDATED, ToolFrame::OnToolBarUpdate )
322    EVT_KEY_DOWN( ToolFrame::OnKeyDown )
323 END_EVENT_TABLE()
324 
325 IMPLEMENT_CLASS( ToolManager, wxEvtHandler );
326 
327 ////////////////////////////////////////////////////////////
328 /// Methods for ToolManager
329 ////////////////////////////////////////////////////////////
330 
BEGIN_EVENT_TABLE(ToolManager,wxEvtHandler)331 BEGIN_EVENT_TABLE( ToolManager, wxEvtHandler )
332    EVT_GRABBER( wxID_ANY, ToolManager::OnGrabber )
333    EVT_TIMER( wxID_ANY, ToolManager::OnTimer )
334 END_EVENT_TABLE()
335 
336 static ToolManager::GetTopPanelHook &getTopPanelHook()
337 {
338    static ToolManager::GetTopPanelHook theHook;
339    return theHook;
340 }
341 
SetGetTopPanelHook(const GetTopPanelHook & hook)342 auto ToolManager::SetGetTopPanelHook( const GetTopPanelHook &hook )
343    -> GetTopPanelHook
344 {
345    auto &theHook = getTopPanelHook();
346    auto result = theHook;
347    theHook = hook;
348    return result;
349 }
350 
351 static const AudacityProject::AttachedObjects::RegisteredFactory key{
__anonea3c83480102( )352   []( AudacityProject &parent ){
353      return std::make_shared< ToolManager >( &parent ); }
354 };
355 
Get(AudacityProject & project)356 ToolManager &ToolManager::Get( AudacityProject &project )
357 {
358    return project.AttachedObjects::Get< ToolManager >( key );
359 }
360 
Get(const AudacityProject & project)361 const ToolManager &ToolManager::Get( const AudacityProject &project )
362 {
363    return Get( const_cast< AudacityProject & >( project ) );
364 }
365 
366 //
367 // Constructor
368 //
ToolManager(AudacityProject * parent)369 ToolManager::ToolManager( AudacityProject *parent )
370 : wxEvtHandler()
371 {
372    wxPoint pt[ 3 ];
373 
374 #if defined(__WXMAC__)
375    // Save original transition
376    mTransition = wxSystemOptions::GetOptionInt( wxMAC_WINDOW_PLAIN_TRANSITION );
377 #endif
378 
379    // Initialize everything
380    mParent = parent;
381    mLastPos.x = mBarPos.x = -1;
382    mLastPos.y = mBarPos.y = -1;
383    mDragWindow = NULL;
384    mDragDock = NULL;
385    mDragBar = NULL;
386 
387    // Create the down arrow
388    pt[ 0 ].x = 0;
389    pt[ 0 ].y = 0;
390    pt[ 1 ].x = 9;
391    pt[ 1 ].y = 9;
392    pt[ 2 ].x = 18;
393    pt[ 2 ].y = 0;
394 
395    // Create the shaped region
396    mDown = std::make_unique<wxRegion>( 3, &pt[0] );
397 
398    // Create the down arrow
399    pt[ 0 ].x = 9;
400    pt[ 0 ].y = 0;
401    pt[ 1 ].x = 0;
402    pt[ 1 ].y = 9;
403    pt[ 2 ].x = 9;
404    pt[ 2 ].y = 18;
405 
406    // Create the shaped region
407    mLeft = std::make_unique<wxRegion>( 3, &pt[0] );
408 
409    // Create the indicator frame
410    // parent is null but FramePtr ensures destruction
411    mIndicator = FramePtr{ safenew wxFrame( NULL,
412                              wxID_ANY,
413                              wxEmptyString,
414                              wxDefaultPosition,
415                              wxSize( 32, 32 ),
416                              wxFRAME_TOOL_WINDOW |
417                              wxFRAME_SHAPED |
418                              wxNO_BORDER |
419                              wxFRAME_NO_TASKBAR |
420                              wxSTAY_ON_TOP )
421    };
422 
423    // Hook the creation event...only needed on GTK, but doesn't hurt for all
424    mIndicator->Bind( wxEVT_CREATE,
425                         &ToolManager::OnIndicatorCreate,
426                         this );
427 
428    // Hook the paint event...needed for all
429    mIndicator->Bind( wxEVT_PAINT,
430                         &ToolManager::OnIndicatorPaint,
431                         this );
432 
433    // It's a little shy
434    mIndicator->Hide();
435 }
436 
CreateWindows()437 void ToolManager::CreateWindows()
438 {
439    auto parent = mParent;
440    auto &window = GetProjectFrame( *parent );
441 
442    // Hook the parents mouse events...using the parent helps greatly
443    // under GTK
444    window.Bind( wxEVT_LEFT_UP,
445                      &ToolManager::OnMouse,
446                      this );
447    window.Bind( wxEVT_MOTION,
448                      &ToolManager::OnMouse,
449                      this );
450    window.Bind( wxEVT_MOUSE_CAPTURE_LOST,
451                      &ToolManager::OnCaptureLost,
452                      this );
453 
454    wxWindow *topDockParent = getTopPanelHook()( window );
455 
456    // Create the top and bottom docks
457    mTopDock = safenew ToolDock( this, topDockParent, TopDockID );
458    mBotDock = safenew ToolDock( this, &window, BotDockID );
459 
460    // Create all of the toolbars
461    // All have the project as parent window
462    wxASSERT(parent);
463 
464    size_t ii = 0;
465    for (const auto &factory : RegisteredToolbarFactory::GetFactories()) {
466       if (factory) {
467          mBars[ii] = factory( *parent );
468       }
469       else
470          wxASSERT( false );
471       ++ii;
472    }
473 
474    // We own the timer
475    mTimer.SetOwner( this );
476 
477    // Process the toolbar config settings
478    ReadConfig();
479 
480    wxEvtHandler::AddFilter(this);
481 }
482 
483 //
484 // Destructor
485 //
486 
Destroy()487 void ToolManager::Destroy()
488 {
489    if ( mTopDock || mBotDock ) { // destroy at most once
490       wxEvtHandler::RemoveFilter(this);
491 
492       // Save the toolbar states
493       WriteConfig();
494 
495       // This function causes the toolbars to be destroyed, so
496       // clear the configuration of the ToolDocks which refer to
497       // these toolbars. This change was needed to stop Audacity
498       // crashing when running with Jaws on Windows 10 1703.
499       mTopDock->GetConfiguration().Clear();
500       mBotDock->GetConfiguration().Clear();
501 
502       mTopDock = mBotDock = nullptr; // indicate that it has been destroyed
503 
504       for ( size_t ii = 0; ii < ToolBarCount; ++ii )
505          mBars[ii].reset();
506 
507       mIndicator.reset();
508    }
509 }
510 
~ToolManager()511 ToolManager::~ToolManager()
512 {
513    Destroy();
514 }
515 
516 // This table describes the default configuration of the toolbars as
517 // a "tree" and must be kept in pre-order traversal.
518 
519 // In fact this tree is more of a broom -- nothing properly branches except
520 // at the root.
521 
522 // "Root" corresponds to left edge of the main window, and successive siblings
523 // go from top to bottom.  But in practice layout may wrap this abstract
524 // configuration if the window size is narrow.
525 
526 static struct DefaultConfigEntry {
527    int barID;
528    int rightOf; // parent
529    int below;   // preceding sibling
530 } DefaultConfigTable [] = {
531    // Top dock row, may wrap
532    { TransportBarID,         NoBarID,                NoBarID                },
533    { ToolsBarID,             TransportBarID,         NoBarID                },
534    { RecordMeterBarID,       ToolsBarID,             NoBarID                },
535    { PlayMeterBarID,         RecordMeterBarID,       NoBarID                },
536    { MixerBarID,             PlayMeterBarID,         NoBarID                },
537    { EditBarID,              MixerBarID,             NoBarID                },
538 
539 // DA: Transcription Toolbar not docked, by default.
540 #ifdef EXPERIMENTAL_DA
541    { TranscriptionBarID,     NoBarID,                NoBarID                },
542 #else
543    { TranscriptionBarID,     EditBarID,              NoBarID                },
544 #endif
545 
546    // start another top dock row
547    { ScrubbingBarID,         NoBarID,                TransportBarID         },
548    { DeviceBarID,            ScrubbingBarID,         TransportBarID         },
549 
550    // Hidden by default in top dock
551    { MeterBarID,             NoBarID,                NoBarID                },
552 
553    // Bottom dock
554    { SelectionBarID,         NoBarID,                NoBarID                },
555    { TimeBarID,              SelectionBarID,         NoBarID                },
556 
557    // Hidden by default in bottom dock
558    { SpectralSelectionBarID, NoBarID,                NoBarID                },
559 };
560 
561 // Static member function.
OnResetToolBars(const CommandContext & context)562 void ToolManager::OnResetToolBars(const CommandContext &context)
563 {
564    auto &project = context.project;
565    auto &toolManager = ToolManager::Get( project );
566 
567    toolManager.Reset();
568    MenuManager::Get(project).ModifyToolbarMenus(project);
569 }
570 
571 
Reset()572 void ToolManager::Reset()
573 {
574    // Disconnect all docked bars
575    for ( const auto &entry : DefaultConfigTable )
576    {
577       int ndx = entry.barID;
578       ToolBar *bar = mBars[ ndx ].get();
579 
580       ToolBarConfiguration::Position position {
581          (entry.rightOf == NoBarID) ? nullptr : mBars[ entry.rightOf ].get(),
582          (entry.below == NoBarID) ? nullptr : mBars[ entry.below ].get()
583       };
584       bar->SetSize( 20,20 );
585 
586       wxWindow *floater;
587       ToolDock *dock;
588       bool expose = true;
589 
590       // Disconnect the bar
591       if( bar->IsDocked() )
592       {
593          bar->GetDock()->Undock( bar );
594          floater = NULL;
595       }
596       else
597       {
598          floater = bar->GetParent();
599       }
600 
601       // Decide which dock.
602       if (ndx == SelectionBarID
603 #ifdef EXPERIMENTAL_SPECTRAL_EDITING
604          || ndx == SpectralSelectionBarID
605 #endif
606          || ndx == TimeBarID
607          )
608          dock = mBotDock;
609       else
610          dock = mTopDock;
611 
612       // PRL: Destroy the tool frame before recreating buttons.
613       // This fixes some subtle sizing problems on macOs.
614       bar->Reparent( dock );
615       //OK (and good) to DELETE floater, as bar is no longer in it.
616       if( floater )
617          floater->Destroy();
618 
619       // Recreate bar buttons (and resize it)
620       bar->SetToDefaultSize();
621       bar->ReCreateButtons();
622       bar->EnableDisableButtons();
623 
624 #if 0
625       if( bar->IsResizable() )
626       {
627          bar->SetSize(bar->GetBestFittingSize());
628       }
629 #endif
630 
631       // Hide some bars.
632       if( ndx == MeterBarID
633 #ifdef EXPERIMENTAL_SPECTRAL_EDITING
634          || ndx == SpectralSelectionBarID
635 #endif
636          || ndx == ScrubbingBarID
637 // DA: Hides three more toolbars.
638 #ifdef EXPERIMENTAL_DA
639          || ndx == DeviceBarID
640          || ndx == TranscriptionBarID
641          || ndx == SelectionBarID
642 #endif
643          )
644          expose = false;
645 
646       // Next condition will always (?) be true, as the reset configuration is
647       // with no floating toolbars.
648       if( dock != NULL )
649       {
650          // when we dock, we reparent, so bar is no longer a child of floater.
651          dock->Dock( bar, false, position );
652          Expose( ndx, expose );
653       }
654       else
655       {
656          // The (tool)bar has a dragger window round it, the floater.
657          // in turn floater will have mParent (the entire App) as its
658          // parent.
659 
660          // Maybe construct a NEW floater
661          // this happens if we have just been bounced out of a dock.
662          if( floater == NULL ) {
663             wxASSERT(mParent);
664             floater = safenew ToolFrame( mParent, this, bar, wxPoint(-1,-1) );
665             bar->Reparent( floater );
666          }
667 
668          // This bar is undocked and invisible.
669          // We are doing a reset toolbars, so even the invisible undocked bars should
670          // be moved somewhere sensible. Put bar near center of window.
671          // If there were multiple hidden toobars the ndx * 10 adjustment means
672          // they won't overlap too much.
673          floater->CentreOnParent( );
674          floater->Move( floater->GetPosition() + wxSize( ndx * 10 - 200, ndx * 10 ));
675          bar->SetDocked( NULL, false );
676          Expose( ndx, false );
677       }
678 
679    }
680    // TODO:??
681    // If audio was playing, we stopped the VU meters,
682    // It would be nice to show them again, but hardly essential as
683    // they will show up again on the next play.
684    // SetVUMeters(AudacityProject *p);
685    Updated();
686 }
687 
RegenerateTooltips()688 void ToolManager::RegenerateTooltips()
689 {
690    for (const auto &bar : mBars) {
691       if (bar)
692          bar->RegenerateTooltips();
693    }
694 }
695 
FilterEvent(wxEvent & event)696 int ToolManager::FilterEvent(wxEvent &event)
697 {
698    // Snoop the global event stream for changes of focused window.  Remember
699    // the last one of our own that is not a grabber.
700 
701    if (event.GetEventType() == wxEVT_KILL_FOCUS) {
702       auto &focusEvent = static_cast<wxFocusEvent&>(event);
703       auto window = focusEvent.GetWindow();
704       auto top = wxGetTopLevelParent(window);
705       if(auto toolFrame = dynamic_cast<ToolFrame*>(top))
706          top = toolFrame->GetParent();
707       // window is that which will GET the focus
708       if ( window &&
709            !dynamic_cast<Grabber*>( window ) &&
710            !dynamic_cast<ToolFrame*>( window ) &&
711            top == FindProjectFrame( mParent ) )
712          // Note this is a dangle-proof wxWindowRef:
713          mLastFocus = window;
714    }
715 
716    return Event_Skip;
717 }
718 
719 //
720 // Read the toolbar states
721 //
ReadConfig()722 void ToolManager::ReadConfig()
723 {
724    wxString oldpath = gPrefs->GetPath();
725    std::vector<int> unordered[ DockCount ];
726    std::vector<ToolBar*> dockedAndHidden;
727    bool show[ ToolBarCount ];
728    int width[ ToolBarCount ];
729    int height[ ToolBarCount ];
730    int x, y;
731    int dock, ndx;
732    bool someFound { false };
733 
734 #if defined(__WXMAC__)
735    // Disable window animation
736    wxSystemOptions::SetOption( wxMAC_WINDOW_PLAIN_TRANSITION, 1 );
737 #endif
738 
739    // Change to the bar root
740    gPrefs->SetPath( wxT("/GUI/ToolBars") );
741 
742    ToolBarConfiguration::Legacy topLegacy, botLegacy;
743 
744    int vMajor, vMinor, vMicro;
745    gPrefs->GetVersionKeysInit(vMajor, vMinor, vMicro);
746    bool useLegacyDock = false;
747    // note that vMajor, vMinor, and vMicro will all be zero if either it's a new audacity.cfg file
748    // or the version is less than 1.3.13 (when there were no version keys according to the comments in
749    // InitPreferences()). So for new audacity.cfg
750    // file useLegacyDock will be true, but this doesn't matter as there are no Dock or DockV2 keys in the file yet.
751    if (vMajor <= 1 ||
752       (vMajor == 2 && (vMinor <= 1 || (vMinor == 2 && vMicro <= 1))))   // version <= 2.2.1
753       useLegacyDock = true;
754 
755 
756    // Load and apply settings for each bar
757    for( ndx = 0; ndx < ToolBarCount; ndx++ )
758    {
759       ToolBar *bar = mBars[ ndx ].get();
760       //wxPoint Center = mParent->GetPosition() + (mParent->GetSize() * 0.33);
761       //wxPoint Center(
762       //   wxSystemSettings::GetMetric( wxSYS_SCREEN_X ) /2 ,
763       //   wxSystemSettings::GetMetric( wxSYS_SCREEN_Y ) /2 );
764 
765       // Change to the bar subkey
766       gPrefs->SetPath( bar->GetSection() );
767 
768       bool bShownByDefault = true;
769       int defaultDock = TopDockID;
770 
771       if( ndx == SelectionBarID )
772          defaultDock = BotDockID;
773       if( ndx == MeterBarID )
774          bShownByDefault = false;
775       if( ndx == ScrubbingBarID )
776          bShownByDefault = false;
777       if( ndx == TimeBarID )
778          defaultDock = BotDockID;
779 
780 #ifdef EXPERIMENTAL_SPECTRAL_EDITING
781       if( ndx == SpectralSelectionBarID ){
782          defaultDock = BotDockID;
783          bShownByDefault = false; // Only show if asked for.
784       }
785 #endif
786 
787       // Read in all the settings
788 
789       if (useLegacyDock)
790          gPrefs->Read( wxT("Dock"), &dock, -1);       // legacy version of DockV2
791       else
792          gPrefs->Read( wxT("DockV2"), &dock, -1);
793 
794       const bool found = (dock != -1);
795       if (found)
796          someFound = true;
797       if (!found)
798          dock = defaultDock;
799 
800       ToolDock *d;
801       ToolBarConfiguration::Legacy *pLegacy;
802       switch(dock)
803       {
804          case TopDockID: d = mTopDock; pLegacy = &topLegacy; break;
805          case BotDockID: d = mBotDock; pLegacy = &botLegacy;  break;
806          default:        d = nullptr; pLegacy = nullptr; break;
807       }
808 
809       bool ordered = ToolBarConfiguration::Read(
810          d ? &d->GetConfiguration() : nullptr,
811          pLegacy,
812          bar, show[ ndx ], bShownByDefault)
813       && found;
814 
815       gPrefs->Read( wxT("X"), &x, -1 );
816       gPrefs->Read( wxT("Y"), &y, -1 );
817       gPrefs->Read( wxT("W"), &width[ ndx ], -1 );
818       gPrefs->Read( wxT("H"), &height[ ndx ], -1 );
819 
820       bar->SetVisible( show[ ndx ] );
821 
822       // Docked or floating?
823       if( dock )
824       {
825          // Default to top dock if the ID isn't valid
826          if( dock < NoDockID || dock > DockCount ) {
827             dock = TopDockID;
828          }
829 
830          // Create the bar with the correct parent
831          if( dock == TopDockID )
832          {
833             bar->Create( mTopDock );
834          }
835          else
836          {
837             bar->Create( mBotDock );
838          }
839 
840          // Set the width and height
841          if( width[ ndx ] != -1 && height[ ndx ] != -1 )
842          {
843             wxSize sz( width[ ndx ], height[ ndx ] );
844             bar->SetSize( sz );
845             bar->ResizingDone();
846          }
847 
848 #ifdef EXPERIMENTAL_SYNC_LOCK
849          // Set the width
850          if( width[ ndx ] >= bar->GetSize().x )
851          {
852             wxSize sz( width[ ndx ], bar->GetSize().y );
853             bar->SetSize( sz );
854             bar->Layout();
855          }
856 #else
857          // note that this section is here because if you had been using sync-lock and now you aren't,
858          // the space for the extra button is stored in audacity.cfg, and so you get an extra space
859          // in the EditToolbar.
860          // It is needed so that the meterToolbar size gets preserved.
861          // Longer-term we should find a better fix for this.
862          wxString thisBar = bar->GetSection();
863          if( thisBar != wxT("Edit"))
864          {
865             // Set the width
866             if( width[ ndx ] >= bar->GetSize().x )
867             {
868                wxSize sz( width[ ndx ], bar->GetSize().y );
869                bar->SetSize( sz );
870                bar->Layout();
871             }
872          }
873 #endif
874          // make a note of docked and hidden toolbars
875          if (!show[ndx])
876             dockedAndHidden.push_back(bar);
877 
878          if (!ordered)
879          {
880             // These must go at the end
881             unordered[ dock - 1 ].push_back( ndx );
882          }
883       }
884       else
885       {
886          // Create the bar (with the top dock being temporary parent)
887          bar->Create( mTopDock );
888 
889          // Construct a NEW floater
890          wxASSERT(mParent);
891          ToolFrame *f = safenew ToolFrame( mParent, this, bar, wxPoint( x, y ) );
892 
893          // Set the width and height
894          if( width[ ndx ] != -1 && height[ ndx ] != -1 )
895          {
896             wxSize sz( width[ ndx ], height[ ndx ] );
897             f->SetSizeHints( sz );
898             f->SetSize( sz );
899             f->Layout();
900             if( (x!=-1) && (y!=-1) )
901                bar->SetPositioned();
902          }
903 
904          // Required on Linux Xfce
905          wxSize msz(width[ndx],height[ndx]-1);
906          bar->GetParent()->SetMinSize(msz);
907 
908          // Inform toolbar of change
909          bar->SetDocked( NULL, false );
910 
911          // Show or hide it
912          Expose( ndx, show[ ndx ] );
913       }
914 
915       // Change back to the bar root
916       //gPrefs->SetPath( wxT("..") );  <-- Causes a warning...
917       // May or may not have gone into a subdirectory,
918       // so use an absolute path.
919       gPrefs->SetPath( wxT("/GUI/ToolBars") );
920    }
921 
922    mTopDock->GetConfiguration().PostRead(topLegacy);
923    mBotDock->GetConfiguration().PostRead(botLegacy);
924 
925    // Add all toolbars to their target dock
926    for( dock = 0; dock < DockCount; dock++ )
927    {
928       ToolDock *d = ( dock + 1 == TopDockID ? mTopDock : mBotDock );
929 
930       d->LoadConfig();
931 
932       // Add all unordered toolbars
933       for( int ord = 0; ord < (int) unordered[ dock ].size(); ord++ )
934       {
935          ToolBar *t = mBars[ unordered[ dock ][ ord ] ].get();
936 
937          // Dock it
938          d->Dock( t, false );
939 
940          // Show or hide the bar
941          Expose( t->GetId(), show[ t->GetId() ] );
942       }
943    }
944 
945    // hidden docked toolbars
946    for (auto bar : dockedAndHidden) {
947       bar->SetVisible(false );
948       bar->GetDock()->Dock(bar, false);
949       bar->Expose(false);
950    }
951 
952    // Restore original config path
953    gPrefs->SetPath( oldpath );
954 
955 #if defined(__WXMAC__)
956    // Reinstate original transition
957    wxSystemOptions::SetOption( wxMAC_WINDOW_PLAIN_TRANSITION, mTransition );
958 #endif
959 
960    if (!someFound)
961       Reset();
962 }
963 
964 //
965 // Save the toolbar states
966 //
WriteConfig()967 void ToolManager::WriteConfig()
968 {
969    if( !gPrefs )
970    {
971       return;
972    }
973 
974    wxString oldpath = gPrefs->GetPath();
975    int ndx;
976 
977    // Change to the bar root
978    gPrefs->SetPath( wxT("/GUI/ToolBars") );
979 
980    // Save state of each bar
981    for( ndx = 0; ndx < ToolBarCount; ndx++ )
982    {
983       ToolBar *bar = mBars[ ndx ].get();
984 
985       // Change to the bar subkey
986       gPrefs->SetPath( bar->GetSection() );
987 
988       // Search both docks for toolbar order
989       bool to = mTopDock->GetConfiguration().Contains( bar );
990       bool bo = mBotDock->GetConfiguration().Contains( bar );
991 
992       // Save
993       // Note that DockV2 was introduced in 2.2.2 to fix bug #1554. Dock is retained so that
994       // the toolbar layout is not changed when opening a version before 2.2.2, and in particular
995       // its value is compatible with versions 2.1.3 to 2.2.1 which have this bug.
996       ToolDock* dock = bar->GetDock();       // dock for both shown and hidden toolbars
997       gPrefs->Write( wxT("DockV2"), static_cast<int>(dock == mTopDock ? TopDockID : dock == mBotDock ? BotDockID : NoDockID ));
998 
999       gPrefs->Write( wxT("Dock"), static_cast<int>( to ? TopDockID : bo ? BotDockID : NoDockID));
1000 
1001       dock = to ? mTopDock : bo ? mBotDock : nullptr;    // dock for shown toolbars
1002       ToolBarConfiguration::Write
1003          (dock ? &dock->GetConfiguration() : nullptr, bar);
1004 
1005       wxPoint pos( -1, -1 );
1006       wxSize sz = bar->GetSize();
1007       if( !bar->IsDocked() && bar->IsPositioned() )
1008       {
1009          pos = bar->GetParent()->GetPosition();
1010          sz = bar->GetParent()->GetSize();
1011       }
1012       gPrefs->Write( wxT("X"), pos.x );
1013       gPrefs->Write( wxT("Y"), pos.y );
1014       gPrefs->Write( wxT("W"), sz.x );
1015       gPrefs->Write( wxT("H"), sz.y );
1016 
1017       // Change back to the bar root
1018       gPrefs->SetPath( wxT("..") );
1019    }
1020 
1021    // Restore original config path
1022    gPrefs->SetPath( oldpath );
1023    gPrefs->Flush();
1024 }
1025 
1026 //
1027 // Return a pointer to the specified toolbar
1028 //
GetToolBar(int type) const1029 ToolBar *ToolManager::GetToolBar( int type ) const
1030 {
1031    return mBars[ type ].get();
1032 }
1033 
1034 //
1035 // Return a pointer to the top dock
1036 //
GetTopDock()1037 ToolDock *ToolManager::GetTopDock()
1038 {
1039    return mTopDock;
1040 }
1041 
GetTopDock() const1042 const ToolDock *ToolManager::GetTopDock() const
1043 {
1044    return mTopDock;
1045 }
1046 
1047 //
1048 // Return a pointer to the bottom dock
1049 //
GetBotDock()1050 ToolDock *ToolManager::GetBotDock()
1051 {
1052    return mBotDock;
1053 }
1054 
GetBotDock() const1055 const ToolDock *ToolManager::GetBotDock() const
1056 {
1057    return mBotDock;
1058 }
1059 
1060 //
1061 // Queues an EVT_TOOLBAR_UPDATED command event to notify any
1062 // interest parties of an updated toolbar or dock layout
1063 //
Updated()1064 void ToolManager::Updated()
1065 {
1066    // Queue an update event
1067    wxCommandEvent e( EVT_TOOLBAR_UPDATED );
1068    GetProjectFrame( *mParent ).GetEventHandler()->AddPendingEvent( e );
1069 }
1070 
1071 //
1072 // Return docked state of specified toolbar
1073 //
IsDocked(int type)1074 bool ToolManager::IsDocked( int type )
1075 {
1076    return mBars[ type ]->IsDocked();
1077 }
1078 
1079 //
1080 // Returns the visibility of the specified toolbar
1081 //
IsVisible(int type)1082 bool ToolManager::IsVisible( int type )
1083 {
1084    ToolBar *t = mBars[ type ].get();
1085 
1086    return t && t->IsVisible();
1087 
1088 #if 0
1089    // If toolbar is floating
1090    if( !t->IsDocked() )
1091    {
1092       // Must return state of floater window
1093       return t->GetParent()->IsShown();
1094    }
1095 
1096    // Return state of docked toolbar
1097    return t->IsShown();
1098 #endif
1099 }
1100 
1101 //
1102 // Toggles the visible/hidden state of a toolbar
1103 //
ShowHide(int type)1104 void ToolManager::ShowHide( int type )
1105 {
1106    Expose( type, !mBars[ type ]->IsVisible() );
1107    Updated();
1108 }
1109 
1110 //
1111 // Set the visible/hidden state of a toolbar
1112 //
Expose(int type,bool show)1113 void ToolManager::Expose( int type, bool show )
1114 {
1115    ToolBar *t = mBars[ type ].get();
1116 
1117    // Handle docked and floaters differently
1118    if( t->IsDocked() )
1119    {
1120       t->GetDock()->Expose( type, show );
1121    }
1122    else
1123    {
1124       t->Expose( show );
1125    }
1126 }
1127 
1128 //
1129 // Ask both docks to (re)layout their bars
1130 //
LayoutToolBars()1131 void ToolManager::LayoutToolBars()
1132 {
1133    // Update the layout
1134    if (mTopDock)
1135    {
1136       mTopDock->LayoutToolBars();
1137    }
1138 
1139    if (mBotDock)
1140    {
1141       mBotDock->LayoutToolBars();
1142    }
1143 }
1144 
1145 //
1146 // Handle toolbar dragging
1147 //
OnMouse(wxMouseEvent & event)1148 void ToolManager::OnMouse( wxMouseEvent & event )
1149 {
1150    // Go ahead and set the event to propagate
1151    event.Skip();
1152 
1153    // Can't do anything if we're not dragging.  This also prevents
1154    // us from intercepting events that don't belong to us from the
1155    // parent since we're Connect()ed to a couple.
1156    if( !mClicked )
1157    {
1158       return;
1159    }
1160 
1161 #if defined(__WXMAC__)
1162    // Disable window animation
1163    wxSystemOptions::SetOption( wxMAC_WINDOW_PLAIN_TRANSITION, 1 );
1164 #endif
1165 
1166    // Retrieve the event position
1167    wxPoint pos =
1168       ( (wxWindow *)event.GetEventObject() )->ClientToScreen( event.GetPosition() ) - mDragOffset;
1169 
1170 
1171    if( !event.LeftIsDown() )
1172    {
1173       // Button was released...finish the drag
1174       // Transition the bar to a dock
1175       if (!mDidDrag) {
1176          if (mPrevDock)
1177             mPrevDock->RestoreConfiguration(mPrevConfiguration);
1178          DoneDragging();
1179          return;
1180       }
1181       else if( mDragDock && !event.ShiftDown() )
1182       {
1183          // Trip over...everyone ashore that's going ashore...
1184          mDragDock->Dock( mDragBar, true, mDragBefore );
1185          Updated();
1186          mDragWindow->ClearBar();
1187 
1188          // Done with the floater
1189          mDragWindow->Destroy();
1190          mDragWindow = nullptr;
1191          mDragBar->Refresh(false);
1192       }
1193       else
1194       {
1195          // Calling SetDocked() to force the grabber button to popup
1196          mDragBar->SetDocked( NULL, false );
1197       }
1198 
1199       DoneDragging();
1200    }
1201    else if( event.Dragging() && pos != mLastPos )
1202    {
1203       if (!mDidDrag) {
1204          // Must set the bar afloat if it's currently docked
1205          mDidDrag = true;
1206          wxPoint mp = event.GetPosition();
1207          mp = GetProjectFrame( *mParent ).ClientToScreen(mp);
1208          if (!mDragWindow) {
1209             // We no longer have control
1210             if (mPrevDock)
1211                mPrevDock->GetConfiguration().Remove( mDragBar );
1212             UndockBar(mp);
1213             // Rearrange the remaining toolbars before trying to re-insert this one.
1214             LayoutToolBars();
1215          }
1216       }
1217 
1218       // Make toolbar follow the mouse
1219       mDragWindow->Move( pos  );
1220 
1221       // Remember to prevent excessive movement
1222       mLastPos = pos;
1223 
1224       // Calc the top dock hittest rectangle
1225       wxRect tr = mTopDock->GetRect();
1226       tr.SetBottom( tr.GetBottom() + 10 );
1227       tr.SetPosition( mTopDock->GetParent()->ClientToScreen( tr.GetPosition() ) );
1228 
1229       // Calc the bottom dock hittest rectangle
1230       wxRect br = mBotDock->GetRect();
1231       br.SetTop( br.GetTop() - 10 );
1232       br.SetBottom( br.GetBottom() + 20 );
1233       br.SetPosition( mBotDock->GetParent()->ClientToScreen( br.GetPosition() ) );
1234 
1235 
1236       // Add half the bar height.  We could use the actual bar height, but that would be confusing as a
1237       // bar removed at a place might not dock back there if just let go.
1238       // Also add 5 pixels in horizontal direction, so that a click without a move (or a very small move)
1239       // lands back where we started.
1240       pos +=  wxPoint( 5, 20 );
1241 
1242 
1243       // To find which dock, rather than test against pos, test against the whole dragger rect.
1244       // This means it is enough to overlap the dock to dock with it.
1245       wxRect barRect = mDragWindow->GetRect();
1246       ToolDock *dock = NULL;
1247       if( tr.Intersects( barRect ) )
1248          dock = mTopDock;
1249       else if( br.Intersects( barRect ) )
1250          dock = mBotDock;
1251 
1252       // Looks like we have a winner...
1253       if( dock )
1254       {
1255          wxPoint p;
1256          wxRect r;
1257 
1258          // Calculate where the bar would be placed
1259          mDragBefore = dock->PositionBar( mDragBar, pos, r );
1260 
1261          // If different than the last time, the indicator must be moved
1262          if( r != mBarPos )
1263          {
1264             wxRect dr = dock->GetRect();
1265 
1266             // Hide the indicator before changing the shape
1267             mIndicator->Hide();
1268 
1269             // Decide which direction the arrow should point
1270             if( r.GetTop() >= dr.GetHeight() )
1271             {
1272                const auto &box = mDown->GetBox();
1273                p.x = dr.GetLeft() + ( dr.GetWidth() / 2 )
1274                  - (box.GetWidth() / 2);
1275                p.y = dr.GetBottom() - box.GetHeight();
1276                mCurrent = mDown.get();
1277             }
1278             else
1279             {
1280                // r is the rectangle of the toolbar being dragged.
1281                // A tall undocked toolbar will become at most 2 tbs
1282                // high when docked, so the triangular drop indicator
1283                // needs to use that height, h, not the bar height
1284                // for calculating where to be drawn.
1285                const int tbs = toolbarSingle + toolbarGap;
1286                int h = wxMin(r.GetHeight(), 2*tbs-1);
1287                p.x = dr.GetLeft() + r.GetLeft();
1288                p.y = dr.GetTop() + r.GetTop() +
1289                   ( ( h - mLeft->GetBox().GetHeight() ) / 2 );
1290                mCurrent = mLeft.get();
1291             }
1292 
1293             // Change the shape while hidden and then show it if okay
1294             mIndicator->SetShape( *mCurrent );
1295             if( !event.ShiftDown() )
1296             {
1297                mIndicator->Show();
1298                mIndicator->Update();
1299             }
1300 
1301             // Move it into position
1302             // LL:  Do this after the Show() since KDE doesn't move the window
1303             //      if it's not shown.  (Do it outside if the previous IF as well)
1304             mIndicator->Move( dock->GetParent()->ClientToScreen( p ) );
1305 
1306             // Remember for next go round
1307             mBarPos = r;
1308          }
1309       }
1310       else
1311       {
1312          // Hide the indicator if it's still shown
1313          if( mBarPos.x != -1 )
1314          {
1315             // Hide any
1316             mIndicator->Hide();
1317             mBarPos.x = -1;
1318             mBarPos.y = -1;
1319          }
1320       }
1321 
1322       // Remember to which dock the drag bar belongs.
1323       mDragDock = dock;
1324    }
1325 
1326 #if defined(__WXMAC__)
1327    // Reinstate original transition
1328    wxSystemOptions::SetOption( wxMAC_WINDOW_PLAIN_TRANSITION, mTransition );
1329 #endif
1330 }
1331 
1332 //
1333 // Deal with NEW capture lost event
1334 //
OnCaptureLost(wxMouseCaptureLostEvent & event)1335 void ToolManager::OnCaptureLost( wxMouseCaptureLostEvent & event )
1336 {
1337    // Can't do anything if we're not dragging.  This also prevents
1338    // us from intercepting events that don't belong to us from the
1339    // parent since we're Connect()ed to a couple.
1340    if( !mDragWindow )
1341    {
1342       event.Skip();
1343       return;
1344    }
1345 
1346    // Simulate button up
1347    wxMouseEvent e(wxEVT_LEFT_UP);
1348    e.SetEventObject(mParent);
1349    OnMouse(e);
1350 }
1351 
1352 //
1353 // Watch for shift key changes
1354 //
OnTimer(wxTimerEvent & event)1355 void ToolManager::OnTimer( wxTimerEvent & event )
1356 {
1357    // Go ahead and set the event to propagate
1358    event.Skip();
1359 
1360    // Can't do anything if we're not dragging.  This also prevents
1361    // us from intercepting events that don't belong to us from the
1362    // parent since we're Connect()ed to a couple.
1363    if( !mDragWindow )
1364    {
1365       return;
1366    }
1367 
1368    bool state = wxGetKeyState( WXK_SHIFT );
1369    if( mLastState != state )
1370    {
1371       mLastState = state;
1372 
1373 #if defined(__WXMAC__)
1374       // Disable window animation
1375       wxSystemOptions::SetOption( wxMAC_WINDOW_PLAIN_TRANSITION, 1 );
1376 #endif
1377 
1378       mIndicator->Show( !state );
1379 
1380 #if defined(__WXMAC__)
1381       // Disable window animation
1382       wxSystemOptions::SetOption( wxMAC_WINDOW_PLAIN_TRANSITION, mTransition );
1383 #endif
1384    }
1385 
1386    return;
1387 }
1388 
1389 //
1390 // Handle Indicator paint events
1391 //
1392 // Really only needed for the Mac since SetBackgroundColour()
1393 // doesn't seem to work with shaped frames.
1394 //
OnIndicatorPaint(wxPaintEvent & event)1395 void ToolManager::OnIndicatorPaint( wxPaintEvent & event )
1396 {
1397    // TODO: Better to use a bitmap than a triangular region.
1398    wxWindow *w = (wxWindow *)event.GetEventObject();
1399    wxPaintDC dc( w );
1400    // TODO: Better (faster) to use the existing spare brush.
1401    wxBrush brush( theTheme.Colour( clrTrackPanelText ) );
1402    dc.SetBackground( brush );
1403    dc.Clear();
1404 }
1405 
1406 //
1407 // Handle Indicator creation event
1408 //
1409 // Without this, the initial Indicator window will be a solid blue square
1410 // until the next time it changes.
1411 //
OnIndicatorCreate(wxWindowCreateEvent & event)1412 void ToolManager::OnIndicatorCreate( wxWindowCreateEvent & event )
1413 {
1414 #if defined(__WXGTK__)
1415    mIndicator->SetShape( *mCurrent );
1416 #endif
1417    event.Skip();
1418 }
1419 
UndockBar(wxPoint mp)1420 void ToolManager::UndockBar( wxPoint mp )
1421 {
1422 #if defined(__WXMAC__)
1423    // Disable window animation
1424    wxSystemOptions::SetOption( wxMAC_WINDOW_PLAIN_TRANSITION, 1 );
1425 #endif
1426 
1427    // Adjust the starting position
1428    mp -= mDragOffset;
1429 
1430    // Inform toolbar of change
1431    mDragBar->SetDocked( NULL, true );
1432    mDragBar->SetPositioned();
1433 
1434    // Construct a NEW floater
1435    wxASSERT(mParent);
1436    mDragWindow = safenew ToolFrame( mParent, this, mDragBar, mp );
1437    mDragWindow->SetLayoutDirection(wxLayout_LeftToRight);
1438    // Make sure the ferry is visible
1439    mDragWindow->Show();
1440 
1441    // Notify parent of change
1442    Updated();
1443 
1444 #if defined(__WXMAC__)
1445    // Reinstate original transition
1446    wxSystemOptions::SetOption( wxMAC_WINDOW_PLAIN_TRANSITION, mTransition );
1447 #endif
1448 }
1449 
1450 //
1451 // Transition a toolbar from float to dragging
1452 //
OnGrabber(GrabberEvent & event)1453 void ToolManager::OnGrabber( GrabberEvent & event )
1454 {
1455    // No need to propagate any further
1456    event.Skip( false );
1457 
1458    if(event.IsEscaping())
1459       return HandleEscapeKey();
1460 
1461    // Remember which bar we're dragging
1462    mDragBar = mBars[ event.GetId() ].get();
1463 
1464    // Remember state, in case of ESCape key later
1465    if (mDragBar->IsDocked()) {
1466       mPrevDock = dynamic_cast<ToolDock*>(mDragBar->GetParent());
1467       wxASSERT(mPrevDock);
1468       mPrevSlot = mPrevDock->GetConfiguration().Find(mDragBar);
1469       mPrevDock->WrapConfiguration(mPrevConfiguration);
1470    }
1471    else
1472       mPrevPosition = mDragBar->GetParent()->GetPosition();
1473 
1474    // Calculate the drag offset
1475    wxPoint mp = event.GetPosition();
1476    mDragOffset = mp -
1477                  mDragBar->GetParent()->ClientToScreen( mDragBar->GetPosition() ) +
1478       wxPoint( 1, 1 );
1479 
1480    mClicked = true;
1481    if( mPrevDock )
1482    {
1483       mDragWindow = nullptr;
1484    }
1485    else
1486    {
1487       mDragWindow = (ToolFrame *) mDragBar->GetParent();
1488    }
1489 
1490    // We want all mouse events from this point on
1491    auto &window = GetProjectFrame( *mParent );
1492    if( !window.HasCapture() )
1493       window.CaptureMouse();
1494 
1495    // Start monitoring shift key changes
1496    mLastState = wxGetKeyState( WXK_SHIFT );
1497    mTimer.Start( 100 );
1498 }
1499 
1500 
HandleEscapeKey()1501 void ToolManager::HandleEscapeKey()
1502 {
1503    if (mDragBar) {
1504       if(mPrevDock) {
1505          // Sheriff John Stone,
1506          // Why don't you leave me alone?
1507          // Well, I feel so break up
1508          // I want to go home.
1509          mPrevDock->RestoreConfiguration(mPrevConfiguration);
1510          mPrevDock->Dock( mDragBar, true, mPrevSlot );
1511          Updated();
1512 
1513          // Done with the floater
1514          mDragWindow->ClearBar();
1515          mDragWindow->Destroy();
1516          mDragWindow = nullptr;
1517          mDragBar->Refresh(false);
1518       }
1519       else {
1520          // Floater remains, and returns to where it begain
1521          auto parent = mDragBar->GetParent();
1522          parent->SetPosition(mPrevPosition);
1523          mDragBar->SetDocked(NULL, false);
1524       }
1525 
1526       DoneDragging();
1527    }
1528 }
1529 
DoneDragging()1530 void ToolManager::DoneDragging()
1531 {
1532    // Done dragging - ensure grabber button isn't pushed
1533    if( mDragBar )
1534    {
1535       mDragBar->SetDocked( mDragBar->GetDock(), false );
1536    }
1537 
1538    // Release capture
1539    auto &window = GetProjectFrame( *mParent );
1540    if( window.HasCapture() )
1541    {
1542       window.ReleaseMouse();
1543    }
1544 
1545    // Hide the indicator
1546    mIndicator->Hide();
1547 
1548    mDragWindow = NULL;
1549    mDragDock = NULL;
1550    mDragBar = NULL;
1551    mPrevDock = NULL;
1552    mPrevSlot = { ToolBarConfiguration::UnspecifiedPosition };
1553    mPrevConfiguration.Clear();
1554    mLastPos.x = mBarPos.x = -1;
1555    mLastPos.y = mBarPos.y = -1;
1556    mTimer.Stop();
1557    mDidDrag = false;
1558    mClicked = false;
1559 
1560    RestoreFocus();
1561 }
1562 
RestoreFocus()1563 bool ToolManager::RestoreFocus()
1564 {
1565    if (mLastFocus) {
1566       auto temp1 = AButton::TemporarilyAllowFocus();
1567       auto temp2 = ASlider::TemporarilyAllowFocus();
1568       auto temp3 = MeterPanelBase::TemporarilyAllowFocus();
1569       mLastFocus->SetFocus();
1570       return true;
1571    }
1572    return false;
1573 }
1574 
1575 #include "../commands/CommandContext.h"
1576 #include "../Menus.h"
1577 
AttachedToolBarMenuItem(ToolBarID id,const CommandID & name,const TranslatableString & label_in,const Registry::OrderingHint & hint,std::vector<ToolBarID> excludeIDs)1578 AttachedToolBarMenuItem::AttachedToolBarMenuItem(
1579    ToolBarID id, const CommandID &name, const TranslatableString &label_in,
1580    const Registry::OrderingHint &hint,
1581    std::vector< ToolBarID > excludeIDs )
1582    : mId{ id }
1583    , mAttachedItem{
1584       Registry::Placement{ wxT("View/Other/Toolbars/Toolbars/Other"), hint },
1585       (  MenuTable::FinderScope(
1586             [this](AudacityProject &) -> CommandHandlerObject&
__anonea3c83480202() 1587                { return *this; } ),
1588          MenuTable::Command( name, label_in,
1589             &AttachedToolBarMenuItem::OnShowToolBar,
1590             AlwaysEnabledFlag,
__anonea3c83480302()1591             CommandManager::Options{}.CheckTest( [id](AudacityProject &project){
1592                auto &toolManager = ToolManager::Get( project );
1593                return toolManager.IsVisible( id ); } ) ) ) }
1594    , mExcludeIds{ std::move( excludeIDs ) }
1595 {}
1596 
OnShowToolBar(const CommandContext & context)1597 void AttachedToolBarMenuItem::OnShowToolBar( const CommandContext &context )
1598 {
1599    auto &project = context.project;
1600    auto &toolManager = ToolManager::Get( project );
1601 
1602    if( !toolManager.IsVisible( mId ) )
1603    {
1604       for ( const auto excludedID : mExcludeIds )
1605          toolManager.Expose( excludedID, false );
1606    }
1607 
1608    toolManager.ShowHide(mId);
1609    MenuManager::Get(project).ModifyToolbarMenus(project);
1610 }
1611