/********************************************************************** Audacity: A Digital Audio Editor ToolBar.cpp Dominic Mazzoni Shane T. Mueller Leland Lucius See ToolBar.h for details. *******************************************************************//** \file ToolBar.cpp Implements ToolBar *//*******************************************************************//** \class ToolBar \brief Works with ToolManager and ToolDock to provide a dockable window in which buttons can be placed. *//**********************************************************************/ #include "../Audacity.h" // for USE_* macros #include "ToolBar.h" #include "../Experimental.h" // For compilers that support precompilation, includes "wx/wx.h". #include #include // for wxUSE_* macros #ifndef WX_PRECOMP #include #include #include #include #include #include #include #include #include #endif /* */ #include "ToolDock.h" #include "../AllThemeResources.h" #include "../AColor.h" #include "../ImageManipulation.h" #include "../Project.h" #include "../commands/CommandManager.h" #include "../widgets/AButton.h" #include "../widgets/Grabber.h" #include "../Prefs.h" //////////////////////////////////////////////////////////// /// ToolBarResizer //////////////////////////////////////////////////////////// // // Width of the resize grab area // #define RWIDTH 4 /// \brief a wxWindow that provides the resizer for a toolbar on the /// right hand side. Responsible for drawing the resizer appearance, /// resizing mouse events and constraining the resizing. class ToolBarResizer final : public wxWindow { public: ToolBarResizer(ToolBar *mBar); virtual ~ToolBarResizer(); // We don't need or want to accept focus. // Note that AcceptsFocusFromKeyboard() is overridden rather than // AcceptsFocus(), so that resize can be cancelled by ESC bool AcceptsFocusFromKeyboard() const override {return false;} private: void OnErase(wxEraseEvent & event); void OnPaint(wxPaintEvent & event); void OnLeftDown(wxMouseEvent & event); void OnLeftUp(wxMouseEvent & event); void OnEnter(wxMouseEvent & event); void OnLeave(wxMouseEvent & event); void OnMotion(wxMouseEvent & event); void ResizeBar(const wxSize &size); void OnCaptureLost(wxMouseCaptureLostEvent & event); void OnKeyDown(wxKeyEvent &event); private: ToolBar *mBar; wxPoint mResizeOffset; wxSize mOrigSize; wxWindowRef mOrigFocus{}; DECLARE_EVENT_TABLE() }; // // Event table // BEGIN_EVENT_TABLE( ToolBarResizer, wxWindow ) EVT_ERASE_BACKGROUND( ToolBarResizer::OnErase ) EVT_PAINT( ToolBarResizer::OnPaint ) EVT_LEFT_DOWN( ToolBarResizer::OnLeftDown ) EVT_LEFT_UP( ToolBarResizer::OnLeftUp ) EVT_ENTER_WINDOW( ToolBarResizer::OnEnter ) EVT_LEAVE_WINDOW( ToolBarResizer::OnLeave ) EVT_MOTION( ToolBarResizer::OnMotion ) EVT_MOUSE_CAPTURE_LOST( ToolBarResizer::OnCaptureLost ) EVT_KEY_DOWN( ToolBarResizer::OnKeyDown ) END_EVENT_TABLE(); ToolBarResizer::ToolBarResizer(ToolBar *bar) : wxWindow(bar, wxID_ANY, wxDefaultPosition, wxSize(RWIDTH, -1)) { mBar = bar; SetCursor( wxCURSOR_SIZEWE ); } ToolBarResizer::~ToolBarResizer() { if(HasCapture()) ReleaseMouse(); } // // Handle background erasure // void ToolBarResizer::OnErase( wxEraseEvent & WXUNUSED(event) ) { // Ignore it to prevent flashing } // // This draws the background of a toolbar // void ToolBarResizer::OnPaint( wxPaintEvent & event ) { wxPaintDC dc( (wxWindow *) event.GetEventObject() ); // Start with a clean background // // Under GTK, we specifically set the toolbar background to the background // colour in the system theme. #if defined( __WXGTK__ ) // dc.SetBackground( wxBrush( wxSystemSettings::GetColour( wxSYS_COLOUR_BACKGROUND ) ) ); #endif dc.SetBackground( wxBrush( theTheme.Colour( clrMedium ) ) ); dc.Clear(); wxSize sz = GetSize(); AColor::Dark( &dc, false ); AColor::Line(dc, sz.x - 4, 0, sz.x - 4, sz.y ); AColor::Line(dc, sz.x - 1, 0, sz.x - 1, sz.y ); } // // Handle toolbar resizing // void ToolBarResizer::OnLeftDown( wxMouseEvent & event ) { // Go ahead and set the event to propagate event.Skip(); // Retrieve the mouse position // Bug 1896: This is at time of processing the event, rather than at time // of generation of event. Works around event.GetPosition() giving // incorrect values if position of resizer is changing. mResizeOffset = wxGetMousePosition()-mBar->GetRect().GetBottomRight(); mOrigSize = mBar->GetSize(); // We want all of the mouse events if( !HasCapture() ) CaptureMouse(); } void ToolBarResizer::OnLeftUp( wxMouseEvent & event ) { // Go ahead and set the event to propagate event.Skip(); if( HasCapture() ) { ReleaseMouse(); if (mOrigFocus) mOrigFocus->SetFocus(); mOrigFocus = nullptr; mBar->ResizingDone(); } } void ToolBarResizer::OnEnter( wxMouseEvent & /*event*/ ) { // Bug 1201: On Mac, unsetting and re-setting the tooltip may be needed // to make it pop up when we want it. const auto text = GetToolTipText(); UnsetToolTip(); SetToolTip(text); if (!mOrigFocus) mOrigFocus = FindFocus(); } void ToolBarResizer::OnLeave( wxMouseEvent & /*event*/ ) { if (!GetCapture()) mOrigFocus = nullptr; } void ToolBarResizer::OnMotion( wxMouseEvent & event ) { // Go ahead and set the event to propagate event.Skip(); if( HasCapture() && event.Dragging() ) { // Retrieve the mouse position // Bug 1896: This is at time of processing the event, rather than at time // of generation of event. Works around event.GetPosition() giving // incorrect values if position of resizer is changing. wxPoint pos = wxGetMousePosition(); wxRect r = mBar->GetRect(); wxSize minsz = mBar->GetMinSize(); wxSize maxsz = mBar->GetMaxSize(); wxSize psz = mBar->GetParent()->GetClientSize(); // Adjust the size based on updated mouse position. r.width = ( pos.x - mResizeOffset.x ) - r.x; // Keep it within max size, if specified if( maxsz != wxDefaultSize ) { if( r.width > maxsz.x ) { r.width = maxsz.x; } if( r.height > maxsz.y ) { r.height = maxsz.y; } } // Constrain if( r.width < minsz.x ) { // Don't allow resizing to go too small r.width = minsz.x; } else if( r.GetRight() > psz.x - 3 ) { // Don't allow resizing to go too large // // The 3 magic pixels are because I'm too chicken to change the // calculations in ToolDock::LayoutToolBars() even though I'm // the one that set them up. :-) r.SetRight( psz.x - 3 ); } ResizeBar( r.GetSize() ); } } void ToolBarResizer::ResizeBar(const wxSize &size) { mBar->SetSize( size ); // Tell everyone we've changed sizes mBar->Updated(); // Refresh our world mBar->GetParent()->Refresh(); mBar->GetParent()->Update(); } void ToolBarResizer::OnCaptureLost( wxMouseCaptureLostEvent & WXUNUSED(event) ) { if( HasCapture() ) { ReleaseMouse(); if (mOrigFocus) mOrigFocus->SetFocus(); mOrigFocus = nullptr; } } void ToolBarResizer::OnKeyDown(wxKeyEvent &event) { event.Skip(); if (HasCapture() && WXK_ESCAPE == event.GetKeyCode()) { ResizeBar( mOrigSize ); ReleaseMouse(); if (mOrigFocus) mOrigFocus->SetFocus(); mOrigFocus = nullptr; } } //////////////////////////////////////////////////////////// /// Methods for ToolBar //////////////////////////////////////////////////////////// // // Define class to RTTI // IMPLEMENT_CLASS( ToolBar, wxPanelWrapper ); // // Custom event // DEFINE_EVENT_TYPE(EVT_TOOLBAR_UPDATED) // // Event table // BEGIN_EVENT_TABLE( ToolBar, wxPanelWrapper ) EVT_PAINT( ToolBar::OnPaint ) EVT_ERASE_BACKGROUND( ToolBar::OnErase ) EVT_MOUSE_EVENTS( ToolBar::OnMouseEvents ) END_EVENT_TABLE() // // Constructor // ToolBar::ToolBar( AudacityProject &project, int type, const TranslatableString &label, const wxString §ion, bool resizable ) : wxPanelWrapper() , mProject{ project } { // Save parameters mType = type; mLabel = label; mSection = section; mResizable = resizable; // Initialize everything mParent = NULL; mHSizer = NULL; mVisible = false; mPositioned = false; mGrabber = NULL; mResizer = NULL; SetId(mType); } // // Destructor // ToolBar::~ToolBar() { } // // Returns the toolbar title // TranslatableString ToolBar::GetTitle() { /* i18n-hint: %s will be replaced by the name of the kind of toolbar.*/ return XO("Audacity %s Toolbar").Format( GetLabel() ); } // // Returns the toolbar label // TranslatableString ToolBar::GetLabel() { return mLabel; } // // Returns the toolbar preferences section // wxString ToolBar::GetSection() { return mSection; } // // Returns the toolbar type // int ToolBar::GetType() { return mType; } // // Set the toolbar label // void ToolBar::SetLabel(const wxString & label) { // Probably shouldn't reach this overload, but perhaps virtual function // dispatch will take us here from a pointer to the wxPanel base class mLabel = Verbatim( label ); } void ToolBar::SetLabel(const TranslatableString & label) { // Only this overload is publicly accessible when you have a pointer to // Toolbar or a subclass of it mLabel = label; } // // Returns whether the toolbar is resizable or not // bool ToolBar::IsResizable() const { return mResizable; } // // Returns the dock state of the toolbar // bool ToolBar::IsDocked() const { return const_cast(this)->GetDock() != nullptr; } // // Returns the visibility of the toolbar // bool ToolBar::IsVisible() const { return mVisible; } void ToolBar::SetVisible( bool bVisible ) { mVisible = bVisible; } // // Show or hide the toolbar // bool ToolBar::Expose( bool show ) { bool was = mVisible; SetVisible( show ); if( IsDocked() ) { Show( show ); if( show ) { Refresh(); } } else { wxWindow * pParent = GetParent(); if( !IsPositioned() && show ){ SetPositioned(); pParent->CentreOnParent(); pParent->Move( pParent->GetPosition() + wxSize( mType*10, mType*10 )); } pParent->Show( show ); } return was; } // // Initialize the toolbar // void ToolBar::Create( wxWindow *parent ) { // Save parameters mParent = parent; // Create the window and label it wxPanelWrapper::Create( mParent, mType, wxDefaultPosition, wxDefaultSize, wxNO_BORDER | wxTAB_TRAVERSAL, GetTitle() ); wxPanelWrapper::SetLabel( GetLabel() ); // Go do the rest of the creation ReCreateButtons(); // ToolManager depends on this appearing to be visible for proper dock construction mVisible = true; } void ToolBar::SetToDefaultSize(){ wxSize sz; sz.SetHeight( -1 ); sz.SetWidth( GetInitialWidth()); SetSize( sz ); } wxSize ToolBar::GetSmartDockedSize() { const int tbs = toolbarSingle + toolbarGap; wxSize sz = GetSize(); // 46 is the size where we switch from expanded to compact. if( sz.y < 46 ) sz.y = tbs-1; else sz.y = 2 * tbs -1; return sz; } void ToolBar::ReCreateButtons() { wxSize sz3 = GetSize(); //wxLogDebug( "x:%i y:%i",sz3.x, sz3.y); // SetSizer(NULL) detaches mHSizer and deletes it. // Do not use Detach() here, as that attempts to detach mHSizer from itself! SetSizer( NULL ); // Get rid of any children we may have DestroyChildren(); mGrabber = NULL; mResizer = NULL; SetLayoutDirection(wxLayout_LeftToRight); // Refresh the background before populating if (!IsDocked()) { GetParent()->Refresh(); } { // Create the main sizer auto ms = std::make_unique(wxHORIZONTAL); // Create the grabber and add it to the main sizer mGrabber = safenew Grabber(this, mType); ms->Add(mGrabber, 0, wxEXPAND | wxALIGN_LEFT | wxALIGN_TOP | wxRIGHT, 1); // Use a box sizer for laying out controls ms->Add((mHSizer = safenew wxBoxSizer(wxHORIZONTAL)), 1, wxEXPAND); // Go add all the rest of the gadgets Populate(); // Add some space for the resize border if (IsResizable()) { // Create the resizer and add it to the main sizer mResizer = safenew ToolBarResizer(this); ms->Add(mResizer, 0, wxEXPAND | wxALIGN_TOP | wxLEFT, 1); mResizer->SetToolTip(_("Click and drag to resize toolbar")); } // Set dock after possibly creating resizer. // (Re)Establish dock state SetDocked(GetDock(), false); // Set the sizer SetSizerAndFit(ms.release()); } // Recalculate the height to be a multiple of toolbarSingle const int tbs = toolbarSingle + toolbarGap; wxSize sz = GetSize(); sz.y = ( ( ( sz.y + tbs -1) / tbs ) * tbs ) - 1; // Set the true AND minimum sizes and do final layout if(IsResizable()) { // JKC we're going to allow all resizable toolbars to be resized // to 1 unit high, typically 27 pixels. wxSize sz2 = sz; sz2.SetWidth(GetMinToolbarWidth()); sz2.y = tbs -1; SetMinSize(sz2); // sz2 is now the minimum size. // sz3 is the size we were. // We're recreating buttons, and we want to preserve original size. // But not if that makes the size too small. // Size at least as big as minimum. if( sz3.y < sz2.y ) sz3.y = sz2.y; if( sz3.x < sz2.x ) sz3.x = sz2.x; SetSize(sz3); } else { SetInitialSize(sz); } Layout(); } // The application preferences have changed, so update any elements that may // depend on them. void ToolBar::UpdatePrefs() { #if wxUSE_TOOLTIPS // Change the tooltip of the grabber if ( mGrabber ) { mGrabber->SetToolTip( GetTitle() ); } // Change the tooltip of the resizer if ( mResizer ) { mResizer->SetToolTip( _("Click and drag to resize toolbar") ); wxSizeEvent e; GetParent()->GetEventHandler()->AddPendingEvent( e ); GetParent()->Refresh(); } #endif return; } // // Return the pointer to the ToolBock where this bar lives // ToolDock *ToolBar::GetDock() { return dynamic_cast(GetParent()); } // // Toggle the docked/floating state // void ToolBar::SetDocked( ToolDock *dock, bool pushed ) { // Remember it // mDock = dock; // Change the tooltip of the grabber #if wxUSE_TOOLTIPS mGrabber->SetToolTip( GetTitle() ); #endif // Set the grabber button state mGrabber->PushButton( pushed ); if (mResizer) { mResizer->Show(dock != NULL); Layout(); } } // // Notify parent of changes // void ToolBar::Updated() { if( IsDocked() ) GetDock()->Updated(); else // Bug 2120. Changing the choice also changes the size of the toolbar so // we need to update the client size, even if undocked. // If modifying/improving this, remember to test both changing the choice, // and clicking on the choice but not actually changing it. GetParent()->SetClientSize( GetSize() + wxSize( 2,2)); //wxCommandEvent e( EVT_TOOLBAR_UPDATED, GetId() ); //GetParent()->GetEventHandler()->AddPendingEvent( e ); } // // Returns a pointer to the main sizer // wxBoxSizer *ToolBar::GetSizer() { return mHSizer; } // // Add a window to the main sizer // void ToolBar::Add( wxWindow *window, int proportion, int flag, int border, wxObject* userData ) { mHSizer->Add( window, proportion, flag, border, userData ); } // // Add a child sizer to the main sizer // void ToolBar::Add( wxSizer *sizer, int proportion, int flag, int border, wxObject* userData ) { mHSizer->Add( sizer, proportion, flag, border, userData ); } // // Add some space to the main sizer // void ToolBar::Add( int width, int height, int proportion, int flag, int border, wxObject* userData ) { mHSizer->Add( width, height, proportion, flag, border, userData ); } // // Adds a spacer to the main sizer // void ToolBar::AddSpacer( int size ) { mHSizer->AddSpacer( size ); } // // Adds a strechable spacer to the main sizer // void ToolBar::AddStretchSpacer( int prop ) { mHSizer->AddStretchSpacer( prop ); } // // Detach a window from the main sizer // void ToolBar::Detach( wxWindow *window ) { mHSizer->Detach( window ); } // // Detach a child sizer from the main sizer // void ToolBar::Detach( wxSizer *sizer ) { mHSizer->Detach( sizer ); } void ToolBar::MakeMacRecoloredImage(teBmps eBmpOut, teBmps eBmpIn ) { theTheme.ReplaceImage( eBmpOut, &theTheme.Image( eBmpIn )); } void ToolBar::MakeRecoloredImage( teBmps eBmpOut, teBmps eBmpIn ) { // Don't recolour the buttons... MakeMacRecoloredImage( eBmpOut, eBmpIn ); } void ToolBar:: MakeButtonBackgroundsLarge() { bool bUseAqua = false; #ifdef EXPERIMENTAL_THEME_PREFS gPrefs->Read( wxT("/GUI/ShowMac"), &bUseAqua, false); #endif #ifdef USE_AQUA_THEME bUseAqua = !bUseAqua; #endif if( bUseAqua ){ MakeMacRecoloredImage( bmpRecoloredUpLarge, bmpMacUpButton ); MakeMacRecoloredImage( bmpRecoloredDownLarge, bmpMacDownButton ); MakeMacRecoloredImage( bmpRecoloredUpHiliteLarge, bmpMacHiliteUpButton ); MakeMacRecoloredImage( bmpRecoloredHiliteLarge, bmpMacHiliteButton ); } else { MakeRecoloredImage( bmpRecoloredUpLarge, bmpUpButtonLarge ); MakeRecoloredImage( bmpRecoloredDownLarge, bmpDownButtonLarge ); MakeRecoloredImage( bmpRecoloredUpHiliteLarge, bmpHiliteUpButtonLarge ); MakeRecoloredImage( bmpRecoloredHiliteLarge, bmpHiliteButtonLarge ); } } void ToolBar::MakeButtonBackgroundsSmall() { bool bUseAqua = false; #ifdef EXPERIMENTAL_THEME_PREFS gPrefs->Read( wxT("/GUI/ShowMac"), &bUseAqua, false); #endif #ifdef USE_AQUA_THEME bUseAqua = !bUseAqua; #endif if( bUseAqua ){ MakeMacRecoloredImage( bmpRecoloredUpSmall, bmpMacUpButtonSmall ); MakeMacRecoloredImage( bmpRecoloredDownSmall, bmpMacDownButtonSmall ); MakeMacRecoloredImage( bmpRecoloredUpHiliteSmall, bmpMacHiliteUpButtonSmall ); MakeMacRecoloredImage( bmpRecoloredHiliteSmall, bmpMacHiliteButtonSmall ); } else { MakeRecoloredImage( bmpRecoloredUpSmall, bmpUpButtonSmall ); MakeRecoloredImage( bmpRecoloredDownSmall, bmpDownButtonSmall ); MakeRecoloredImage( bmpRecoloredUpHiliteSmall, bmpHiliteUpButtonSmall ); MakeRecoloredImage( bmpRecoloredHiliteSmall, bmpHiliteButtonSmall ); } } /// Makes a button and its four different state bitmaps /// @param parent Parent window for the button. /// @param eUp Background for when button is Up. /// @param eDown Background for when button is Down. /// @param eHilite Background for when button is Hilit. /// @param eStandardUp Foreground when enabled, up. /// @param eStandardDown Foreground when enabled, down. /// @param eDisabled Foreground when disabled. /// @param id Windows Id. /// @param placement Placement position /// @param processdownevents true iff button handles down events. /// @param size Size of the background. AButton * ToolBar::MakeButton(wxWindow *parent, teBmps eUp, teBmps eDown, teBmps eHilite, teBmps eDownHi, teBmps eStandardUp, teBmps eStandardDown, teBmps eDisabled, wxWindowID id, wxPoint placement, bool processdownevents, wxSize size) { // wxMax to cater for case of image being bigger than the button. int xoff = wxMax( 0, (size.GetWidth() - theTheme.Image(eStandardUp).GetWidth())/2); int yoff = wxMax( 0, (size.GetHeight() - theTheme.Image(eStandardUp).GetHeight())/2); typedef std::unique_ptr wxImagePtr; wxImagePtr up2 (OverlayImage(eUp, eStandardUp, xoff, yoff)); wxImagePtr hilite2 (OverlayImage(eHilite, eStandardUp, xoff, yoff)); wxImagePtr down2 (OverlayImage(eDown, eStandardDown, xoff + 1, yoff + 1)); wxImagePtr downHi2 (OverlayImage(eDownHi, eStandardDown, xoff + 1, yoff + 1)); wxImagePtr disable2 (OverlayImage(eUp, eDisabled, xoff, yoff)); wxASSERT(parent); // to justify safenew AButton * button = safenew AButton(parent, id, placement, size, *up2, *hilite2, *down2, *downHi2, *disable2, processdownevents); return button; } //static void ToolBar::MakeAlternateImages(AButton &button, int idx, teBmps eUp, teBmps eDown, teBmps eHilite, teBmps eDownHi, teBmps eStandardUp, teBmps eStandardDown, teBmps eDisabled, wxSize size) { // wxMax to cater for case of image being bigger than the button. int xoff = wxMax( 0, (size.GetWidth() - theTheme.Image(eStandardUp).GetWidth())/2); int yoff = wxMax( 0, (size.GetHeight() - theTheme.Image(eStandardUp).GetHeight())/2); typedef std::unique_ptr wxImagePtr; wxImagePtr up (OverlayImage(eUp, eStandardUp, xoff, yoff)); wxImagePtr hilite (OverlayImage(eHilite, eStandardUp, xoff, yoff)); wxImagePtr down (OverlayImage(eDown, eStandardDown, xoff + 1, yoff + 1)); wxImagePtr downHi (OverlayImage(eDownHi, eStandardDown, xoff + 1, yoff + 1)); wxImagePtr disable (OverlayImage(eUp, eDisabled, xoff, yoff)); button.SetAlternateImages(idx, *up, *hilite, *down, *downHi, *disable); } void ToolBar::SetButtonToolTip (AudacityProject &theProject, AButton &button, const ComponentInterfaceSymbol commands[], size_t nCommands) { TranslatableString result; const auto project = &theProject; const auto commandManager = project ? &CommandManager::Get( *project ) : nullptr; if (commandManager) result = commandManager->DescribeCommandsAndShortcuts(commands, nCommands); button.SetToolTip( result ); } // // This changes the state a button (from up to down or vice versa) // void ToolBar::SetButton( bool down, AButton * button ) { if( down ) { button->PushDown(); } else { button->PopUp(); } } // // Handle background erasure // void ToolBar::OnErase( wxEraseEvent & WXUNUSED(event) ) { // Ignore it to prevent flashing } // // This draws the background of a toolbar // void ToolBar::OnPaint( wxPaintEvent & WXUNUSED(event) ) { wxPaintDC dc( this ); // Themed background colour. dc.SetBackground( wxBrush( theTheme.Colour( clrMedium ) ) ); dc.Clear(); Repaint( &dc ); } void ToolBar::OnMouseEvents(wxMouseEvent &event) { // Do this hack so scrubber can detect mouse drags anywhere event.ResumePropagation(wxEVENT_PROPAGATE_MAX); event.Skip(); } int ToolBar::GetResizeGrabberWidth() { return RWIDTH; } namespace { RegisteredToolbarFactory::Functions &GetFunctions() { static RegisteredToolbarFactory::Functions factories( ToolBarCount ); return factories; } } RegisteredToolbarFactory::RegisteredToolbarFactory( int id, const Function &function) { wxASSERT( id >= 0 && id < ToolBarCount ); GetFunctions()[ id ] = function; } auto RegisteredToolbarFactory::GetFactories() -> const Functions& { return GetFunctions(); }