summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCaolán McNamara <caolanm@redhat.com>2020-04-09 11:41:00 +0100
committerCaolán McNamara <caolanm@redhat.com>2020-04-16 20:28:24 +0200
commitbc0e0f633b05c4f91b6695488fc9e5c127507ba5 (patch)
treecca168c8aced9207ffa877693b856440764de341
parentde1dadd591862e38242cc2de7a4a658a9a8b67ac (diff)
tdf#131120 use a replacement for GtkComboBox
the problems with GtkComboBox we have are: 1) https://gitlab.gnome.org/GNOME/gtk/issues/1910 has_entry long menus take forever to appear (tdf#125388) on measuring each row, the GtkComboBox GtkTreeMenu will call its area_apply_attributes_cb function on the row, but that calls gtk_tree_menu_get_path_item which then loops through each child of the menu looking for the widget of the row, so performance drops to useless. All area_apply_attributes_cb does it set menu item sensitivity, so block it from running with fragile hackery which assumes that the unwanted callback is the only one with a 2) https://gitlab.gnome.org/GNOME/gtk/issues/94 when a super tall combobox menu is activated, and the selected entry is sufficiently far down the list, then the menu doesn't appear under wayland 3) https://gitlab.gnome.org/GNOME/gtk/issues/310 no typeahead support 4) we want to be able to control the width of the button, but have a drop down menu which is not limited to the width of the button 5) https://bugs.documentfoundation.org/show_bug.cgi?id=131120 super tall menu doesn't appear under X sometimes In general we often pack a lot into the comboboxes and the default ones don't like that. Overlay scrolling is turned off for the GtkTreeView replacement because otherwise there are null-derefs in gtk on indicator->scrollbar on repeated reopenings Change-Id: I1b6164020996377341b5992d593a027b76021f65 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/91990 Tested-by: Jenkins Reviewed-by: Caolán McNamara <caolanm@redhat.com>
-rw-r--r--solenv/sanitizers/ui/vcl.suppr2
-rw-r--r--sw/uiconfig/swriter/ui/navigatorpanel.ui2
-rw-r--r--vcl/UIConfig_vcl.mk3
-rw-r--r--vcl/inc/unx/gtk/gtkframe.hxx15
-rw-r--r--vcl/uiconfig/ui/combobox.ui103
-rw-r--r--vcl/unx/gtk3/gtk3gtkframe.cxx15
-rw-r--r--vcl/unx/gtk3/gtk3gtkinst.cxx1124
7 files changed, 874 insertions, 390 deletions
diff --git a/solenv/sanitizers/ui/vcl.suppr b/solenv/sanitizers/ui/vcl.suppr
index e5ad013909f9..b26735592919 100644
--- a/solenv/sanitizers/ui/vcl.suppr
+++ b/solenv/sanitizers/ui/vcl.suppr
@@ -3,6 +3,8 @@ vcl/uiconfig/ui/aboutbox.ui://GtkLabel[@id='logoreplacement'] orphan-label
vcl/uiconfig/ui/aboutbox.ui://GtkTextView[@id='version'] no-labelled-by
vcl/uiconfig/ui/aboutbox.ui://GtkLabel[@id='description'] orphan-label
vcl/uiconfig/ui/aboutbox.ui://GtkLabel[@id='copyright'] orphan-label
+vcl/uiconfig/ui/combobox.ui://GtkEntry[@id='entry'] no-labelled-by
+vcl/uiconfig/ui/combobox.ui://GtkToggleButton[@id='button'] button-no-label
vcl/uiconfig/ui/cupspassworddialog.ui://GtkLabel[@id='text'] orphan-label
vcl/uiconfig/ui/printdialog.ui://GtkEntry[@id='pageedit-nospin'] no-labelled-by
vcl/uiconfig/ui/printdialog.ui://GtkLabel[@id='totalnumpages'] orphan-label
diff --git a/sw/uiconfig/swriter/ui/navigatorpanel.ui b/sw/uiconfig/swriter/ui/navigatorpanel.ui
index 1f0b21543508..5e8a8daf1950 100644
--- a/sw/uiconfig/swriter/ui/navigatorpanel.ui
+++ b/sw/uiconfig/swriter/ui/navigatorpanel.ui
@@ -217,8 +217,6 @@
<column type="gchararray"/>
<!-- column-name image -->
<column type="GdkPixbuf"/>
- <!-- column-name surface -->
- <column type="CairoSurface"/>
</columns>
</object>
<object class="GtkGrid" id="NavigatorPanel">
diff --git a/vcl/UIConfig_vcl.mk b/vcl/UIConfig_vcl.mk
index c00d0d461f23..7941303b69da 100644
--- a/vcl/UIConfig_vcl.mk
+++ b/vcl/UIConfig_vcl.mk
@@ -11,7 +11,7 @@ $(eval $(call gb_UIConfig_UIConfig,vcl))
$(eval $(call gb_UIConfig_add_uifiles,vcl,\
vcl/uiconfig/ui/aboutbox \
- vcl/uiconfig/ui/wizard \
+ vcl/uiconfig/ui/combobox \
vcl/uiconfig/ui/cupspassworddialog \
vcl/uiconfig/ui/editmenu \
vcl/uiconfig/ui/errornocontentdialog \
@@ -24,6 +24,7 @@ $(eval $(call gb_UIConfig_add_uifiles,vcl,\
vcl/uiconfig/ui/printprogressdialog \
vcl/uiconfig/ui/querydialog \
vcl/uiconfig/ui/screenshotparent \
+ vcl/uiconfig/ui/wizard \
))
$(eval $(call gb_UIConfig_add_a11yerrors_uifiles,vcl,\
diff --git a/vcl/inc/unx/gtk/gtkframe.hxx b/vcl/inc/unx/gtk/gtkframe.hxx
index d9d2d0d6631b..f62dd649dbef 100644
--- a/vcl/inc/unx/gtk/gtkframe.hxx
+++ b/vcl/inc/unx/gtk/gtkframe.hxx
@@ -529,6 +529,21 @@ AtkObject* ooo_fixed_get_accessible(GtkWidget *obj);
} // extern "C"
+#if !GTK_CHECK_VERSION(3, 22, 0)
+enum GdkAnchorHints
+{
+ GDK_ANCHOR_FLIP_X = 1 << 0,
+ GDK_ANCHOR_FLIP_Y = 1 << 1,
+ GDK_ANCHOR_SLIDE_X = 1 << 2,
+ GDK_ANCHOR_SLIDE_Y = 1 << 3,
+ GDK_ANCHOR_RESIZE_X = 1 << 4,
+ GDK_ANCHOR_RESIZE_Y = 1 << 5,
+ GDK_ANCHOR_FLIP = GDK_ANCHOR_FLIP_X | GDK_ANCHOR_FLIP_Y,
+ GDK_ANCHOR_SLIDE = GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_SLIDE_Y,
+ GDK_ANCHOR_RESIZE = GDK_ANCHOR_RESIZE_X | GDK_ANCHOR_RESIZE_Y
+};
+#endif
+
#endif // INCLUDED_VCL_INC_UNX_GTK_GTKFRAME_HXX
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/vcl/uiconfig/ui/combobox.ui b/vcl/uiconfig/ui/combobox.ui
new file mode 100644
index 000000000000..167ae1a7f5ac
--- /dev/null
+++ b/vcl/uiconfig/ui/combobox.ui
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.2 -->
+<interface domain="vcl">
+ <requires lib="gtk+" version="3.18"/>
+ <object class="GtkBox" id="box">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="no_show_all">True</property>
+ <child>
+ <object class="GtkEntry" id="entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="no_show_all">True</property>
+ <property name="activates_default">True</property>
+ <style>
+ <class name="combo"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="no_show_all">True</property>
+ <property name="always_show_image">True</property>
+ <property name="draw_indicator">True</property>
+ <child>
+ <object class="GtkBox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImage" id="arrow">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">pan-down-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <style>
+ <class name="combo"/>
+ </style>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ <object class="GtkWindow" id="popup">
+ <property name="name">gtk-combobox-popup-window</property>
+ <property name="can_focus">False</property>
+ <property name="type">popup</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="type_hint">combo</property>
+ <child type="titlebar">
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow">
+ <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>
+ <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="headers_clickable">False</property>
+ <property name="enable_search">False</property>
+ <property name="search_column">0</property>
+ <property name="hover_selection">True</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>
+ </child>
+ </object>
+</interface>
diff --git a/vcl/unx/gtk3/gtk3gtkframe.cxx b/vcl/unx/gtk3/gtk3gtkframe.cxx
index 99dbbe49162a..18db569607ee 100644
--- a/vcl/unx/gtk3/gtk3gtkframe.cxx
+++ b/vcl/unx/gtk3/gtk3gtkframe.cxx
@@ -2926,21 +2926,6 @@ void swapDirection(GdkGravity& gravity)
}
-#if !GTK_CHECK_VERSION(3, 22, 0)
-enum GdkAnchorHints
-{
- GDK_ANCHOR_FLIP_X = 1 << 0,
- GDK_ANCHOR_FLIP_Y = 1 << 1,
- GDK_ANCHOR_SLIDE_X = 1 << 2,
- GDK_ANCHOR_SLIDE_Y = 1 << 3,
- GDK_ANCHOR_RESIZE_X = 1 << 4,
- GDK_ANCHOR_RESIZE_Y = 1 << 5,
- GDK_ANCHOR_FLIP = GDK_ANCHOR_FLIP_X | GDK_ANCHOR_FLIP_Y,
- GDK_ANCHOR_SLIDE = GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_SLIDE_Y,
- GDK_ANCHOR_RESIZE = GDK_ANCHOR_RESIZE_X | GDK_ANCHOR_RESIZE_Y
-};
-#endif
-
void GtkSalFrame::signalRealize(GtkWidget*, gpointer frame)
{
GtkSalFrame* pThis = static_cast<GtkSalFrame*>(frame);
diff --git a/vcl/unx/gtk3/gtk3gtkinst.cxx b/vcl/unx/gtk3/gtk3gtkinst.cxx
index f21de3229b21..e24c41a0b4ce 100644
--- a/vcl/unx/gtk3/gtk3gtkinst.cxx
+++ b/vcl/unx/gtk3/gtk3gtkinst.cxx
@@ -1984,7 +1984,6 @@ protected:
private:
bool m_bTakeOwnership;
- bool m_bFrozen;
bool m_bDraggedOver;
sal_uInt16 m_nLastMouseButton;
sal_uInt16 m_nLastMouseClicks;
@@ -2360,7 +2359,6 @@ public:
, m_pMouseEventBox(nullptr)
, m_pBuilder(pBuilder)
, m_bTakeOwnership(bTakeOwnership)
- , m_bFrozen(false)
, m_bDraggedOver(false)
, m_nLastMouseButton(0)
, m_nLastMouseClicks(0)
@@ -2890,17 +2888,13 @@ public:
virtual void freeze() override
{
gtk_widget_freeze_child_notify(m_pWidget);
- m_bFrozen = true;
}
virtual void thaw() override
{
gtk_widget_thaw_child_notify(m_pWidget);
- m_bFrozen = false;
}
- bool get_frozen() const { return m_bFrozen; }
-
virtual css::uno::Reference<css::datatransfer::dnd::XDropTarget> get_drop_target() override
{
if (!m_xDropTarget)
@@ -6890,6 +6884,122 @@ void do_ungrab(GtkWidget* pWidget)
gdk_device_ungrab(pKeyboard, nCurrentTime);
}
+void show_menu_older_gtk(GtkWidget* pMenuButton, GtkWindow* pMenu)
+{
+ //place the toplevel just below its launcher button
+ GtkWidget* pToplevel = gtk_widget_get_toplevel(pMenuButton);
+ gint x, y, absx, absy;
+ gtk_widget_translate_coordinates(pMenuButton, pToplevel, 0, 0, &x, &y);
+ GdkWindow *pWindow = gtk_widget_get_window(pToplevel);
+ gdk_window_get_position(pWindow, &absx, &absy);
+
+ x += absx;
+ y += absy;
+
+ gint nButtonHeight = gtk_widget_get_allocated_height(pMenuButton);
+ y += nButtonHeight;
+
+ gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pToplevel)), pMenu);
+ gtk_window_set_transient_for(pMenu, GTK_WINDOW(pToplevel));
+
+ GtkRequisition req;
+ gtk_widget_get_preferred_size(GTK_WIDGET(pMenu), nullptr, &req);
+ gint nMenuWidth = req.width;
+ gint nMenuHeight = req.height;
+
+ tools::Rectangle aWorkArea(::get_monitor_workarea(pMenuButton));
+
+ // shrink it a little, I find it reassuring to see a little margin with a
+ // long menu to know the menu is fully on screen
+ aWorkArea.AdjustTop(8);
+ aWorkArea.AdjustBottom(-8);
+ gint endx = x + nMenuWidth;
+ if (endx > aWorkArea.Right())
+ {
+ x -= endx - aWorkArea.Right();
+ if (x < 0)
+ x = 0;
+ }
+
+ gint endy = y + nMenuHeight;
+ gint nMissingBelow = endy - aWorkArea.Bottom();
+ if (nMissingBelow > 0)
+ {
+ gint nNewY = y - (nButtonHeight + nMenuHeight);
+ if (nNewY < aWorkArea.Top())
+ {
+ gint nMissingAbove = aWorkArea.Top() - nNewY;
+ if (nMissingBelow <= nMissingAbove)
+ nMenuHeight -= nMissingBelow;
+ else
+ {
+ nMenuHeight -= nMissingAbove;
+ y = aWorkArea.Top();
+ }
+ gtk_widget_set_size_request(GTK_WIDGET(pMenu), nMenuWidth, nMenuHeight);
+ }
+ else
+ y = nNewY;
+ }
+
+ gtk_window_move(pMenu, x, y);
+}
+
+bool show_menu_newer_gtk(GtkWidget* pComboBox, GtkWindow* pMenu)
+{
+ static auto window_move_to_rect = reinterpret_cast<void (*) (GdkWindow*, const GdkRectangle*, GdkGravity,
+ GdkGravity, GdkAnchorHints, gint, gint)>(
+ dlsym(nullptr, "gdk_window_move_to_rect"));
+ if (!window_move_to_rect)
+ return false;
+
+#if defined(GDK_WINDOWING_X11)
+ // under wayland gdk_window_move_to_rect works great for me, but in my current
+ // gtk 3.24 under X it leaves part of long menus outside the work area
+ GdkDisplay *pDisplay = gtk_widget_get_display(pComboBox);
+ if (DLSYM_GDK_IS_X11_DISPLAY(pDisplay))
+ return false;
+#endif
+
+ //place the toplevel just below its launcher button
+ GtkWidget* pToplevel = gtk_widget_get_toplevel(pComboBox);
+ gint x, y;
+ gtk_widget_translate_coordinates(pComboBox, pToplevel, 0, 0, &x, &y);
+
+ gtk_widget_realize(GTK_WIDGET(pMenu));
+ gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pToplevel)), pMenu);
+ gtk_window_set_transient_for(pMenu, GTK_WINDOW(pToplevel));
+
+ GtkRequisition req;
+ gtk_widget_get_preferred_size(GTK_WIDGET(pMenu), nullptr, &req);
+ gint nMenuWidth = req.width;
+
+ gint nButtonHeight = gtk_widget_get_allocated_height(pComboBox);
+
+ GdkGravity rect_anchor = GDK_GRAVITY_SOUTH, menu_anchor = GDK_GRAVITY_NORTH;
+ GdkRectangle rect {static_cast<int>(x),
+ static_cast<int>(y),
+ static_cast<int>(nMenuWidth),
+ static_cast<int>(nButtonHeight) };
+ GdkWindow* toplevel = gtk_widget_get_window(GTK_WIDGET(pMenu));
+
+ window_move_to_rect(toplevel, &rect, rect_anchor, menu_anchor,
+ static_cast<GdkAnchorHints>(GDK_ANCHOR_FLIP_Y | GDK_ANCHOR_RESIZE_Y |
+ GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_RESIZE_X),
+ 0, 0);
+
+ return true;
+}
+
+void show_menu(GtkWidget* pMenuButton, GtkWindow* pMenu)
+{
+ // try with gdk_window_move_to_rect, but if that's not available, try without
+ if (!show_menu_newer_gtk(pMenuButton, pMenu))
+ show_menu_older_gtk(pMenuButton, pMenu);
+ gtk_widget_show_all(GTK_WIDGET(pMenu));
+ gtk_widget_grab_focus(GTK_WIDGET(pMenu));
+ do_grab(GTK_WIDGET(pMenu));
+}
class GtkInstanceMenuButton : public GtkInstanceToggleButton, public MenuHelper, public virtual weld::MenuButton
{
@@ -6926,6 +7036,7 @@ private:
gtk_container_add(GTK_CONTAINER(m_pPopover), pChild);
g_object_unref(pChild);
+ // so gdk_window_move_to_rect will work again the next time
gtk_widget_unrealize(GTK_WIDGET(m_pMenuHack));
}
else
@@ -6940,46 +7051,7 @@ private:
gtk_container_add(GTK_CONTAINER(m_pMenuHack), pChild);
g_object_unref(pChild);
- //place the toplevel just below its launcher button
- GtkWidget* pToplevel = gtk_widget_get_toplevel(GTK_WIDGET(m_pMenuButton));
- gint x, y, absx, absy;
- gtk_widget_translate_coordinates(GTK_WIDGET(m_pMenuButton), pToplevel, 0, 0, &x, &y);
- GdkWindow *pWindow = gtk_widget_get_window(pToplevel);
- gdk_window_get_position(pWindow, &absx, &absy);
- x += absx;
- y += absy;
-
- gint nButtonHeight = gtk_widget_get_allocated_height(GTK_WIDGET(m_pMenuButton));
- y += nButtonHeight;
-
- gtk_window_group_add_window(gtk_window_get_group(GTK_WINDOW(pToplevel)), m_pMenuHack);
- gtk_window_set_transient_for(m_pMenuHack, GTK_WINDOW(pToplevel));
-
- gtk_widget_realize(GTK_WIDGET(m_pMenuHack));
-
- tools::Rectangle aWorkArea(::get_monitor_workarea(GTK_WIDGET(m_pMenuHack)));
- gint endx = x + gtk_widget_get_allocated_width(GTK_WIDGET(m_pMenuHack));
- if (endx > aWorkArea.Right())
- {
- x -= endx - aWorkArea.Right();
- if (x < 0)
- x = 0;
- }
- gint nMenuHeight = gtk_widget_get_allocated_height(GTK_WIDGET(m_pMenuHack));
- gint endy = y + nMenuHeight;
- if (endy > aWorkArea.Bottom())
- {
- y -= nButtonHeight + nMenuHeight;
- if (y < 0)
- y = 0;
- }
-
- gtk_window_move(m_pMenuHack, x, y);
- gtk_widget_show_all(GTK_WIDGET(m_pMenuHack));
-
- gtk_widget_grab_focus(GTK_WIDGET(m_pMenuHack));
-
- do_grab(GTK_WIDGET(m_pMenuHack));
+ show_menu(GTK_WIDGET(m_pMenuButton), m_pMenuHack);
}
}
@@ -8765,6 +8837,44 @@ struct CompareGtkTreePath
}
};
+int get_height_row(GtkTreeView* pTreeView, GList* pColumns)
+{
+ gint nMaxRowHeight = 0;
+ for (GList* pEntry = g_list_first(pColumns); pEntry; pEntry = g_list_next(pEntry))
+ {
+ GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
+ GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
+ for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
+ {
+ GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
+ gint nRowHeight;
+ gtk_cell_renderer_get_preferred_height(pCellRenderer, GTK_WIDGET(pTreeView), nullptr, &nRowHeight);
+ nMaxRowHeight = std::max(nMaxRowHeight, nRowHeight);
+ }
+ g_list_free(pRenderers);
+ }
+ return nMaxRowHeight;
+}
+
+int get_height_row_separator(GtkTreeView* pTreeView)
+{
+ gint nVerticalSeparator;
+ gtk_widget_style_get(GTK_WIDGET(pTreeView), "vertical-separator", &nVerticalSeparator, nullptr);
+ return nVerticalSeparator;
+}
+
+int get_height_rows(GtkTreeView* pTreeView, GList* pColumns, int nRows)
+{
+ gint nMaxRowHeight = get_height_row(pTreeView, pColumns);
+ gint nVerticalSeparator = get_height_row_separator(pTreeView);
+ return (nMaxRowHeight * nRows) + (nVerticalSeparator * (nRows + 1));
+}
+
+int get_height_rows(int nRowHeight, int nSeparatorHeight, int nRows)
+{
+ return (nRowHeight * nRows) + (nSeparatorHeight * (nRows + 1));
+}
+
class GtkInstanceTreeView : public GtkInstanceContainer, public virtual weld::TreeView
{
private:
@@ -10707,25 +10817,7 @@ public:
virtual int get_height_rows(int nRows) const override
{
- gint nMaxRowHeight = 0;
- for (GList* pEntry = g_list_first(m_pColumns); pEntry; pEntry = g_list_next(pEntry))
- {
- GtkTreeViewColumn* pColumn = GTK_TREE_VIEW_COLUMN(pEntry->data);
- GList *pRenderers = gtk_cell_layout_get_cells(GTK_CELL_LAYOUT(pColumn));
- for (GList* pRenderer = g_list_first(pRenderers); pRenderer; pRenderer = g_list_next(pRenderer))
- {
- GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
- gint nRowHeight;
- gtk_cell_renderer_get_preferred_height(pCellRenderer, GTK_WIDGET(m_pTreeView), nullptr, &nRowHeight);
- nMaxRowHeight = std::max(nMaxRowHeight, nRowHeight);
- }
- g_list_free(pRenderers);
- }
-
- gint nVerticalSeparator;
- gtk_widget_style_get(GTK_WIDGET(m_pTreeView), "vertical-separator", &nVerticalSeparator, nullptr);
-
- return (nMaxRowHeight * nRows) + (nVerticalSeparator * (nRows + 1));
+ return ::get_height_rows(m_pTreeView, m_pColumns, nRows);
}
virtual Size get_size_request() const override
@@ -12493,49 +12585,44 @@ public:
}
-#define g_signal_handlers_block_by_data(instance, data) \
- g_signal_handlers_block_matched ((instance), G_SIGNAL_MATCH_DATA, 0, 0, nullptr, nullptr, (data))
-
-/* tdf#125388 on measuring each row, the GtkComboBox GtkTreeMenu will call
- its area_apply_attributes_cb function on the row, but that calls
- gtk_tree_menu_get_path_item which then loops through each child of the
- menu looking for the widget of the row, so performance drops to useless.
+namespace {
- All area_apply_attributes_cb does it set menu item sensitivity, so block it from running
- with fragile hackery which assumes that the unwanted callback is the only one with a
- user_data of the ComboBox GtkTreeMenu */
-static void disable_area_apply_attributes_cb(GtkWidget* pItem, gpointer userdata)
+GtkBuilder* makeComboBoxBuilder()
{
- GtkMenuItem* pMenuItem = GTK_MENU_ITEM(pItem);
- GtkWidget* child = gtk_bin_get_child(GTK_BIN(pMenuItem));
- if (!child)
- return;
- GtkCellView* pCellView = GTK_CELL_VIEW(child);
- GtkCellLayout* pCellLayout = GTK_CELL_LAYOUT(pCellView);
- GtkCellArea* pCellArea = gtk_cell_layout_get_area(pCellLayout);
- g_signal_handlers_block_by_data(pCellArea, userdata);
+ OUString aUri(VclBuilderContainer::getUIRootDir() + "vcl/ui/combobox.ui");
+ OUString aPath;
+ osl::FileBase::getSystemPathFromFileURL(aUri, aPath);
+ return gtk_builder_new_from_file(OUStringToOString(aPath, RTL_TEXTENCODING_UTF8).getStr());
}
-namespace {
-
class GtkInstanceComboBox : public GtkInstanceContainer, public vcl::ISearchableStringList, public virtual weld::ComboBox
{
private:
+ GtkBuilder* m_pComboBuilder;
GtkComboBox* m_pComboBox;
+ GtkTreeView* m_pTreeView;
+ GtkWindow* m_pMenuWindow;
GtkTreeModel* m_pTreeModel;
- GtkCellRenderer* m_pTextRenderer;
- GtkMenu* m_pMenu;
+ GtkCellRenderer* m_pButtonTextRenderer;
+ GtkCellRenderer* m_pMenuTextRenderer;
GtkWidget* m_pToggleButton;
+ GtkWidget* m_pEntry;
+ GtkCellView* m_pCellView;
std::unique_ptr<vcl::Font> m_xFont;
std::unique_ptr<comphelper::string::NaturalStringSorter> m_xSorter;
vcl::QuickSelectionEngine m_aQuickSelectionEngine;
std::vector<int> m_aSeparatorRows;
+ bool m_bHoverSelection;
bool m_bPopupActive;
bool m_bAutoComplete;
bool m_bAutoCompleteCaseSensitive;
bool m_bChangedByMenu;
+ 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;
@@ -12543,9 +12630,10 @@ private:
gulong m_nEntryActivateSignalId;
gulong m_nEntryFocusInSignalId;
gulong m_nEntryFocusOutSignalId;
- gulong m_nOriginalMenuActivateEventId;
- gulong m_nMenuActivateSignalId;
+ gulong m_nEntryKeyPressEventSignalId;
guint m_nAutoCompleteIdleId;
+ gint m_nNonCustomLineHeight;
+ gint m_nPrePopupCursorPos;
static gboolean idleAutoComplete(gpointer widget)
{
@@ -12638,7 +12726,7 @@ private:
}
}
- static void signalChanged(GtkComboBox*, gpointer widget)
+ static void signalChanged(GtkEntry*, gpointer widget)
{
GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
SolarMutexGuard aGuard;
@@ -12651,24 +12739,97 @@ private:
m_bChangedByMenu = false;
}
- static void signalPopupToggled(GtkComboBox*, GParamSpec*, gpointer widget)
+ static void signalPopupToggled(GtkToggleButton* /*pToggleButton*/, gpointer widget)
{
GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
pThis->signal_popup_toggled();
}
+ int get_popup_height()
+ {
+ int nMaxRows = Application::GetSettings().GetStyleSettings().GetListBoxMaximumLineCount();
+ int nRows = std::min(nMaxRows, get_count());
+
+ 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)
+ {
+ 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);
+ }
+ }
+
+ return nHeight;
+ }
+
+ void toggle_menu()
+ {
+ if (!gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(m_pToggleButton)))
+ {
+ 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));
+
+ if (!m_bActivateCalled)
+ set_cursor(m_nPrePopupCursorPos);
+ }
+ else
+ {
+ if (!m_bHoverSelection)
+ {
+ gtk_tree_view_set_hover_selection(m_pTreeView, true);
+ m_bHoverSelection = true;
+ }
+
+ 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 = std::max(size.width, nComboWidth);
+ gint nPopupHeight = get_popup_height();
+
+ gtk_widget_set_size_request(GTK_WIDGET(m_pMenuWindow), nPopupWidth, nPopupHeight);
+
+ m_nPrePopupCursorPos = get_active();
+ m_bActivateCalled = false;
+ show_menu(pComboBox, m_pMenuWindow);
+ }
+ }
+
virtual void signal_popup_toggled() override
{
m_aQuickSelectionEngine.Reset();
- gboolean bIsShown(false);
- g_object_get(m_pComboBox, "popup-shown", &bIsShown, nullptr);
- if (m_bPopupActive != bool(bIsShown))
+
+ 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();
- //restore focus to the entry view when the popup is gone, which
- //is what the vcl case does, to ease the transition a little
- gtk_widget_grab_focus(m_pWidget);
+ if (!m_bPopupActive)
+ {
+ //restore focus to the entry view when the popup is gone, which
+ //is what the vcl case does, to ease the transition a little
+ grab_focus();
+ }
}
}
@@ -12717,7 +12878,7 @@ private:
{
SolarMutexGuard aGuard;
if (m_aEntryActivateHdl.Call(*this))
- g_signal_stop_emission_by_name(get_entry(), "activate");
+ g_signal_stop_emission_by_name(m_pEntry, "activate");
}
}
@@ -12767,14 +12928,6 @@ private:
return -1;
}
- GtkEntry* get_entry()
- {
- GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pComboBox));
- if (!GTK_IS_ENTRY(pChild))
- return nullptr;
- return GTK_ENTRY(pChild);
- }
-
bool separator_function(int nIndex)
{
return std::find(m_aSeparatorRows.begin(), m_aSeparatorRows.end(), nIndex) != m_aSeparatorRows.end();
@@ -12805,32 +12958,11 @@ private:
return pThis->signal_key_press(pEvent);
}
- static void signalMenuActivate(GtkWidget* pWidget, const gchar *path, gpointer widget)
- {
- GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
- return pThis->signal_menu_activate(pWidget, path);
- }
-
- void signal_menu_activate(GtkWidget* pWidget, const gchar *path)
- {
- // we disabled the original menu-active to get our own handler in first
- // so we know before changed is called that it will be called by the
- // menu, now block our handler and unblock the original and replay the
- // event to call the original handler
- m_bChangedByMenu = true;
- g_signal_handler_block(m_pMenu, m_nMenuActivateSignalId);
- g_signal_handler_unblock(m_pMenu, m_nOriginalMenuActivateEventId);
- guint nMenuActivateSignalId = g_signal_lookup("menu-activate", G_TYPE_FROM_INSTANCE(m_pMenu));
- g_signal_emit(pWidget, nMenuActivateSignalId, 0, path);
- g_signal_handler_block(m_pMenu, m_nOriginalMenuActivateEventId);
- g_signal_handler_unblock(m_pMenu, m_nMenuActivateSignalId);
- }
-
- // tdf#131076 we want return in a GtkComboBox to act like return in a
+ // 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_pComboBox);
+ GtkWidget *pComboBox = GTK_WIDGET(m_pToggleButton);
GtkWidget *pToplevel = gtk_widget_get_toplevel(pComboBox);
GtkWindow *pWindow = GTK_WINDOW(pToplevel);
if (!pWindow)
@@ -12844,8 +12976,104 @@ private:
return bDone;
}
+ static gboolean signalEntryKeyPress(GtkWidget*, GdkEventKey* pEvent, gpointer widget)
+ {
+ GtkInstanceComboBox* pThis = static_cast<GtkInstanceComboBox*>(widget);
+ 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();
+ int nActive = get_active() + 1;
+ while (nActive < nCount && separator_function(nActive))
+ ++nActive;
+ if (nActive < nCount)
+ set_active(nActive);
+ 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 nActive = get_active() - 1;
+ while (nActive >= 0 && separator_function(nActive))
+ --nActive;
+ if (nActive >= 0)
+ set_active(nActive);
+ bDone = true;
+ }
+ break;
+ }
+ case KEY_PAGEUP:
+ {
+ sal_uInt16 nKeyMod = aKeyCode.GetModifier();
+ if (!nKeyMod)
+ {
+ int nCount = get_count();
+ int nActive = 0;
+ while (nActive < nCount && separator_function(nActive))
+ ++nActive;
+ if (nActive < nCount)
+ set_active(nActive);
+ bDone = true;
+ }
+ break;
+ }
+ case KEY_PAGEDOWN:
+ {
+ sal_uInt16 nKeyMod = aKeyCode.GetModifier();
+ if (!nKeyMod)
+ {
+ int nActive = get_count() - 1;
+ while (nActive >= 0 && separator_function(nActive))
+ --nActive;
+ if (nActive >= 0)
+ set_active(nActive);
+ 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();
@@ -12864,11 +13092,34 @@ private:
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 deactive
- if (nCode == KEY_RETURN && !pEvent->state && !m_bPopupActive)
+ 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 && !pEvent->state && !m_bPopupActive)
@@ -12878,6 +13129,9 @@ private:
break;
}
+ if (!bDone && !m_pEntry)
+ bDone = signal_entry_key_press(pEvent);
+
return bDone;
}
@@ -12899,27 +13153,55 @@ private:
return reinterpret_cast<sal_Int64>(entry) - 1;
}
- int get_selected_entry() const
+ void set_cursor(int pos)
{
- if (m_bPopupActive && m_pMenu)
+ if (pos == -1)
{
- GList* pChildren = gtk_container_get_children(GTK_CONTAINER(m_pMenu));
- auto nRet = g_list_index(pChildren, gtk_menu_shell_get_selected_item(GTK_MENU_SHELL(m_pMenu)));
- g_list_free(pChildren);
- return nRet;
+ 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
- return get_active();
+ {
+ 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);
+ }
}
- void set_selected_entry(int nSelect)
+ int tree_view_get_cursor() const
{
- if (m_bPopupActive && m_pMenu)
+ int nRet = -1;
+
+ GtkTreePath* path;
+ gtk_tree_view_get_cursor(m_pTreeView, &path, nullptr);
+ if (path)
{
- GList* pChildren = gtk_container_get_children(GTK_CONTAINER(m_pMenu));
- gtk_menu_shell_select_item(GTK_MENU_SHELL(m_pMenu), GTK_WIDGET(g_list_nth_data(pChildren, nSelect)));
- g_list_free(pChildren);
+ 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();
+ }
+
+ void set_selected_entry(int nSelect)
+ {
+ if (m_bPopupActive)
+ set_cursor(nSelect);
else
set_active(nSelect);
}
@@ -12954,169 +13236,273 @@ private:
set_selected_entry(nSelect);
}
- // https://gitlab.gnome.org/GNOME/gtk/issues/310
- //
- // in the absence of a built-in solution
- // b) support typeahead for the menu itself, typing into the menu will
- // select via the vcl selection engine, a matching entry. Clearly
- // this is cheating, brittle and not a long term solution.
- void install_menu_typeahead()
+ static void signalGrabBroken(GtkWidget*, GdkEventGrabBroken *pEvent, gpointer widget)
{
- AtkObject* pAtkObj = gtk_combo_box_get_popup_accessible(m_pComboBox);
- if (!pAtkObj)
- return;
- if (!GTK_IS_ACCESSIBLE(pAtkObj))
- return;
- GtkWidget* pWidget = gtk_accessible_get_widget(GTK_ACCESSIBLE(pAtkObj));
- if (!pWidget)
- return;
- if (!GTK_IS_MENU(pWidget))
- return;
- m_pMenu = GTK_MENU(pWidget);
+ 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(gtk_widget_get_window(pWidget), &xoffset, &yoffset);
- guint nKeyPressSignalId = g_signal_lookup("key-press-event", GTK_TYPE_MENU);
- gulong nOriginalMenuKeyPressEventId = g_signal_handler_find(m_pMenu,
- static_cast<GSignalMatchType>(G_SIGNAL_MATCH_DATA | G_SIGNAL_MATCH_ID),
- nKeyPressSignalId, 0,
- nullptr, nullptr, m_pComboBox);
+ GtkAllocation alloc;
+ gtk_widget_get_allocation(pWidget, &alloc);
+ xoffset += alloc.x;
+ yoffset += alloc.y;
- guint nMenuActivateSignalId = g_signal_lookup("menu-activate", G_TYPE_FROM_INSTANCE(m_pMenu));
- m_nOriginalMenuActivateEventId = g_signal_handler_find(m_pMenu,
- static_cast<GSignalMatchType>(G_SIGNAL_MATCH_DATA | G_SIGNAL_MATCH_ID),
- nMenuActivateSignalId, 0,
- nullptr, nullptr, m_pComboBox);
+ 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;
- g_signal_handler_block(m_pMenu, m_nOriginalMenuActivateEventId);
- m_nMenuActivateSignalId = g_signal_connect(m_pMenu, "menu-activate", G_CALLBACK(signalMenuActivate), this);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
- g_signal_handler_block(m_pMenu, nOriginalMenuKeyPressEventId);
- g_signal_connect(m_pMenu, "key-press-event", G_CALLBACK(signalKeyPress), this);
+ return false;
}
- static void find_toggle_button(GtkWidget *pWidget, gpointer user_data)
+ static gboolean signalMotion(GtkWidget*, GdkEventMotion*, gpointer widget)
{
- if (g_strcmp0(gtk_widget_get_name(pWidget), "GtkToggleButton") == 0)
+ 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)
{
- GtkWidget **ppToggleButton = static_cast<GtkWidget**>(user_data);
- *ppToggleButton = pWidget;
+ gtk_tree_view_set_hover_selection(m_pTreeView, true);
+ m_bHoverSelection = true;
}
- else if (GTK_IS_CONTAINER(pWidget))
- gtk_container_forall(GTK_CONTAINER(pWidget), find_toggle_button, user_data);
+ }
+
+ 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
+ set_cursor(nActive);
+ enable_notify_events();
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(m_pToggleButton), false);
+ fire_signal_changed();
}
public:
- GtkInstanceComboBox(GtkComboBox* pComboBox, GtkInstanceBuilder* pBuilder, bool bTakeOwnership)
- : GtkInstanceContainer(GTK_CONTAINER(pComboBox), pBuilder, bTakeOwnership)
+ 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_pTreeModel(gtk_combo_box_get_model(m_pComboBox))
- , m_pMenu(nullptr)
- , m_pToggleButton(nullptr)
+ , m_pTreeView(GTK_TREE_VIEW(gtk_builder_get_object(pComboBuilder, "treeview")))
+ , 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(true)
, m_bPopupActive(false)
, m_bAutoComplete(false)
, m_bAutoCompleteCaseSensitive(false)
, m_bChangedByMenu(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_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_nOriginalMenuActivateEventId(0)
- , m_nMenuActivateSignalId(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)
{
+ insertParent(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);
+ gtk_combo_box_set_model(m_pComboBox, nullptr);
+ 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));
- if (!g_list_length(cells))
+ // 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))
{
- //Always use the same text column renderer layout
- m_pTextRenderer = gtk_cell_renderer_text_new();
- gtk_cell_layout_pack_end(GTK_CELL_LAYOUT(m_pComboBox), m_pTextRenderer, true);
- gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(m_pComboBox), m_pTextRenderer, "text", 0, nullptr);
- }
- else
- {
- m_pTextRenderer = static_cast<GtkCellRenderer*>(cells->data);
- if (g_list_length(cells) == 2)
+ GtkCellRenderer* pCellRenderer = GTK_CELL_RENDERER(pRenderer->data);
+ bool bTextRenderer = pCellRenderer == m_pMenuTextRenderer;
+ gtk_tree_view_column_pack_end(pCol, pCellRenderer, bTextRenderer);
+ if (!bTextRenderer)
{
- //The ComboBox is always going to show the column associated with
- //the entry when there is one, left to its own devices this image
- //column will be after it, but we want it before
- gtk_cell_layout_reorder(GTK_CELL_LAYOUT(m_pComboBox), m_pTextRenderer, 1);
+ 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);
}
}
- g_list_free(cells);
- if (GtkEntry* pEntry = get_entry())
+ 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(pEntry, "insert-text", G_CALLBACK(signalEntryInsertText), this);
- m_nEntryActivateSignalId = g_signal_connect(pEntry, "activate", G_CALLBACK(signalEntryActivate), this);
- m_nEntryFocusInSignalId = g_signal_connect(pEntry, "focus-in-event", G_CALLBACK(signalEntryFocusIn), this);
- m_nEntryFocusOutSignalId = g_signal_connect(pEntry, "focus-out-event", G_CALLBACK(signalEntryFocusOut), this);
+ 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_nKeyPressEventSignalId = g_signal_connect(m_pWidget, "key-press-event", G_CALLBACK(signalKeyPress), this);
+ m_nEntryKeyPressEventSignalId = 0;
+ m_nKeyPressEventSignalId = g_signal_connect(m_pToggleButton, "key-press-event", G_CALLBACK(signalKeyPress), this);
}
- find_toggle_button(GTK_WIDGET(m_pComboBox), &m_pToggleButton);
+ g_list_free(cells);
- install_menu_typeahead();
+ 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);
}
virtual int get_active() const override
{
- return gtk_combo_box_get_active(m_pComboBox);
+ return tree_view_get_cursor();
}
virtual OUString get_active_id() const override
{
- const gchar* pText = gtk_combo_box_get_active_id(m_pComboBox);
- return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
+ int nActive = get_active();
+ return nActive != -1 ? get_id(nActive) : OUString();
}
virtual void set_active_id(const OUString& rStr) override
{
- disable_notify_events();
- OString aId(OUStringToOString(rStr, RTL_TEXTENCODING_UTF8));
- gtk_combo_box_set_active_id(m_pComboBox, aId.getStr());
+ set_active(find_id(rStr));
m_bChangedByMenu = false;
- enable_notify_events();
}
virtual void set_size_request(int nWidth, int nHeight) override
{
- // tweak the cell render to get a narrower size to stick
- if (nWidth != -1)
+ if (m_pButtonTextRenderer)
{
- // 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_pTextRenderer), "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_pTextRenderer, m_pWidget, &min, nullptr);
- gtk_cell_renderer_set_fixed_size(m_pTextRenderer, 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)
+ // tweak the cell render to get a narrower size to stick
+ if (nWidth != -1)
{
- // now set the cell to the max width which it can be within the
- // requested widget width
- gtk_cell_renderer_set_fixed_size(m_pTextRenderer, nWidth - nNonCellWidth, -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);
}
- }
- else
- {
- g_object_set(G_OBJECT(m_pTextRenderer), "ellipsize", PANGO_ELLIPSIZE_NONE, nullptr);
- gtk_cell_renderer_set_fixed_size(m_pTextRenderer, -1, -1);
}
gtk_widget_set_size_request(m_pWidget, nWidth, nHeight);
@@ -13125,73 +13511,49 @@ public:
virtual void set_active(int pos) override
{
disable_notify_events();
- gtk_combo_box_set_active(m_pComboBox, pos);
+
+ set_cursor(pos);
+
+ if (m_pEntry)
+ {
+ if (pos != -1)
+ gtk_entry_set_text(GTK_ENTRY(m_pEntry), OUStringToOString(get_text(pos), RTL_TEXTENCODING_UTF8).getStr());
+ else
+ gtk_entry_set_text(GTK_ENTRY(m_pEntry), "");
+ }
+
m_bChangedByMenu = false;
enable_notify_events();
}
virtual OUString get_active_text() const override
{
- if (gtk_combo_box_get_has_entry(m_pComboBox))
+ if (m_pEntry)
{
- GtkWidget *pEntry = gtk_bin_get_child(GTK_BIN(m_pComboBox));
- const gchar* pText = gtk_entry_get_text(GTK_ENTRY(pEntry));
+ const gchar* pText = gtk_entry_get_text(GTK_ENTRY(m_pEntry));
return OUString(pText, pText ? strlen(pText) : 0, RTL_TEXTENCODING_UTF8);
}
- GtkTreeIter iter;
- if (!gtk_combo_box_get_active_iter(m_pComboBox, &iter))
- return OUString();
-
- gint col = gtk_combo_box_get_entry_text_column(m_pComboBox);
- gchar* pStr = nullptr;
- gtk_tree_model_get(m_pTreeModel, &iter, col, &pStr, -1);
- OUString sRet(pStr, pStr ? strlen(pStr) : 0, RTL_TEXTENCODING_UTF8);
- g_free(pStr);
+ int nActive = get_active();
+ if (nActive == -1)
+ return OUString();
- return sRet;
+ return get_text(nActive);
}
virtual OUString get_text(int pos) const override
{
- return get(pos, 0);
+ return get(pos, m_nTextCol);
}
virtual OUString get_id(int pos) const override
{
- gint id_column = gtk_combo_box_get_id_column(m_pComboBox);
- return get(pos, id_column);
+ return get(pos, m_nIdCol);
}
virtual void set_id(int pos, const OUString& rId) override
{
- gint id_column = gtk_combo_box_get_id_column(m_pComboBox);
- set(pos, id_column, rId);
- }
-
- // https://gitlab.gnome.org/GNOME/gtk/issues/94
- // when a super tall combobox menu is activated, and the selected entry is sufficiently
- // far down the list, then the menu doesn't appear under wayland
- void bodge_wayland_menu_not_appearing()
- {
- if (get_frozen())
- return;
- if (has_entry())
- return;
-#if defined(GDK_WINDOWING_WAYLAND)
- GdkDisplay *pDisplay = gtk_widget_get_display(m_pWidget);
- if (DLSYM_GDK_IS_WAYLAND_DISPLAY(pDisplay))
- {
- gtk_combo_box_set_wrap_width(m_pComboBox, get_count() > 30 ? 1 : 0);
- }
-#endif
- }
-
- // https://gitlab.gnome.org/GNOME/gtk/issues/1910
- // has_entry long menus take forever to appear (tdf#125388)
- void bodge_area_apply_attributes_cb()
- {
- gtk_container_foreach(GTK_CONTAINER(m_pMenu), disable_area_apply_attributes_cb, m_pMenu);
+ set(pos, m_nIdCol, rId);
}
virtual void insert_vector(const std::vector<weld::ComboBoxEntry>& rItems, bool bKeepExisting) override
@@ -13216,7 +13578,6 @@ public:
gtk_list_store_remove(GTK_LIST_STORE(m_pTreeModel), &iter);
m_aSeparatorRows.erase(std::remove(m_aSeparatorRows.begin(), m_aSeparatorRows.end(), pos), m_aSeparatorRows.end());
enable_notify_events();
- bodge_wayland_menu_not_appearing();
}
virtual void insert(int pos, const OUString& rText, const OUString* pId, const OUString* pIconName, VirtualDevice* pImageSurface) override
@@ -13225,7 +13586,6 @@ public:
GtkTreeIter iter;
insert_row(GTK_LIST_STORE(m_pTreeModel), iter, pos, pId, rText, pIconName, pImageSurface);
enable_notify_events();
- bodge_wayland_menu_not_appearing();
}
virtual void insert_separator(int pos, const OUString& rId) override
@@ -13234,11 +13594,10 @@ public:
GtkTreeIter iter;
pos = pos == -1 ? get_count() : pos;
m_aSeparatorRows.push_back(pos);
- if (!gtk_combo_box_get_row_separator_func(m_pComboBox))
- gtk_combo_box_set_row_separator_func(m_pComboBox, separatorFunction, this, nullptr);
+ 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, "", nullptr, nullptr);
enable_notify_events();
- bodge_wayland_menu_not_appearing();
}
virtual int get_count() const override
@@ -13248,13 +13607,12 @@ public:
virtual int find_text(const OUString& rStr) const override
{
- return find(rStr, 0);
+ return find(rStr, m_nTextCol);
}
virtual int find_id(const OUString& rId) const override
{
- gint id_column = gtk_combo_box_get_id_column(m_pComboBox);
- return find(rId, id_column);
+ return find(rId, m_nIdCol);
}
virtual void clear() override
@@ -13264,7 +13622,6 @@ public:
m_aSeparatorRows.clear();
gtk_combo_box_set_row_separator_func(m_pComboBox, nullptr, nullptr, nullptr);
enable_notify_events();
- bodge_wayland_menu_not_appearing();
}
virtual void make_sorted() override
@@ -13273,8 +13630,8 @@ public:
::comphelper::getProcessComponentContext(),
Application::GetSettings().GetUILanguageTag().getLocale()));
GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
- gtk_tree_sortable_set_sort_column_id(pSortable, 0, GTK_SORT_ASCENDING);
- gtk_tree_sortable_set_sort_func(pSortable, 0, default_sort_func, m_xSorter.get(), nullptr);
+ 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
@@ -13284,59 +13641,47 @@ public:
virtual void set_entry_message_type(weld::EntryMessageType eType) override
{
- GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pComboBox));
- assert(GTK_IS_ENTRY(pChild));
- GtkEntry* pEntry = GTK_ENTRY(pChild);
- ::set_entry_message_type(pEntry, eType);
+ assert(m_pEntry);
+ ::set_entry_message_type(GTK_ENTRY(m_pEntry), eType);
}
virtual void set_entry_text(const OUString& rText) override
{
- GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pComboBox));
- assert(pChild && GTK_IS_ENTRY(pChild));
- GtkEntry* pEntry = GTK_ENTRY(pChild);
+ assert(m_pEntry);
disable_notify_events();
- gtk_entry_set_text(pEntry, OUStringToOString(rText, RTL_TEXTENCODING_UTF8).getStr());
+ 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
{
- GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pComboBox));
- assert(pChild && GTK_IS_ENTRY(pChild));
- GtkEntry* pEntry = GTK_ENTRY(pChild);
+ assert(m_pEntry);
disable_notify_events();
- gtk_entry_set_width_chars(pEntry, nChars);
- gtk_entry_set_max_width_chars(pEntry, nChars);
+ 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
{
- GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pComboBox));
- assert(pChild && GTK_IS_ENTRY(pChild));
- GtkEntry* pEntry = GTK_ENTRY(pChild);
+ assert(m_pEntry);
disable_notify_events();
- gtk_entry_set_max_length(pEntry, nChars);
+ gtk_entry_set_max_length(GTK_ENTRY(m_pEntry), nChars);
enable_notify_events();
}
virtual void select_entry_region(int nStartPos, int nEndPos) override
{
- GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pComboBox));
- assert(pChild && GTK_IS_ENTRY(pChild));
- GtkEntry* pEntry = GTK_ENTRY(pChild);
+ assert(m_pEntry);
disable_notify_events();
- gtk_editable_select_region(GTK_EDITABLE(pEntry), nStartPos, nEndPos);
+ gtk_editable_select_region(GTK_EDITABLE(m_pEntry), nStartPos, nEndPos);
enable_notify_events();
}
virtual bool get_entry_selection_bounds(int& rStartPos, int &rEndPos) override
{
- GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pComboBox));
- assert(pChild && GTK_IS_ENTRY(pChild));
- GtkEntry* pEntry = GTK_ENTRY(pChild);
- return gtk_editable_get_selection_bounds(GTK_EDITABLE(pEntry), &rStartPos, &rEndPos);
+ 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
@@ -13347,20 +13692,16 @@ public:
virtual void set_entry_placeholder_text(const OUString& rText) override
{
- GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pComboBox));
- assert(pChild && GTK_IS_ENTRY(pChild));
- GtkEntry* pEntry = GTK_ENTRY(pChild);
- gtk_entry_set_placeholder_text(pEntry, rText.toUtf8().getStr());
+ assert(m_pEntry);
+ gtk_entry_set_placeholder_text(GTK_ENTRY(m_pEntry), rText.toUtf8().getStr());
}
virtual void set_entry_font(const vcl::Font& rFont) override
{
m_xFont.reset(new vcl::Font(rFont));
PangoAttrList* pAttrList = create_attr_list(rFont);
- GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pComboBox));
- assert(pChild && GTK_IS_ENTRY(pChild));
- GtkEntry* pEntry = GTK_ENTRY(pChild);
- gtk_entry_set_attributes(pEntry, pAttrList);
+ assert(m_pEntry);
+ gtk_entry_set_attributes(GTK_ENTRY(m_pEntry), pAttrList);
pango_attr_list_unref(pAttrList);
}
@@ -13368,52 +13709,54 @@ public:
{
if (m_xFont)
return *m_xFont;
- GtkWidget* pChild = gtk_bin_get_child(GTK_BIN(m_pComboBox));
- assert(pChild && GTK_IS_ENTRY(pChild));
- GtkEntry* pEntry = GTK_ENTRY(pChild);
- PangoContext* pContext = gtk_widget_get_pango_context(GTK_WIDGET(pEntry));
+ 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 (GtkEntry* pEntry = get_entry())
+ if (m_pEntry)
{
- g_signal_handler_block(pEntry, m_nEntryInsertTextSignalId);
- g_signal_handler_block(pEntry, m_nEntryActivateSignalId);
- g_signal_handler_block(pEntry, m_nEntryFocusInSignalId);
- g_signal_handler_block(pEntry, m_nEntryFocusOutSignalId);
+ 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_pComboBox, m_nKeyPressEventSignalId);
+ 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_pComboBox, m_nChangedSignalId);
- g_signal_handler_block(m_pComboBox, m_nPopupShownSignalId);
+ 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_pComboBox, m_nPopupShownSignalId);
- g_signal_handler_unblock(m_pComboBox, m_nChangedSignalId);
+ 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 (GtkEntry* pEntry = get_entry())
+ if (m_pEntry)
{
- g_signal_handler_unblock(pEntry, m_nEntryActivateSignalId);
- g_signal_handler_unblock(pEntry, m_nEntryFocusInSignalId);
- g_signal_handler_unblock(pEntry, m_nEntryFocusOutSignalId);
- g_signal_handler_unblock(pEntry, m_nEntryInsertTextSignalId);
+ 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_pComboBox, m_nKeyPressEventSignalId);
+ g_signal_handler_unblock(m_pToggleButton, m_nKeyPressEventSignalId);
}
virtual void freeze() override
@@ -13421,7 +13764,7 @@ public:
disable_notify_events();
g_object_ref(m_pTreeModel);
GtkInstanceContainer::freeze();
- gtk_combo_box_set_model(m_pComboBox, nullptr);
+ gtk_tree_view_set_model(m_pTreeView, nullptr);
if (m_xSorter)
{
GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
@@ -13436,15 +13779,13 @@ public:
if (m_xSorter)
{
GtkTreeSortable* pSortable = GTK_TREE_SORTABLE(m_pTreeModel);
- gtk_tree_sortable_set_sort_column_id(pSortable, 0, GTK_SORT_ASCENDING);
+ gtk_tree_sortable_set_sort_column_id(pSortable, m_nTextCol, GTK_SORT_ASCENDING);
}
- gtk_combo_box_set_model(m_pComboBox, m_pTreeModel);
+ gtk_tree_view_set_model(m_pTreeView, m_pTreeModel);
+
GtkInstanceContainer::thaw();
g_object_unref(m_pTreeModel);
enable_notify_events();
-
- bodge_wayland_menu_not_appearing();
- bodge_area_apply_attributes_cb();
}
virtual bool get_popup_shown() const override
@@ -13466,9 +13807,19 @@ public:
weld::Widget::connect_focus_out(rLink);
}
+ virtual void grab_focus() override
+ {
+ disable_notify_events();
+ if (m_pEntry)
+ gtk_entry_grab_focus_without_selecting(GTK_ENTRY(m_pEntry));
+ else
+ gtk_widget_grab_focus(m_pToggleButton);
+ enable_notify_events();
+ }
+
virtual bool has_focus() const override
{
- return gtk_widget_has_focus(m_pToggleButton) || GtkInstanceWidget::has_focus();
+ return gtk_widget_has_focus(m_pToggleButton) || gtk_widget_has_focus(m_pEntry) || GtkInstanceWidget::has_focus();
}
virtual bool changed_by_direct_pick() const override
@@ -13478,27 +13829,27 @@ public:
virtual ~GtkInstanceComboBox() override
{
- if (m_nOriginalMenuActivateEventId)
- g_signal_handler_unblock(m_pMenu, m_nOriginalMenuActivateEventId);
- if (m_nMenuActivateSignalId)
- g_signal_handler_disconnect(m_pMenu, m_nMenuActivateSignalId);
if (m_nAutoCompleteIdleId)
g_source_remove(m_nAutoCompleteIdleId);
- if (GtkEntry* pEntry = get_entry())
+ if (m_pEntry)
{
- g_signal_handler_disconnect(pEntry, m_nEntryInsertTextSignalId);
- g_signal_handler_disconnect(pEntry, m_nEntryActivateSignalId);
- g_signal_handler_disconnect(pEntry, m_nEntryFocusInSignalId);
- g_signal_handler_disconnect(pEntry, m_nEntryFocusOutSignalId);
+ g_signal_handler_disconnect(m_pEntry, m_nChangedSignalId);
+ g_signal_handler_disconnect(m_pEntry, 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);
}
else
- g_signal_handler_disconnect(m_pComboBox, m_nKeyPressEventSignalId);
+ g_signal_handler_disconnect(m_pToggleButton, m_nKeyPressEventSignalId);
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_pComboBox, m_nChangedSignalId);
- g_signal_handler_disconnect(m_pComboBox, m_nPopupShownSignalId);
+ g_signal_handler_disconnect(m_pTreeView, m_nRowActivatedSignalId);
+ g_signal_handler_disconnect(m_pToggleButton, m_nPopupShownSignalId);
+
+ g_object_unref(m_pComboBuilder);
}
};
@@ -14436,7 +14787,36 @@ public:
if (!pComboBox)
return nullptr;
auto_add_parentless_widgets_to_container(GTK_WIDGET(pComboBox));
- return std::make_unique<GtkInstanceComboBox>(pComboBox, this, bTakeOwnership);
+
+ /* we replace GtkComboBox because of difficulties with too tall menus
+
+ 1) https://gitlab.gnome.org/GNOME/gtk/issues/1910
+ has_entry long menus take forever to appear (tdf#125388)
+
+ on measuring each row, the GtkComboBox GtkTreeMenu will call
+ its area_apply_attributes_cb function on the row, but that calls
+ gtk_tree_menu_get_path_item which then loops through each child of the
+ menu looking for the widget of the row, so performance drops to useless.
+
+ All area_apply_attributes_cb does it set menu item sensitivity, so block it from running
+ with fragile hackery which assumes that the unwanted callback is the only one with a
+
+ 2) https://gitlab.gnome.org/GNOME/gtk/issues/94
+ when a super tall combobox menu is activated, and the selected
+ entry is sufficiently far down the list, then the menu doesn't
+ appear under wayland
+
+ 3) https://gitlab.gnome.org/GNOME/gtk/issues/310
+ no typeahead support
+
+ 4) we want to be able to control the width of the button, but have a drop down menu which
+ is not limited to the width of the button
+
+ 5) https://bugs.documentfoundation.org/show_bug.cgi?id=131120
+ super tall menu doesn't appear under X sometimes
+ */
+ GtkBuilder* pComboBuilder = makeComboBoxBuilder();
+ return std::make_unique<GtkInstanceComboBox>(pComboBuilder, pComboBox, this, bTakeOwnership);
}
virtual std::unique_ptr<weld::TreeView> weld_tree_view(const OString &id, bool bTakeOwnership) override