diff options
author | Caolán McNamara <caolanm@redhat.com> | 2019-12-22 17:08:01 +0000 |
---|---|---|
committer | Caolán McNamara <caolanm@redhat.com> | 2020-04-24 17:48:00 +0200 |
commit | 16309a9516c1f173056fc103c6428e74217c7927 (patch) | |
tree | a9c469560345db2047857836cb708971112471a3 /vcl | |
parent | 1d23ff22c112f5b2bac23d9329a72c399007bf98 (diff) |
weld StylesPropertyPanel
and SvxStyleToolBoxControl, etc.
use a GtkOverlay to support the submenu hackery in the Style
wysiwyg combobox dropdown
Change-Id: I17baa56f382243070ee49e6d707e97324e4f4d67
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/85720
Tested-by: Jenkins
Reviewed-by: Caolán McNamara <caolanm@redhat.com>
Diffstat (limited to 'vcl')
-rw-r--r-- | vcl/source/app/salvtables.cxx | 74 | ||||
-rw-r--r-- | vcl/uiconfig/ui/combobox.ui | 50 | ||||
-rw-r--r-- | vcl/unx/gtk3/gtk3gtkinst.cxx | 166 |
3 files changed, 250 insertions, 40 deletions
diff --git a/vcl/source/app/salvtables.cxx b/vcl/source/app/salvtables.cxx index 35c604955179..5313ae39d01f 100644 --- a/vcl/source/app/salvtables.cxx +++ b/vcl/source/app/salvtables.cxx @@ -5691,6 +5691,8 @@ protected: // owner for ListBox/ComboBox UserData std::vector<std::unique_ptr<OUString>> m_aUserData; VclPtr<vcl_type> m_xComboBox; + ScopedVclPtr<MenuButton> m_xMenuButton; + OUString m_sMenuButtonRow; public: SalInstanceComboBox(vcl_type* pComboBox, SalInstanceBuilder* pBuilder, bool bTakeOwnership) @@ -5714,7 +5716,10 @@ public: // ComboBoxes are comprised of multiple subwidgets, consider the lot as // one thing for focus - virtual bool has_focus() const override { return m_xWidget->HasChildPathFocus(); } + virtual bool has_focus() const override + { + return m_xWidget->HasChildPathFocus() || (m_xMenuButton && (m_xMenuButton->HasFocus() || m_xMenuButton->InPopupMode())); + } virtual OUString get_active_id() const override { @@ -5827,8 +5832,19 @@ public: { vcl::RenderContext* pRenderContext = pEvent->GetRenderContext(); auto nPos = pEvent->GetItemId(); - signal_custom_render(*pRenderContext, pEvent->GetRect(), pEvent->IsSelected(), get_id(nPos)); + const tools::Rectangle& rRect = pEvent->GetRect(); + const OUString sId = get_id(nPos); + signal_custom_render(*pRenderContext, rRect, pEvent->IsSelected(), sId); m_xComboBox->DrawEntry(*pEvent, false, false); // draw separator + + if (m_xMenuButton && m_xMenuButton->IsVisible() && m_sMenuButtonRow == sId) + { + if (m_xMenuButton->GetParent() != pEvent->GetWindow()) + m_xMenuButton->SetParent(pEvent->GetWindow()); + int nButtonWidth = get_menu_button_width(); + m_xMenuButton->SetSizePixel(Size(nButtonWidth, rRect.GetHeight())); + m_xMenuButton->SetPosPixel(Point(rRect.GetWidth() - nButtonWidth, rRect.getY())); + } } VclPtr<VirtualDevice> create_render_virtual_device() const override @@ -5836,10 +5852,30 @@ public: return VclPtr<VirtualDevice>::Create(); } - virtual void HandleEventListener(VclWindowEvent& rEvent) override + virtual void set_item_menu(const OString& rIdent, weld::Menu* pMenu) override { - if (rEvent.GetId() == VclEventId::DropdownPreOpen - || rEvent.GetId() == VclEventId::DropdownClose) + SalInstanceMenu* pInstanceMenu = dynamic_cast<SalInstanceMenu*>(pMenu); + + PopupMenu* pPopup = pInstanceMenu ? pInstanceMenu->getMenu() : nullptr; + + if (!m_xMenuButton) + m_xMenuButton = VclPtr<MenuButton>::Create(m_xComboBox, WB_FLATBUTTON | WB_NOPOINTERFOCUS); + + m_xMenuButton->SetPopupMenu(pPopup); + m_xMenuButton->Show(pPopup != nullptr); + m_sMenuButtonRow = OUString::fromUtf8(rIdent); + } + + int get_menu_button_width() const override + { + const int nButtonWidth = 20; + return nButtonWidth; + } + + void CallHandleEventListener(VclWindowEvent& rEvent) + { + if (rEvent.GetId() == VclEventId::DropdownPreOpen || + rEvent.GetId() == VclEventId::DropdownClose) { signal_popup_toggled(); return; @@ -5947,6 +5983,11 @@ public: assert(false && "not implemented"); } + virtual void HandleEventListener(VclWindowEvent& rEvent) override + { + CallHandleEventListener(rEvent); + } + virtual ~SalInstanceComboBoxWithoutEdit() override { m_xComboBox->SetSelectHdl(Link<ListBox&, void>()); @@ -6083,9 +6124,7 @@ public: auto nOldEntryHeight = m_xComboBox->GetDropDownEntryHeight(); auto nDropDownLineCount = m_xComboBox->GetDropDownLineCount(); - Size aRowSize(signal_custom_get_size(*m_xComboBox, OUString())); m_xComboBox->EnableUserDraw(true); - m_xComboBox->SetUserItemSize(aRowSize); m_xComboBox->SetUserDrawHdl(LINK(this, SalInstanceComboBoxWithEdit, UserDrawHdl)); // adjust the line count to fit approx the height it would have been before @@ -6115,6 +6154,16 @@ public: m_xComboBox->SetMRUEntries(rEntries); } + virtual void HandleEventListener(VclWindowEvent& rEvent) override + { + if (rEvent.GetId() == VclEventId::DropdownPreOpen) + { + Size aRowSize(signal_custom_get_size(*m_xComboBox)); + m_xComboBox->SetUserItemSize(aRowSize); + } + CallHandleEventListener(rEvent); + } + virtual ~SalInstanceComboBoxWithEdit() override { m_xComboBox->SetTextFilter(nullptr); @@ -6251,6 +6300,17 @@ public: assert(false && "not implemented"); } + virtual void set_item_menu(const OString&, weld::Menu*) override + { + assert(false && "not implemented"); + } + + int get_menu_button_width() const override + { + assert(false && "not implemented"); + return 0; + } + VclPtr<VirtualDevice> create_render_virtual_device() const override { return VclPtr<VirtualDevice>::Create(); diff --git a/vcl/uiconfig/ui/combobox.ui b/vcl/uiconfig/ui/combobox.ui index e5d31743fcf1..24b07878f69b 100644 --- a/vcl/uiconfig/ui/combobox.ui +++ b/vcl/uiconfig/ui/combobox.ui @@ -64,6 +64,20 @@ <class name="linked"/> </style> </object> + <object class="GtkMenuButton" id="overlaybutton"> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="no_show_all">True</property> + <property name="draw_indicator">True</property> + <property name="use_popover">False</property> + <child> + <object class="GtkImage" id="overlayarrow"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="icon_name">pan-down-symbolic</property> + </object> + </child> + </object> <object class="GtkWindow" id="popup"> <property name="name">gtk-combobox-popup-window</property> <property name="can_focus">False</property> @@ -75,26 +89,34 @@ <placeholder/> </child> <child> - <object class="GtkScrolledWindow" id="scrolledwindow"> + <object class="GtkOverlay" id="overlay"> <property name="visible">True</property> - <property name="can_focus">True</property> - <property name="hscrollbar_policy">never</property> - <property name="shadow_type">in</property> - <property name="overlay_scrolling">False</property> + <property name="can_focus">False</property> <child> - <object class="GtkTreeView" id="treeview"> + <object class="GtkScrolledWindow" id="scrolledwindow"> <property name="visible">True</property> <property name="can_focus">True</property> - <property name="headers_visible">False</property> - <property name="headers_clickable">False</property> - <property name="enable_search">False</property> - <property name="search_column">0</property> - <property name="show_expanders">False</property> - <property name="activate_on_single_click">True</property> - <child internal-child="selection"> - <object class="GtkTreeSelection"/> + <property name="hscrollbar_policy">never</property> + <property name="shadow_type">in</property> + <property name="overlay_scrolling">False</property> + <child> + <object class="GtkTreeView" id="treeview"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="headers_visible">False</property> + <property name="enable_search">False</property> + <property name="search_column">0</property> + <property name="show_expanders">False</property> + <property name="activate_on_single_click">True</property> + <child internal-child="selection"> + <object class="GtkTreeSelection"/> + </child> + </object> </child> </object> + <packing> + <property name="index">-1</property> + </packing> </child> </object> </child> diff --git a/vcl/unx/gtk3/gtk3gtkinst.cxx b/vcl/unx/gtk3/gtk3gtkinst.cxx index 4a0f975b2fd1..04a6f68837ea 100644 --- a/vcl/unx/gtk3/gtk3gtkinst.cxx +++ b/vcl/unx/gtk3/gtk3gtkinst.cxx @@ -8899,6 +8899,21 @@ int get_height_rows(int nRowHeight, int nSeparatorHeight, int nRows) return (nRowHeight * nRows) + (nSeparatorHeight * (nRows + 1)); } +tools::Rectangle get_row_area(GtkTreeView* pTreeView, GList* pColumns, GtkTreePath* pPath) +{ + tools::Rectangle aRet; + + GdkRectangle aRect; + for (GList* pEntry = g_list_last(pColumns); pEntry; pEntry = g_list_previous(pEntry)) + { + GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data); + gtk_tree_view_get_cell_area(pTreeView, pPath, pColumn, &aRect); + aRet.Union(tools::Rectangle(aRect.x, aRect.y, aRect.x + aRect.width, aRect.y + aRect.height)); + } + + return aRet; +} + class GtkInstanceTreeView : public GtkInstanceContainer, public virtual weld::TreeView { private: @@ -11042,22 +11057,11 @@ public: virtual tools::Rectangle get_row_area(const weld::TreeIter& rIter) const override { - tools::Rectangle aRet; - const GtkInstanceTreeIter& rGtkIter = static_cast<const GtkInstanceTreeIter&>(rIter); GtkTreeModel* pModel = GTK_TREE_MODEL(m_pTreeStore); GtkTreePath* pPath = gtk_tree_model_get_path(pModel, const_cast<GtkTreeIter*>(&rGtkIter.iter)); - - GdkRectangle aRect; - for (GList* pEntry = g_list_last(m_pColumns); pEntry; pEntry = g_list_previous(pEntry)) - { - GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data); - gtk_tree_view_get_cell_area(m_pTreeView, pPath, pColumn, &aRect); - aRet.Union(tools::Rectangle(aRect.x, aRect.y, aRect.x + aRect.width, aRect.y + aRect.height)); - } - + tools::Rectangle aRet = ::get_row_area(m_pTreeView, m_pColumns, pPath); gtk_tree_path_free(pPath); - return aRet; } @@ -12529,12 +12533,32 @@ struct GtkTreeRowReferenceDeleter } }; +// pop down the toplevel combobox menu when something is activated from a custom +// submenu, i.e. wysiwyg style menu +class CustomRenderMenuButtonHelper : public MenuHelper +{ +private: + GtkToggleButton* m_pComboBox; +public: + CustomRenderMenuButtonHelper(GtkMenu* pMenu, GtkToggleButton* pComboBox) + : MenuHelper(pMenu, false) + , m_pComboBox(pComboBox) + { + } + virtual void signal_activate(GtkMenuItem*) override + { + gtk_toggle_button_set_active(m_pComboBox, false); + } +}; + class GtkInstanceComboBox : public GtkInstanceContainer, public vcl::ISearchableStringList, public virtual weld::ComboBox { private: GtkBuilder* m_pComboBuilder; GtkComboBox* m_pComboBox; + GtkOverlay* m_pOverlay; GtkTreeView* m_pTreeView; + GtkMenuButton* m_pOverlayButton; // button that the StyleDropdown uses on an active row GtkWindow* m_pMenuWindow; GtkTreeModel* m_pTreeModel; GtkCellRenderer* m_pButtonTextRenderer; @@ -12542,11 +12566,14 @@ private: GtkWidget* m_pToggleButton; GtkWidget* m_pEntry; GtkCellView* m_pCellView; + std::unique_ptr<CustomRenderMenuButtonHelper> m_xCustomMenuButtonHelper; std::unique_ptr<vcl::Font> m_xFont; std::unique_ptr<comphelper::string::NaturalStringSorter> m_xSorter; vcl::QuickSelectionEngine m_aQuickSelectionEngine; std::vector<std::unique_ptr<GtkTreeRowReference, GtkTreeRowReferenceDeleter>> m_aSeparatorRows; + OUString m_sMenuButtonRow; bool m_bHoverSelection; + bool m_bMouseInOverlayButton; bool m_bPopupActive; bool m_bAutoComplete; bool m_bAutoCompleteCaseSensitive; @@ -13296,7 +13323,7 @@ private: void signal_motion() { // if hover-selection was disabled after pressing a key, then turn it back on again - if (!m_bHoverSelection) + if (!m_bHoverSelection && !m_bMouseInOverlayButton) { gtk_tree_view_set_hover_selection(m_pTreeView, true); m_bHoverSelection = true; @@ -13366,8 +13393,6 @@ private: break; } } - -//TODO set_active(0); } while (m_nMRUCount > m_nMaxMRUCount) @@ -13489,12 +13514,70 @@ private: enable_notify_events(); } + static gboolean signalGetChildPosition(GtkOverlay*, GtkWidget*, GdkRectangle* pAllocation, gpointer widget) + { + GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget); + return pThis->signal_get_child_position(pAllocation); + } + + bool signal_get_child_position(GdkRectangle* pAllocation) + { + if (!gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton))) + return false; + if (!gtk_widget_get_realized(GTK_WIDGET(m_pTreeView))) + return false; + int nRow = find_id_including_mru(m_sMenuButtonRow, true); + if (nRow == -1) + return false; + + gtk_widget_get_preferred_width(GTK_WIDGET(m_pOverlayButton), &pAllocation->width, nullptr); + + GtkTreePath* pPath = gtk_tree_path_new_from_indices(nRow, -1); + GList* pColumns = gtk_tree_view_get_columns(m_pTreeView); + tools::Rectangle aRect = get_row_area(m_pTreeView, pColumns, pPath); + gtk_tree_path_free(pPath); + g_list_free(pColumns); + + pAllocation->x = aRect.Right() - pAllocation->width; + pAllocation->y = aRect.Top(); + pAllocation->height = aRect.GetHeight(); + + return true; + } + + static gboolean signalOverlayButtonCrossing(GtkWidget*, GdkEventCrossing* pEvent, gpointer widget) + { + GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget); + pThis->signal_overlay_button_crossing(pEvent->type == GDK_ENTER_NOTIFY); + return false; + } + + void signal_overlay_button_crossing(bool bEnter) + { + m_bMouseInOverlayButton = bEnter; + if (bEnter) + { + if (m_bHoverSelection) + { + // once toggled button is pressed, turn off hover selection until + // mouse leaves the overlay button + gtk_tree_view_set_hover_selection(m_pTreeView, false); + m_bHoverSelection = false; + } + int nRow = find_id_including_mru(m_sMenuButtonRow, true); + assert(nRow != -1); + tree_view_set_cursor(nRow); // select the buttons row + } + } + public: GtkInstanceComboBox(GtkBuilder* pComboBuilder, GtkComboBox* pComboBox, GtkInstanceBuilder* pBuilder, bool bTakeOwnership) : GtkInstanceContainer(GTK_CONTAINER(gtk_builder_get_object(pComboBuilder, "box")), pBuilder, bTakeOwnership) , m_pComboBuilder(pComboBuilder) , m_pComboBox(pComboBox) + , m_pOverlay(GTK_OVERLAY(gtk_builder_get_object(pComboBuilder, "overlay"))) , m_pTreeView(GTK_TREE_VIEW(gtk_builder_get_object(pComboBuilder, "treeview"))) + , m_pOverlayButton(GTK_MENU_BUTTON(gtk_builder_get_object(pComboBuilder, "overlaybutton"))) , m_pMenuWindow(GTK_WINDOW(gtk_builder_get_object(pComboBuilder, "popup"))) , m_pTreeModel(gtk_combo_box_get_model(pComboBox)) , m_pButtonTextRenderer(nullptr) @@ -13503,6 +13586,7 @@ public: , m_pCellView(nullptr) , m_aQuickSelectionEngine(*this) , m_bHoverSelection(false) + , m_bMouseInOverlayButton(false) , m_bPopupActive(false) , m_bAutoComplete(false) , m_bAutoCompleteCaseSensitive(false) @@ -13615,6 +13699,11 @@ public: // support typeahead for the menu itself, typing into the menu will // select via the vcl selection engine, a matching entry. g_signal_connect(m_pMenuWindow, "key-press-event", G_CALLBACK(signalKeyPress), this); + + g_signal_connect(m_pOverlay, "get-child-position", G_CALLBACK(signalGetChildPosition), this); + gtk_overlay_add_overlay(m_pOverlay, GTK_WIDGET(m_pOverlayButton)); + g_signal_connect(m_pOverlayButton, "leave-notify-event", G_CALLBACK(signalOverlayButtonCrossing), this); + g_signal_connect(m_pOverlayButton, "enter-notify-event", G_CALLBACK(signalOverlayButtonCrossing), this); } virtual int get_active() const override @@ -13986,7 +14075,9 @@ public: virtual bool has_focus() const override { - return gtk_widget_has_focus(m_pToggleButton) || gtk_widget_has_focus(m_pEntry) || GtkInstanceWidget::has_focus(); + return gtk_widget_has_focus(m_pToggleButton) || gtk_widget_has_focus(m_pEntry) || + gtk_widget_has_focus(GTK_WIDGET(m_pOverlayButton)) || + gtk_widget_has_focus(GTK_WIDGET(m_pTreeView)) || GtkInstanceWidget::has_focus(); } virtual bool changed_by_direct_pick() const override @@ -14017,9 +14108,9 @@ public: signal_custom_render(rOutput, rRect, bSelected, rId); } - Size call_signal_custom_get_size(VirtualDevice& rOutput, const OUString& rId) + Size call_signal_custom_get_size(VirtualDevice& rOutput) { - return signal_custom_get_size(rOutput, rId); + return signal_custom_get_size(rOutput); } VclPtr<VirtualDevice> create_render_virtual_device() const override @@ -14027,6 +14118,19 @@ public: return create_virtual_device(); } + virtual void set_item_menu(const OString& rIdent, weld::Menu* pMenu) override + { + m_xCustomMenuButtonHelper.reset(); + GtkInstanceMenu* pPopoverWidget = dynamic_cast<GtkInstanceMenu*>(pMenu); + GtkWidget* pMenuWidget = GTK_WIDGET(pPopoverWidget ? pPopoverWidget->getMenu() : nullptr); + gtk_menu_button_set_popup(m_pOverlayButton, pMenuWidget); + gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), pMenuWidget != nullptr); + gtk_widget_queue_resize_no_redraw(GTK_WIDGET(m_pOverlayButton)); // force location recalc + if (pMenuWidget) + m_xCustomMenuButtonHelper.reset(new CustomRenderMenuButtonHelper(GTK_MENU(pMenuWidget), GTK_TOGGLE_BUTTON(m_pToggleButton))); + m_sMenuButtonRow = OUString::fromUtf8(rIdent); + } + OUString get_mru_entries() const override { const sal_Unicode cSep = ';'; @@ -14073,8 +14177,21 @@ public: m_nMRUCount = nMRUCount; } + int get_menu_button_width() const override + { + bool bVisible = gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton)); + if (!bVisible) + gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), true); + gint nWidth; + gtk_widget_get_preferred_width(GTK_WIDGET(m_pOverlayButton), &nWidth, nullptr); + if (!bVisible) + gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), false); + return nWidth; + } + virtual ~GtkInstanceComboBox() override { + m_xCustomMenuButtonHelper.reset(); do_clear(); if (m_nAutoCompleteIdleId) g_source_remove(m_nAutoCompleteIdleId); @@ -14129,7 +14246,7 @@ bool custom_cell_renderer_surface_get_preferred_size(GtkCellRenderer *cell, if (GtkInstanceTreeView* pTreeView = dynamic_cast<GtkInstanceTreeView*>(pWidget)) aSize = pTreeView->call_signal_custom_get_size(*cellsurface->device, sId); else if (GtkInstanceComboBox* pComboBox = dynamic_cast<GtkInstanceComboBox*>(pWidget)) - aSize = pComboBox->call_signal_custom_get_size(*cellsurface->device, sId); + aSize = pComboBox->call_signal_custom_get_size(*cellsurface->device); if (orientation == GTK_ORIENTATION_HORIZONTAL) { @@ -14446,11 +14563,22 @@ public: assert(false && "not implemented"); } + virtual void set_item_menu(const OString&, weld::Menu*) override + { + assert(false && "not implemented"); + } + VclPtr<VirtualDevice> create_render_virtual_device() const override { return create_virtual_device(); } + int get_menu_button_width() const override + { + assert(false && "not implemented"); + return 0; + } + virtual ~GtkInstanceEntryTreeView() override { if (m_nAutoCompleteIdleId) |