diff --git a/docs/changes.txt b/docs/changes.txt index 41ecaf1d11..108b3ba36e 100644 --- a/docs/changes.txt +++ b/docs/changes.txt @@ -81,6 +81,7 @@ All (GUI): - Improve stock items consistency and aesthetics (dhowland). - Fix bug with missing items in overflowing AUI toolbar (Maarten Bent). - Revert to left-aligning wxSpinCtrl contents by default. +- Make wxRibbonButtonBar buttons more customizable (Max Maisel). wxGTK: diff --git a/include/wx/ribbon/art.h b/include/wx/ribbon/art.h index 5dc3ebf2d6..ad3b3c6532 100644 --- a/include/wx/ribbon/art.h +++ b/include/wx/ribbon/art.h @@ -381,12 +381,18 @@ public: wxRibbonButtonKind kind, wxRibbonButtonBarButtonState size, const wxString& label, + wxCoord text_min_width, wxSize bitmap_size_large, wxSize bitmap_size_small, wxSize* button_size, wxRect* normal_region, wxRect* dropdown_region) = 0; + virtual wxCoord GetButtonBarButtonTextWidth( + wxDC& dc, const wxString& label, + wxRibbonButtonKind kind, + wxRibbonButtonBarButtonState size) = 0; + virtual wxSize GetMinimisedPanelMinimumSize( wxDC& dc, const wxRibbonPanel* wnd, @@ -584,12 +590,18 @@ public: wxRibbonButtonKind kind, wxRibbonButtonBarButtonState size, const wxString& label, + wxCoord text_min_width, wxSize bitmap_size_large, wxSize bitmap_size_small, wxSize* button_size, wxRect* normal_region, wxRect* dropdown_region) wxOVERRIDE; + wxCoord GetButtonBarButtonTextWidth( + wxDC& dc, const wxString& label, + wxRibbonButtonKind kind, + wxRibbonButtonBarButtonState size) wxOVERRIDE; + wxSize GetMinimisedPanelMinimumSize( wxDC& dc, const wxRibbonPanel* wnd, diff --git a/include/wx/ribbon/buttonbar.h b/include/wx/ribbon/buttonbar.h index 2f3693cb2c..17ba124e57 100644 --- a/include/wx/ribbon/buttonbar.h +++ b/include/wx/ribbon/buttonbar.h @@ -139,6 +139,22 @@ public: virtual void EnableButton(int button_id, bool enable = true); virtual void ToggleButton(int button_id, bool checked); + virtual void SetButtonIcon( + int button_id, + const wxBitmap& bitmap, + const wxBitmap& bitmap_small = wxNullBitmap, + const wxBitmap& bitmap_disabled = wxNullBitmap, + const wxBitmap& bitmap_small_disabled = wxNullBitmap); + + virtual void SetButtonText(int button_id, const wxString& label); + virtual void SetButtonTextMinWidth(int button_id, + int min_width_medium, int min_width_large); + virtual void SetButtonTextMinWidth(int button_id, const wxString& label); + virtual void SetButtonMinSizeClass(int button_id, + wxRibbonButtonBarButtonState min_size_class); + virtual void SetButtonMaxSizeClass(int button_id, + wxRibbonButtonBarButtonState max_size_class); + virtual wxRibbonButtonBarButtonBase *GetActiveItem() const; virtual wxRibbonButtonBarButtonBase *GetHoveredItem() const; @@ -171,7 +187,14 @@ protected: void CommonInit(long style); void MakeLayouts(); - bool TryCollapseLayout(wxRibbonButtonBarLayout* original, size_t first_btn, size_t* last_button); + void TryCollapseLayout(wxRibbonButtonBarLayout* original, + size_t first_btn, size_t* last_button, + wxRibbonButtonBarButtonState target_size); + void MakeBitmaps(wxRibbonButtonBarButtonBase* base, + const wxBitmap& bitmap_large, + const wxBitmap& bitmap_large_disabled, + const wxBitmap& bitmap_small, + const wxBitmap& bitmap_small_disabled); static wxBitmap MakeResizedBitmap(const wxBitmap& original, wxSize size); static wxBitmap MakeDisabledBitmap(const wxBitmap& original); void FetchButtonSizeInfo(wxRibbonButtonBarButtonBase* button, diff --git a/interface/wx/ribbon/art.h b/interface/wx/ribbon/art.h index e22d092fea..055949e76a 100644 --- a/interface/wx/ribbon/art.h +++ b/interface/wx/ribbon/art.h @@ -934,6 +934,9 @@ public: be returned. @param label The label of the button. + @param text_min_width + The minimum width of the button label. + Set this to 0 if it is not used. @param bitmap_size_large The size of all "large" bitmaps on the button bar. @param bitmap_size_small @@ -953,11 +956,40 @@ public: wxRibbonButtonKind kind, wxRibbonButtonBarButtonState size, const wxString& label, + wxCoord text_min_width, wxSize bitmap_size_large, wxSize bitmap_size_small, wxSize* button_size, wxRect* normal_region, wxRect* dropdown_region) = 0; + + /** + Gets the width of the string if it is used as + a wxRibbonButtonBar button label. + + @param dc + A device context to use when one is required for size calculations. + @param label + The string whose width shall be calculated. + @param kind + The kind of button. + @param size + The size-class to calculate the size for. Buttons on a button bar + can have three distinct sizes: wxRIBBON_BUTTONBAR_BUTTON_SMALL, + wxRIBBON_BUTTONBAR_BUTTON_MEDIUM, and wxRIBBON_BUTTONBAR_BUTTON_LARGE. + If the requested size-class is not applicable, then NULL should + be returned. + + @return Width of the given label text in pixel. + + @note This function only works with single-line strings. + + @since 3.1.2 + */ + virtual wxCoord GetButtonBarButtonTextWidth( + wxDC& dc, const wxString& label, + wxRibbonButtonKind kind, + wxRibbonButtonBarButtonState size) = 0; /** Calculate the size of a minimised ribbon panel. @@ -1208,6 +1240,7 @@ public: wxRibbonButtonKind kind, wxRibbonButtonBarButtonState size, const wxString& label, + wxCoord text_min_width, wxSize bitmap_size_large, wxSize bitmap_size_small, wxSize* button_size, diff --git a/interface/wx/ribbon/buttonbar.h b/interface/wx/ribbon/buttonbar.h index 27ea3d46ce..9b18fba7ce 100644 --- a/interface/wx/ribbon/buttonbar.h +++ b/interface/wx/ribbon/buttonbar.h @@ -476,6 +476,142 @@ public: */ virtual void ToggleButton(int button_id, bool checked); + /** + Changes the bitmap of an existing button. + + @param button_id + ID of the button to manipulate. + @param bitmap + Large bitmap of the new button. Must be the same size as all other + large bitmaps used on the button bar. + @param bitmap_small + Small bitmap of the new button. If left as null, then a small + bitmap will be automatically generated. Must be the same size as + all other small bitmaps used on the button bar. + @param bitmap_disabled + Large bitmap of the new button when it is disabled. If left as + null, then a bitmap will be automatically generated from @a bitmap. + @param bitmap_small_disabled + Small bitmap of the new button when it is disabled. If left as + null, then a bitmap will be automatically generated from @a + bitmap_small. + + @since 3.1.2 + */ + virtual void SetButtonIcon( + int button_id, + const wxBitmap& bitmap, + const wxBitmap& bitmap_small = wxNullBitmap, + const wxBitmap& bitmap_disabled = wxNullBitmap, + const wxBitmap& bitmap_small_disabled = wxNullBitmap); + + /** + Changes the label text of an existing button. + + @param button_id + ID of the button to manipulate. + @param label + New label of the button. + + @remarks + If text size has changed, Realize() must be called + on the top level wxRibbonBar object to recalculate panel sizes. + Use SetButtonTextMinWidth() to avoid calling Realize() + after every change. + + @see SetButtonTextMinWidth + + @since 3.1.2 + */ + virtual void SetButtonText(int button_id, const wxString& label); + + /** + Sets the minimum width of the button label, to indicate to + the wxRibbonArtProvider layout mechanism that this is the + minimum required size. + + You have to call Realize() after calling this function to + apply the given minimum width. + + @param button_id + ID of the button to manipulate. + @param min_width_medium + Requested minimum width of the button text in pixel + if the button is medium size. + @param min_width_medium + Requested minimum width of the button text in pixel + if the button is large size. + + @remarks + This function is used together with SetButtonText() to change + button labels on the fly without modifying the button bar layout. + + @see SetButtonText() + + @since 3.1.2 + */ + virtual void SetButtonTextMinWidth(int button_id, + int min_width_medium, int min_width_large); + + /** + Sets the minimum width of the button label, to indicate to + the wxRibbonArtProvider layout mechanism that this is the + minimum required size. + + You have to call Realize() after calling this function to + apply the given minimum width. + + @param button_id + ID of the button to manipulate. + @param label + The minimum width is set to the width of this label. + + @remarks + This function is used together with SetButtonText() to change + button labels on the fly without modifying the button bar layout. + + @see SetButtonText() + + @since 3.1.2 + */ + virtual void SetButtonTextMinWidth(int button_id, const wxString& label); + + /** + Sets the minimum size class of a ribbon button. + + You have to call Realize() after calling this function to + apply the given minimum size. + + @param button_id + ID of the button to manipulate. + @param min_size_class + The minimum size-class of the button. Buttons on a button bar + can have three distinct sizes: wxRIBBON_BUTTONBAR_BUTTON_SMALL, + wxRIBBON_BUTTONBAR_BUTTON_MEDIUM, and wxRIBBON_BUTTONBAR_BUTTON_LARGE. + + @since 3.1.2 + */ + virtual void SetButtonMinSizeClass(int button_id, + wxRibbonButtonBarButtonState min_size_class); + + /** + Sets the maximum size class of a ribbon button. + + You have to call Realize() after calling this function to + apply the given maximum size. + + @param button_id + ID of the button to manipulate. + @param max_size_class + The maximum size-class of the button. Buttons on a button bar + can have three distinct sizes: wxRIBBON_BUTTONBAR_BUTTON_SMALL, + wxRIBBON_BUTTONBAR_BUTTON_MEDIUM, and wxRIBBON_BUTTONBAR_BUTTON_LARGE. + + @since 3.1.2 + */ + virtual void SetButtonMaxSizeClass(int button_id, + wxRibbonButtonBarButtonState max_size_class); + /** Returns the active item of the button bar or NULL if there is none. The active button is the one being clicked. diff --git a/samples/ribbon/ribbondemo.cpp b/samples/ribbon/ribbondemo.cpp index 9600a3ac90..8a7de6d5fe 100644 --- a/samples/ribbon/ribbondemo.cpp +++ b/samples/ribbon/ribbondemo.cpp @@ -60,6 +60,8 @@ public: ID_SELECTION_EXPAND_H, ID_SELECTION_EXPAND_V, ID_SELECTION_CONTRACT, + ID_BUTTON_XX, + ID_BUTTON_XY, ID_PRIMARY_COLOUR, ID_SECONDARY_COLOUR, ID_DEFAULT_PROVIDER, @@ -84,7 +86,15 @@ public: ID_UI_CHANGE_TEXT_UPDATED, ID_REMOVE_PAGE, ID_HIDE_PAGES, - ID_SHOW_PAGES + ID_SHOW_PAGES, + ID_PLUS_MINUS, + ID_CHANGE_LABEL, + ID_SMALL_BUTTON_1, + ID_SMALL_BUTTON_2, + ID_SMALL_BUTTON_3, + ID_SMALL_BUTTON_4, + ID_SMALL_BUTTON_5, + ID_SMALL_BUTTON_6 }; void OnEnableUpdateUI(wxUpdateUIEvent& evt); @@ -134,6 +144,8 @@ public: void OnRemovePage(wxRibbonButtonBarEvent& evt); void OnHidePages(wxRibbonButtonBarEvent& evt); void OnShowPages(wxRibbonButtonBarEvent& evt); + void OnPlusMinus(wxRibbonButtonBarEvent& evt); + void OnChangeLabel(wxRibbonButtonBarEvent& evt); void OnTogglePanels(wxCommandEvent& evt); void OnRibbonBarToggled(wxRibbonBarEvent& evt); void OnRibbonBarHelpClicked(wxRibbonBarEvent& evt); @@ -169,6 +181,10 @@ protected: bool m_bChecked; wxString m_new_text; + wxRibbonButtonBar* m_mutable_button_bar; + bool m_plus_minus_state; + bool m_change_label_state; + wxDECLARE_EVENT_TABLE(); }; @@ -241,6 +257,8 @@ EVT_RIBBONPANEL_EXTBUTTON_ACTIVATED(wxID_ANY, MyFrame::OnExtButton) EVT_RIBBONBUTTONBAR_CLICKED(ID_REMOVE_PAGE, MyFrame::OnRemovePage) EVT_RIBBONBUTTONBAR_CLICKED(ID_HIDE_PAGES, MyFrame::OnHidePages) EVT_RIBBONBUTTONBAR_CLICKED(ID_SHOW_PAGES, MyFrame::OnShowPages) +EVT_RIBBONBUTTONBAR_CLICKED(ID_PLUS_MINUS, MyFrame::OnPlusMinus) +EVT_RIBBONBUTTONBAR_CLICKED(ID_CHANGE_LABEL, MyFrame::OnChangeLabel) EVT_RIBBONBAR_TOGGLED(wxID_ANY, MyFrame::OnRibbonBarToggled) EVT_RIBBONBAR_HELP_CLICK(wxID_ANY, MyFrame::OnRibbonBarHelpClicked) EVT_SIZE(MyFrame::OnSizeEvent) @@ -352,13 +370,22 @@ MyFrame::MyFrame() sizer_panelcombo->SetMinSize(wxSize(150, -1)); sizer_panelcombo2->SetMinSize(wxSize(150, -1)); - //not using wxWrapSizer(wxHORIZONTAL) as it reports an incorrect min height - wxSizer* sizer_panelsizer = new wxBoxSizer(wxVERTICAL); - sizer_panelsizer->AddStretchSpacer(1); - sizer_panelsizer->Add(sizer_panelcombo, 0, wxALL|wxEXPAND, 2); - sizer_panelsizer->Add(sizer_panelcombo2, 0, wxALL|wxEXPAND, 2); - sizer_panelsizer->AddStretchSpacer(1); - sizer_panel->SetSizer(sizer_panelsizer); + wxRibbonButtonBar* bar = new wxRibbonButtonBar(sizer_panel, wxID_ANY); + bar->AddButton(ID_BUTTON_XX, wxT("xx"), ribbon_xpm); + bar->AddButton(ID_BUTTON_XY, wxT("xy"), ribbon_xpm); + // This prevents ribbon buttons in panels with sizer from collapsing. + bar->SetButtonMinSizeClass(ID_BUTTON_XX, wxRIBBON_BUTTONBAR_BUTTON_LARGE); + bar->SetButtonMinSizeClass(ID_BUTTON_XY, wxRIBBON_BUTTONBAR_BUTTON_LARGE); + + wxSizer* sizer_panelsizer_h = new wxBoxSizer(wxHORIZONTAL); + wxSizer* sizer_panelsizer_v = new wxBoxSizer(wxVERTICAL); + sizer_panelsizer_v->AddStretchSpacer(1); + sizer_panelsizer_v->Add(sizer_panelcombo, 0, wxALL|wxEXPAND, 2); + sizer_panelsizer_v->Add(sizer_panelcombo2, 0, wxALL|wxEXPAND, 2); + sizer_panelsizer_v->AddStretchSpacer(1); + sizer_panelsizer_h->Add(bar, 0, wxEXPAND); + sizer_panelsizer_h->Add(sizer_panelsizer_v, 0); + sizer_panel->SetSizer(sizer_panelsizer_h); wxFont label_font(8, wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_LIGHT); m_bitmap_creation_dc.SetFont(label_font); @@ -418,10 +445,57 @@ MyFrame::MyFrame() bar->AddButton(ID_REMOVE_PAGE, wxT("Remove"), wxArtProvider::GetBitmap(wxART_DELETE, wxART_OTHER, wxSize(24, 24))); bar->AddButton(ID_HIDE_PAGES, wxT("Hide Pages"), ribbon_xpm); bar->AddButton(ID_SHOW_PAGES, wxT("Show Pages"), ribbon_xpm); + + panel = new wxRibbonPanel(page, wxID_ANY, wxT("Button bar manipulation"), ribbon_xpm); + m_mutable_button_bar = new wxRibbonButtonBar(panel, wxID_ANY); + m_mutable_button_bar->AddButton(ID_PLUS_MINUS, wxT("+/-"), + wxArtProvider::GetBitmap(wxART_PLUS, wxART_OTHER, wxSize(24, 24))); + m_plus_minus_state = false; + m_mutable_button_bar->AddButton(ID_CHANGE_LABEL, wxT("short"), ribbon_xpm); + m_mutable_button_bar->SetButtonTextMinWidth(ID_CHANGE_LABEL, wxT("some long text")); + m_change_label_state = false; + + panel = new wxRibbonPanel(page, wxID_ANY, wxT("Always medium buttons"), ribbon_xpm); + bar = new wxRibbonButtonBar(panel, wxID_ANY); + bar->AddButton(ID_SMALL_BUTTON_1, wxT("Button 1"), ribbon_xpm); + bar->SetButtonMaxSizeClass(ID_SMALL_BUTTON_1, wxRIBBON_BUTTONBAR_BUTTON_MEDIUM); + bar->AddButton(ID_SMALL_BUTTON_2, wxT("Button 2"), ribbon_xpm); + bar->SetButtonMaxSizeClass(ID_SMALL_BUTTON_2, wxRIBBON_BUTTONBAR_BUTTON_MEDIUM); + bar->AddButton(ID_SMALL_BUTTON_3, wxT("Button 3"), ribbon_xpm); + bar->AddButton(ID_SMALL_BUTTON_4, wxT("Button 4"), ribbon_xpm); + bar->AddButton(ID_SMALL_BUTTON_5, wxT("Button 5"), ribbon_xpm); + bar->SetButtonMaxSizeClass(ID_SMALL_BUTTON_5, wxRIBBON_BUTTONBAR_BUTTON_MEDIUM); + bar->AddButton(ID_SMALL_BUTTON_6, wxT("Button 6"), ribbon_xpm); + bar->SetButtonMaxSizeClass(ID_SMALL_BUTTON_6, wxRIBBON_BUTTONBAR_BUTTON_MEDIUM); } new wxRibbonPage(m_ribbon, wxID_ANY, wxT("Highlight Page"), empty_xpm); m_ribbon->AddPageHighlight(m_ribbon->GetPageCount()-1); + { + wxRibbonPage* page = new wxRibbonPage(m_ribbon, wxID_ANY, wxT("Advanced"), empty_xpm); + wxRibbonPanel* panel = new wxRibbonPanel(page, wxID_ANY, wxT("Button bar manipulation"), ribbon_xpm); + m_mutable_button_bar = new wxRibbonButtonBar(panel, wxID_ANY); + m_mutable_button_bar->AddButton(ID_PLUS_MINUS, wxT("+/-"), + wxArtProvider::GetBitmap(wxART_PLUS, wxART_OTHER, wxSize(24, 24))); + m_plus_minus_state = false; + m_mutable_button_bar->AddButton(ID_CHANGE_LABEL, wxT("short"), ribbon_xpm); + m_mutable_button_bar->SetButtonTextMinWidth(ID_CHANGE_LABEL, wxT("some long text")); + m_change_label_state = false; + + panel = new wxRibbonPanel(page, wxID_ANY, wxT("Always medium buttons"), ribbon_xpm); + wxRibbonButtonBar* bar = new wxRibbonButtonBar(panel, wxID_ANY); + bar->AddButton(ID_SMALL_BUTTON_1, wxT("Button 1"), ribbon_xpm); + bar->SetButtonMaxSizeClass(ID_SMALL_BUTTON_1, wxRIBBON_BUTTONBAR_BUTTON_MEDIUM); + bar->AddButton(ID_SMALL_BUTTON_2, wxT("Button 2"), ribbon_xpm); + bar->SetButtonMaxSizeClass(ID_SMALL_BUTTON_2, wxRIBBON_BUTTONBAR_BUTTON_MEDIUM); + bar->AddButton(ID_SMALL_BUTTON_3, wxT("Button 3"), ribbon_xpm); + bar->AddButton(ID_SMALL_BUTTON_4, wxT("Button 4"), ribbon_xpm); + bar->AddButton(ID_SMALL_BUTTON_5, wxT("Button 5"), ribbon_xpm); + bar->SetButtonMaxSizeClass(ID_SMALL_BUTTON_5, wxRIBBON_BUTTONBAR_BUTTON_MEDIUM); + bar->AddButton(ID_SMALL_BUTTON_6, wxT("Button 6"), ribbon_xpm); + bar->SetButtonMaxSizeClass(ID_SMALL_BUTTON_6, wxRIBBON_BUTTONBAR_BUTTON_MEDIUM); + } + m_ribbon->Realize(); m_logwindow = new wxTextCtrl(this, wxID_ANY, wxEmptyString, @@ -1038,6 +1112,36 @@ void MyFrame::OnShowPages(wxRibbonButtonBarEvent& WXUNUSED(evt)) m_ribbon->Realize(); } +void MyFrame::OnPlusMinus(wxRibbonButtonBarEvent& WXUNUSED(evt)) +{ + if(m_plus_minus_state) + { + m_mutable_button_bar->SetButtonIcon(ID_PLUS_MINUS, + wxArtProvider::GetBitmap(wxART_PLUS, wxART_OTHER, wxSize(24, 24))); + m_plus_minus_state = false; + } + else + { + m_mutable_button_bar->SetButtonIcon(ID_PLUS_MINUS, + wxArtProvider::GetBitmap(wxART_MINUS, wxART_OTHER, wxSize(24, 24))); + m_plus_minus_state = true; + } +} + +void MyFrame::OnChangeLabel(wxRibbonButtonBarEvent& WXUNUSED(evt)) +{ + if(m_change_label_state) + { + m_mutable_button_bar->SetButtonText(ID_CHANGE_LABEL, wxT("short")); + m_change_label_state = false; + } + else + { + m_mutable_button_bar->SetButtonText(ID_CHANGE_LABEL, wxT("some long text")); + m_change_label_state = true; + } +} + void MyFrame::OnRibbonBarToggled(wxRibbonBarEvent& WXUNUSED(evt)) { AddText(wxString::Format("Ribbon bar %s.", diff --git a/src/ribbon/art_msw.cpp b/src/ribbon/art_msw.cpp index 2baddeef67..f2a85811cd 100644 --- a/src/ribbon/art_msw.cpp +++ b/src/ribbon/art_msw.cpp @@ -3036,6 +3036,7 @@ bool wxRibbonMSWArtProvider::GetButtonBarButtonSize( wxRibbonButtonKind kind, wxRibbonButtonBarButtonState size, const wxString& label, + wxCoord text_min_width, wxSize bitmap_size_large, wxSize bitmap_size_small, wxSize* button_size, @@ -3074,9 +3075,11 @@ bool wxRibbonMSWArtProvider::GetButtonBarButtonSize( // Small bitmap, with label to the right { GetButtonBarButtonSize(dc, wnd, kind, wxRIBBON_BUTTONBAR_BUTTON_SMALL, - label, bitmap_size_large, bitmap_size_small, button_size, - normal_region, dropdown_region); + label, text_min_width, bitmap_size_large, bitmap_size_small, + button_size, normal_region, dropdown_region); int text_size = dc.GetTextExtent(label).GetWidth(); + if(text_size < text_min_width) + text_size = text_min_width; button_size->SetWidth(button_size->GetWidth() + text_size); switch(kind) { @@ -3101,6 +3104,8 @@ bool wxRibbonMSWArtProvider::GetButtonBarButtonSize( wxCoord label_height; wxCoord best_width; dc.GetTextExtent(label, &best_width, &label_height); + if(best_width < text_min_width) + best_width = text_min_width; int last_line_extra_width = 0; if(kind != wxRIBBON_BUTTON_NORMAL && kind != wxRIBBON_BUTTON_TOGGLE) { @@ -3114,6 +3119,8 @@ bool wxRibbonMSWArtProvider::GetButtonBarButtonSize( int width = wxMax( dc.GetTextExtent(label.Left(i)).GetWidth(), dc.GetTextExtent(label.Mid(i + 1)).GetWidth() + last_line_extra_width); + if(best_width < text_min_width) + best_width = text_min_width; if(width < best_width) { best_width = width; @@ -3149,6 +3156,47 @@ bool wxRibbonMSWArtProvider::GetButtonBarButtonSize( return true; } +wxCoord wxRibbonMSWArtProvider::GetButtonBarButtonTextWidth( + wxDC& dc, const wxString& label, + wxRibbonButtonKind kind, + wxRibbonButtonBarButtonState size) +{ + wxCoord best_width = 0; + dc.SetFont(m_button_bar_label_font); + + if((size & wxRIBBON_BUTTONBAR_BUTTON_SIZE_MASK) + == wxRIBBON_BUTTONBAR_BUTTON_LARGE) + { + best_width = dc.GetTextExtent(label).GetWidth(); + int last_line_extra_width = 0; + if(kind != wxRIBBON_BUTTON_NORMAL && kind != wxRIBBON_BUTTON_TOGGLE) + { + last_line_extra_width += 8; + } + size_t i; + for(i = 0; i < label.Len(); ++i) + { + if(wxRibbonCanLabelBreakAtPosition(label, i)) + { + int width = wxMax( + dc.GetTextExtent(label.Left(i)).GetWidth(), + dc.GetTextExtent(label.Mid(i + 1)).GetWidth() + last_line_extra_width); + if(width < best_width) + { + best_width = width; + } + } + } + } + else if((size & wxRIBBON_BUTTONBAR_BUTTON_SIZE_MASK) + == wxRIBBON_BUTTONBAR_BUTTON_MEDIUM) + { + best_width = dc.GetTextExtent(label).GetWidth(); + } + + return best_width; +} + wxSize wxRibbonMSWArtProvider::GetMinimisedPanelMinimumSize( wxDC& dc, const wxRibbonPanel* wnd, diff --git a/src/ribbon/buttonbar.cpp b/src/ribbon/buttonbar.cpp index 4fecb0e647..5be028a710 100644 --- a/src/ribbon/buttonbar.cpp +++ b/src/ribbon/buttonbar.cpp @@ -75,10 +75,16 @@ public: wxRibbonButtonBarButtonState GetLargestSize() { - if(sizes[wxRIBBON_BUTTONBAR_BUTTON_LARGE].is_supported) + if(sizes[wxRIBBON_BUTTONBAR_BUTTON_LARGE].is_supported + && max_size_class >= wxRIBBON_BUTTONBAR_BUTTON_LARGE) + { return wxRIBBON_BUTTONBAR_BUTTON_LARGE; - if(sizes[wxRIBBON_BUTTONBAR_BUTTON_MEDIUM].is_supported) + } + if(sizes[wxRIBBON_BUTTONBAR_BUTTON_MEDIUM].is_supported + && max_size_class >= wxRIBBON_BUTTONBAR_BUTTON_MEDIUM) + { return wxRIBBON_BUTTONBAR_BUTTON_MEDIUM; + } wxASSERT(sizes[wxRIBBON_BUTTONBAR_BUTTON_SMALL].is_supported); return wxRIBBON_BUTTONBAR_BUTTON_SMALL; } @@ -91,14 +97,16 @@ public: switch(*size) { case wxRIBBON_BUTTONBAR_BUTTON_LARGE: - if(sizes[wxRIBBON_BUTTONBAR_BUTTON_MEDIUM].is_supported) + if(sizes[wxRIBBON_BUTTONBAR_BUTTON_MEDIUM].is_supported + && min_size_class <= wxRIBBON_BUTTONBAR_BUTTON_MEDIUM) { *size = wxRIBBON_BUTTONBAR_BUTTON_MEDIUM; break; } wxFALLTHROUGH; case wxRIBBON_BUTTONBAR_BUTTON_MEDIUM: - if(sizes[wxRIBBON_BUTTONBAR_BUTTON_SMALL].is_supported) + if(sizes[wxRIBBON_BUTTONBAR_BUTTON_SMALL].is_supported + && min_size_class <= wxRIBBON_BUTTONBAR_BUTTON_SMALL) { *size = wxRIBBON_BUTTONBAR_BUTTON_SMALL; break; @@ -118,7 +126,10 @@ public: wxBitmap bitmap_large_disabled; wxBitmap bitmap_small; wxBitmap bitmap_small_disabled; + wxCoord text_min_width[3]; wxRibbonButtonBarButtonSizeInfo sizes[3]; + wxRibbonButtonBarButtonState min_size_class; + wxRibbonButtonBarButtonState max_size_class; wxClientDataContainer client_data; int id; wxRibbonButtonKind kind; @@ -323,41 +334,16 @@ wxRibbonButtonBarButtonBase* wxRibbonButtonBar::InsertButton( wxRibbonButtonBarButtonBase* base = new wxRibbonButtonBarButtonBase; base->id = button_id; base->label = label; - base->bitmap_large = bitmap; - if(!base->bitmap_large.IsOk()) - { - base->bitmap_large = MakeResizedBitmap(base->bitmap_small, - m_bitmap_size_large); - } - else if(base->bitmap_large.GetScaledSize() != m_bitmap_size_large) - { - base->bitmap_large = MakeResizedBitmap(base->bitmap_large, - m_bitmap_size_large); - } - base->bitmap_small = bitmap_small; - if(!base->bitmap_small.IsOk()) - { - base->bitmap_small = MakeResizedBitmap(base->bitmap_large, - m_bitmap_size_small); - } - else if(base->bitmap_small.GetScaledSize() != m_bitmap_size_small) - { - base->bitmap_small = MakeResizedBitmap(base->bitmap_small, - m_bitmap_size_small); - } - base->bitmap_large_disabled = bitmap_disabled; - if(!base->bitmap_large_disabled.IsOk()) - { - base->bitmap_large_disabled = MakeDisabledBitmap(base->bitmap_large); - } - base->bitmap_small_disabled = bitmap_small_disabled; - if(!base->bitmap_small_disabled.IsOk()) - { - base->bitmap_small_disabled = MakeDisabledBitmap(base->bitmap_small); - } + MakeBitmaps(base, bitmap, bitmap_disabled, + bitmap_small, bitmap_small_disabled); base->kind = kind; base->help_string = help_string; base->state = 0; + base->text_min_width[0] = 0; + base->text_min_width[1] = 0; + base->text_min_width[2] = 0; + base->min_size_class = wxRIBBON_BUTTONBAR_BUTTON_SMALL; + base->max_size_class = wxRIBBON_BUTTONBAR_BUTTON_LARGE; wxClientDC temp_dc(this); FetchButtonSizeInfo(base, wxRIBBON_BUTTONBAR_BUTTON_SMALL, temp_dc); @@ -457,9 +443,9 @@ void wxRibbonButtonBar::FetchButtonSizeInfo(wxRibbonButtonBarButtonBase* button, if(m_art) { info.is_supported = m_art->GetButtonBarButtonSize(dc, this, - button->kind, size, button->label, m_bitmap_size_large, - m_bitmap_size_small, &info.size, &info.normal_region, - &info.dropdown_region); + button->kind, size, button->label, button->text_min_width[size], + m_bitmap_size_large, m_bitmap_size_small, &info.size, + &info.normal_region, &info.dropdown_region); } else info.is_supported = false; @@ -595,6 +581,102 @@ void wxRibbonButtonBar::ToggleButton(int button_id, bool checked) } } +void wxRibbonButtonBar::SetButtonIcon( + int button_id, + const wxBitmap& bitmap, + const wxBitmap& bitmap_small, + const wxBitmap& bitmap_disabled, + const wxBitmap& bitmap_small_disabled) +{ + wxRibbonButtonBarButtonBase* base = GetItemById(button_id); + if(base == NULL) + return; + MakeBitmaps(base, bitmap, bitmap_small, + bitmap_disabled, bitmap_small_disabled); + Refresh(); +} + +void wxRibbonButtonBar::SetButtonText(int button_id, const wxString& label) +{ + wxRibbonButtonBarButtonBase* base = GetItemById(button_id); + if(base == NULL) + return; + base->label = label; + + wxClientDC temp_dc(this); + FetchButtonSizeInfo(base, wxRIBBON_BUTTONBAR_BUTTON_SMALL, temp_dc); + FetchButtonSizeInfo(base, wxRIBBON_BUTTONBAR_BUTTON_MEDIUM, temp_dc); + FetchButtonSizeInfo(base, wxRIBBON_BUTTONBAR_BUTTON_LARGE, temp_dc); + m_layouts_valid = false; + Refresh(); +} + +void wxRibbonButtonBar::SetButtonTextMinWidth(int button_id, + int min_width_medium, int min_width_large) +{ + wxRibbonButtonBarButtonBase* base = GetItemById(button_id); + if(base == NULL) + return; + base->text_min_width[0] = 0; + base->text_min_width[1] = min_width_medium; + base->text_min_width[2] = min_width_large; + wxClientDC temp_dc(this); + FetchButtonSizeInfo(base, wxRIBBON_BUTTONBAR_BUTTON_SMALL, temp_dc); + FetchButtonSizeInfo(base, wxRIBBON_BUTTONBAR_BUTTON_MEDIUM, temp_dc); + FetchButtonSizeInfo(base, wxRIBBON_BUTTONBAR_BUTTON_LARGE, temp_dc); + m_layouts_valid = false; +} + +void wxRibbonButtonBar::SetButtonTextMinWidth( + int button_id, const wxString& label) +{ + wxRibbonButtonBarButtonBase* base = GetItemById(button_id); + if(base == NULL) + return; + wxClientDC temp_dc(this); + base->text_min_width[wxRIBBON_BUTTONBAR_BUTTON_MEDIUM] = + m_art->GetButtonBarButtonTextWidth( + temp_dc, label, base->kind, wxRIBBON_BUTTONBAR_BUTTON_MEDIUM); + base->text_min_width[wxRIBBON_BUTTONBAR_BUTTON_LARGE] = + m_art->GetButtonBarButtonTextWidth( + temp_dc, label, base->kind, wxRIBBON_BUTTONBAR_BUTTON_LARGE); + + FetchButtonSizeInfo(base, wxRIBBON_BUTTONBAR_BUTTON_SMALL, temp_dc); + FetchButtonSizeInfo(base, wxRIBBON_BUTTONBAR_BUTTON_MEDIUM, temp_dc); + FetchButtonSizeInfo(base, wxRIBBON_BUTTONBAR_BUTTON_LARGE, temp_dc); + m_layouts_valid = false; +} + +void wxRibbonButtonBar::SetButtonMinSizeClass(int button_id, + wxRibbonButtonBarButtonState min_size_class) +{ + wxRibbonButtonBarButtonBase* base = GetItemById(button_id); + if(base == NULL) + return; + if(base->max_size_class < min_size_class) + { + wxFAIL_MSG("Button minimum size is larger than maximum size"); + return; + } + base->min_size_class = min_size_class; + m_layouts_valid = false; +} + +void wxRibbonButtonBar::SetButtonMaxSizeClass(int button_id, + wxRibbonButtonBarButtonState max_size_class) +{ + wxRibbonButtonBarButtonBase* base = GetItemById(button_id); + if(base == NULL) + return; + if(base->min_size_class > max_size_class) + { + wxFAIL_MSG("Button maximum size is smaller than minimum size"); + return; + } + base->max_size_class = max_size_class; + m_layouts_valid = false; +} + void wxRibbonButtonBar::SetArtProvider(wxRibbonArtProvider* art) { if(art == m_art) @@ -865,8 +947,27 @@ void wxRibbonButtonBar::MakeLayouts() } size_t btn_count = m_buttons.Count(); size_t btn_i; + + // Determine available height: + // 1 large button or, if not found, 3 medium or small buttons + int available_height = 0; + bool large_button_found = false; + for(btn_i = 0; btn_i < btn_count; ++btn_i) { - // Best layout : all buttons large, stacking horizontally + wxRibbonButtonBarButtonBase* button = m_buttons.Item(btn_i); + wxRibbonButtonBarButtonState size_class = button->GetLargestSize(); + available_height = wxMax(available_height, + button->sizes[size_class].size.GetHeight()); + if(size_class == wxRIBBON_BUTTONBAR_BUTTON_LARGE) + large_button_found = true; + } + if(!large_button_found) + available_height *= 3; + + int stacked_width = 0; + { + // Best layout : all buttons large, stacking horizontally, + // small buttons small, stacked vertically wxRibbonButtonBarLayout* layout = new wxRibbonButtonBarLayout; wxPoint cursor(0, 0); layout->overall_size.SetHeight(0); @@ -877,54 +978,111 @@ void wxRibbonButtonBar::MakeLayouts() instance.position = cursor; instance.size = button->GetLargestSize(); wxSize& size = button->sizes[instance.size].size; - cursor.x += size.GetWidth(); - layout->overall_size.SetHeight(wxMax(layout->overall_size.GetHeight(), - size.GetHeight())); + + if(instance.size < wxRIBBON_BUTTONBAR_BUTTON_LARGE) + { + stacked_width = wxMax(stacked_width, size.GetWidth()); + if(cursor.y + size.GetHeight() >= available_height) + { + cursor.y = 0; + cursor.x += stacked_width; + stacked_width = 0; + } + else + { + cursor.y += size.GetHeight(); + } + } + else + { + if(cursor.y != 0) + { + cursor.y = 0; + cursor.x += stacked_width; + stacked_width = 0; + instance.position = cursor; + } + cursor.x += size.GetWidth(); + } layout->buttons.Add(instance); } - layout->overall_size.SetWidth(cursor.x); + layout->overall_size.SetHeight(available_height); + layout->overall_size.SetWidth(cursor.x + stacked_width); m_layouts.Add(layout); } if(btn_count >= 2) { // Collapse the rightmost buttons and stack them vertically - size_t iLast = btn_count - 1; - while(TryCollapseLayout(m_layouts.Last(), iLast, &iLast) && iLast > 0) + // if they are not already small. If rightmost buttons can't + // be collapsed because "min_size_class" is set, try it again + // starting from second rightmost button and so on. + size_t iLast = btn_count; + while(iLast-- > 0) { - --iLast; + TryCollapseLayout(m_layouts.Last(), iLast, &iLast, + wxRIBBON_BUTTONBAR_BUTTON_MEDIUM); } + + // TODO: small buttons are not implemented yet in + // art_msw.cpp:2581 and will be invisible + /*iLast = btn_count; + while(iLast-- > 0) + { + TryCollapseLayout(m_layouts.Last(), iLast, &iLast, + wxRIBBON_BUTTONBAR_BUTTON_SMALL); + }*/ } } -bool wxRibbonButtonBar::TryCollapseLayout(wxRibbonButtonBarLayout* original, - size_t first_btn, size_t* last_button) +void wxRibbonButtonBar::TryCollapseLayout(wxRibbonButtonBarLayout* original, + size_t first_btn, size_t* last_button, + wxRibbonButtonBarButtonState target_size) { size_t btn_count = m_buttons.Count(); size_t btn_i; int used_height = 0; int used_width = 0; + int original_column_width = 0; int available_width = 0; - int available_height = 0; + int available_height = original->overall_size.GetHeight(); + // Search for button range from right which should be + // collapsed into a column of small buttons. for(btn_i = first_btn + 1; btn_i > 0; /* decrement is inside loop */) { --btn_i; wxRibbonButtonBarButtonBase* button = m_buttons.Item(btn_i); wxRibbonButtonBarButtonState large_size_class = button->GetLargestSize(); wxSize large_size = button->sizes[large_size_class].size; - int t_available_height = wxMax(available_height, - large_size.GetHeight()); - int t_available_width = available_width + large_size.GetWidth(); - wxRibbonButtonBarButtonState small_size_class = large_size_class; - if(!button->GetSmallerSize(&small_size_class)) + int t_available_width = available_width; + + original_column_width = wxMax(original_column_width, + large_size.GetWidth()); + + // Top button in column: add column width to available width + if(original->buttons.Item(btn_i).position.y == 0) { - return false; + t_available_width += original_column_width; + original_column_width = 0; + } + + wxRibbonButtonBarButtonState small_size_class = large_size_class; + if(large_size_class > target_size) + { + if(!button->GetSmallerSize(&small_size_class, + small_size_class - target_size)) + { + // Large button that cannot shrink: stop search + ++btn_i; + break; + } } wxSize small_size = button->sizes[small_size_class].size; int t_used_height = used_height + small_size.GetHeight(); int t_used_width = wxMax(used_width, small_size.GetWidth()); - if(t_used_height > t_available_height) + // Height is full: stop search + if(t_used_height > available_height) { ++btn_i; break; @@ -934,13 +1092,13 @@ bool wxRibbonButtonBar::TryCollapseLayout(wxRibbonButtonBarLayout* original, used_height = t_used_height; used_width = t_used_width; available_width = t_available_width; - available_height = t_available_height; } } + // Layout got wider than before or no suitable button found: abort if(btn_i >= first_btn || used_width >= available_width) { - return false; + return; } if(last_button != NULL) { @@ -950,27 +1108,23 @@ bool wxRibbonButtonBar::TryCollapseLayout(wxRibbonButtonBarLayout* original, wxRibbonButtonBarLayout* layout = new wxRibbonButtonBarLayout; WX_APPEND_ARRAY(layout->buttons, original->buttons); wxPoint cursor(layout->buttons.Item(btn_i).position); - bool preserve_height = false; - if(btn_i == 0) - { - // If height isn't preserved (i.e. it is reduced), then the minimum - // size for the button bar will decrease, preventing the original - // layout from being used (in some cases). - // It may be a good idea to always preserve the height, but for now - // it is only done when the first button is involved in a collapse. - preserve_height = true; - } + cursor.y = 0; for(; btn_i <= first_btn; ++btn_i) { wxRibbonButtonBarButtonInstance& instance = layout->buttons.Item(btn_i); - instance.base->GetSmallerSize(&instance.size); + if(instance.size > target_size) + { + instance.base->GetSmallerSize(&instance.size, + instance.size - target_size); + } instance.position = cursor; cursor.y += instance.base->sizes[instance.size].size.GetHeight(); } int x_adjust = available_width - used_width; + // Adjust x coords of buttons right of shrinked column for(; btn_i < btn_count; ++btn_i) { wxRibbonButtonBarButtonInstance& instance = layout->buttons.Item(btn_i); @@ -985,16 +1139,59 @@ bool wxRibbonButtonBar::TryCollapseLayout(wxRibbonButtonBarLayout* original, { delete layout; wxFAIL_MSG("Layout collapse resulted in increased size"); - return false; + return; } - if(preserve_height) - { - layout->overall_size.SetHeight(original->overall_size.GetHeight()); - } + // If height isn't preserved (i.e. it is reduced), then the minimum + // size for the button bar will decrease, preventing the original + // layout from being used (in some cases). + // If neither "min_size_class" nor "max_size_class" is set, this is + // only required when the first button is involved in a collapse but + // if small, medium and large buttons as well as min/max size classes + // are involved this is always a good idea. + layout->overall_size.SetHeight(original->overall_size.GetHeight()); m_layouts.Add(layout); - return true; +} + +void wxRibbonButtonBar::MakeBitmaps(wxRibbonButtonBarButtonBase* base, + const wxBitmap& bitmap_large, + const wxBitmap& bitmap_large_disabled, + const wxBitmap& bitmap_small, + const wxBitmap& bitmap_small_disabled) +{ + base->bitmap_large = bitmap_large; + if(!base->bitmap_large.IsOk()) + { + base->bitmap_large = MakeResizedBitmap(base->bitmap_small, + m_bitmap_size_large); + } + else if(base->bitmap_large.GetScaledSize() != m_bitmap_size_large) + { + base->bitmap_large = MakeResizedBitmap(base->bitmap_large, + m_bitmap_size_large); + } + base->bitmap_small = bitmap_small; + if(!base->bitmap_small.IsOk()) + { + base->bitmap_small = MakeResizedBitmap(base->bitmap_large, + m_bitmap_size_small); + } + else if(base->bitmap_small.GetScaledSize() != m_bitmap_size_small) + { + base->bitmap_small = MakeResizedBitmap(base->bitmap_small, + m_bitmap_size_small); + } + base->bitmap_large_disabled = bitmap_large_disabled; + if(!base->bitmap_large_disabled.IsOk()) + { + base->bitmap_large_disabled = MakeDisabledBitmap(base->bitmap_large); + } + base->bitmap_small_disabled = bitmap_small_disabled; + if(!base->bitmap_small_disabled.IsOk()) + { + base->bitmap_small_disabled = MakeDisabledBitmap(base->bitmap_small); + } } void wxRibbonButtonBar::OnMouseMove(wxMouseEvent& evt)