vcl/unx/gtk3/gtkinst.cxx | 2162 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 1942 insertions(+), 220 deletions(-)
New commits: commit 0b212dc041c96e706b1b3321fcb9151d20d5dd1c Author: Caolán McNamara <caol...@redhat.com> AuthorDate: Fri May 28 09:56:27 2021 +0100 Commit: Caolán McNamara <caol...@redhat.com> CommitDate: Fri May 28 14:05:33 2021 +0200 gtk4: reenable ComboBox lets find out if we can return to using the real GtkComboBox instead of a look-alike replacement. Change-Id: I28d005b37835f09ace095a3642f7c06be6f1e27b Reviewed-on: https://gerrit.libreoffice.org/c/core/+/116321 Tested-by: Jenkins Reviewed-by: Caolán McNamara <caol...@redhat.com> diff --git a/vcl/unx/gtk3/gtkinst.cxx b/vcl/unx/gtk3/gtkinst.cxx index e16c22b951ba..9815ccf53967 100644 --- a/vcl/unx/gtk3/gtkinst.cxx +++ b/vcl/unx/gtk3/gtkinst.cxx @@ -11196,7 +11196,6 @@ namespace return found; } -#if !GTK_CHECK_VERSION(4, 0, 0) void insert_row(GtkListStore* pListStore, GtkTreeIter& iter, int pos, const OUString* pId, std::u16string_view rText, const OUString* pIconName, const VirtualDevice* pDevice) { if (!pIconName && !pDevice) @@ -11245,7 +11244,6 @@ namespace } } } -#endif } namespace @@ -16067,10 +16065,10 @@ void GtkInstanceDrawingArea::im_context_set_cursor_location(const tools::Rectang } -#if !GTK_CHECK_VERSION(4, 0, 0) - namespace { +#if !GTK_CHECK_VERSION(4, 0, 0) + GtkBuilder* makeMenuToggleButtonBuilder() { OUString aUri(AllSettings::GetUIRootDir() + "vcl/ui/menutogglebutton.ui"); @@ -16105,22 +16103,26 @@ public: } }; -class GtkInstanceComboBox : public GtkInstanceContainer, public vcl::ISearchableStringList, public virtual weld::ComboBox +#endif + +#if GTK_CHECK_VERSION(4, 0, 0) + +class GtkInstanceComboBox : public GtkInstanceWidget, 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; +// 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; GtkCellRenderer* m_pMenuTextRenderer; - GtkWidget* m_pToggleButton; +// GtkWidget* m_pToggleButton; GtkWidget* m_pEntry; + GtkEditable* m_pEditable; GtkCellView* m_pCellView; - std::unique_ptr<CustomRenderMenuButtonHelper> m_xCustomMenuButtonHelper; +// 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; @@ -16136,17 +16138,17 @@ private: bool m_bActivateCalled; gint m_nTextCol; gint m_nIdCol; - gulong m_nToggleFocusInSignalId; - gulong m_nToggleFocusOutSignalId; +// gulong m_nToggleFocusInSignalId; +// gulong m_nToggleFocusOutSignalId; gulong m_nRowActivatedSignalId; gulong m_nChangedSignalId; gulong m_nPopupShownSignalId; - gulong m_nKeyPressEventSignalId; +// gulong m_nKeyPressEventSignalId; gulong m_nEntryInsertTextSignalId; gulong m_nEntryActivateSignalId; - gulong m_nEntryFocusInSignalId; - gulong m_nEntryFocusOutSignalId; - gulong m_nEntryKeyPressEventSignalId; +// gulong m_nEntryFocusInSignalId; +// gulong m_nEntryFocusOutSignalId; +// gulong m_nEntryKeyPressEventSignalId; guint m_nAutoCompleteIdleId; gint m_nNonCustomLineHeight; gint m_nPrePopupCursorPos; @@ -16264,12 +16266,13 @@ private: m_bChangedByMenu = false; } - static void signalPopupToggled(GtkToggleButton* /*pToggleButton*/, gpointer widget) + static void signalPopupToggled(GObject*, GParamSpec*, gpointer widget) { GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget); pThis->signal_popup_toggled(); } +#if 0 int get_popup_height(gint& rPopupWidth) { const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); @@ -16309,7 +16312,9 @@ private: return nHeight; } +#endif +#if 0 void toggle_menu() { if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pToggleButton))) @@ -16365,14 +16370,19 @@ private: show_menu(pComboBox, m_pMenuWindow); } } +#endif virtual void signal_popup_toggled() override { m_aQuickSelectionEngine.Reset(); - toggle_menu(); + // toggle_menu(); + + GValue value = G_VALUE_INIT; + g_value_init(&value, G_TYPE_BOOLEAN); + g_object_get_property(G_OBJECT(m_pComboBox), "popup-shown", &value); + bool bIsShown = g_value_get_boolean(&value); - bool bIsShown = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pToggleButton)); if (m_bPopupActive != bIsShown) { m_bPopupActive = bIsShown; @@ -16514,6 +16524,7 @@ private: return bRet; } +#if 0 // https://gitlab.gnome.org/GNOME/gtk/issues/310 // // in the absence of a built-in solution @@ -16525,11 +16536,13 @@ private: GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget); return pThis->signal_key_press(pEvent); } +#endif // tdf#131076 we want return in a ComboBox to act like return in a // GtkEntry and activate the default dialog/assistant button bool combobox_activate() { +#if 0 GtkWidget *pComboBox = GTK_WIDGET(m_pToggleButton); GtkWidget *pToplevel = widget_get_toplevel(pComboBox); GtkWindow *pWindow = GTK_WINDOW(pToplevel); @@ -16542,8 +16555,12 @@ private: if (pDefaultWidget && pDefaultWidget != m_pToggleButton && gtk_widget_get_sensitive(pDefaultWidget)) bDone = gtk_widget_activate(pDefaultWidget); return bDone; +#else + return false; +#endif } +#if 0 static gboolean signalEntryKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget) { GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget); @@ -16706,6 +16723,7 @@ private: return bDone; } +#endif vcl::StringEntryIdentifier typeahead_getEntry(int nPos, OUString& out_entryText) const { @@ -16727,6 +16745,7 @@ private: void tree_view_set_cursor(int pos) { +#if 0 if (pos == -1) { gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(m_pTreeView)); @@ -16743,12 +16762,13 @@ private: gtk_cell_view_set_displayed_row(m_pCellView, path); gtk_tree_path_free(path); } +#endif } int tree_view_get_cursor() const { int nRet = -1; - +#if 0 GtkTreePath* path; gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr); if (path) @@ -16758,6 +16778,7 @@ private: nRet = indices[depth-1]; gtk_tree_path_free(path); } +#endif return nRet; } @@ -16808,6 +16829,7 @@ private: set_typeahead_selected_entry(nSelect); } +#if 0 static void signalGrabBroken(GtkWidget*, GdkEventGrabBroken *pEvent, gpointer widget) { GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget); @@ -16878,6 +16900,7 @@ private: m_bHoverSelection = true; } } +#endif static void signalRowActivated(GtkTreeView*, GtkTreePath*, GtkTreeViewColumn*, gpointer widget) { @@ -16891,12 +16914,12 @@ private: m_bChangedByMenu = true; disable_notify_events(); int nActive = get_active(); - if (m_pEntry) - gtk_entry_set_text(GTK_ENTRY(m_pEntry), OUStringToOString(get_text(nActive), RTL_TEXTENCODING_UTF8).getStr()); + if (m_pEditable) + gtk_editable_set_text(m_pEditable, OUStringToOString(get_text(nActive), RTL_TEXTENCODING_UTF8).getStr()); else tree_view_set_cursor(nActive); enable_notify_events(); - gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false); +// gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false); fire_signal_changed(); update_mru(); } @@ -16904,7 +16927,7 @@ private: void do_clear() { disable_notify_events(); - gtk_tree_view_set_row_separator_func(m_pTreeView, nullptr, nullptr, nullptr); +// gtk_tree_view_set_row_separator_func(m_pTreeView, nullptr, nullptr, nullptr); m_aSeparatorRows.clear(); gtk_list_store_clear(GTK_LIST_STORE(m_pTreeModel)); m_nMRUCount = 0; @@ -16963,7 +16986,7 @@ private: int get_active_including_mru() const { - return tree_view_get_cursor(); + return gtk_combo_box_get_active(m_pComboBox); } void set_active_including_mru(int pos, bool bInteractive) @@ -16972,12 +16995,12 @@ private: tree_view_set_cursor(pos); - if (m_pEntry) + if (m_pEditable) { if (pos != -1) - gtk_entry_set_text(GTK_ENTRY(m_pEntry), OUStringToOString(get_text_including_mru(pos), RTL_TEXTENCODING_UTF8).getStr()); + gtk_editable_set_text(m_pEditable, OUStringToOString(get_text_including_mru(pos), RTL_TEXTENCODING_UTF8).getStr()); else - gtk_entry_set_text(GTK_ENTRY(m_pEntry), ""); + gtk_editable_set_text(m_pEditable, ""); } m_bChangedByMenu = false; @@ -17049,8 +17072,10 @@ private: { disable_notify_events(); GtkTreeIter iter; +#if 0 if (!gtk_tree_view_get_row_separator_func(m_pTreeView)) gtk_tree_view_set_row_separator_func(m_pTreeView, separatorFunction, this, nullptr); +#endif insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, &rId, u"", nullptr, nullptr); GtkTreePath* pPath = gtk_tree_path_new_from_indices(pos, -1); m_aSeparatorRows.emplace_back(gtk_tree_row_reference_new(m_pTreeModel, pPath)); @@ -17066,6 +17091,7 @@ private: enable_notify_events(); } +#if 0 static gboolean signalGetChildPosition(GtkOverlay*, GtkWidget*, GdkRectangle* pAllocation, gpointer widget) { GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget); @@ -17121,21 +17147,7 @@ private: assert(nRow != -1); tree_view_set_cursor(nRow); // select the buttons row } - - void signal_combo_mnemonic_activate() - { - if (m_pEntry) - gtk_widget_grab_focus(m_pEntry); - else - gtk_widget_grab_focus(m_pToggleButton); - } - - static gboolean signalComboMnemonicActivate(GtkWidget*, gboolean, gpointer widget) - { - GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget); - pThis->signal_combo_mnemonic_activate(); - return true; - } +#endif int include_mru(int pos) { @@ -17145,18 +17157,18 @@ private: } public: - GtkInstanceComboBox(GtkBuilder* pComboBuilder, GtkComboBox* pComboBox, GtkInstanceBuilder* pBuilder, bool bTakeOwnership) - : GtkInstanceContainer(GTK_CONTAINER(gtk_builder_get_object(pComboBuilder, "box")), pBuilder, bTakeOwnership) - , m_pComboBuilder(pComboBuilder) + GtkInstanceComboBox(GtkComboBox* pComboBox, GtkInstanceBuilder* pBuilder, bool bTakeOwnership) + : GtkInstanceWidget(GTK_WIDGET(pComboBox), pBuilder, bTakeOwnership) , 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_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) - , m_pToggleButton(GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "button"))) - , m_pEntry(GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "entry"))) +// , m_pToggleButton(GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "button"))) + , m_pEntry(GTK_IS_ENTRY(gtk_combo_box_get_child(pComboBox)) ? gtk_combo_box_get_child(pComboBox) : nullptr) + , m_pEditable(GTK_EDITABLE(m_pEntry)) , m_pCellView(nullptr) , m_aQuickSelectionEngine(*this) , m_bHoverSelection(false) @@ -17169,11 +17181,11 @@ public: , m_bActivateCalled(false) , m_nTextCol(gtk_combo_box_get_entry_text_column(pComboBox)) , m_nIdCol(gtk_combo_box_get_id_column(pComboBox)) - , m_nToggleFocusInSignalId(0) - , m_nToggleFocusOutSignalId(0) - , m_nRowActivatedSignalId(g_signal_connect(m_pTreeView, "row-activated", G_CALLBACK(signalRowActivated), this)) - , m_nChangedSignalId(g_signal_connect(m_pEntry, "changed", G_CALLBACK(signalChanged), this)) - , m_nPopupShownSignalId(g_signal_connect(m_pToggleButton, "toggled", G_CALLBACK(signalPopupToggled), this)) +// , m_nToggleFocusInSignalId(0) +// , m_nToggleFocusOutSignalId(0) +// , m_nRowActivatedSignalId(g_signal_connect(m_pTreeView, "row-activated", G_CALLBACK(signalRowActivated), this)) + , m_nChangedSignalId(g_signal_connect(m_pComboBox, "changed", G_CALLBACK(signalChanged), this)) + , m_nPopupShownSignalId(g_signal_connect(m_pComboBox, "notify::popup-shown", G_CALLBACK(signalPopupToggled), this)) , m_nAutoCompleteIdleId(0) , m_nNonCustomLineHeight(-1) , m_nPrePopupCursorPos(-1) @@ -17182,119 +17194,41 @@ public: { int nActive = gtk_combo_box_get_active(m_pComboBox); - if (gtk_style_context_has_class(gtk_widget_get_style_context(GTK_WIDGET(m_pComboBox)), "small-button")) - gtk_style_context_add_class(gtk_widget_get_style_context(GTK_WIDGET(getContainer())), "small-button"); - - insertAsParent(GTK_WIDGET(m_pComboBox), GTK_WIDGET(getContainer())); - gtk_widget_set_visible(GTK_WIDGET(m_pComboBox), false); - gtk_widget_set_no_show_all(GTK_WIDGET(m_pComboBox), true); - - gtk_tree_view_set_model(m_pTreeView, m_pTreeModel); - /* tdf#136455 gtk_combo_box_set_model with a null Model should be good - enough. But in practice, while the ComboBox model is unset, GTK - doesn't unset the ComboBox menus model, so that remains listening to - additions to the ListStore and slowing things down massively. - Using a new model does reset the menu to listen to that unused one instead */ - gtk_combo_box_set_model(m_pComboBox, GTK_TREE_MODEL(gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING))); - - GtkTreeViewColumn* pCol = gtk_tree_view_column_new(); - gtk_tree_view_append_column(m_pTreeView, pCol); - - bool bPixbufUsedSurface = gtk_tree_model_get_n_columns(m_pTreeModel) == 4; - - GList* cells = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(m_pComboBox)); - // move the cell renderers from the combobox to the replacement treeview - m_pMenuTextRenderer = static_cast<GtkCellRenderer*>(cells->data); - for (GList* pRenderer = g_list_first(cells); pRenderer; pRenderer = g_list_next(pRenderer)) - { - GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data); - bool bTextRenderer = pCellRenderer == m_pMenuTextRenderer; - gtk_tree_view_column_pack_end(pCol, pCellRenderer, bTextRenderer); - if (!bTextRenderer) - { - if (bPixbufUsedSurface) - gtk_tree_view_column_set_attributes(pCol, pCellRenderer, "surface", 3, nullptr); - else - gtk_tree_view_column_set_attributes(pCol, pCellRenderer, "pixbuf", 2, nullptr); - } - } - - gtk_tree_view_column_set_attributes(pCol, m_pMenuTextRenderer, "text", m_nTextCol, nullptr); - if (gtk_combo_box_get_has_entry(m_pComboBox)) { m_bAutoComplete = true; - m_nEntryInsertTextSignalId = g_signal_connect(m_pEntry, "insert-text", G_CALLBACK(signalEntryInsertText), this); + m_nEntryInsertTextSignalId = g_signal_connect(m_pEditable, "insert-text", G_CALLBACK(signalEntryInsertText), this); m_nEntryActivateSignalId = g_signal_connect(m_pEntry, "activate", G_CALLBACK(signalEntryActivate), this); - m_nEntryFocusInSignalId = g_signal_connect(m_pEntry, "focus-in-event", G_CALLBACK(signalEntryFocusIn), this); - m_nEntryFocusOutSignalId = g_signal_connect(m_pEntry, "focus-out-event", G_CALLBACK(signalEntryFocusOut), this); - m_nEntryKeyPressEventSignalId = g_signal_connect(m_pEntry, "key-press-event", G_CALLBACK(signalEntryKeyPress), this); - m_nKeyPressEventSignalId = 0; +// m_nEntryFocusInSignalId = g_signal_connect(m_pEntry, "focus-in-event", G_CALLBACK(signalEntryFocusIn), this); +// m_nEntryFocusOutSignalId = g_signal_connect(m_pEntry, "focus-out-event", G_CALLBACK(signalEntryFocusOut), this); +// m_nEntryKeyPressEventSignalId = g_signal_connect(m_pEntry, "key-press-event", G_CALLBACK(signalEntryKeyPress), this); +// m_nKeyPressEventSignalId = 0; } else { - gtk_widget_set_visible(m_pEntry, false); - m_pEntry = nullptr; - - GtkWidget* pArrow = GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "arrow")); - gtk_container_child_set(getContainer(), m_pToggleButton, "expand", true, nullptr); - - auto m_pCellArea = gtk_cell_area_box_new(); - m_pCellView = GTK_CELL_VIEW(gtk_cell_view_new_with_context(m_pCellArea, nullptr)); - gtk_widget_set_hexpand(GTK_WIDGET(m_pCellView), true); - GtkBox* pBox = GTK_BOX(gtk_widget_get_parent(pArrow)); - - gint nImageSpacing(2); - GtkStyleContext *pContext = gtk_widget_get_style_context(GTK_WIDGET(m_pToggleButton)); - gtk_style_context_get_style(pContext, "image-spacing", &nImageSpacing, nullptr); - gtk_box_set_spacing(pBox, nImageSpacing); - - gtk_box_pack_start(pBox, GTK_WIDGET(m_pCellView), false, true, 0); - - gtk_cell_view_set_fit_model(m_pCellView, true); - gtk_cell_view_set_model(m_pCellView, m_pTreeModel); - - m_pButtonTextRenderer = gtk_cell_renderer_text_new(); - gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(m_pCellView), m_pButtonTextRenderer, true); - gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pCellView), m_pButtonTextRenderer, "text", m_nTextCol, nullptr); - if (g_list_length(cells) > 1) - { - GtkCellRenderer* pCellRenderer = gtk_cell_renderer_pixbuf_new(); - gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(m_pCellView), pCellRenderer, false); - if (bPixbufUsedSurface) - gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pCellView), pCellRenderer, "surface", 3, nullptr); - else - gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pCellView), pCellRenderer, "pixbuf", 2, nullptr); - } - - gtk_widget_show_all(GTK_WIDGET(m_pCellView)); - m_nEntryInsertTextSignalId = 0; m_nEntryActivateSignalId = 0; - m_nEntryFocusInSignalId = 0; - m_nEntryFocusOutSignalId = 0; - m_nEntryKeyPressEventSignalId = 0; - m_nKeyPressEventSignalId = g_signal_connect(m_pToggleButton, "key-press-event", G_CALLBACK(signalKeyPress), this); +// m_nEntryFocusInSignalId = 0; +// m_nEntryFocusOutSignalId = 0; +// m_nEntryKeyPressEventSignalId = 0; +// m_nKeyPressEventSignalId = g_signal_connect(m_pToggleButton, "key-press-event", G_CALLBACK(signalKeyPress), this); } - g_list_free(cells); - if (nActive != -1) tree_view_set_cursor(nActive); - g_signal_connect(getContainer(), "mnemonic-activate", G_CALLBACK(signalComboMnemonicActivate), this); - - g_signal_connect(m_pMenuWindow, "grab-broken-event", G_CALLBACK(signalGrabBroken), this); - g_signal_connect(m_pMenuWindow, "button-press-event", G_CALLBACK(signalButtonPress), this); - g_signal_connect(m_pMenuWindow, "motion-notify-event", G_CALLBACK(signalMotion), this); +// g_signal_connect(m_pMenuWindow, "grab-broken-event", G_CALLBACK(signalGrabBroken), this); +// g_signal_connect(m_pMenuWindow, "button-press-event", G_CALLBACK(signalButtonPress), this); +// g_signal_connect(m_pMenuWindow, "motion-notify-event", G_CALLBACK(signalMotion), this); // 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_pMenuWindow, "key-press-event", G_CALLBACK(signalKeyPress), this); +#if 0 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); +#endif } virtual int get_active() const override @@ -17370,9 +17304,9 @@ public: virtual OUString get_active_text() const override { - if (m_pEntry) + if (m_pEditable) { - const gchar* pText = gtk_entry_get_text(GTK_ENTRY(m_pEntry)); + const gchar* pText = gtk_editable_get_text(m_pEditable); return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8); } @@ -17501,18 +17435,18 @@ public: virtual void set_entry_text(const OUString& rText) override { - assert(m_pEntry); + assert(m_pEditable); disable_notify_events(); - gtk_entry_set_text(GTK_ENTRY(m_pEntry), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr()); + gtk_editable_set_text(m_pEditable, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr()); enable_notify_events(); } virtual void set_entry_width_chars(int nChars) override { - assert(m_pEntry); + assert(m_pEditable); disable_notify_events(); - gtk_entry_set_width_chars(GTK_ENTRY(m_pEntry), nChars); - gtk_entry_set_max_width_chars(GTK_ENTRY(m_pEntry), nChars); + gtk_editable_set_width_chars(m_pEditable, nChars); + gtk_editable_set_max_width_chars(m_pEditable, nChars); enable_notify_events(); } @@ -17526,16 +17460,16 @@ public: virtual void select_entry_region(int nStartPos, int nEndPos) override { - assert(m_pEntry); + assert(m_pEditable); disable_notify_events(); - gtk_editable_select_region(GTK_EDITABLE(m_pEntry), nStartPos, nEndPos); + gtk_editable_select_region(m_pEditable, nStartPos, nEndPos); enable_notify_events(); } virtual bool get_entry_selection_bounds(int& rStartPos, int &rEndPos) override { - assert(m_pEntry); - return gtk_editable_get_selection_bounds(GTK_EDITABLE(m_pEntry), &rStartPos, &rEndPos); + assert(m_pEditable); + return gtk_editable_get_selection_bounds(m_pEditable, &rStartPos, &rEndPos); } virtual void set_entry_completion(bool bEnable, bool bCaseSensitive) override @@ -17552,26 +17486,26 @@ public: virtual void set_entry_editable(bool bEditable) override { - assert(m_pEntry); - gtk_editable_set_editable(GTK_EDITABLE(m_pEntry), bEditable); + assert(m_pEditable); + gtk_editable_set_editable(m_pEditable, bEditable); } virtual void cut_entry_clipboard() override { assert(m_pEntry); - gtk_editable_cut_clipboard(GTK_EDITABLE(m_pEntry)); + gtk_widget_activate_action(m_pEntry, "cut.clipboard", nullptr); } virtual void copy_entry_clipboard() override { assert(m_pEntry); - gtk_editable_copy_clipboard(GTK_EDITABLE(m_pEntry)); + gtk_widget_activate_action(m_pEntry, "copy.clipboard", nullptr); } virtual void paste_entry_clipboard() override { assert(m_pEntry); - gtk_editable_paste_clipboard(GTK_EDITABLE(m_pEntry)); + gtk_widget_activate_action(m_pEntry, "paste.clipboard", nullptr); } virtual void set_entry_font(const vcl::Font& rFont) override @@ -17597,57 +17531,61 @@ public: virtual void disable_notify_events() override { - if (m_pEntry) + if (m_pEditable) { - g_signal_handler_block(m_pEntry, m_nEntryInsertTextSignalId); + g_signal_handler_block(m_pEditable, m_nEntryInsertTextSignalId); g_signal_handler_block(m_pEntry, m_nEntryActivateSignalId); - g_signal_handler_block(m_pEntry, m_nEntryFocusInSignalId); - g_signal_handler_block(m_pEntry, m_nEntryFocusOutSignalId); - g_signal_handler_block(m_pEntry, m_nEntryKeyPressEventSignalId); - g_signal_handler_block(m_pEntry, m_nChangedSignalId); +// g_signal_handler_block(m_pEntry, m_nEntryFocusInSignalId); +// g_signal_handler_block(m_pEntry, m_nEntryFocusOutSignalId); +// g_signal_handler_block(m_pEntry, m_nEntryKeyPressEventSignalId); } +#if 0 else g_signal_handler_block(m_pToggleButton, m_nKeyPressEventSignalId); - if (m_nToggleFocusInSignalId) - g_signal_handler_block(m_pToggleButton, m_nToggleFocusInSignalId); - if (m_nToggleFocusOutSignalId) - g_signal_handler_block(m_pToggleButton, m_nToggleFocusOutSignalId); - g_signal_handler_block(m_pTreeView, m_nRowActivatedSignalId); - g_signal_handler_block(m_pToggleButton, m_nPopupShownSignalId); - GtkInstanceContainer::disable_notify_events(); +#endif +// if (m_nToggleFocusInSignalId) +// g_signal_handler_block(m_pToggleButton, m_nToggleFocusInSignalId); +// if (m_nToggleFocusOutSignalId) +// g_signal_handler_block(m_pToggleButton, m_nToggleFocusOutSignalId); +// g_signal_handler_block(m_pTreeView, m_nRowActivatedSignalId); + g_signal_handler_block(m_pComboBox, m_nPopupShownSignalId); + g_signal_handler_block(m_pComboBox, m_nChangedSignalId); + GtkInstanceWidget::disable_notify_events(); } virtual void enable_notify_events() override { - GtkInstanceContainer::enable_notify_events(); - g_signal_handler_unblock(m_pToggleButton, m_nPopupShownSignalId); - g_signal_handler_unblock(m_pTreeView, m_nRowActivatedSignalId); - if (m_nToggleFocusInSignalId) - g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusInSignalId); - if (m_nToggleFocusOutSignalId) - g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusOutSignalId); - if (m_pEntry) + GtkInstanceWidget::enable_notify_events(); + g_signal_handler_unblock(m_pComboBox, m_nChangedSignalId); + g_signal_handler_unblock(m_pComboBox, m_nPopupShownSignalId); +// g_signal_handler_unblock(m_pTreeView, m_nRowActivatedSignalId); +// if (m_nToggleFocusInSignalId) +// g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusInSignalId); +// if (m_nToggleFocusOutSignalId) +// g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusOutSignalId); + if (m_pEditable) { - g_signal_handler_unblock(m_pEntry, m_nChangedSignalId); g_signal_handler_unblock(m_pEntry, m_nEntryActivateSignalId); - g_signal_handler_unblock(m_pEntry, m_nEntryFocusInSignalId); - g_signal_handler_unblock(m_pEntry, m_nEntryFocusOutSignalId); - g_signal_handler_unblock(m_pEntry, m_nEntryKeyPressEventSignalId); - g_signal_handler_unblock(m_pEntry, m_nEntryInsertTextSignalId); +// g_signal_handler_unblock(m_pEntry, m_nEntryFocusInSignalId); +// g_signal_handler_unblock(m_pEntry, m_nEntryFocusOutSignalId); +// g_signal_handler_unblock(m_pEntry, m_nEntryKeyPressEventSignalId); + g_signal_handler_unblock(m_pEditable, m_nEntryInsertTextSignalId); } +#if 0 else g_signal_handler_unblock(m_pToggleButton, m_nKeyPressEventSignalId); +#endif } virtual void freeze() override { disable_notify_events(); bool bIsFirstFreeze = IsFirstFreeze(); - GtkInstanceContainer::freeze(); + GtkInstanceWidget::freeze(); if (bIsFirstFreeze) { g_object_ref(m_pTreeModel); - gtk_tree_view_set_model(m_pTreeView, nullptr); +// gtk_tree_view_set_model(m_pTreeView, nullptr); g_object_freeze_notify(G_OBJECT(m_pTreeModel)); if (m_xSorter) { @@ -17669,10 +17607,10 @@ public: gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING); } g_object_thaw_notify(G_OBJECT(m_pTreeModel)); - gtk_tree_view_set_model(m_pTreeView, m_pTreeModel); +// gtk_tree_view_set_model(m_pTreeView, m_pTreeModel); g_object_unref(m_pTreeModel); } - GtkInstanceContainer::thaw(); + GtkInstanceWidget::thaw(); enable_notify_events(); } @@ -17683,16 +17621,16 @@ public: virtual void connect_focus_in(const Link<Widget&, void>& rLink) override { - if (!m_nToggleFocusInSignalId) - m_nToggleFocusInSignalId = g_signal_connect_after(m_pToggleButton, "focus-in-event", G_CALLBACK(signalFocusIn), this); - GtkInstanceContainer::connect_focus_in(rLink); +// if (!m_nToggleFocusInSignalId) +// m_nToggleFocusInSignalId = g_signal_connect_after(m_pToggleButton, "focus-in-event", G_CALLBACK(signalFocusIn), this); + GtkInstanceWidget::connect_focus_in(rLink); } virtual void connect_focus_out(const Link<Widget&, void>& rLink) override { - if (!m_nToggleFocusOutSignalId) - m_nToggleFocusOutSignalId = g_signal_connect_after(m_pToggleButton, "focus-out-event", G_CALLBACK(signalFocusOut), this); - GtkInstanceContainer::connect_focus_out(rLink); +// if (!m_nToggleFocusOutSignalId) +// m_nToggleFocusOutSignalId = g_signal_connect_after(m_pToggleButton, "focus-out-event", G_CALLBACK(signalFocusOut), this); + GtkInstanceWidget::connect_focus_out(rLink); } virtual void grab_focus() override @@ -17702,7 +17640,10 @@ public: if (m_pEntry) gtk_widget_grab_focus(m_pEntry); else - gtk_widget_grab_focus(m_pToggleButton); + { +// gtk_widget_grab_focus(m_pToggleButton); + gtk_widget_grab_focus(GTK_WIDGET(m_pComboBox)); + } } virtual bool has_focus() const override @@ -17710,14 +17651,16 @@ public: if (m_pEntry && gtk_widget_has_focus(m_pEntry)) return true; - if (gtk_widget_has_focus(m_pToggleButton)) - return true; +// if (gtk_widget_has_focus(m_pToggleButton)) +// return true; +#if 0 if (gtk_widget_get_visible(GTK_WIDGET(m_pMenuWindow))) { if (gtk_widget_has_focus(GTK_WIDGET(m_pOverlayButton)) || gtk_widget_has_focus(GTK_WIDGET(m_pTreeView))) return true; } +#endif return GtkInstanceWidget::has_focus(); } @@ -17731,6 +17674,7 @@ public: { if (bOn == m_bCustomRenderer) return; +#if 0 GList* pColumns = gtk_tree_view_get_columns(m_pTreeView); // keep the original height around for optimal popup height calculation m_nNonCustomLineHeight = bOn ? ::get_height_row(m_pTreeView, pColumns) : -1; @@ -17755,6 +17699,7 @@ public: } g_list_free(pColumns); m_bCustomRenderer = bOn; +#endif } void call_signal_custom_render(VirtualDevice& rOutput, const tools::Rectangle& rRect, bool bSelected, const OUString& rId) @@ -17774,6 +17719,7 @@ public: virtual void set_item_menu(const OString& rIdent, weld::Menu* pMenu) override { +#if 0 m_xCustomMenuButtonHelper.reset(); GtkInstanceMenu* pPopoverWidget = dynamic_cast<GtkInstanceMenu*>(pMenu); GtkWidget* pMenuWidget = GTK_WIDGET(pPopoverWidget ? pPopoverWidget->getMenu() : nullptr); @@ -17783,6 +17729,7 @@ public: if (pMenuWidget) m_xCustomMenuButtonHelper.reset(new CustomRenderMenuButtonHelper(GTK_MENU(pMenuWidget), GTK_TOGGLE_BUTTON(m_pToggleButton))); m_sMenuButtonRow = OUString::fromUtf8(rIdent); +#endif } OUString get_mru_entries() const override @@ -17833,6 +17780,7 @@ public: int get_menu_button_width() const override { +#if 0 bool bVisible = gtk_widget_get_visible(GTK_WIDGET(m_pOverlayButton)); if (!bVisible) gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), true); @@ -17841,22 +17789,1796 @@ public: if (!bVisible) gtk_widget_set_visible(GTK_WIDGET(m_pOverlayButton), false); return nWidth; +#else + return 0; +#endif } virtual ~GtkInstanceComboBox() override { - m_xCustomMenuButtonHelper.reset(); +// m_xCustomMenuButtonHelper.reset(); do_clear(); if (m_nAutoCompleteIdleId) g_source_remove(m_nAutoCompleteIdleId); - if (m_pEntry) + if (m_pEditable) { - g_signal_handler_disconnect(m_pEntry, m_nChangedSignalId); - g_signal_handler_disconnect(m_pEntry, m_nEntryInsertTextSignalId); + g_signal_handler_disconnect(m_pEditable, m_nEntryInsertTextSignalId); g_signal_handler_disconnect(m_pEntry, m_nEntryActivateSignalId); - g_signal_handler_disconnect(m_pEntry, m_nEntryFocusInSignalId); - g_signal_handler_disconnect(m_pEntry, m_nEntryFocusOutSignalId); - g_signal_handler_disconnect(m_pEntry, m_nEntryKeyPressEventSignalId); +// g_signal_handler_disconnect(m_pEntry, m_nEntryFocusInSignalId); +// g_signal_handler_disconnect(m_pEntry, m_nEntryFocusOutSignalId); +// g_signal_handler_disconnect(m_pEntry, m_nEntryKeyPressEventSignalId); + } +#if 0 + else + g_signal_handler_disconnect(m_pToggleButton, m_nKeyPressEventSignalId); +#endif +// if (m_nToggleFocusInSignalId) +// g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusInSignalId); +// if (m_nToggleFocusOutSignalId) +// g_signal_handler_disconnect(m_pToggleButton, m_nToggleFocusOutSignalId); +// g_signal_handler_disconnect(m_pTreeView, m_nRowActivatedSignalId); + g_signal_handler_disconnect(m_pComboBox, m_nPopupShownSignalId); + g_signal_handler_disconnect(m_pComboBox, m_nChangedSignalId); + +// gtk_tree_view_set_model(m_pTreeView, nullptr); + + } +}; + +#else + +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; + GtkCellRenderer* m_pMenuTextRenderer; + 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; + bool m_bChangedByMenu; + bool m_bCustomRenderer; + bool m_bActivateCalled; + gint m_nTextCol; + gint m_nIdCol; + gulong m_nToggleFocusInSignalId; + gulong m_nToggleFocusOutSignalId; + gulong m_nRowActivatedSignalId; + gulong m_nChangedSignalId; + gulong m_nPopupShownSignalId; + gulong m_nKeyPressEventSignalId; + gulong m_nEntryInsertTextSignalId; + gulong m_nEntryActivateSignalId; + gulong m_nEntryFocusInSignalId; + gulong m_nEntryFocusOutSignalId; + gulong m_nEntryKeyPressEventSignalId; + guint m_nAutoCompleteIdleId; + gint m_nNonCustomLineHeight; + gint m_nPrePopupCursorPos; + int m_nMRUCount; + int m_nMaxMRUCount; + + static gboolean idleAutoComplete(gpointer widget) + { + GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget); + pThis->auto_complete(); + return false; + } + + void auto_complete() + { + m_nAutoCompleteIdleId = 0; + OUString aStartText = get_active_text(); + int nStartPos, nEndPos; + get_entry_selection_bounds(nStartPos, nEndPos); + int nMaxSelection = std::max(nStartPos, nEndPos); + if (nMaxSelection != aStartText.getLength()) + return; + + disable_notify_events(); + int nActive = get_active(); + int nStart = nActive; + + if (nStart == -1) + nStart = 0; + + int nPos = -1; + + int nZeroRow = 0; + if (m_nMRUCount) + nZeroRow += (m_nMRUCount + 1); + + if (!m_bAutoCompleteCaseSensitive) + { + // Try match case insensitive from current position + nPos = starts_with(m_pTreeModel, aStartText, 0, nStart, false); + if (nPos == -1 && nStart != 0) + { + // Try match case insensitive, but from start + nPos = starts_with(m_pTreeModel, aStartText, 0, nZeroRow, false); + } + } + + if (nPos == -1) + { + // Try match case sensitive from current position + nPos = starts_with(m_pTreeModel, aStartText, 0, nStart, true); + if (nPos == -1 && nStart != 0) + { + // Try match case sensitive, but from start + nPos = starts_with(m_pTreeModel, aStartText, 0, nZeroRow, true); + } + } + + if (nPos != -1) + { + OUString aText = get_text_including_mru(nPos); + if (aText != aStartText) + { + SolarMutexGuard aGuard; + set_active_including_mru(nPos, true); + } + select_entry_region(aText.getLength(), aStartText.getLength()); + } + enable_notify_events(); + } + + static void signalEntryInsertText(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength, + gint* position, gpointer widget) + { + GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget); + SolarMutexGuard aGuard; + pThis->signal_entry_insert_text(pEntry, pNewText, nNewTextLength, position); + } + + void signal_entry_insert_text(GtkEntry* pEntry, const gchar* pNewText, gint nNewTextLength, gint* position) + { + // first filter inserted text + if (m_aEntryInsertTextHdl.IsSet()) + { + OUString sText(pNewText, nNewTextLength, RTL_TEXTENCODING_UTF8); + const bool bContinue = m_aEntryInsertTextHdl.Call(sText); + if (bContinue && !sText.isEmpty()) + { + OString sFinalText(OUStringToOString(sText, RTL_TEXTENCODING_UTF8)); + g_signal_handlers_block_by_func(pEntry, reinterpret_cast<gpointer>(signalEntryInsertText), this); + gtk_editable_insert_text(GTK_EDITABLE(pEntry), sFinalText.getStr(), sFinalText.getLength(), position); + g_signal_handlers_unblock_by_func(pEntry, reinterpret_cast<gpointer>(signalEntryInsertText), this); + } + g_signal_stop_emission_by_name(pEntry, "insert-text"); + } + if (m_bAutoComplete) + { + // now check for autocompletes + if (m_nAutoCompleteIdleId) + g_source_remove(m_nAutoCompleteIdleId); + m_nAutoCompleteIdleId = g_idle_add(idleAutoComplete, this); + } + } + + static void signalChanged(GtkEntry*, gpointer widget) + { + GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget); + SolarMutexGuard aGuard; + pThis->fire_signal_changed(); + } + + void fire_signal_changed() + { + signal_changed(); + m_bChangedByMenu = false; + } + + static void signalPopupToggled(GtkToggleButton* /*pToggleButton*/, gpointer widget) + { + GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget); + pThis->signal_popup_toggled(); + } + + int get_popup_height(gint& rPopupWidth) + { + const StyleSettings& rSettings = Application::GetSettings().GetStyleSettings(); + + int nMaxRows = rSettings.GetListBoxMaximumLineCount(); + bool bAddScrollWidth = false; + int nRows = get_count_including_mru(); + if (nMaxRows < nRows) + { + nRows = nMaxRows; + bAddScrollWidth = true; + } + + GList* pColumns = gtk_tree_view_get_columns(m_pTreeView); + gint nRowHeight = get_height_row(m_pTreeView, pColumns); + g_list_free(pColumns); + + gint nSeparatorHeight = get_height_row_separator(m_pTreeView); + gint nHeight = get_height_rows(nRowHeight, nSeparatorHeight, nRows); + + // if we're using a custom renderer, limit the height to the height nMaxRows would be + // for a normal renderer, and then round down to how many custom rows fit in that + // space + if (m_nNonCustomLineHeight != -1 && nRowHeight) + { + gint nNormalHeight = get_height_rows(m_nNonCustomLineHeight, nSeparatorHeight, nMaxRows); + if (nHeight > nNormalHeight) + { + gint nRowsOnly = nNormalHeight - get_height_rows(0, nSeparatorHeight, nMaxRows); + gint nCustomRows = (nRowsOnly + (nRowHeight - 1)) / nRowHeight; + nHeight = get_height_rows(nRowHeight, nSeparatorHeight, nCustomRows); + } + } + + if (bAddScrollWidth) + rPopupWidth += rSettings.GetScrollBarSize(); + + return nHeight; + } + + void toggle_menu() + { + if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pToggleButton))) + { + if (m_bHoverSelection) + { + // turn hover selection back off until mouse is moved again + // *after* menu is shown again + gtk_tree_view_set_hover_selection(m_pTreeView, false); + m_bHoverSelection = false; + } + + do_ungrab(GTK_WIDGET(m_pMenuWindow)); + + gtk_widget_hide(GTK_WIDGET(m_pMenuWindow)); + + // so gdk_window_move_to_rect will work again the next time + gtk_widget_unrealize(GTK_WIDGET(m_pMenuWindow)); + + gtk_widget_set_size_request(GTK_WIDGET(m_pMenuWindow), -1, -1); + + if (!m_bActivateCalled) + tree_view_set_cursor(m_nPrePopupCursorPos); + + // undo show_menu tooltip blocking + GtkWidget* pParent = widget_get_toplevel(m_pToggleButton); + GtkSalFrame* pFrame = pParent ? GtkSalFrame::getFromWindow(pParent) : nullptr; + if (pFrame) + pFrame->UnblockTooltip(); + } + else + { + GtkWidget* pComboBox = GTK_WIDGET(getContainer()); + + gint nComboWidth = gtk_widget_get_allocated_width(pComboBox); + GtkRequisition size; + gtk_widget_get_preferred_size(GTK_WIDGET(m_pMenuWindow), nullptr, &size); + + gint nPopupWidth = size.width; + gint nPopupHeight = get_popup_height(nPopupWidth); + nPopupWidth = std::max(nPopupWidth, nComboWidth); + + gtk_widget_set_size_request(GTK_WIDGET(m_pMenuWindow), nPopupWidth, nPopupHeight); + + m_nPrePopupCursorPos = get_active(); + + m_bActivateCalled = false; + + // if we are in mru mode always start with the cursor at the top of the menu + if (m_nMaxMRUCount) + tree_view_set_cursor(0); + + show_menu(pComboBox, m_pMenuWindow); + } + } + + virtual void signal_popup_toggled() override + { + m_aQuickSelectionEngine.Reset(); + + toggle_menu(); + + bool bIsShown = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pToggleButton)); + if (m_bPopupActive != bIsShown) + { + m_bPopupActive = bIsShown; + ComboBox::signal_popup_toggled(); + if (!m_bPopupActive && m_pEntry) + { + disable_notify_events(); + //restore focus to the GtkEntry when the popup is gone, which + //is what the vcl case does, to ease the transition a little + gtk_widget_grab_focus(m_pEntry); + enable_notify_events(); + } + } + } + + static gboolean signalEntryFocusIn(GtkWidget*, GdkEvent*, gpointer widget) + { + GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget); + pThis->signal_entry_focus_in(); + return false; + } + + void signal_entry_focus_in() + { + signal_focus_in(); + } + + static gboolean signalEntryFocusOut(GtkWidget*, GdkEvent*, gpointer widget) + { + GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget); + pThis->signal_entry_focus_out(); + return false; + } + + void signal_entry_focus_out() + { + // if we have an untidy selection on losing focus remove the selection + int nStartPos, nEndPos; + if (get_entry_selection_bounds(nStartPos, nEndPos)) + { + int nMin = std::min(nStartPos, nEndPos); + int nMax = std::max(nStartPos, nEndPos); + if (nMin != 0 || nMax != get_active_text().getLength()) + select_entry_region(0, 0); + } + signal_focus_out(); + } + + static void signalEntryActivate(GtkEntry*, gpointer widget) + { + GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget); + pThis->signal_entry_activate(); + } + + void signal_entry_activate() + { + if (m_aEntryActivateHdl.IsSet()) + { + SolarMutexGuard aGuard; + if (m_aEntryActivateHdl.Call(*this)) + g_signal_stop_emission_by_name(m_pEntry, "activate"); + } + update_mru(); + } + + OUString get(int pos, int col) const + { + OUString sRet; + GtkTreeIter iter; + if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos)) + { + gchar* pStr; + gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1); + sRet = OUString(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8); + g_free(pStr); + } + return sRet; + } + + void set(int pos, int col, std::u16string_view rText) + { + GtkTreeIter iter; + if (gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos)) + { + OString aStr(OUStringToOString(rText, RTL_TEXTENCODING_UTF8)); + gtk_list_store_set(GTK_LIST_STORE(m_pTreeModel), &iter, col, aStr.getStr(), -1); + } + } + + int find(std::u16string_view rStr, int col, bool bSearchMRUArea) const + { + GtkTreeIter iter; + if (!gtk_tree_model_get_iter_first(m_pTreeModel, &iter)) + return -1; + + int nRet = 0; + + if (!bSearchMRUArea && m_nMRUCount) + { + if (!gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, m_nMRUCount + 1)) + return -1; + nRet += (m_nMRUCount + 1); + } + + OString aStr(OUStringToOString(rStr, RTL_TEXTENCODING_UTF8).getStr()); + do + { + gchar* pStr; + gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1); + const bool bEqual = g_strcmp0(pStr, aStr.getStr()) == 0; + g_free(pStr); + if (bEqual) + return nRet; + ++nRet; + } while (gtk_tree_model_iter_next(m_pTreeModel, &iter)); + + return -1; + } + + bool separator_function(const GtkTreePath* path) + { + return ::separator_function(path, m_aSeparatorRows); + } + + bool separator_function(int pos) + { + GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1); + bool bRet = separator_function(path); + gtk_tree_path_free(path); + return bRet; + } + + static gboolean separatorFunction(GtkTreeModel* pTreeModel, GtkTreeIter* pIter, gpointer widget) + { + GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget); + GtkTreePath* path = gtk_tree_model_get_path(pTreeModel, pIter); + bool bRet = pThis->separator_function(path); + gtk_tree_path_free(path); + return bRet; + } + + // https://gitlab.gnome.org/GNOME/gtk/issues/310 + // + // in the absence of a built-in solution + // a) support typeahead for the case where there is no entry widget, typing ahead + // into the button itself will select via the vcl selection engine, a matching + // entry + static gboolean signalKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget) + { + GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget); + return pThis->signal_key_press(pEvent); + } + + // tdf#131076 we want return in a ComboBox to act like return in a + // GtkEntry and activate the default dialog/assistant button + bool combobox_activate() + { + GtkWidget *pComboBox = GTK_WIDGET(m_pToggleButton); + GtkWidget *pToplevel = widget_get_toplevel(pComboBox); + GtkWindow *pWindow = GTK_WINDOW(pToplevel); + if (!pWindow) + return false; + if (!GTK_IS_DIALOG(pWindow) && !GTK_IS_ASSISTANT(pWindow)) + return false; + bool bDone = false; + GtkWidget *pDefaultWidget = gtk_window_get_default_widget(pWindow); + if (pDefaultWidget && pDefaultWidget != m_pToggleButton && gtk_widget_get_sensitive(pDefaultWidget)) + bDone = gtk_widget_activate(pDefaultWidget); + return bDone; + } + + static gboolean signalEntryKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget) + { + GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget); + LocalizeDecimalSeparator(pEvent); + return pThis->signal_entry_key_press(pEvent); + } + + bool signal_entry_key_press(const GdkEventKey* pEvent) + { + KeyEvent aKEvt(GtkToVcl(*pEvent)); + + vcl::KeyCode aKeyCode = aKEvt.GetKeyCode(); + + bool bDone = false; + + auto nCode = aKeyCode.GetCode(); + switch (nCode) + { + case KEY_DOWN: + { + sal_uInt16 nKeyMod = aKeyCode.GetModifier(); + if (!nKeyMod) + { + int nCount = get_count_including_mru(); + int nActive = get_active_including_mru() + 1; + while (nActive < nCount && separator_function(nActive)) + ++nActive; + if (nActive < nCount) + set_active_including_mru(nActive, true); + bDone = true; + } + else if (nKeyMod == KEY_MOD2 && !m_bPopupActive) + { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), true); + bDone = true; + } + break; + } + case KEY_UP: + { + sal_uInt16 nKeyMod = aKeyCode.GetModifier(); + if (!nKeyMod) + { + int nStartBound = m_bPopupActive ? 0 : (m_nMRUCount + 1); + int nActive = get_active_including_mru() - 1; + while (nActive >= nStartBound && separator_function(nActive)) + --nActive; + if (nActive >= nStartBound) + set_active_including_mru(nActive, true); + bDone = true; + } + break; + } + case KEY_PAGEUP: + { + sal_uInt16 nKeyMod = aKeyCode.GetModifier(); + if (!nKeyMod) + { + int nCount = get_count_including_mru(); + int nStartBound = m_bPopupActive ? 0 : (m_nMRUCount + 1); + int nActive = nStartBound; + while (nActive < nCount && separator_function(nActive)) + ++nActive; + if (nActive < nCount) + set_active_including_mru(nActive, true); + bDone = true; + } + break; + } + case KEY_PAGEDOWN: + { + sal_uInt16 nKeyMod = aKeyCode.GetModifier(); + if (!nKeyMod) + { + int nActive = get_count_including_mru() - 1; + int nStartBound = m_bPopupActive ? 0 : (m_nMRUCount + 1); + while (nActive >= nStartBound && separator_function(nActive)) + --nActive; + if (nActive >= nStartBound) + set_active_including_mru(nActive, true); + bDone = true; + } + break; + } + default: + break; + } + + return bDone; + } + + bool signal_key_press(const GdkEventKey* pEvent) + { + if (m_bHoverSelection) + { + // once a key is pressed, turn off hover selection until mouse is + // moved again otherwise when the treeview scrolls it jumps to the + // position under the mouse. + gtk_tree_view_set_hover_selection(m_pTreeView, false); + m_bHoverSelection = false; + } + + KeyEvent aKEvt(GtkToVcl(*pEvent)); + + vcl::KeyCode aKeyCode = aKEvt.GetKeyCode(); + + bool bDone = false; + + auto nCode = aKeyCode.GetCode(); + switch (nCode) + { + case KEY_DOWN: + case KEY_UP: + case KEY_PAGEUP: + case KEY_PAGEDOWN: + case KEY_HOME: + case KEY_END: + case KEY_LEFT: + case KEY_RIGHT: + case KEY_RETURN: + { + m_aQuickSelectionEngine.Reset(); + sal_uInt16 nKeyMod = aKeyCode.GetModifier(); + // tdf#131076 don't let bare return toggle menu popup active, but do allow deactivate + if (nCode == KEY_RETURN && !nKeyMod && !m_bPopupActive) + bDone = combobox_activate(); + else if (nCode == KEY_UP && nKeyMod == KEY_MOD2 && m_bPopupActive) + { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false); + bDone = true; + } + else if (nCode == KEY_DOWN && nKeyMod == KEY_MOD2 && !m_bPopupActive) + { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), true); + bDone = true; + } + break; + } + case KEY_ESCAPE: + { + m_aQuickSelectionEngine.Reset(); + if (m_bPopupActive) + { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false); + bDone = true; + } + break; + } + default: + // tdf#131076 let base space toggle menu popup when it's not already visible + if (nCode == KEY_SPACE && !aKeyCode.GetModifier() && !m_bPopupActive) + bDone = false; + else + bDone = m_aQuickSelectionEngine.HandleKeyEvent(aKEvt); + break; + } + + if (!bDone && !m_pEntry) + bDone = signal_entry_key_press(pEvent); + + return bDone; + } + + vcl::StringEntryIdentifier typeahead_getEntry(int nPos, OUString& out_entryText) const + { + int nEntryCount(get_count_including_mru()); + if (nPos >= nEntryCount) + nPos = 0; + out_entryText = get_text_including_mru(nPos); + + // vcl::StringEntryIdentifier does not allow for 0 values, but our position is 0-based + // => normalize + return reinterpret_cast<vcl::StringEntryIdentifier>(nPos + 1); + } + + static int typeahead_getEntryPos(vcl::StringEntryIdentifier entry) + { + // our pos is 0-based, but StringEntryIdentifier does not allow for a NULL + return reinterpret_cast<sal_Int64>(entry) - 1; + } + + void tree_view_set_cursor(int pos) + { + if (pos == -1) + { + gtk_tree_selection_unselect_all(gtk_tree_view_get_selection(m_pTreeView)); + if (m_pCellView) + gtk_cell_view_set_displayed_row(m_pCellView, nullptr); + } + else + { + GtkTreePath* path = gtk_tree_path_new_from_indices(pos, -1); + if (gtk_tree_view_get_model(m_pTreeView)) + gtk_tree_view_scroll_to_cell(m_pTreeView, path, nullptr, false, 0, 0); + gtk_tree_view_set_cursor(m_pTreeView, path, nullptr, false); + if (m_pCellView) + gtk_cell_view_set_displayed_row(m_pCellView, path); + gtk_tree_path_free(path); + } + } + + int tree_view_get_cursor() const + { + int nRet = -1; + + GtkTreePath* path; + gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr); + if (path) + { + gint depth; + gint* indices = gtk_tree_path_get_indices_with_depth(path, &depth); + nRet = indices[depth-1]; + gtk_tree_path_free(path); + } + + return nRet; + } + + int get_selected_entry() const + { + if (m_bPopupActive) + return tree_view_get_cursor(); + else + return get_active_including_mru(); + } + + void set_typeahead_selected_entry(int nSelect) + { + if (m_bPopupActive) + tree_view_set_cursor(nSelect); + else + set_active_including_mru(nSelect, true); + } + + virtual vcl::StringEntryIdentifier CurrentEntry(OUString& out_entryText) const override + { + int nCurrentPos = get_selected_entry(); + return typeahead_getEntry((nCurrentPos == -1) ? 0 : nCurrentPos, out_entryText); + } + + virtual vcl::StringEntryIdentifier NextEntry(vcl::StringEntryIdentifier currentEntry, OUString& out_entryText) const override + { + int nNextPos = typeahead_getEntryPos(currentEntry) + 1; + return typeahead_getEntry(nNextPos, out_entryText); + } + + virtual void SelectEntry(vcl::StringEntryIdentifier entry) override + { + int nSelect = typeahead_getEntryPos(entry); + if (nSelect == get_selected_entry()) + { + // ignore that. This method is a callback from the QuickSelectionEngine, which means the user attempted + // to select the given entry by typing its starting letters. No need to act. + return; + } + + // normalize + int nCount = get_count_including_mru(); + if (nSelect >= nCount) + nSelect = nCount ? nCount-1 : -1; + + set_typeahead_selected_entry(nSelect); + } + + static void signalGrabBroken(GtkWidget*, GdkEventGrabBroken *pEvent, gpointer widget) + { + GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget); + pThis->grab_broken(pEvent); + } + + void grab_broken(const GdkEventGrabBroken *event) + { + if (event->grab_window == nullptr) + { + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false); + } + else + { + //try and regrab, so when we lose the grab to the menu of the color palette + //combobox we regain it so the color palette doesn't itself disappear on next + //click on the color palette combobox + do_grab(GTK_WIDGET(m_pMenuWindow)); + } + } + + static gboolean signalButtonPress(GtkWidget* pWidget, GdkEventButton* pEvent, gpointer widget) + { + GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget); + return pThis->button_press(pWidget, pEvent); + } + + bool button_press(GtkWidget* pWidget, GdkEventButton* pEvent) + { + //we want to pop down if the button was pressed outside our popup + gdouble x = pEvent->x_root; + gdouble y = pEvent->y_root; + gint xoffset, yoffset; + gdk_window_get_root_origin(widget_get_surface(pWidget), &xoffset, &yoffset); + + GtkAllocation alloc; + gtk_widget_get_allocation(pWidget, &alloc); + xoffset += alloc.x; + yoffset += alloc.y; + + gtk_widget_get_allocation(GTK_WIDGET(m_pMenuWindow), &alloc); + gint x1 = alloc.x + xoffset; + gint y1 = alloc.y + yoffset; + gint x2 = x1 + alloc.width; + gint y2 = y1 + alloc.height; + + if (x > x1 && x < x2 && y > y1 && y < y2) + return false; + + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false); + + return false; + } + + static gboolean signalMotion(GtkWidget*, GdkEventMotion*, gpointer widget) + { + GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget); + pThis->signal_motion(); + return false; + } + + void signal_motion() + { + // if hover-selection was disabled after pressing a key, then turn it back on again + if (!m_bHoverSelection && !m_bMouseInOverlayButton) + { + gtk_tree_view_set_hover_selection(m_pTreeView, true); + m_bHoverSelection = true; + } + } + + static void signalRowActivated(GtkTreeView*, GtkTreePath*, GtkTreeViewColumn*, gpointer widget) + { + GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget); + pThis->handle_row_activated(); + } + + void handle_row_activated() + { + m_bActivateCalled = true; + m_bChangedByMenu = true; + disable_notify_events(); + int nActive = get_active(); + if (m_pEntry) + gtk_entry_set_text(GTK_ENTRY(m_pEntry), OUStringToOString(get_text(nActive), RTL_TEXTENCODING_UTF8).getStr()); + else + tree_view_set_cursor(nActive); + enable_notify_events(); + gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false); + fire_signal_changed(); + update_mru(); + } + + void do_clear() + { + disable_notify_events(); + gtk_tree_view_set_row_separator_func(m_pTreeView, nullptr, nullptr, nullptr); + m_aSeparatorRows.clear(); + gtk_list_store_clear(GTK_LIST_STORE(m_pTreeModel)); + m_nMRUCount = 0; + enable_notify_events(); + } + + virtual int get_max_mru_count() const override + { + return m_nMaxMRUCount; + } + + virtual void set_max_mru_count(int nMaxMRUCount) override + { + m_nMaxMRUCount = nMaxMRUCount; + update_mru(); + } + + void update_mru() + { + int nMRUCount = m_nMRUCount; + + if (m_nMaxMRUCount) + { + OUString sActiveText = get_active_text(); + OUString sActiveId = get_active_id(); + insert_including_mru(0, sActiveText, &sActiveId, nullptr, nullptr); + ++m_nMRUCount; + + for (int i = 1; i < m_nMRUCount - 1; ++i) + { + if (get_text_including_mru(i) == sActiveText) + { + remove_including_mru(i); + --m_nMRUCount; + break; + } + } + } + + while (m_nMRUCount > m_nMaxMRUCount) + { + remove_including_mru(m_nMRUCount - 1); + --m_nMRUCount; + } + + if (m_nMRUCount && !nMRUCount) + insert_separator_including_mru(m_nMRUCount, "separator"); + else if (!m_nMRUCount && nMRUCount) + remove_including_mru(m_nMRUCount); // remove separator + } + + int get_count_including_mru() const + { + return gtk_tree_model_iter_n_children(m_pTreeModel, nullptr); + } + + int get_active_including_mru() const + { + return tree_view_get_cursor(); + } + + void set_active_including_mru(int pos, bool bInteractive) + { + disable_notify_events(); + + tree_view_set_cursor(pos); + + if (m_pEntry) + { + if (pos != -1) + gtk_entry_set_text(GTK_ENTRY(m_pEntry), OUStringToOString(get_text_including_mru(pos), RTL_TEXTENCODING_UTF8).getStr()); + else + gtk_entry_set_text(GTK_ENTRY(m_pEntry), ""); + } + + m_bChangedByMenu = false; + enable_notify_events(); + + if (bInteractive && !m_bPopupActive) + signal_changed(); + } + + int find_text_including_mru(std::u16string_view rStr, bool bSearchMRU) const + { + return find(rStr, m_nTextCol, bSearchMRU); + } + + int find_id_including_mru(std::u16string_view rId, bool bSearchMRU) const + { + return find(rId, m_nIdCol, bSearchMRU); + } + + OUString get_text_including_mru(int pos) const + { + return get(pos, m_nTextCol); + } + + OUString get_id_including_mru(int pos) const + { + return get(pos, m_nIdCol); + } + + void set_id_including_mru(int pos, std::u16string_view rId) + { + set(pos, m_nIdCol, rId); + } + + void remove_including_mru(int pos) + { + disable_notify_events(); + GtkTreeIter iter; + gtk_tree_model_iter_nth_child(m_pTreeModel, &iter, nullptr, pos); + if (!m_aSeparatorRows.empty()) + { + bool bFound = false; + + GtkTreePath* pPath = gtk_tree_path_new_from_indices(pos, -1); + + for (auto aIter = m_aSeparatorRows.begin(); aIter != m_aSeparatorRows.end(); ++aIter) + { + GtkTreePath* seppath = gtk_tree_row_reference_get_path(aIter->get()); + if (seppath) + { + if (gtk_tree_path_compare(pPath, seppath) == 0) + bFound = true; + gtk_tree_path_free(seppath); + } + if (bFound) + { + m_aSeparatorRows.erase(aIter); + break; + } + } + + gtk_tree_path_free(pPath); + } + gtk_list_store_remove(GTK_LIST_STORE(m_pTreeModel), &iter); + enable_notify_events(); + } + + void insert_separator_including_mru(int pos, const OUString& rId) + { + disable_notify_events(); + GtkTreeIter iter; + if (!gtk_tree_view_get_row_separator_func(m_pTreeView)) + gtk_tree_view_set_row_separator_func(m_pTreeView, separatorFunction, this, nullptr); + insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, &rId, u"", nullptr, nullptr); + GtkTreePath* pPath = gtk_tree_path_new_from_indices(pos, -1); + m_aSeparatorRows.emplace_back(gtk_tree_row_reference_new(m_pTreeModel, pPath)); + gtk_tree_path_free(pPath); + enable_notify_events(); + } + + void insert_including_mru(int pos, std::u16string_view rText, const OUString* pId, const OUString* pIconName, const VirtualDevice* pImageSurface) + { + disable_notify_events(); + GtkTreeIter iter; + insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, pId, rText, pIconName, pImageSurface); + 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) + return; + + 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 + } + + void signal_combo_mnemonic_activate() + { + if (m_pEntry) + gtk_widget_grab_focus(m_pEntry); + else + gtk_widget_grab_focus(m_pToggleButton); + } + + static gboolean signalComboMnemonicActivate(GtkWidget*, gboolean, gpointer widget) + { + GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget); + pThis->signal_combo_mnemonic_activate(); + return true; + } + + int include_mru(int pos) + { + if (m_nMRUCount && pos != -1) + pos += (m_nMRUCount + 1); + return pos; + } + +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) + , m_pToggleButton(GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "button"))) + , m_pEntry(GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "entry"))) + , m_pCellView(nullptr) + , m_aQuickSelectionEngine(*this) + , m_bHoverSelection(false) + , m_bMouseInOverlayButton(false) + , m_bPopupActive(false) + , m_bAutoComplete(false) + , m_bAutoCompleteCaseSensitive(false) + , m_bChangedByMenu(false) + , m_bCustomRenderer(false) + , m_bActivateCalled(false) + , m_nTextCol(gtk_combo_box_get_entry_text_column(pComboBox)) + , m_nIdCol(gtk_combo_box_get_id_column(pComboBox)) + , m_nToggleFocusInSignalId(0) + , m_nToggleFocusOutSignalId(0) + , m_nRowActivatedSignalId(g_signal_connect(m_pTreeView, "row-activated", G_CALLBACK(signalRowActivated), this)) + , m_nChangedSignalId(g_signal_connect(m_pEntry, "changed", G_CALLBACK(signalChanged), this)) + , m_nPopupShownSignalId(g_signal_connect(m_pToggleButton, "toggled", G_CALLBACK(signalPopupToggled), this)) + , m_nAutoCompleteIdleId(0) + , m_nNonCustomLineHeight(-1) + , m_nPrePopupCursorPos(-1) + , m_nMRUCount(0) + , m_nMaxMRUCount(0) + { + int nActive = gtk_combo_box_get_active(m_pComboBox); + + if (gtk_style_context_has_class(gtk_widget_get_style_context(GTK_WIDGET(m_pComboBox)), "small-button")) + gtk_style_context_add_class(gtk_widget_get_style_context(GTK_WIDGET(getContainer())), "small-button"); + + insertAsParent(GTK_WIDGET(m_pComboBox), GTK_WIDGET(getContainer())); + gtk_widget_set_visible(GTK_WIDGET(m_pComboBox), false); + gtk_widget_set_no_show_all(GTK_WIDGET(m_pComboBox), true); + + gtk_tree_view_set_model(m_pTreeView, m_pTreeModel); + /* tdf#136455 gtk_combo_box_set_model with a null Model should be good + enough. But in practice, while the ComboBox model is unset, GTK + doesn't unset the ComboBox menus model, so that remains listening to + additions to the ListStore and slowing things down massively. + Using a new model does reset the menu to listen to that unused one instead */ + gtk_combo_box_set_model(m_pComboBox, GTK_TREE_MODEL(gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING))); + + GtkTreeViewColumn* pCol = gtk_tree_view_column_new(); + gtk_tree_view_append_column(m_pTreeView, pCol); + + bool bPixbufUsedSurface = gtk_tree_model_get_n_columns(m_pTreeModel) == 4; + + GList* cells = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(m_pComboBox)); + // move the cell renderers from the combobox to the replacement treeview + m_pMenuTextRenderer = static_cast<GtkCellRenderer*>(cells->data); + for (GList* pRenderer = g_list_first(cells); pRenderer; pRenderer = g_list_next(pRenderer)) + { + GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data); + bool bTextRenderer = pCellRenderer == m_pMenuTextRenderer; + gtk_tree_view_column_pack_end(pCol, pCellRenderer, bTextRenderer); + if (!bTextRenderer) + { + if (bPixbufUsedSurface) + gtk_tree_view_column_set_attributes(pCol, pCellRenderer, "surface", 3, nullptr); + else + gtk_tree_view_column_set_attributes(pCol, pCellRenderer, "pixbuf", 2, nullptr); + } + } + + gtk_tree_view_column_set_attributes(pCol, m_pMenuTextRenderer, "text", m_nTextCol, nullptr); + + if (gtk_combo_box_get_has_entry(m_pComboBox)) + { + m_bAutoComplete = true; + m_nEntryInsertTextSignalId = g_signal_connect(m_pEntry, "insert-text", G_CALLBACK(signalEntryInsertText), this); + m_nEntryActivateSignalId = g_signal_connect(m_pEntry, "activate", G_CALLBACK(signalEntryActivate), this); + m_nEntryFocusInSignalId = g_signal_connect(m_pEntry, "focus-in-event", G_CALLBACK(signalEntryFocusIn), this); + m_nEntryFocusOutSignalId = g_signal_connect(m_pEntry, "focus-out-event", G_CALLBACK(signalEntryFocusOut), this); + m_nEntryKeyPressEventSignalId = g_signal_connect(m_pEntry, "key-press-event", G_CALLBACK(signalEntryKeyPress), this); + m_nKeyPressEventSignalId = 0; + } + else + { + gtk_widget_set_visible(m_pEntry, false); + m_pEntry = nullptr; + + GtkWidget* pArrow = GTK_WIDGET(gtk_builder_get_object(pComboBuilder, "arrow")); + gtk_container_child_set(getContainer(), m_pToggleButton, "expand", true, nullptr); + + auto m_pCellArea = gtk_cell_area_box_new(); + m_pCellView = GTK_CELL_VIEW(gtk_cell_view_new_with_context(m_pCellArea, nullptr)); + gtk_widget_set_hexpand(GTK_WIDGET(m_pCellView), true); + GtkBox* pBox = GTK_BOX(gtk_widget_get_parent(pArrow)); + + gint nImageSpacing(2); + GtkStyleContext *pContext = gtk_widget_get_style_context(GTK_WIDGET(m_pToggleButton)); + gtk_style_context_get_style(pContext, "image-spacing", &nImageSpacing, nullptr); + gtk_box_set_spacing(pBox, nImageSpacing); + + gtk_box_pack_start(pBox, GTK_WIDGET(m_pCellView), false, true, 0); + + gtk_cell_view_set_fit_model(m_pCellView, true); + gtk_cell_view_set_model(m_pCellView, m_pTreeModel); + + m_pButtonTextRenderer = gtk_cell_renderer_text_new(); + gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(m_pCellView), m_pButtonTextRenderer, true); + gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pCellView), m_pButtonTextRenderer, "text", m_nTextCol, nullptr); + if (g_list_length(cells) > 1) + { + GtkCellRenderer* pCellRenderer = gtk_cell_renderer_pixbuf_new(); + gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(m_pCellView), pCellRenderer, false); + if (bPixbufUsedSurface) + gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pCellView), pCellRenderer, "surface", 3, nullptr); + else + gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pCellView), pCellRenderer, "pixbuf", 2, nullptr); + } + + gtk_widget_show_all(GTK_WIDGET(m_pCellView)); + + m_nEntryInsertTextSignalId = 0; + m_nEntryActivateSignalId = 0; + m_nEntryFocusInSignalId = 0; + m_nEntryFocusOutSignalId = 0; + m_nEntryKeyPressEventSignalId = 0; + m_nKeyPressEventSignalId = g_signal_connect(m_pToggleButton, "key-press-event", G_CALLBACK(signalKeyPress), this); + } + + g_list_free(cells); + + if (nActive != -1) + tree_view_set_cursor(nActive); + + g_signal_connect(getContainer(), "mnemonic-activate", G_CALLBACK(signalComboMnemonicActivate), this); + + g_signal_connect(m_pMenuWindow, "grab-broken-event", G_CALLBACK(signalGrabBroken), this); + g_signal_connect(m_pMenuWindow, "button-press-event", G_CALLBACK(signalButtonPress), this); + g_signal_connect(m_pMenuWindow, "motion-notify-event", G_CALLBACK(signalMotion), this); + // 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 + { + int nActive = get_active_including_mru(); + if (nActive == -1) + return -1; + + if (m_nMRUCount) + { + if (nActive < m_nMRUCount) + nActive = find_text(get_text_including_mru(nActive)); + else + nActive -= (m_nMRUCount + 1); + } + + return nActive; + } + + virtual OUString get_active_id() const override + { + int nActive = get_active(); + return nActive != -1 ? get_id(nActive) : OUString(); + } + + virtual void set_active_id(const OUString& rStr) override + { + set_active(find_id(rStr)); + m_bChangedByMenu = false; + } + + virtual void set_size_request(int nWidth, int nHeight) override + { + if (m_pButtonTextRenderer) + { + // tweak the cell render to get a narrower size to stick + if (nWidth != -1) + { + // this bit isn't great, I really want to be able to ellipse the text in the comboboxtext itself and let + // the popup menu render them in full, in the interim ellipse both of them + g_object_set(G_OBJECT(m_pButtonTextRenderer), "ellipsize", PANGO_ELLIPSIZE_MIDDLE, nullptr); + + // to find out how much of the width of the combobox belongs to the cell, set + // the cell and widget to the min cell width and see what the difference is + int min; + gtk_cell_renderer_get_preferred_width(m_pButtonTextRenderer, m_pWidget, &min, nullptr); + gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, min, -1); + gtk_widget_set_size_request(m_pWidget, min, -1); + int nNonCellWidth = get_preferred_size().Width() - min; + + int nCellWidth = nWidth - nNonCellWidth; + if (nCellWidth >= 0) + { + // now set the cell to the max width which it can be within the + // requested widget width + gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, nWidth - nNonCellWidth, -1); + } + } + else + { + g_object_set(G_OBJECT(m_pButtonTextRenderer), "ellipsize", PANGO_ELLIPSIZE_NONE, nullptr); + gtk_cell_renderer_set_fixed_size(m_pButtonTextRenderer, -1, -1); + } + } + + gtk_widget_set_size_request(m_pWidget, nWidth, nHeight); + } + + virtual void set_active(int pos) override + { + set_active_including_mru(include_mru(pos), false); + } + + virtual OUString get_active_text() const override + { + if (m_pEntry) + { + const gchar* pText = gtk_entry_get_text(GTK_ENTRY(m_pEntry)); + return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8); + } + + int nActive = get_active(); + if (nActive == -1) + return OUString(); + + return get_text(nActive); + } + + virtual OUString get_text(int pos) const override + { + if (m_nMRUCount) + pos += (m_nMRUCount + 1); + return get_text_including_mru(pos); + } + + virtual OUString get_id(int pos) const override + { + if (m_nMRUCount) + pos += (m_nMRUCount + 1); + return get_id_including_mru(pos); + } + + virtual void set_id(int pos, const OUString& rId) override + { + if (m_nMRUCount) + pos += (m_nMRUCount + 1); + set_id_including_mru(pos, rId); + } + + virtual void insert_vector(const std::vector<weld::ComboBoxEntry>& rItems, bool bKeepExisting) override + { + freeze(); + + int nInsertionPoint; + if (!bKeepExisting) + { + clear(); + nInsertionPoint = 0; + } + else + nInsertionPoint = get_count(); + + GtkTreeIter iter; + // tdf#125241 inserting backwards is faster + for (auto aI = rItems.rbegin(); aI != rItems.rend(); ++aI) + { + const auto& rItem = *aI; + insert_row(GTK_LIST_STORE(m_pTreeModel), iter, nInsertionPoint, rItem.sId.isEmpty() ? nullptr : &rItem.sId, + rItem.sString, rItem.sImage.isEmpty() ? nullptr : &rItem.sImage, nullptr); + } + + thaw(); + } + + virtual void remove(int pos) override + { + if (m_nMRUCount) + pos += (m_nMRUCount + 1); + remove_including_mru(pos); + } + + virtual void insert(int pos, const OUString& rText, const OUString* pId, const OUString* pIconName, VirtualDevice* pImageSurface) override + { + insert_including_mru(include_mru(pos), rText, pId, pIconName, pImageSurface); + } + + virtual void insert_separator(int pos, const OUString& rId) override + { + pos = pos == -1 ? get_count() : pos; + if (m_nMRUCount) + pos += (m_nMRUCount + 1); + insert_separator_including_mru(pos, rId); + } + + virtual int get_count() const override + { + int nCount = get_count_including_mru(); + if (m_nMRUCount) + nCount -= (m_nMRUCount + 1); + return nCount; + } + + virtual int find_text(const OUString& rStr) const override + { + int nPos = find_text_including_mru(rStr, false); + if (nPos != -1 && m_nMRUCount) + nPos -= (m_nMRUCount + 1); + return nPos; + } + + virtual int find_id(const OUString& rId) const override + { + int nPos = find_id_including_mru(rId, false); + if (nPos != -1 && m_nMRUCount) + nPos -= (m_nMRUCount + 1); + return nPos; + } + + virtual void clear() override + { + do_clear(); + } + + virtual void make_sorted() override + { + m_xSorter.reset(new comphelper::string::NaturalStringSorter( + ::comphelper::getProcessComponentContext(), + Application::GetSettings().GetUILanguageTag().getLocale())); + GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel); + gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING); + gtk_tree_sortable_set_sort_func(pSortable, m_nTextCol, default_sort_func, m_xSorter.get(), nullptr); + } + + virtual bool has_entry() const override + { + return gtk_combo_box_get_has_entry(m_pComboBox); + } + + virtual void set_entry_message_type(weld::EntryMessageType eType) override + { + assert(m_pEntry); + ::set_entry_message_type(GTK_ENTRY(m_pEntry), eType); + } + + virtual void set_entry_text(const OUString& rText) override + { + assert(m_pEntry); + disable_notify_events(); + gtk_entry_set_text(GTK_ENTRY(m_pEntry), OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr()); + enable_notify_events(); + } + + virtual void set_entry_width_chars(int nChars) override + { + assert(m_pEntry); + disable_notify_events(); + gtk_entry_set_width_chars(GTK_ENTRY(m_pEntry), nChars); + gtk_entry_set_max_width_chars(GTK_ENTRY(m_pEntry), nChars); + enable_notify_events(); + } + + virtual void set_entry_max_length(int nChars) override + { + assert(m_pEntry); + disable_notify_events(); + gtk_entry_set_max_length(GTK_ENTRY(m_pEntry), nChars); + enable_notify_events(); + } + + virtual void select_entry_region(int nStartPos, int nEndPos) override + { + assert(m_pEntry); + disable_notify_events(); + gtk_editable_select_region(GTK_EDITABLE(m_pEntry), nStartPos, nEndPos); + enable_notify_events(); + } + + virtual bool get_entry_selection_bounds(int& rStartPos, int &rEndPos) override + { + assert(m_pEntry); + return gtk_editable_get_selection_bounds(GTK_EDITABLE(m_pEntry), &rStartPos, &rEndPos); + } + + virtual void set_entry_completion(bool bEnable, bool bCaseSensitive) override + { + m_bAutoComplete = bEnable; + m_bAutoCompleteCaseSensitive = bCaseSensitive; + } + + virtual void set_entry_placeholder_text(const OUString& rText) override + { + assert(m_pEntry); + gtk_entry_set_placeholder_text(GTK_ENTRY(m_pEntry), rText.toUtf8().getStr()); + } + + virtual void set_entry_editable(bool bEditable) override + { + assert(m_pEntry); + gtk_editable_set_editable(GTK_EDITABLE(m_pEntry), bEditable); + } + + virtual void cut_entry_clipboard() override + { + assert(m_pEntry); + gtk_editable_cut_clipboard(GTK_EDITABLE(m_pEntry)); + } + + virtual void copy_entry_clipboard() override + { + assert(m_pEntry); + gtk_editable_copy_clipboard(GTK_EDITABLE(m_pEntry)); + } + + virtual void paste_entry_clipboard() override + { + assert(m_pEntry); + gtk_editable_paste_clipboard(GTK_EDITABLE(m_pEntry)); + } + + virtual void set_entry_font(const vcl::Font& rFont) override + { + m_xFont.reset(new vcl::Font(rFont)); + assert(m_pEntry); + PangoAttrList* pOrigList = gtk_entry_get_attributes(GTK_ENTRY(m_pEntry)); + PangoAttrList* pAttrList = pOrigList ? pango_attr_list_copy(pOrigList) : pango_attr_list_new(); + update_attr_list(pAttrList, rFont); + gtk_entry_set_attributes(GTK_ENTRY(m_pEntry), pAttrList); + pango_attr_list_unref(pAttrList); + } + + virtual vcl::Font get_entry_font() override + { + if (m_xFont) + return *m_xFont; + assert(m_pEntry); + PangoContext* pContext = gtk_widget_get_pango_context(m_pEntry); + return pango_to_vcl(pango_context_get_font_description(pContext), + Application::GetSettings().GetUILanguageTag().getLocale()); + } + + virtual void disable_notify_events() override + { + if (m_pEntry) + { + g_signal_handler_block(m_pEntry, m_nEntryInsertTextSignalId); + g_signal_handler_block(m_pEntry, m_nEntryActivateSignalId); + g_signal_handler_block(m_pEntry, m_nEntryFocusInSignalId); + g_signal_handler_block(m_pEntry, m_nEntryFocusOutSignalId); + g_signal_handler_block(m_pEntry, m_nEntryKeyPressEventSignalId); + g_signal_handler_block(m_pEntry, m_nChangedSignalId); + } + else + g_signal_handler_block(m_pToggleButton, m_nKeyPressEventSignalId); + if (m_nToggleFocusInSignalId) + g_signal_handler_block(m_pToggleButton, m_nToggleFocusInSignalId); + if (m_nToggleFocusOutSignalId) + g_signal_handler_block(m_pToggleButton, m_nToggleFocusOutSignalId); + g_signal_handler_block(m_pTreeView, m_nRowActivatedSignalId); + g_signal_handler_block(m_pToggleButton, m_nPopupShownSignalId); + GtkInstanceContainer::disable_notify_events(); + } + + virtual void enable_notify_events() override + { + GtkInstanceContainer::enable_notify_events(); + g_signal_handler_unblock(m_pToggleButton, m_nPopupShownSignalId); + g_signal_handler_unblock(m_pTreeView, m_nRowActivatedSignalId); + if (m_nToggleFocusInSignalId) + g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusInSignalId); + if (m_nToggleFocusOutSignalId) + g_signal_handler_unblock(m_pToggleButton, m_nToggleFocusOutSignalId); + if (m_pEntry) + { + g_signal_handler_unblock(m_pEntry, m_nChangedSignalId); + g_signal_handler_unblock(m_pEntry, m_nEntryActivateSignalId); + g_signal_handler_unblock(m_pEntry, m_nEntryFocusInSignalId); + g_signal_handler_unblock(m_pEntry, m_nEntryFocusOutSignalId); + g_signal_handler_unblock(m_pEntry, m_nEntryKeyPressEventSignalId); + g_signal_handler_unblock(m_pEntry, m_nEntryInsertTextSignalId); + } + else + g_signal_handler_unblock(m_pToggleButton, m_nKeyPressEventSignalId); + } + + virtual void freeze() override + { + disable_notify_events(); + bool bIsFirstFreeze = IsFirstFreeze(); + GtkInstanceContainer::freeze(); + if (bIsFirstFreeze) + { + g_object_ref(m_pTreeModel); + gtk_tree_view_set_model(m_pTreeView, nullptr); + g_object_freeze_notify(G_OBJECT(m_pTreeModel)); + if (m_xSorter) + { + GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel); + gtk_tree_sortable_set_sort_column_id(pSortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, GTK_SORT_ASCENDING); + } + } + enable_notify_events(); + } + + virtual void thaw() override + { + disable_notify_events(); + if (IsLastThaw()) + { + if (m_xSorter) + { + GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel); + gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING); + } + g_object_thaw_notify(G_OBJECT(m_pTreeModel)); + gtk_tree_view_set_model(m_pTreeView, m_pTreeModel); + g_object_unref(m_pTreeModel); + } + GtkInstanceContainer::thaw(); + enable_notify_events(); + } + + virtual bool get_popup_shown() const override + { + return m_bPopupActive; + } + + virtual void connect_focus_in(const Link<Widget&, void>& rLink) override + { + if (!m_nToggleFocusInSignalId) + m_nToggleFocusInSignalId = g_signal_connect_after(m_pToggleButton, "focus-in-event", G_CALLBACK(signalFocusIn), this); + GtkInstanceContainer::connect_focus_in(rLink); + } + + virtual void connect_focus_out(const Link<Widget&, void>& rLink) override + { + if (!m_nToggleFocusOutSignalId) + m_nToggleFocusOutSignalId = g_signal_connect_after(m_pToggleButton, "focus-out-event", G_CALLBACK(signalFocusOut), this); + GtkInstanceContainer::connect_focus_out(rLink); + } + + virtual void grab_focus() override + { + if (has_focus()) + return; + if (m_pEntry) + gtk_widget_grab_focus(m_pEntry); + else + gtk_widget_grab_focus(m_pToggleButton); + } + + virtual bool has_focus() const override + { + if (m_pEntry && gtk_widget_has_focus(m_pEntry)) + return true; + + if (gtk_widget_has_focus(m_pToggleButton)) + return true; + + if (gtk_widget_get_visible(GTK_WIDGET(m_pMenuWindow))) + { + if (gtk_widget_has_focus(GTK_WIDGET(m_pOverlayButton)) || gtk_widget_has_focus(GTK_WIDGET(m_pTreeView))) + return true; + } + + return GtkInstanceWidget::has_focus(); + } + + virtual bool changed_by_direct_pick() const override + { + return m_bChangedByMenu; + } + + virtual void set_custom_renderer(bool bOn) override + { + if (bOn == m_bCustomRenderer) + return; + GList* pColumns = gtk_tree_view_get_columns(m_pTreeView); + // keep the original height around for optimal popup height calculation + m_nNonCustomLineHeight = bOn ? ::get_height_row(m_pTreeView, pColumns) : -1; + GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pColumns->data); + gtk_cell_layout_clear(GTK_CELL_LAYOUT(pColumn)); + if (bOn) + { + GtkCellRenderer *pRenderer = custom_cell_renderer_surface_new(); + GValue value = G_VALUE_INIT; + g_value_init(&value, G_TYPE_POINTER); + g_value_set_pointer(&value, static_cast<gpointer>(this)); + g_object_set_property(G_OBJECT(pRenderer), "instance", &value); + gtk_tree_view_column_pack_start(pColumn, pRenderer, true); + gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol); + gtk_tree_view_column_add_attribute(pColumn, pRenderer, "id", m_nIdCol); + } + else + { + GtkCellRenderer *pRenderer = gtk_cell_renderer_text_new(); + gtk_tree_view_column_pack_start(pColumn, pRenderer, true); + gtk_tree_view_column_add_attribute(pColumn, pRenderer, "text", m_nTextCol); + } + g_list_free(pColumns); + m_bCustomRenderer = bOn; + } + + void call_signal_custom_render(VirtualDevice& rOutput, const tools::Rectangle& rRect, bool bSelected, const OUString& rId) + { + signal_custom_render(rOutput, rRect, bSelected, rId); + } + + Size call_signal_custom_get_size(VirtualDevice& rOutput) + { + return signal_custom_get_size(rOutput); + } + + VclPtr<VirtualDevice> create_render_virtual_device() const override + { + 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 = ';'; + + OUStringBuffer aEntries; + for (sal_Int32 n = 0; n < m_nMRUCount; n++) + { + aEntries.append(get_text_including_mru(n)); + if (n < m_nMRUCount - 1) + aEntries.append(cSep); + } + return aEntries.makeStringAndClear(); + } + + virtual void set_mru_entries(const OUString& rEntries) override + { + const sal_Unicode cSep = ';'; + + // Remove old MRU entries + for (sal_Int32 n = m_nMRUCount; n;) + remove_including_mru(--n); + + sal_Int32 nMRUCount = 0; + sal_Int32 nIndex = 0; + do + { + OUString aEntry = rEntries.getToken(0, cSep, nIndex); + // Accept only existing entries + int nPos = find_text(aEntry); + if (nPos != -1) + { + OUString sId = get_id(nPos); + insert_including_mru(0, aEntry, &sId, nullptr, nullptr); + ++nMRUCount; + } + } + while (nIndex >= 0); + ... etc. - the rest is truncated _______________________________________________ Libreoffice-commits mailing list libreoffice-comm...@lists.freedesktop.org https://lists.freedesktop.org/mailman/listinfo/libreoffice-commits