Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
toolbar.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
/*
5 * Authors: see git history
6 *
7 * Copyright (C) 2018 Authors
8 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
9 */
10
11#include "toolbar.h"
12
13#include <glibmm/main.h>
14#include <gtkmm/button.h>
15#include <gtkmm/image.h>
16#include <gtkmm/menubutton.h>
17#include <gtkmm/popover.h>
18#include <map>
19
20#include "desktop.h"
21#include "ui/util.h"
22#include "ui/widget/canvas.h"
23
24namespace Inkscape::UI::Toolbar {
25
26struct ToolbarWidget::MenuButton
27{
28 // Constructor to initialize data members
29 MenuButton(int priority, int group_size, Gtk::MenuButton *menu_btn,
30 std::stack<std::pair<Gtk::Widget *, Gtk::Widget *>> toolbar_children)
31 : priority(priority)
32 , group_size(group_size)
33 , menu_btn(menu_btn)
34 , toolbar_children(std::move(toolbar_children))
35 {}
36
37 // Data members
38 int priority;
39 int group_size;
40 Gtk::MenuButton *menu_btn;
41 std::stack<std::pair<Gtk::Widget *, Gtk::Widget *>> popover_children;
42 std::stack<std::pair<Gtk::Widget *, Gtk::Widget *>> toolbar_children;
43};
44
46 : _toolbar{toolbar}
47{
49}
50
52
53static bool isMatchingPattern(const std::string &str, const std::string &pattern)
54{
55 // Early exit if string length is less than pattern length (guaranteed mismatch)
56 if (str.size() < pattern.size()) {
57 return false;
58 }
59
60 for (size_t i = 0; i < pattern.size(); ++i) {
61 if (std::tolower(str[i]) != std::tolower(pattern[i])) {
62 return false; // Mismatch found, stop comparing
63 }
64 }
65
66 // All characters matched
67 return true;
68}
69
71{
72 std::map<std::string, std::pair<int, std::stack<std::pair<Gtk::Widget *, Gtk::Widget *>>>> menu_btn_groups;
73 auto children = UI::get_children(_toolbar);
74 int position = 0;
75
76 // Iterate over all the children of this toolbar.
77 for (auto child : children) {
78 // Find out the CSS classes associated with each child.
79 auto css_classes = child->get_css_classes();
80 int group_size = 1;
81
82 // Iterate over all the CSS classes and find out
83 // the movable children by searching for classes
84 // which contains the "priority" prefix(case-insensitive).
85 for (const auto &c : css_classes) {
86 if (isMatchingPattern(c, "priority")) {
87 // Check if the group_size is also defined.
88 bool group_size_defined = false;
89 for (const auto &cl : css_classes) {
90 if (isMatchingPattern(cl, "groupsize")) {
91 group_size = cl[cl.size() - 1] - '0';
92 group_size_defined = true;
93 }
94 }
95
96 // Store this child in the map.
97 Gtk::Widget *prev_child = (position == 0) ? nullptr : children[position - 1];
98 auto it = menu_btn_groups.find(c);
99
100 if (it != menu_btn_groups.end()) {
101 // This group already exists.
102 // Push this child in the vector.
103 it->second.second.emplace(prev_child, child);
104 if (group_size_defined) {
105 it->second.first = group_size;
106 }
107 } else {
108 std::stack<std::pair<Gtk::Widget *, Gtk::Widget *>> toolbar_children;
109 toolbar_children.emplace(prev_child, child);
110 menu_btn_groups.insert({c, {group_size, toolbar_children}});
111 }
112 }
113 }
114 position++;
115 }
116
117 // Now, start inserting menu buttons in the toolbar.
118 for (auto [key, value] : menu_btn_groups) {
119 // The map is lexicographically sorted on the basis of priorities.
120 // Step 1: Find out the priority of this group.
121 // Assumption: The last character of the class name stores the
122 // value of the priority.
123 auto priority = key[key.size() - 1] - '0';
124
125 // Add this menu button to the _menu_btns vector.
126 _insert_menu_btn(priority, value.first, value.second);
127
128 // The menu button added at the end would be the first to
129 // collapse or expand.
130 _active_mb_index = _menu_btns.size() - 1;
131 }
132
133 // Insert a very large value to prevent the toolbar
134 // from expanding when all the menu buttons are in the
135 // expanded state.
136 _size_needed.push(10000);
137}
138
139void ToolbarWidget::_insert_menu_btn(int priority, int group_size,
140 std::stack<std::pair<Gtk::Widget *, Gtk::Widget *>> toolbar_children)
141{
142 auto menu_btn = Gtk::make_managed<Gtk::MenuButton>();
143 auto popover = Gtk::make_managed<Gtk::Popover>();
144 auto box = Gtk::make_managed<Gtk::Box>(_toolbar.get_orientation(), 4);
145
146 if (_toolbar.get_orientation() == Gtk::Orientation::VERTICAL) {
147 menu_btn->set_direction(Gtk::ArrowType::LEFT);
148 }
149
150 popover->set_child(*box);
151 menu_btn->set_popover(*popover);
152
153 // Insert this menu button right next to its topmost toolbar child.
154 _toolbar.insert_child_after(*menu_btn, *toolbar_children.top().second);
155 menu_btn->set_visible(false);
156
157 // Add this menu button to the _menu_btns vector.
158 _menu_btns.push_back(
159 std::make_unique<MenuButton>(priority, group_size, menu_btn, std::move(toolbar_children)));
160}
161
162void ToolbarWidget::measure_vfunc(Gtk::Orientation orientation, int for_size, int &min, int &nat, int &min_baseline, int &nat_baseline) const
163{
164 _toolbar.measure(orientation, for_size, min, nat, min_baseline, nat_baseline);
165
166 if (_toolbar.get_orientation() == orientation) {
167 // Return too-small value to allow shrinking.
168 min = 0;
169 }
170}
171
177
178static int min_dimension(Gtk::Widget const *widget, Gtk::Orientation const orientation)
179{
180 int min = 0;
181 int ignore = 0;
182 widget->measure(orientation, -1, min, ignore, ignore, ignore);
183 return min;
184};
185
187{
188 if (_resizing || _active_mb_index < 0) {
189 return;
190 }
191
192 auto const orientation = _toolbar.get_orientation();
193 auto const allocated_size = orientation == Gtk::Orientation::VERTICAL ? height : width;
194 int min_size = min_dimension(&_toolbar, orientation);
195
196 _resizing = true;
197 if (allocated_size < min_size) {
198 // Shrinkage required.
199 while (allocated_size < min_size) {
200 if (_menu_btns[_active_mb_index]->toolbar_children.empty()) {
201 // This menu button can no longer be collapsed. Switch to the next
202 // menu button (towards the left of the vector).
203 if (_active_mb_index > 0) {
204 _active_mb_index -= 1;
205 continue;
206 } else {
207 // Reaching this point indicates that the toolbar cannot be shrunk any further.
208 _resizing = false;
209 return;
210 }
211 }
212
213 // Now, move the toolbar_children of this menu button to the popover.
214 auto mb = _menu_btns[_active_mb_index].get();
215 auto popover_box = dynamic_cast<Gtk::Box *>(mb->menu_btn->get_popover()->get_child());
216 _move_children(&_toolbar, popover_box, mb->toolbar_children, mb->popover_children, mb->group_size);
217 mb->menu_btn->set_visible(true);
218
219 int old = min_size;
220 min_size = min_dimension(&_toolbar, orientation);
221 int change = old - min_size;
222 _size_needed.push(change);
223 }
224 } else if (allocated_size > min_size) {
225 // Once the allocated size of the toolbar is greater than its
226 // minimum size, try to re-insert a group of elements back
227 // into the toolbar.
228 if (!(allocated_size > min_size + _size_needed.top())) {
229 // Not enough space, skip.
230 _resizing = false;
231 return;
232 }
233
234 // Expand until there are children left in the popovers.
235 while (_active_mb_index < _menu_btns.size()) {
236 // Check if the currently active menu button is expandable or not.
237 if (_menu_btns[_active_mb_index]->popover_children.empty()) {
238 // This menu button can no longer be expanded. Switch to the next
239 // menu button (towards the right of the vector).
240 if (_active_mb_index < _menu_btns.size() - 1) {
241 _active_mb_index += 1;
242 continue;
243 } else {
244 // Reaching this point indicates that the toolbar cannot be expanded any further.
245 // Set this menu button invisible and return.
246 // _menu_btns[_active_mb_index]->set_visible(false);
247 _resizing = false;
248 return;
249 }
250 }
251
252 auto mb = _menu_btns[_active_mb_index].get();
253
254 // See if we have enough space to expand the topmost collapsed button.
255 int req_size = min_size + _size_needed.top();
256
257 if (req_size > allocated_size) {
258 // Not enough space - stop.
259 break;
260 }
261
262 // Move a group of widgets back into the toolbar.
263 auto popover_box = dynamic_cast<Gtk::Box *>(mb->menu_btn->get_popover()->get_child());
264 _move_children(popover_box, &_toolbar, mb->toolbar_children, mb->popover_children, mb->group_size, true);
265 _size_needed.pop();
266
267 if (mb->popover_children.empty()) {
268 // Set it invisible only if all the children have moved to the toolbar.
269 mb->menu_btn->set_visible(false);
270 }
271
272 min_size = min_dimension(&_toolbar, orientation);
273 }
274 }
275
276 _resizing = false;
277}
278
280{
281 Glib::ustring icon_name = "go-down";
282
283 if (auto btn = dynamic_cast<Gtk::Button *>(child); btn && _toolbar.get_orientation() == Gtk::Orientation::HORIZONTAL) {
284 // Find the icon name from the child image.
285 if (auto image = dynamic_cast<Gtk::Image *>(btn->get_child())) {
286 auto icon = image->get_icon_name();
287 if (icon != "") {
288 icon_name = icon;
289 }
290 } else {
291 // Find the icon name from the button itself.
292 auto icon = btn->get_icon_name();
293 if (icon != "") {
294 icon_name = icon;
295 }
296 }
297 }
298
299 auto menu_btn = _menu_btns[_active_mb_index]->menu_btn;
300 menu_btn->set_always_show_arrow(!(icon_name == "go-down"));
301 menu_btn->set_icon_name(icon_name.c_str());
302}
303
304void ToolbarWidget::_move_children(Gtk::Box *src, Gtk::Box *dest,
305 std::stack<std::pair<Gtk::Widget *, Gtk::Widget *>> &tb_children,
306 std::stack<std::pair<Gtk::Widget *, Gtk::Widget *>> &popover_children, int group_size,
307 bool is_expanding)
308{
309 while (group_size--) {
310 Gtk::Widget *child;
311 Gtk::Widget *prev_child;
312
313 if (is_expanding) {
314 std::tie(prev_child, child) = popover_children.top();
315 popover_children.pop();
316 tb_children.emplace(prev_child, child);
317 } else {
318 std::tie(prev_child, child) = tb_children.top();
319 tb_children.pop();
320 popover_children.emplace(prev_child, child);
321 }
322
323 child->reference();
324
325 src->remove(*child);
326
327 // is_expanding will be true when the children are being put back into
328 // the toolbar. In that case, insert the children at their previous
329 // positions.
330 if (is_expanding) {
331 if (!prev_child) {
332 dest->insert_child_at_start(*child);
333 } else {
334 dest->insert_child_after(*child, *prev_child);
335 }
336
337 if (!popover_children.empty()) {
338 _update_menu_btn_image(popover_children.top().second);
339 }
340 } else {
341 dest->prepend(*child);
343 }
344
345 child->unreference();
346 }
347}
348
350{
351 // Lifecycle model for toolbars requires desktop to be unset before destruction.
352 assert(!_desktop);
353}
354
356{
357 if (_desktop) {
358 _desktop->getCanvas()->grab_focus();
359 }
360}
361
362} // namespace Inkscape::UI::Toolbar
363
364/*
365 Local Variables:
366 mode:c++
367 c-file-style:"stroustrup"
368 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
369 indent-tabs-mode:nil
370 fill-column:99
371 End:
372*/
373// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Inkscape canvas widget.
void _resize_handler(int width, int height)
Definition toolbar.cpp:186
void _insert_menu_btn(int priority, int group_size, std::stack< std::pair< Gtk::Widget *, Gtk::Widget * > > toolbar_children)
Definition toolbar.cpp:139
void _update_menu_btn_image(Gtk::Widget *child)
Definition toolbar.cpp:279
void measure_vfunc(Gtk::Orientation orientation, int for_size, int &min, int &nat, int &min_baseline, int &nat_baseline) const override
Definition toolbar.cpp:162
void _move_children(Gtk::Box *src, Gtk::Box *dest, std::stack< std::pair< Gtk::Widget *, Gtk::Widget * > > &tb_children, std::stack< std::pair< Gtk::Widget *, Gtk::Widget * > > &popover_children, int group_size, bool is_expanding=false)
Definition toolbar.cpp:304
void on_size_allocate(int width, int height, int baseline) override
Definition toolbar.cpp:172
std::vector< std::unique_ptr< MenuButton > > _menu_btns
Definition toolbar.h:52
virtual void on_size_allocate(int width, int height, int baseline)
Definition bin.cpp:57
void set_child(Gtk::Widget *child)
Sets (parents) the child widget, or unsets (unparents) it if child is null.
Definition bin.cpp:40
Inkscape::UI::Widget::Canvas * getCanvas() const
Definition desktop.h:190
double c[8][4]
Editable view implementation.
std::unique_ptr< Magick::Image > image
static bool isMatchingPattern(const std::string &str, const std::string &pattern)
Definition toolbar.cpp:53
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
STL namespace.
static cairo_user_data_key_t key
Ocnode * child[8]
Definition quantize.cpp:33
double height
double width
static int constexpr min_dimension
TODO: insert short description here.