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