Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
page-toolbar.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
6/* Authors:
7 * Martin Owens <doctormo@geek-2.com>
8 * Vaibhav Malik <vaibhavmalik2018@gmail.com>
9
10 * Copyright (C) 2021 Tavmjong Bah
11 *
12 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
13 */
14
15#include "page-toolbar.h"
16
17#include <glibmm/i18n.h>
18#include <glibmm/regex.h>
19#include <gtkmm/box.h>
20#include <gtkmm/comboboxtext.h>
21#include <gtkmm/entry.h>
22#include <gtkmm/entrycompletion.h>
23#include <gtkmm/label.h>
24#include <gtkmm/liststore.h>
25#include <gtkmm/popover.h>
26#include <gtkmm/separator.h>
27#include <sigc++/functors/mem_fun.h>
28
29#include "desktop.h"
30#include "document-undo.h"
31#include "document.h"
32#include "extension/db.h"
33#include "extension/template.h"
34#include "io/resource.h"
35#include "object/sp-page.h"
36#include "ui/builder-utils.h"
37#include "ui/icon-names.h"
38#include "ui/popup-menu.h"
39#include "ui/tools/pages-tool.h"
40#include "ui/util.h"
41#include "util/paper.h"
42#include "util/units.h"
43
45
46namespace Inkscape::UI::Toolbar {
47
48class SearchCols : public Gtk::TreeModel::ColumnRecord
49{
50public:
51 // These types must match those for the model in the ui file
52 SearchCols()
53 {
54 add(name);
55 add(label);
56 add(key);
57 }
58 Gtk::TreeModelColumn<Glib::ustring> name; // translated name
59 Gtk::TreeModelColumn<Glib::ustring> label; // translated label
60 Gtk::TreeModelColumn<Glib::ustring> key;
61};
62
64 : PageToolbar{create_builder("toolbar-page.ui")}
65{}
66
67PageToolbar::PageToolbar(Glib::RefPtr<Gtk::Builder> const &builder)
68 : Toolbar{get_widget<Gtk::Box>(builder, "page-toolbar")}
69 , _combo_page_sizes(get_widget<Gtk::ComboBoxText>(builder, "_combo_page_sizes"))
70 , _text_page_margins(get_widget<Gtk::Entry>(builder, "_text_page_margins"))
71 , _margin_popover(get_widget<Gtk::Popover>(builder, "margin_popover"))
72 , _text_page_bleeds(get_widget<Gtk::Entry>(builder, "_text_page_bleeds"))
73 , _text_page_label(get_widget<Gtk::Entry>(builder, "_text_page_label"))
74 , _label_page_pos(get_widget<Gtk::Label>(builder, "_label_page_pos"))
75 , _btn_page_backward(get_widget<Gtk::Button>(builder, "_btn_page_backward"))
76 , _btn_page_foreward(get_widget<Gtk::Button>(builder, "_btn_page_foreward"))
77 , _btn_page_delete(get_widget<Gtk::Button>(builder, "_btn_page_delete"))
78 , _btn_move_toggle(get_widget<Gtk::Button>(builder, "_btn_move_toggle"))
79 , _sep1(get_widget<Gtk::Separator>(builder, "_sep1"))
80 , _sizes_list(get_object<Gtk::ListStore>(builder, "_sizes_list"))
81 , _sizes_search(get_object<Gtk::ListStore>(builder, "_sizes_search"))
82 , _margin_top(get_derived_widget<UI::Widget::MathSpinButton>(builder, "_margin_top"))
83 , _margin_right(get_derived_widget<UI::Widget::MathSpinButton>(builder, "_margin_right"))
84 , _margin_bottom(get_derived_widget<UI::Widget::MathSpinButton>(builder, "_margin_bottom"))
85 , _margin_left(get_derived_widget<UI::Widget::MathSpinButton>(builder, "_margin_left"))
86{
87 set_name("PageToolbar");
88
89 _text_page_label.signal_changed().connect(sigc::mem_fun(*this, &PageToolbar::labelEdited));
90
91 get_object<Gtk::EntryCompletion>(builder, "_sizes_searcher")
92 ->signal_match_selected()
93 .connect(
94 [this] (Gtk::TreeModel::iterator const &iter) {
95 SearchCols cols;
96 Gtk::TreeModel::Row row = *iter;
97 Glib::ustring preset_key = row[cols.key];
98 sizeChoose(preset_key);
99 return false;
100 },
101 false);
102
103 _text_page_bleeds.signal_activate().connect(sigc::mem_fun(*this, &PageToolbar::bleedsEdited));
104 _text_page_margins.signal_activate().connect(sigc::mem_fun(*this, &PageToolbar::marginsEdited));
105
106 _margin_popover.set_name("MarginPopover");
107 _margin_popover.set_parent(*this);
108
109 _text_page_margins.signal_icon_press().connect([&](Gtk::Entry::IconPosition) {
110 if (auto page = _document->getPageManager().getSelected()) {
111 auto const &margin = page->getMarginBox();
112 auto unit = _document->getDisplayUnit()->abbr;
114 _margin_top.set_value(margin.top().toValue(unit) * scale[Geom::Y]);
115 _margin_right.set_value(margin.right().toValue(unit) * scale[Geom::X]);
116 _margin_bottom.set_value(margin.bottom().toValue(unit) * scale[Geom::Y]);
117 _margin_left.set_value(margin.left().toValue(unit) * scale[Geom::X]);
118 _text_page_bleeds.set_text(page->getBleedLabel());
119 }
121 });
122 _margin_top.signal_value_changed().connect(sigc::mem_fun(*this, &PageToolbar::marginTopEdited));
123 _margin_right.signal_value_changed().connect(sigc::mem_fun(*this, &PageToolbar::marginRightEdited));
124 _margin_bottom.signal_value_changed().connect(sigc::mem_fun(*this, &PageToolbar::marginBottomEdited));
125 _margin_left.signal_value_changed().connect(sigc::mem_fun(*this, &PageToolbar::marginLeftEdited));
126
127 dynamic_cast<Gtk::Entry &>(*_combo_page_sizes.get_child()).set_completion(get_object<Gtk::EntryCompletion>(builder, "_sizes_searcher"));
128
129 _combo_page_sizes.set_id_column(2);
130 _combo_page_sizes.signal_changed().connect([this] {
131 std::string preset_key = _combo_page_sizes.get_active_id();
132 if (!preset_key.empty()) {
133 sizeChoose(preset_key);
134 }
135 });
136
137 _entry_page_sizes = dynamic_cast<Gtk::Entry *>(_combo_page_sizes.get_child());
138 // TODO(Gtk::DropDown migration): set up the tooltip for the dropdown's button.
139 if (_entry_page_sizes) {
140 _entry_page_sizes->set_placeholder_text(_("ex.: 100x100cm"));
141 _entry_page_sizes->set_tooltip_text(_("Type in width & height of a page. (ex.: 15x10cm, 10in x 100mm)\n"
142 "or choose preset from dropdown."));
143 _entry_page_sizes->add_css_class("symbolic");
144 _entry_page_sizes->signal_activate().connect(sigc::mem_fun(*this, &PageToolbar::sizeChanged));
145
146 _entry_page_sizes->signal_icon_press().connect([this] (Gtk::Entry::IconPosition) {
148 DocumentUndo::maybeDone(_document, "page-resize", _("Resize Page"), INKSCAPE_ICON("tool-pages"));
149 setSizeText();
150 });
151 _entry_page_sizes->set_icon_tooltip_text(_("Change page orientation"), Gtk::Entry::IconPosition::SECONDARY);
152 _entry_page_sizes->property_has_focus().signal_changed().connect([this] {
153 if (!_document)
154 return;
155 auto const display_only = !has_focus();
156 setSizeText(nullptr, display_only);
157 });
158
160 }
161
163}
164
165PageToolbar::~PageToolbar() = default;
166
168{
169 if (_desktop) {
170 // Disconnect previous page changed signal
171 _page_selected.disconnect();
172 _pages_changed.disconnect();
173 _page_modified.disconnect();
174 _document = nullptr;
175 }
176
178
179 if (_desktop) {
181 assert(_document);
182
185 });
186
187 // Save the document and page_manager for future use.
188 auto &page_manager = _document->getPageManager();
189 // Connect the page changed signal and indicate changed
190 _pages_changed = page_manager.connectPagesChanged(sigc::mem_fun(*this, &PageToolbar::pagesChanged));
191 _page_selected = page_manager.connectPageSelected(sigc::mem_fun(*this, &PageToolbar::selectionChanged));
192 // Update everything now.
193 pagesChanged();
194 }
195}
196
201{
202 SearchCols cols;
203
206
207 for (auto tmod : extensions) {
208 if (!tmod->can_resize())
209 continue;
210 for (auto preset : tmod->get_presets()) {
211 auto label = preset->get_label();
212 if (!label.empty()) label = _(label.c_str());
213
214 if (preset->is_visible(Inkscape::Extension::TEMPLATE_SIZE_LIST)) {
215 // Goes into drop down
216 Gtk::TreeModel::Row row = *_sizes_list->append();
217 row[cols.name] = _(preset->get_name().c_str());
218 row[cols.label] = " <small><span fgalpha=\"50%\">" + label + "</span></small>";
219 row[cols.key] = preset->get_key();
220 }
221 if (preset->is_visible(Inkscape::Extension::TEMPLATE_SIZE_SEARCH)) {
222 // Goes into text search
223 Gtk::TreeModel::Row row = *_sizes_search->append();
224 row[cols.name] = _(preset->get_name().c_str());
225 row[cols.label] = label;
226 row[cols.key] = preset->get_key();
227 }
228 }
229 }
230}
231
233{
234 auto text = _text_page_label.get_text();
235 if (auto page = _document->getPageManager().getSelected()) {
236 page->setLabel(text.empty() ? nullptr : text.c_str());
237 DocumentUndo::maybeDone(_document, "page-relabel", _("Relabel Page"), INKSCAPE_ICON("tool-pages"));
238 }
239}
240
242{
243 auto text = _text_page_bleeds.get_text();
244
245 // And modifiction to the bleed causes pages to be enabled
246 auto &pm = _document->getPageManager();
247 pm.enablePages();
248
249 if (auto page = pm.getSelected()) {
250 page->setBleed(text);
251 DocumentUndo::maybeDone(_document, "page-bleed", _("Edit page bleed"), INKSCAPE_ICON("tool-pages"));
252 _text_page_bleeds.set_text(page->getBleedLabel());
253 }
254}
255
257{
258 auto text = _text_page_margins.get_text();
259
260 // And modifiction to the margin causes pages to be enabled
261 auto &pm = _document->getPageManager();
262 pm.enablePages();
263
264 if (auto page = pm.getSelected()) {
265 page->setMargin(text);
266 DocumentUndo::maybeDone(_document, "page-margin", _("Edit page margin"), INKSCAPE_ICON("tool-pages"));
268 }
269}
270
272{
273 marginSideEdited(0, _margin_top.get_text());
274}
287void PageToolbar::marginSideEdited(int side, const Glib::ustring &value)
288{
289 // And modifiction to the margin causes pages to be enabled
290 auto &pm = _document->getPageManager();
291 pm.enablePages();
292
293 if (auto page = pm.getSelected()) {
294 page->setMarginSide(side, value, false);
295 DocumentUndo::maybeDone(_document, "page-margin", _("Edit page margin"), INKSCAPE_ICON("tool-pages"));
297 }
298}
299
300void PageToolbar::sizeChoose(const std::string &preset_key)
301{
302 if (auto preset = Extension::Template::get_any_preset(preset_key)) {
303 auto &pm = _document->getPageManager();
304 // The page orientation is a part of the toolbar widget, so we pass this
305 // as a specially named pref, the extension can then decide to use it or not.
306 auto p_rect = pm.getSelectedPageRect();
307 std::string orient = p_rect.width() > p_rect.height() ? "land" : "port";
308
309 auto page = pm.getSelected();
310 preset->resize_to_template(_document, page, {
311 {"orientation", orient},
312 });
313 if (page) {
314 page->setSizeLabel(preset->get_name());
315 }
316
317 setSizeText();
318 DocumentUndo::maybeDone(_document, "page-resize", _("Resize Page"), INKSCAPE_ICON("tool-pages"));
319 } else {
320 // Page not found, i.e., "Custom" was selected or user is typing in.
321 _entry_page_sizes->grab_focus();
322 }
323}
324
328double PageToolbar::_unit_to_size(std::string number, std::string unit_str,
329 std::string const &backup)
330{
331 // We always support comma, even if not in that particular locale.
332 std::replace(number.begin(), number.end(), ',', '.');
333 double value = std::stod(number);
334
335 // Get the best unit, for example 50x40cm means cm for both
336 if (unit_str.empty() && !backup.empty())
337 unit_str = backup;
338 if (unit_str == "\"")
339 unit_str = "in";
340 if (unit_str == "'")
341 unit_str = "ft";
342
343 // Output is always in px as it's the most useful.
344 auto px = Inkscape::Util::UnitTable::get().getUnit("px");
345
346 // Convert from user entered unit to display unit
347 if (!unit_str.empty())
348 return Inkscape::Util::Quantity::convert(value, unit_str, px);
349
350 // Default unit is the document's display unit
351 auto unit = _document->getDisplayUnit();
352 return Inkscape::Util::Quantity::convert(value, unit, px);
353}
354
364{
365 // Parse the size out of the typed text if possible.
366 auto cb_text = _combo_page_sizes.get_active_text();
367
368 // Remove parens from auto generated names
369 auto pos1 = cb_text.find_first_of("(");
370 auto pos2 = cb_text.find_first_of(")");
371 if (pos1 != cb_text.npos && pos2 != cb_text.npos && pos1 < pos2) {
372 cb_text = cb_text.substr(pos1+1, pos2-pos1-1);
373 }
374
375 // This does not support negative values, because pages can not be negatively sized.
376 static auto const arg = "([0-9]+[\\.,]?[0-9]*|\\.[0-9]+) ?(px|mm|cm|m|in|\\\"|ft|')?";
377 static auto const regex = Glib::Regex::create(Glib::ustring{"^ *"} + arg + " *([ *Xx×,\\-]) *" + arg + " *$", Glib::Regex::CompileFlags::OPTIMIZE);
378
379 Glib::MatchInfo match;
380 if (regex->match(cb_text, match)) {
381 // Convert the desktop px back into document units for 'resizePage'
382 auto const width_unit = match.fetch(2);
383 auto const height_unit = match.fetch(5);
384 double width = _unit_to_size(match.fetch(1), width_unit, height_unit);
385 double height = _unit_to_size(match.fetch(4), height_unit, width_unit);
386 if (width > 0 && height > 0) {
388 DocumentUndo::done(_document, _("Set page size"), INKSCAPE_ICON("tool-pages"));
389 }
390 }
391 setSizeText();
392}
393
397void PageToolbar::setSizeText(SPPage *page, bool display_only)
398{
399 _size_edited.block();
400 SearchCols cols;
401
402 if (!page)
404
406
407 // If this is a known size in our list, add the size paren to it.
408 for (auto &row : _sizes_search->children()) {
409 if (label == row[cols.name].operator Glib::ustring().raw()) {
410 label = label + " (" + row[cols.label] + ")";
411 break;
412 }
413 }
414 _entry_page_sizes->set_text(label);
415
416 // Orientation button
417 auto box = page ? page->getDesktopRect() : *_document->preferredBounds();
418 auto const icon = box.width() > box.height() ? "page-landscape" : "page-portrait";
419 if (box.width() == box.height()) {
420 _entry_page_sizes->unset_icon(Gtk::Entry::IconPosition::SECONDARY);
421 } else {
422 _entry_page_sizes->set_icon_from_icon_name(INKSCAPE_ICON(icon), Gtk::Entry::IconPosition::SECONDARY);
423 }
424
425 if (!display_only) {
426 // The user has started editing the combo box; we set up a convenient initial state.
427 // Select text if box is currently in focus.
428 if (_entry_page_sizes->has_focus()) {
429 _entry_page_sizes->select_region(0, -1);
430 }
431 }
432 _size_edited.unblock();
433}
434
436{
437 _text_page_margins.set_text(page ? page->getMarginLabel() : "");
438 _text_page_margins.set_sensitive(true);
439}
440
445
447{
448 _label_edited.block();
449 _page_modified.disconnect();
450 auto &page_manager = _document->getPageManager();
451 _text_page_label.set_tooltip_text(_("Page label"));
452
454
455 // Set label widget content with page label.
456 if (page) {
457 _text_page_label.set_sensitive(true);
458 _text_page_label.set_placeholder_text(page->getDefaultLabel());
459
460 if (auto label = page->label()) {
461 _text_page_label.set_text(label);
462 } else {
463 _text_page_label.set_text("");
464 }
465
466
467 // TRANSLATORS: "%1" is replaced with the page we are on, and "%2" is the total number of pages.
468 auto label = Glib::ustring::compose(_("%1/%2"), page->getPagePosition(), page_manager.getPageCount());
469 _label_page_pos.set_label(label);
470
471 _page_modified = page->connectModified([this] (SPObject *obj, unsigned flags) {
472 if (auto page = cast<SPPage>(obj)) {
473 // Make sure we don't 'select' on removal of the page
474 if (flags & SP_OBJECT_MODIFIED_FLAG) {
476 }
477 }
478 });
479 } else {
480 _text_page_label.set_text("");
481 _text_page_label.set_sensitive(false);
482 _text_page_label.set_placeholder_text(_("Single Page Document"));
483 _label_page_pos.set_label(_("1/-"));
484
485 _page_modified = _document->connectModified([this] (unsigned) {
486 selectionChanged(nullptr);
487 });
488 }
489 if (!page_manager.hasPrevPage() && !page_manager.hasNextPage()) {
490 _sep1.set_visible(false);
491 _label_page_pos.set_visible(false);
492 _btn_page_backward.set_visible(false);
493 _btn_page_foreward.set_visible(false);
494 _btn_page_delete.set_visible(false);
495 _btn_move_toggle.set_sensitive(false);
496 } else {
497 // Set the forward and backward button sensitivities
498 _sep1.set_visible(true);
499 _label_page_pos.set_visible(true);
500 _btn_page_backward.set_visible(true);
501 _btn_page_foreward.set_visible(true);
502 _btn_page_backward.set_sensitive(page_manager.hasPrevPage());
503 _btn_page_foreward.set_sensitive(page_manager.hasNextPage());
504 _btn_page_delete.set_visible(true);
505 _btn_move_toggle.set_sensitive(true);
506 }
508 _label_edited.unblock();
509}
510
511} // namespace Inkscape::UI::Toolbar
512
513/*
514 Local Variables:
515 mode:c++
516 c-file-style:"stroustrup"
517 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
518 indent-tabs-mode:nil
519 fill-column:99
520 End:
521*/
522// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
double scale
Definition aa.cpp:228
Gtk builder utilities.
uint64_t page
Definition canvas.cpp:171
int margin
Definition canvas.cpp:166
C width() const
Get the horizontal extent of the rectangle.
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
static void maybeDone(SPDocument *document, const gchar *keyconst, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
TemplateList & get_template_list(TemplateList &ou_list)
Create a list of all the Template extensions.
Definition db.cpp:228
std::list< Template * > TemplateList
Definition db.h:57
static std::shared_ptr< TemplatePreset > get_any_preset(const std::string &key)
Return the template preset based on the key from any template class (static method).
Definition template.cpp:352
sigc::connection connectPagesChanged(const sigc::slot< void()> &slot)
void changeOrientation()
Change page orientation, landscape to portrait and back.
void enablePages()
Enables multi page support by turning the document viewBox into the first page.
std::string getSizeLabel(SPPage *page=nullptr)
Return a page's size label, or match via width and height.
Geom::Rect getSelectedPageRect() const
Returns the selected page rect, OR the viewbox rect.
SPPage * getSelected() const
void resizePage(double width, double height)
sigc::scoped_connection _pages_changed
Inkscape::UI::Widget::MathSpinButton & _margin_left
Inkscape::UI::Widget::MathSpinButton & _margin_bottom
void setDesktop(SPDesktop *desktop) override
Glib::RefPtr< Gtk::ListStore > _sizes_list
sigc::scoped_connection _doc_connection
Inkscape::UI::Widget::MathSpinButton & _margin_top
sigc::scoped_connection _label_edited
void sizeChoose(const std::string &preset_key)
Glib::RefPtr< Gtk::ListStore > _sizes_search
void populate_sizes()
Take all selectable page sizes and add to search and dropdowns.
void setSizeText(SPPage *page=nullptr, bool display_only=true)
Sets the size of the current page into the entry page size.
sigc::scoped_connection _page_modified
Gtk::ComboBoxText & _combo_page_sizes
sigc::scoped_connection _size_edited
double _unit_to_size(std::string number, std::string unit_str, std::string const &backup)
Convert the parsed sections of a text input into a desktop pixel value.
void marginSideEdited(int side, const Glib::ustring &value)
Inkscape::UI::Widget::MathSpinButton & _margin_right
void setMarginText(SPPage *page=nullptr)
void sizeChanged()
A manually typed input size, parse out what we can understand from the text or ignore it if the text ...
sigc::scoped_connection _page_selected
Base class for all tool toolbars.
Definition toolbar.h:72
virtual void setDesktop(SPDesktop *desktop)
Definition toolbar.h:76
static double convert(double from_dist, Unit const *from, Unit const *to)
Convert distances.
Definition units.cpp:588
static UnitTable & get()
Definition units.cpp:441
Unit const * getUnit(Glib::ustring const &name) const
Retrieve a given unit based on its string identifier.
Definition units.cpp:316
Glib::ustring abbr
Definition units.h:81
To do: update description of desktop.
Definition desktop.h:149
sigc::connection connectDocumentReplaced(F &&slot)
Definition desktop.h:257
SPDocument * getDocument() const
Definition desktop.h:189
Typed SVG document implementation.
Definition document.h:101
Geom::OptRect preferredBounds() const
Definition document.cpp:963
sigc::connection connectModified(ModifiedSignal::slot_type slot)
Inkscape::PageManager & getPageManager()
Definition document.h:162
Geom::Scale getDocumentScale(bool computed=true) const
Returns document scale as defined by width/height (in pixels) and viewBox (real world to user-units).
Definition document.cpp:758
Inkscape::Util::Unit const * getDisplayUnit()
guaranteed not to return nullptr
Definition document.cpp:726
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
Editable view implementation.
TODO: insert short description here.
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Macro for icon names used in Inkscape.
Glib::ustring label
Definition desktop.h:50
DB db
This is the actual database object.
Definition db.cpp:32
Glib::RefPtr< Ob > get_object(Glib::RefPtr< Gtk::Builder > const &builder, char const *id)
static void popup_at(Gtk::Popover &popover, Gtk::Widget &widget, double const x_offset, double const y_offset, int width, int height)
W & get_widget(const Glib::RefPtr< Gtk::Builder > &builder, const char *id)
W & get_derived_widget(const Glib::RefPtr< Gtk::Builder > &builder, const char *id, Args &&... args)
Glib::RefPtr< Gtk::Builder > create_builder(const char *filename)
Page toolbar.
Helpers to connect signals to events that popup a menu in both GTK3 and GTK4.
Inkscape::IO::Resource - simple resource API.
SPPage – a page object.
SPDesktop * desktop
double height
double width
Glib::ustring name
Definition toolbars.cpp:55
Glib::RefPtr< Gtk::Builder > builder