[Libreoffice-commits] core.git: solenv/sanitizers sw/uiconfig vcl/inc vcl/uiconfig vcl/UIConfig_vcl.mk vcl/unx

Caolán McNamara (via logerrit) logerrit at kemper.freedesktop.org
Thu Apr 16 18:29:00 UTC 2020


 solenv/sanitizers/ui/vcl.suppr           |    2 
 sw/uiconfig/swriter/ui/navigatorpanel.ui |    2 
 vcl/UIConfig_vcl.mk                      |    3 
 vcl/inc/unx/gtk/gtkframe.hxx             |   15 
 vcl/uiconfig/ui/combobox.ui              |  103 ++
 vcl/unx/gtk3/gtk3gtkframe.cxx            |   15 
 vcl/unx/gtk3/gtk3gtkinst.cxx             | 1124 ++++++++++++++++++++-----------
 7 files changed, 874 insertions(+), 390 deletions(-)

New commits:
commit bc0e0f633b05c4f91b6695488fc9e5c127507ba5
Author:     Caolán McNamara <caolanm at redhat.com>
AuthorDate: Thu Apr 9 11:41:00 2020 +0100
Commit:     Caolán McNamara <caolanm at redhat.com>
CommitDate: Thu Apr 16 20:28:24 2020 +0200

    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 at redhat.com>

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


More information about the Libreoffice-commits mailing list