From a5db7bbb2bd0c61eedb0dd1cd71d398095450074 Mon Sep 17 00:00:00 2001 From: Leland Lucius Date: Sun, 12 Apr 2020 01:14:08 -0500 Subject: [PATCH] Bug 2318 - Linux: Time Toolbar is needlessly resized too large when accommodating longer time format --- src/toolbars/TimerToolBar.cpp | 531 ++++++++++++++------------------ src/toolbars/TimerToolBar.h | 72 ++--- src/toolbars/ToolBar.cpp | 21 +- src/toolbars/ToolManager.cpp | 15 + src/widgets/NumericTextCtrl.cpp | 168 ++++++---- src/widgets/NumericTextCtrl.h | 9 +- 6 files changed, 402 insertions(+), 414 deletions(-) diff --git a/src/toolbars/TimerToolBar.cpp b/src/toolbars/TimerToolBar.cpp index 61ba7ffa0..737db99a1 100644 --- a/src/toolbars/TimerToolBar.cpp +++ b/src/toolbars/TimerToolBar.cpp @@ -2,7 +2,7 @@ Audacity: A Digital Audio Editor - TimerToolBar.cpp + TimeToolBar.cpp This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -11,14 +11,7 @@ *//*******************************************************************/ - #include "../Audacity.h" -#include "../Experimental.h" - -#include "SelectionBar.h" - -#include "SelectionBarListener.h" -#include "ToolManager.h" // For compilers that support precompilation, includes "wx/wx.h". #include @@ -26,393 +19,331 @@ #include // for wxUSE_* macros #ifndef WX_PRECOMP -#include #include -#include #include -#include -#include #endif -#include -#include "SelectionBarListener.h" -#include "SelectionBar.h" #include "TimerToolBar.h" +#include "ToolManager.h" +#include "SelectionBarListener.h" -//#include "../widgets/AButton.h" #include "../AudioIO.h" -#include "../AColor.h" -#include "../KeyboardCapture.h" -#include "../Prefs.h" -#include "../Project.h" #include "../ProjectAudioIO.h" -#include "../ProjectSettings.h" -#include "../Snap.h" #include "../ViewInfo.h" -#include "../widgets/NumericTextCtrl.h" -#include "../AllThemeResources.h" - -#if wxUSE_ACCESSIBILITY -#include "../widgets/WindowAccessible.h" -#endif IMPLEMENT_CLASS(TimerToolBar, ToolBar); -enum { - TimerToolBarFirstID = 2700, - AudioTimeID, -}; - BEGIN_EVENT_TABLE(TimerToolBar, ToolBar) - EVT_SIZE(TimerToolBar::OnSize) - EVT_IDLE( TimerToolBar::OnIdle ) EVT_COMMAND(wxID_ANY, EVT_TIMETEXTCTRL_UPDATED, TimerToolBar::OnUpdate) - EVT_COMMAND(wxID_ANY, EVT_CAPTURE_KEY, TimerToolBar::OnCaptureKey) + EVT_SIZE(TimerToolBar::OnSize) + EVT_IDLE(TimerToolBar::OnIdle) END_EVENT_TABLE() -TimerToolBar::TimerToolBar( AudacityProject &project ) -: ToolBar(project, TimeBarID, XO("Time"), wxT("Time"),true), -mListener(NULL), mAudioTime(NULL) +TimerToolBar::TimerToolBar(AudacityProject &project) +: ToolBar(project, TimeBarID, XO("Time"), wxT("Time"), true), + mListener(NULL), + mAudioTime(NULL) { - mMinWidth = 50; - mDigitHeight = 48; - mbReady = false; - mbPreserveHeight = false; - mbPreserveWidth = false; - mRate = (double) gPrefs->Read(wxT("/SamplingRate/DefaultProjectSampleRate"), - AudioIO::GetOptimalSupportedSampleRate()); } TimerToolBar::~TimerToolBar() { } -TimerToolBar &TimerToolBar::Get( AudacityProject &project ) +TimerToolBar &TimerToolBar::Get(AudacityProject &project) { - auto &toolManager = ToolManager::Get( project ); - return *static_cast( toolManager.GetToolBar(TimeBarID) ); + auto &toolManager = ToolManager::Get(project); + return *static_cast(toolManager.GetToolBar(TimeBarID)); } -const TimerToolBar &TimerToolBar::Get( const AudacityProject &project ) +const TimerToolBar &TimerToolBar::Get(const AudacityProject &project) { - return Get( const_cast( project )) ; -} - -void TimerToolBar::Create(wxWindow * parent) -{ - mbIsCreating = true; - ToolBar::Create(parent); - UpdatePrefs(); - mbIsCreating = false; -} - -NumericTextCtrl * TimerToolBar::AddTime( - const TranslatableString &Name, int id, wxSizer * pSizer) -{ - auto formatName = mListener ? mListener->TT_GetAudioTimeFormat() - : NumericConverter::HoursMinsSecondsFormat(); - auto pCtrl = safenew NumericTextCtrl( - this, id, NumericConverter::TIME, formatName, 0.0, mRate); - pCtrl->SetName(Name); - pCtrl->SetReadOnly(true); - pCtrl->SetDigitSize( mDigitHeight * 0.66,mDigitHeight ); - pSizer->Add(pCtrl, 0, wxALIGN_CENTER , 0); - return pCtrl; + return Get(const_cast(project)) ; } void TimerToolBar::Populate() { + // Get the default sample rate + auto rate = gPrefs->Read(wxT("/SamplingRate/DefaultProjectSampleRate"), + AudioIO::GetOptimalSupportedSampleRate()); - wxSizer *mainSizer = GetSizer(); + // Get the default time format + auto format = NumericConverter::HoursMinsSecondsFormat(); - mAudioTime = AddTime(XO("Audio Position"), AudioTimeID, mainSizer); - - mainSizer->Layout(); - RegenerateTooltips(); - SetMinSize(GetSizer()->GetMinSize()); + // Create the read-only time control + mAudioTime = safenew NumericTextCtrl(this, wxID_ANY, NumericConverter::TIME, format, 0.0, rate); + mAudioTime->SetName(XO("Audio Position")); + mAudioTime->SetReadOnly(true); + + // Add it to the toolbar + Add(mAudioTime, 0, wxALIGN_CENTER, 0); + + // Calculate the width to height ratio + wxSize digitSize = mAudioTime->GetDigitSize(); + mDigitRatio = (float)digitSize.x / digitSize.y; + + // Establish initial resizing limits +// SetResizingLimits(); +} + +void TimerToolBar::UpdatePrefs() +{ + // Since the language may have changed, we need to force an update to accommodate + // different length text + wxCommandEvent e; + e.SetInt(mAudioTime->GetFormatIndex()); + OnUpdate(e); + + // Language may have changed so reset label + SetLabel(XO("Time")); + + // Give the toolbar a chance + ToolBar::UpdatePrefs(); +} + +void TimerToolBar::SetToDefaultSize() +{ + // Reset + SetMaxSize(wxDefaultSize); + SetMinSize(wxDefaultSize); + + // Set the default time format + SetAudioTimeFormat(NumericConverter::HoursMinsSecondsFormat()); + + // Set the default size + SetSize(GetInitialWidth(), 48); + + // Inform others the toobar has changed + Updated(); +} + +wxSize TimerToolBar::GetDockedSize() +{ + wxSize sz = GetSize(); + + // Anything less than a single height bar becomes single height + if (sz.y <= toolbarSingle) { + sz.y = toolbarSingle; + } + // Otherwise it will be a double height bar + else { + sz.y = 2 * toolbarSingle + toolbarGap; + } + + return sz; +} + +void TimerToolBar::SetDocked(ToolDock *dock, bool pushed) +{ + // It's important to call this FIRST since it unhides the resizer control. + // Not doing so causes the calculated best size to be off by the width + // of the resizer. + ToolBar::SetDocked(dock, pushed); + + // Recalculate the min and max limits + SetResizingLimits(); + + // When moving from floater to dock, fit to toolbar since the resizer will + // be mispositioned + if (dock) { + // Fit() while retaining height + SetSize(GetBestSize().x, GetSize().y); + + // Inform others the toolbar has changed + Updated(); + } } void TimerToolBar::SetListener(TimerToolBarListener *l) { - mbPreserveWidth = true; + // Remember the listener mListener = l; + + // Get (and set) the saved time format SetAudioTimeFormat(mListener->TT_GetAudioTimeFormat()); - mbPreserveWidth = false; }; - -void TimerToolBar::UpdatePrefs() -{ - mbPreserveWidth = true; - wxCommandEvent e; - e.SetInt(mAudioTime->GetFormatIndex()); - OnUpdate(e); - mbPreserveWidth = false; - - SetLabel(XO("Time")); - ToolBar::UpdatePrefs(); -} - void TimerToolBar::SetAudioTimeFormat(const NumericFormatSymbol & format) { - bool changed = - mAudioTime->SetFormatString(mAudioTime->GetBuiltinFormat(format)); - - // Test first whether changed, to avoid infinite recursion from OnUpdate - if ( changed || !mbReady ) { - if (!mbReady) { - //wxLogDebug("Ready!"); - mbReady = true; - } + // Set the format if it's different from previous + if (mAudioTime->SetFormatString(mAudioTime->GetBuiltinFormat(format))) { + // Simulate an update since the format has changed. wxCommandEvent e; e.SetInt(mAudioTime->GetFormatIndex()); OnUpdate(e); } } +// The intention of this is to get the resize handle in the +// correct position, after we've let go in dragging. +void TimerToolBar::ResizingDone() +{ + // Fit() while retaining height + SetSize(GetBestSize().x, GetSize().y); + + // Inform others the toobar has changed + Updated(); +} + +void TimerToolBar::SetResizingLimits() +{ + // Reset limits + SetMinSize(wxDefaultSize); + SetMaxSize(wxDefaultSize); + + // If docked we use the current bar height since it's always a single or double height + // toolbar. For floaters, single height toolbar is the minimum height. + int minH = IsDocked() ? GetSize().y : toolbarSingle; + + // Get the content size given the smallest digit height we allow + wxSize minSize = ComputeSizing(17); + + // Account for any borders added by the window manager + minSize.x += (mAudioTime->GetSize().x - mAudioTime->GetClientSize().x); + + // Calculate the space used by other controls and sizer borders with this toolbar + wxSize outer = (GetSize() - GetSizer()->GetSize()); + + // And account for them in the width + minSize.x += outer.x; + + // Override the height + minSize.y = minH; + + // Get the maximum digit height we can use. This is restricted to the toolbar's + // current height minus any control borders + int digH = minH - (mAudioTime->GetSize().y - mAudioTime->GetClientSize().y); + + // Get the content size using the digit height, if docked. Otherwise use the + // maximum digit height we allow. + wxSize maxSize = ComputeSizing(IsDocked() ? digH : 100); + + // Account for the other controls and sizer borders within this toolbar + maxSize.x += outer.x; + + // Account for any borders added by the window manager and +1 to keep toolbar + // from dropping to next smaller size when grabbing the resizer. + maxSize.x += (mAudioTime->GetSize().x - mAudioTime->GetClientSize().x) + 1; + + // Override the height + maxSize.y = IsDocked() ? minH : wxDefaultCoord; + + // And finally set them both + SetMinSize(minSize); + SetMaxSize(maxSize); +} + // Called when the format drop downs is changed. // This causes recreation of the toolbar contents. void TimerToolBar::OnUpdate(wxCommandEvent &evt) { - //wxLogDebug("OnUpdate"); - int index = evt.GetInt(); - wxWindow *w = FindFocus(); - - bool bHasFocus = (w == mAudioTime); - mbPreserveHeight = true; - - // If docked, font height is determined by toolbar height. - // If undocked, determined by last resize. - if (IsDocked()) - { - mDigitHeight = GetSize().GetHeight() - 6; - mDigitHeight = wxMin(mDigitHeight, 25); - } - evt.Skip(false); + + // Reset to allow resizing to work + SetMinSize(wxDefaultSize); + SetMaxSize(wxDefaultSize); + // Save format name before recreating the controls so they resize properly - { - auto format = mAudioTime->GetBuiltinName(index); - if (mListener) - mListener->TT_SetAudioTimeFormat(format); + if (mListener) { + mListener->TT_SetAudioTimeFormat(mAudioTime->GetBuiltinName(evt.GetInt())); } - wxSize sz = GetSize(); - RegenerateTooltips(); - // ReCreateButtons() will get rid of our sizers and controls - // so reset pointer first. - mAudioTime = NULL; - ReCreateButtons(); - - auto x = GetSizer()->GetMinSize().GetX(); - SetMinSize(wxSize(x, sz.GetHeight())); + // Go set the new size limits SetResizingLimits(); - if( bHasFocus ) - mAudioTime->SetFocus(); + // Fit() while retaining height + SetSize(GetBestSize().x, GetSize().y); + + // Inform others the toobar has changed Updated(); - mbPreserveHeight = false; } -void TimerToolBar::SetDocked(ToolDock *dock, bool pushed) +void TimerToolBar::OnSize(wxSizeEvent &evt) { - //wxLogDebug("SetDocked"); - ToolBar::SetDocked(dock, pushed); - if (!mbReady) + evt.Skip(); + + // Can fire before we're ready + if (!mAudioTime) { return; - - // Do not use IsDocked() here. It's not valid! - if ( dock == nullptr) { - // The min height when undocked is always 22 - wxSize sz = GetMinSize(); - sz.y = 22; - SetMinSize(sz); - } - else { - //SetResizingLimits(); - ResizingDone(); } -} + // Make sure everything is where it's supposed to be + Layout(); -void TimerToolBar::SetToDefaultSize(){ - wxSize sz; - sz.SetHeight( 48 ); - sz.SetWidth( GetInitialWidth()); - SetSize( sz ); -} + // Get the sizer's size and remove any borders the time control might have. + wxSize sizerBR = GetSizer()->GetSize() - (mAudioTime->GetSize() - mAudioTime->GetClientSize()); -void TimerToolBar::SetResizingLimits() { + // Get the content size of the time control. This can be different than the + // control itself due to borders and sizer enduced changes. + wxSize timeBR = mAudioTime->GetDimensions(); - //wxLogDebug("Set Resizing Limits"); - if (!IsDocked()) { - wxWindow * pWnd = GetParent(); - ToolFrame * pFrame = dynamic_cast(pWnd); - Layout(); - if (pFrame) { - pFrame->Layout(); - // Set smallest conceivable min size - pFrame->SetMinSize(wxSize(80, 24)); - // Fit frame around the toolbar - pFrame->Fit(); + // Get the current digit box height + int h = mAudioTime->GetDigitSize().y; - // The resize handle takes 39 pixels. - // And was not accounted for in the Fit(). - wxSize sz = pFrame->GetSize(); - sz.x += 39; - sz.y += 2; - pFrame->SetSize(sz); - // Now compute and lock in the min frame size - // using aspect ratio, so that dragging won't go - // smaller than this. - pFrame->LockInMinSize(this); - } - } - else - { - Fit(); // Toolbar size to fit around minimum sizer. - Layout(); - //GetSizer()->RecalcSizes(); - wxSize sz1 = GetSizer()->GetMinSize(); - int minHeight = 21; - if (sz1.y > minHeight) { - sz1.x = (sz1.x * minHeight) / sz1.y; - GetSizer()->SetMinSize(sz1); - SetMinSize(sz1); - } - } -} - -// We are given the size of the toolbar. -// Compute digit Height and layout the Numeric Control. -void TimerToolBar::ResizeTime( const wxSize & sz) { - - int mx = sz.GetWidth() - 25; - int my = sz.GetHeight(); - //wxLogDebug("ResizeTime %i,%i",mx,my); - - // Digit height is 2 less than y - // OR fits to width, if width is a bit low. - int h = my-2; - h = wxMax(17, h); - h = wxMin(h, 77); - - //wxLogDebug("Try h=%i dimensions(%i,%i)", h,mx,my); - h++; - do { + // Increase current size to find the best fit within the new size + if (sizerBR.x > timeBR.x && sizerBR.y > timeBR.y) { + do { + h++; + timeBR = ComputeSizing(h); + } while (h < 150 && sizerBR.x > timeBR.x && sizerBR.y > timeBR.y); h--; - mAudioTime->SetDigitSize(h*0.66, h); - } while ((h > 17) && mAudioTime->IsTooBig(mx, my)); - mAudioTime->Layout(); - //wxLogDebug(" accept height:%i", h); - - mDigitHeight = h; -} - -// This 'OnSize' function is also called during moving the -// toolbar. -void TimerToolBar::OnSize(wxSizeEvent & ev) -{ - //wxLogDebug("OnSize"); - if (!mbReady) - return; - - ev.Skip(); - - if (!mAudioTime) - return; - - // If we are changing the time format, then we - // preserve the height. Otherwise we size the font to fit - // the space given. - if (mbPreserveWidth || !mbPreserveHeight) { - ResizeTime( ev.GetSize() ); + } + // In all other cases, we need to decrease current size to fit within new size + else if (sizerBR.x < timeBR.x || sizerBR.y < timeBR.y) { + do { + h--; + timeBR = ComputeSizing(h); + } while (h > 8 && (sizerBR.x < timeBR.x || sizerBR.y < timeBR.y)); } - // Refresh and update immediately, so that we don't get - // to see grot from partly redrawn toolbar during - // the resizing. - Refresh(true); + if (h != mAudioTime->GetDigitSize().y) { + mAudioTime->SetDigitSize(h * mDigitRatio, h); + } + + // Redraw the display immediately to smooth out resizing Update(); } -// The intention of this is to get the resize handle in the -// correct position, after we've let go in dragging. -void TimerToolBar::ResizingDone() { - if (!mbReady) - return; - wxSize sz = GetSize(); - //wxLogDebug("ReSizingDone %i,%i",sz.x,sz.y); - - Fit(); - sz.x = GetSize().GetX(); - //wxLogDebug("Fitted %i,%i",sz.x,sz.y); - SetSize(sz); - Updated(); -} - - -void TimerToolBar::SetTimes(double audio) -{ - mAudioTime->SetValue(wxMax( 0.0, audio)); -} - -void TimerToolBar::OnFocus(wxFocusEvent &event) -{ - KeyboardCapture::OnFocus(*this, event); -} - -void TimerToolBar::OnCaptureKey(wxCommandEvent &event) -{ - wxKeyEvent *kevent = (wxKeyEvent *)event.GetEventObject(); - wxWindow *w = FindFocus(); - int keyCode = kevent->GetKeyCode(); - - // Convert numeric keypad entries. - if ((keyCode >= WXK_NUMPAD0) && (keyCode <= WXK_NUMPAD9)) { - keyCode -= WXK_NUMPAD0 - '0'; - } - - if (keyCode >= '0' && keyCode <= '9') { - return; - } - - event.Skip(); -} - -void TimerToolBar::OnIdle( wxIdleEvent &evt ) +void TimerToolBar::OnIdle(wxIdleEvent &evt) { evt.Skip(); - auto &project = mProject; double audioTime; - auto &projectAudioIO = ProjectAudioIO::Get( project ); - if ( projectAudioIO.IsAudioActive() ){ + auto &projectAudioIO = ProjectAudioIO::Get(mProject); + if (projectAudioIO.IsAudioActive()) { auto gAudioIO = AudioIOBase::Get(); audioTime = gAudioIO->GetStreamTime(); } else { - const auto &playRegion = ViewInfo::Get( project ).playRegion; + const auto &playRegion = ViewInfo::Get(mProject).playRegion; audioTime = playRegion.GetStart(); } - SetTimes( audioTime); + mAudioTime->SetValue(wxMax(0.0, audioTime)); } -static RegisteredToolbarFactory factory{ TimeBarID, - []( AudacityProject &project ){ - return ToolBar::Holder{ safenew TimerToolBar{ project } }; } +static RegisteredToolbarFactory factory +{ + TimeBarID, + []( AudacityProject &project ) + { + return ToolBar::Holder{ safenew TimerToolBar{ project } }; + } }; -#ifdef EXPERIMENTAL_TIMER_TOOLBAR namespace { -AttachedToolBarMenuItem sAttachment{ +AttachedToolBarMenuItem sAttachment +{ + TimeBarID, + wxT("ShowTimeTB"), /* i18n-hint: Clicking this menu item shows the toolbar for viewing actual time of the cursor */ - TimeBarID, wxT("ShowTimeTB"), XXO("&Time Toolbar"), - { Registry::OrderingHint::After, "ShowSelectionTB" } + XXO("&Time Toolbar"), + { + Registry::OrderingHint::After, + "ShowSelectionTB" + } }; } -#endif diff --git a/src/toolbars/TimerToolBar.h b/src/toolbars/TimerToolBar.h index 694600b6d..15036a92f 100644 --- a/src/toolbars/TimerToolBar.h +++ b/src/toolbars/TimerToolBar.h @@ -2,26 +2,25 @@ Audacity: A Digital Audio Editor - TimerToolBar.h + TimeToolBar.h Jonatã Bolzan Loss **********************************************************************/ -#ifndef __AUDACITY_BIG_COUNTER__ -#define __AUDACITY_BIG_COUNTER__ +#ifndef __AUDACITY_TIME_TOOLBAR__ +#define __AUDACITY_TIME_TOOLBAR__ #include #include "ToolBar.h" -#include "ToolManager.h" +#include "../widgets/NumericTextCtrl.h" -class SelectionBarListener; class NumericTextCtrl; -class wxSize; +class TimerToolBarListener; -class TimerToolBar final : public ToolBar { - +class TimerToolBar final : public ToolBar +{ public: TimerToolBar(AudacityProject &project); virtual ~TimerToolBar(); @@ -29,56 +28,41 @@ public: static TimerToolBar &Get(AudacityProject &project); static const TimerToolBar &Get(const AudacityProject &project); - void Create(wxWindow *parent) override; - void Populate() override; void Repaint(wxDC * WXUNUSED(dc)) override {}; void EnableDisableButtons() override {}; void UpdatePrefs() override; - void OnUpdate(wxCommandEvent &evt); - void SetTimes(double audio); - + void RegenerateTooltips() override {}; + int GetInitialWidth() override {return 250;} + int GetMinToolbarWidth() override {return 50;} + void SetToDefaultSize() override; + wxSize GetDockedSize() override; + void SetDocked(ToolDock *dock, bool pushed) override; void SetListener(TimerToolBarListener *l); void SetAudioTimeFormat(const NumericFormatSymbol & format); - void RegenerateTooltips() override {}; - - int GetInitialWidth() override {return 250;} - int GetMinToolbarWidth() override { return mMinWidth; } - void SetToDefaultSize() override; - wxSize GetDockedSize() override { - return GetSmartDockedSize(); - }; - void SetDocked(ToolDock *dock, bool pushed) override; - void ResizeTime( const wxSize & sz ); - void SetResizingLimits(); void ResizingDone() override; private: - NumericTextCtrl * AddTime( const TranslatableString &Name, int id, - wxSizer * pSizer); - - void OnFocus(wxFocusEvent &event); - void OnCaptureKey(wxCommandEvent &event); + void SetResizingLimits(); + wxSize ComputeSizing(int digitH); + + void OnUpdate(wxCommandEvent &evt); void OnSize(wxSizeEvent &evt); - void OnIdle( wxIdleEvent &evt ); - - TimerToolBarListener * mListener; - double mRate; - double mAudio; - int mMinWidth; - int mDigitHeight; - bool mbReady; - bool mbIsCreating; - bool mbPreserveWidth; - bool mbPreserveHeight; - - NumericTextCtrl *mAudioTime; - wxChoice *mSnapTo; - + void OnIdle(wxIdleEvent &evt); + + TimerToolBarListener *mListener; + NumericTextCtrl *mAudioTime; + float mDigitRatio; + public: DECLARE_CLASS(TimerToolBar) DECLARE_EVENT_TABLE() }; +inline wxSize TimerToolBar::ComputeSizing(int digitH) +{ + return mAudioTime->ComputeSizing(false, digitH * mDigitRatio, digitH); +} + #endif diff --git a/src/toolbars/ToolBar.cpp b/src/toolbars/ToolBar.cpp index d4368b146..144ace4b8 100644 --- a/src/toolbars/ToolBar.cpp +++ b/src/toolbars/ToolBar.cpp @@ -228,17 +228,31 @@ void ToolBarResizer::OnMotion( wxMouseEvent & event ) wxPoint pos = wxGetMousePosition(); wxRect r = mBar->GetRect(); - wxSize msz = mBar->GetMinSize(); + 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 specificed + 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 < msz.x ) + if( r.width < minsz.x ) { // Don't allow resizing to go too small - r.width = msz.x; + r.width = minsz.x; } else if( r.GetRight() > psz.x - 3 ) { @@ -538,6 +552,7 @@ void ToolBar::ReCreateButtons() // Set dock after possibly creating resizer. // (Re)Establish dock state SetDocked(GetDock(), false); + // Set the sizer SetSizerAndFit(ms.release()); } diff --git a/src/toolbars/ToolManager.cpp b/src/toolbars/ToolManager.cpp index 6dfb54fc0..451c69c94 100644 --- a/src/toolbars/ToolManager.cpp +++ b/src/toolbars/ToolManager.cpp @@ -231,6 +231,21 @@ void ToolFrame::OnMotion( wxMouseEvent & event ) wxRect rect = GetRect(); rect.SetBottomRight( pos ); + + // Keep it within max size, if specificed + wxSize maxsz = mBar->GetMaxSize(); + if (maxsz != wxDefaultSize) + { + if (maxsz.x != wxDefaultCoord && rect.width > maxsz.x) + { + rect.width = maxsz.x; + } + if (maxsz.y != wxDefaultCoord && rect.height > maxsz.y) + { + rect.height = maxsz.y; + } + } + if( rect.width < mMinSize.x ) { rect.width = mMinSize.x; diff --git a/src/widgets/NumericTextCtrl.cpp b/src/widgets/NumericTextCtrl.cpp index 704ae3f43..5b3096a94 100644 --- a/src/widgets/NumericTextCtrl.cpp +++ b/src/widgets/NumericTextCtrl.cpp @@ -1318,12 +1318,17 @@ NumericTextCtrl::NumericTextCtrl(wxWindow *parent, wxWindowID id, { mAllowInvalidValue = false; - mDigitBoxW = 10; - mDigitBoxH = 16; + mDigitBoxW = 11; + mDigitBoxH = 19; + + mBorderLeft = 1; + mBorderTop = 1; + mBorderRight = 1; + mBorderBottom = 1; mReadOnly = options.readOnly; mMenuEnabled = options.menuEnabled; - mButtonWidth = 9; + mButtonWidth = mMenuEnabled ? 9 : 0; SetLayoutDirection(wxLayout_LeftToRight); Layout(); @@ -1458,78 +1463,119 @@ void NumericTextCtrl::SetInvalidValue(double invalidValue) SetValue(invalidValue); } - -void NumericTextCtrl::ComputeSizing() +wxSize NumericTextCtrl::ComputeSizing(bool update, wxCoord boxW, wxCoord boxH) { - unsigned int i, j; - int x, pos; + // Get current box size + if (boxW == 0) { + boxW = mDigitBoxW; + } - wxMemoryDC memDC; + if (boxH == 0) { + boxH = mDigitBoxH; + } + boxH -= (mBorderTop + mBorderBottom); - // Placeholder bitmap so the memDC has something to reference - mBackgroundBitmap = std::make_unique(1, 1, 24); - memDC.SelectObject(*mBackgroundBitmap); + // We can use the screen device context since we're not drawing to it + wxScreenDC dc; - mDigits.clear(); + // First calculate a rough point size + wxFont pf(wxSize(boxW, boxH), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL); + int fontSize = pf.GetPointSize(); + wxCoord strW; + wxCoord strH; - mBorderLeft = 1; - mBorderTop = 1; - mBorderRight = 1; - mBorderBottom = 1; - - int fontSize = 4; - wxCoord strW, strH; - wxString exampleText = wxT("0"); - - // Keep making the font bigger until it's too big, then subtract one. - memDC.SetFont(wxFont(fontSize, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL)); - memDC.GetTextExtent(exampleText, &strW, &strH); - while (strW <= mDigitBoxW && strH <= mDigitBoxH) { - fontSize++; - memDC.SetFont(wxFont(fontSize, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL)); - memDC.GetTextExtent(exampleText, &strW, &strH); + // Now decrease it until we fit within our digit box + dc.SetFont(pf); + dc.GetTextExtent(wxT("0"), &strW, &strH); + while (strW > boxW || strH > boxH) { + dc.SetFont(wxFont(--fontSize, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL)); + dc.GetTextExtent(wxT("0"), &strW, &strH); } fontSize--; - mDigitFont = std::make_unique(fontSize, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL); - memDC.SetFont(*mDigitFont); - memDC.GetTextExtent(exampleText, &strW, &strH); - mDigitW = strW; - mDigitH = strH; + // Create the digit font with the new point size + if (update) { + mDigitFont = std::make_unique(fontSize, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL); + dc.SetFont(*mDigitFont); + + // Remember the actual digit width and height using the new font + dc.GetTextExtent(wxT("0"), &mDigitW, &mDigitH); + } // The label font should be a little smaller - fontSize--; - mLabelFont = std::make_unique(fontSize, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL); + std::unique_ptr labelFont = std::make_unique(fontSize - 1, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL); - // Figure out the x-position of each field and label in the box - x = mBorderLeft; - pos = 0; - - memDC.SetFont(*mLabelFont); - memDC.GetTextExtent(mPrefix, &strW, &strH); - x += strW; - pos += mPrefix.length(); - - for(i = 0; i < mFields.size(); i++) { - mFields[i].fieldX = x; - for(j=0; j<(unsigned int)mFields[i].digits; j++) { - mDigits.push_back(DigitInfo(i, j, pos, wxRect(x, mBorderTop, - mDigitBoxW, mDigitBoxH))); - x += mDigitBoxW; - pos++; - } - - mFields[i].labelX = x; - memDC.GetTextExtent(mFields[i].label, &strW, &strH); - pos += mFields[i].label.length(); - x += strW; - mFields[i].fieldW = x; + // Use the label font for all remaining measurements since only non-digit text is left + dc.SetFont(*labelFont); + + // Remember the pointer if updating + if (update) { + mLabelFont = std::move(labelFont); } - mWidth = x + mBorderRight; - mHeight = mDigitBoxH + mBorderTop + mBorderBottom; -} + // Get the width of the prefix, if any + dc.GetTextExtent(mPrefix, &strW, &strH); + // Bump x-position to the end of the prefix + int x = mBorderLeft + strW; + + if (update) { + // Set the character position past the prefix + int pos = mPrefix.length(); + + // Reset digits array + mDigits.clear(); + + // Figure out the x-position of each field and label in the box + for (int i = 0, fcnt = mFields.size(); i < fcnt; ++i) { + // Get the size of the label + dc.GetTextExtent(mFields[i].label, &strW, &strH); + + // Remember this field's x-position + mFields[i].fieldX = x; + + // Remember metrics for each digit + for (int j = 0, dcnt = mFields[i].digits; j < dcnt; ++j) { + mDigits.push_back(DigitInfo(i, j, pos, wxRect(x, mBorderTop, boxW, boxH))); + x += boxW; + pos++; + } + + // Remember the label's x-position + mFields[i].labelX = x; + + // Bump to end of label + x += strW; + + // Remember the label's width + mFields[i].fieldW = x; + + // Bump character position to end of label + pos += mFields[i].label.length(); + } + } + else { + // Determine the maximum x-position (length) of the remaining fields + for (int i = 0, fcnt = mFields.size(); i < fcnt; ++i) { + // Get the size of the label + dc.GetTextExtent(mFields[i].label, &strW, &strH); + + // Just bump to next field + x += (boxW * mFields[i].digits) + strW; + } + } + + // Calculate the maximum dimensions + wxSize dim(x + mBorderRight, boxH + mBorderTop + mBorderBottom); + + // Save maximumFinally, calculate the minimum dimensions + if (update) { + mWidth = dim.x; + mHeight = dim.y; + } + + return wxSize(dim.x + mButtonWidth, dim.y); +} bool NumericTextCtrl::Layout() { diff --git a/src/widgets/NumericTextCtrl.h b/src/widgets/NumericTextCtrl.h index 7efd745bf..c87f5edba 100644 --- a/src/widgets/NumericTextCtrl.h +++ b/src/widgets/NumericTextCtrl.h @@ -192,8 +192,8 @@ class NumericTextCtrl final : public wxControl, public NumericConverter // Hide the inherited function that takes wxString void SetName( const TranslatableString &name ); + wxSize ComputeSizing(bool update = true, wxCoord digitW = 0, wxCoord digitH = 0); bool Layout() override; - void ComputeSizing(); void Fit() override; void SetSampleRate(double sampleRate); @@ -207,6 +207,8 @@ class NumericTextCtrl final : public wxControl, public NumericConverter void SetFieldFocus(int /* digit */); + wxSize GetDimensions() { return wxSize(mWidth + mButtonWidth, mHeight); } + wxSize GetDigitSize() { return wxSize(mDigitBoxW, mDigitBoxH); } void SetDigitSize(int width, int height); void SetReadOnly(bool readOnly = true); void EnableMenu(bool enable = true); @@ -219,11 +221,6 @@ class NumericTextCtrl final : public wxControl, public NumericConverter int GetFocusedField() { return mLastField; } int GetFocusedDigit() { return mFocusedDigit; } - // give a sane aspect ratio even if zero height. - bool IsTooBig(int width, int height) { - ComputeSizing(); - return (mWidth > width) || (mHeight > height); - } private: