Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
popover-menu.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
5/*
6 * Authors:
7 * Daniel Boles <dboles.src+inkscape@gmail.com>
8 *
9 * Copyright (C) 2023 Daniel Boles
10 *
11 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
12 */
13
15
16#include <glibmm/main.h>
17#include <gtkmm/grid.h>
18#include <gtkmm/label.h>
19#include <gtkmm/scrolledwindow.h>
20#include <gtkmm/separator.h>
21#include <gtkmm/window.h>
22
23#include "ui/popup-menu.h"
24#include "ui/util.h"
27
28namespace Inkscape::UI::Widget {
29
30// Make our Grid have CSS name `menu` to try to piggyback “real” Menusʼ theming.
31// Ditto, we leave Popover as `popover` so we don't lose normal Popover theming.
32class PopoverMenuGrid final
33 : public CssNameClassInit
34 , public Gtk::Grid
35{
36public:
37 [[nodiscard]] PopoverMenuGrid()
38 : Glib::ObjectBase{"PopoverMenuGrid"}
39 , CssNameClassInit{"menu"}
40 , Gtk::Grid{}
41 {
42 add_css_class("menu");
43 set_orientation(Gtk::Orientation::VERTICAL);
44 }
45};
46
47PopoverMenu::PopoverMenu(Gtk::PositionType const position, bool has_arrow)
48 : Glib::ObjectBase{"PopoverMenu"}
49 , Gtk::Popover{}
50 , _scrolled_window{*Gtk::make_managed<Gtk::ScrolledWindow>()}
51 , _grid {*Gtk::make_managed<PopoverMenuGrid >()}
52{
53 add_css_class("popover-menu");
54 add_css_class("menu");
55
56 set_position(position);
57 set_has_arrow(has_arrow);
58
59 _scrolled_window.set_propagate_natural_width (true);
60 _scrolled_window.set_propagate_natural_height(true);
61 _scrolled_window.set_child(_grid);
62 set_child(_scrolled_window);
63
64 signal_show().connect([this]
65 {
67
69
70 // FIXME: Initially focused item is sometimes wrong on first popup. GTK bug?
71 // Grabbing focus in ::show does not always work & sometimes even crashes :(
72 // For now, just remove possibly wrong, visible selection until hover/keynav
73 // This is also nicer for menus with only 1 item, like the ToolToolbar popup
74 Glib::signal_idle().connect_once( [this]{ unset_items_focus_hover(nullptr); });
75 });
76}
77
78void PopoverMenu::attach(Gtk::Widget &item,
79 int const left_attach, int const right_attach,
80 int const top_attach, int const bottom_attach)
81{
83
84 auto const width = right_attach - left_attach;
85 auto const height = bottom_attach - top_attach;
86 _grid.attach(item, left_attach, top_attach, width, height);
87 _items.push_back(&item);
88}
89
90void PopoverMenu::append(Gtk::Widget &item)
91{
93
94 _grid.attach_next_to(item, Gtk::PositionType::BOTTOM);
95 _items.push_back(&item);
96}
97
98void PopoverMenu::prepend(Gtk::Widget &item)
99{
101
102 _grid.attach_next_to(item, Gtk::PositionType::TOP);
103 _items.push_back(&item);
104}
105
106void PopoverMenu::remove(Gtk::Widget &item)
107{
108 // Check was added with one of our methods, is not Grid, etc.
109 auto const it = std::find(_items.begin(), _items.end(), &item);
110 g_return_if_fail(it != _items.end());
111
112 _grid.remove(item);
113 _items.erase(it);
114}
115
116void PopoverMenu::append_section_label(Glib::ustring const &markup)
117{
118 auto const label = Gtk::make_managed<Gtk::Label>();
119 label->set_markup(markup);
120 auto const item = Gtk::make_managed<PopoverMenuItem>();
121 item->set_child(*label);
122 item->set_sensitive(false);
123 append(*item);
124}
125
127{
128 append(*Gtk::make_managed<Gtk::Separator>(Gtk::Orientation::HORIZONTAL));
129}
130
131void PopoverMenu::popup_at(Gtk::Widget &widget,
132 int const x_offset, int const y_offset)
133{
134 ::Inkscape::UI::popup_at(*this, widget, x_offset, y_offset);
135}
136
137void PopoverMenu::popup_at_center(Gtk::Widget &widget)
138{
139 ::Inkscape::UI::popup_at_center(*this, widget);
140}
141
142std::vector<Gtk::Widget *> const &PopoverMenu::get_items()
143{
144 return _items;
145}
146
148{
149 // Check no one (accidentally?) removes our Grid or ScrolledWindow.
150 // GtkPopover interposes a content widget and ScrolledWindow a Viewport, so:
151 g_assert(is_descendant_of(_scrolled_window, *this));
153}
154
156{
157 static constexpr int padding = 16; // Spare some window size for border etc.
158 auto &window = dynamic_cast<Gtk::Window const &>(*get_root());
159 _scrolled_window.set_max_content_width (window.get_width () - 2 * padding);
160 _scrolled_window.set_max_content_height(window.get_height() - 2 * padding);
161}
162
163bool PopoverMenu::activate(Glib::ustring const &search) {
164 bool match = false;
165 Gtk::Widget *fallback_match = nullptr;
166 for (auto item : _items) {
167 if (!_active_search) {
168 _active_search = Gtk::make_managed<Gtk::Label>(search);
169 _active_search->get_style_context()->add_class("menu_search");
170 _active_search->set_xalign(0.1);
171 _grid.attach_next_to(*_active_search, Gtk::PositionType::BOTTOM);
172 }
173 for (auto const widg : UI::get_children(*item)) {
174 item->unset_state_flags(Gtk::StateFlags::FOCUSED | Gtk::StateFlags::PRELIGHT);
175 if (!search.empty()) {
176 for (auto const mi : UI::get_children(*widg)) {
177 if (auto label = dynamic_cast<Gtk::Label *>(mi)) {
178 auto text_data = label->get_text();
179 // if not matched and search == begining of label
180 if (!match && text_data.size() >= search.size()) {
181 if (text_data.substr(0, search.size()).lowercase() == search.lowercase()) {
182 match = true;
183 item->grab_focus();
184 break;
185 }
186 if (!fallback_match && text_data.lowercase().find(search.lowercase()) != Glib::ustring::npos) {
187 fallback_match = item;
188 }
189 }
190 }
191 }
192 }
193 }
194 }
195 if (!match && fallback_match) {
196 match = true;
197 fallback_match->grab_focus();
198 }
199 if (_active_search) {
200 if (search.empty()) {
202 } else {
203 auto searchstring = !pango_version_check(1, 50, 0) ?
204 "<span size=\"x-large\" line_height=\"0.7\">⌕</span><small> %1</small>" :
205 "<span size=\"large\">⌕</span><small> %1</small>";
206 _active_search->set_markup(Glib::ustring::compose(searchstring, search));
207 _active_search->show();
208 }
209 }
210 return match;
211}
212
213void PopoverMenu::unset_items_focus_hover(Gtk::Widget * const except_active)
214{
215 for (auto const item : _items) {
216 if (item != except_active) {
217 item->unset_state_flags(Gtk::StateFlags::FOCUSED | Gtk::StateFlags::PRELIGHT);
218 }
219 }
220}
221
223{
224 for (auto item : _items) {
225 _grid.remove(*item);
226 }
227 _items.clear();
228}
229
230} // namespace Inkscape::UI::Widget
231
232/*
233 Local Variables:
234 mode:c++
235 c-file-style:"stroustrup"
236 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
237 indent-tabs-mode:nil
238 fill-column:99
239 End:
240*/
241// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
CssNameClassInit(Glib::ustring const &css_name)
bool activate(Glib::ustring const &search)
Find and active from string.
void popup_at_center(Gtk::Widget &widget)
As popup_at() but point to center of widget.
void append_separator()
Append a horizontal separator.
void unset_items_focus_hover(Gtk::Widget *except_active)
void append(Gtk::Widget &child)
Add new row containing child, at start/top.
void remove_all()
Remove all items.
void prepend(Gtk::Widget &child)
Add new row containing child, at end/bottom.
void popup_at(Gtk::Widget &widget, int x_offset=0, int y_offset=0)
Replace Gtk::Menu::popup_at_pointer.
std::vector< Gtk::Widget * > const & get_items()
Get the list of menu items (children of our grid) Take copy, not reference, if you iterate & change i...
PopoverMenu(Gtk::PositionType position, bool has_arrow=false)
Create popover with CSS classes .menu & .popover-menu, positioned as requested.
void remove(Gtk::Widget &child)
Remove added child.
void append_section_label(Glib::ustring const &markup)
Append label, w/ markup & the .dim-label style class.
std::vector< Gtk::Widget * > _items
void attach(Gtk::Widget &child, int left_attach, int right_attach, int top_attach, int bottom_attach)
Add child at pos as per Gtk::Menu::attach()
Gtk::ScrolledWindow & _scrolled_window
virtual void hide(unsigned int key)
Definition sp-item.cpp:1324
A class that can be inherited to set the CSS name of a Gtk::Widget subclass.
SPItem * item
Glib::ustring label
Definition desktop.h:50
Custom widgets.
Definition desktop.h:126
static constexpr int height
void popup_at_center(Gtk::Popover &popover, Gtk::Widget &widget)
As popup_at() but point to center of widget.
bool is_descendant_of(Gtk::Widget const &descendant, Gtk::Widget const &ancestor)
Returns if widget is a descendant of given ancestor, i.e.: itself, a child, or a childʼs child.
Definition util.cpp:195
static void popup_at(Gtk::Popover &popover, Gtk::Widget &widget, double const x_offset, double const y_offset, int width, int height)
std::vector< Gtk::Widget * > get_children(Gtk::Widget &widget)
Get a vector of the widgetʼs children, from get_first_child() through each get_next_sibling().
Definition util.cpp:141
A replacement for GTK3ʼs Gtk::MenuItem, as removed in GTK4.
A replacement for GTK3ʼs Gtk::Menu, as removed in GTK4.
Helpers to connect signals to events that popup a menu in both GTK3 and GTK4.
unsigned long mi
Definition quantize.cpp:40
double width