Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
document-properties.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
6/* Authors:
7 * bulia byak <buliabyak@users.sf.net>
8 * Bryce W. Harrington <bryce@bryceharrington.org>
9 * Lauris Kaplinski <lauris@kaplinski.com>
10 * Jon Phillips <jon@rejon.org>
11 * Ralf Stephan <ralf@ark.in-berlin.de> (Gtkmm)
12 * Diederik van Lierop <mail@diedenrezi.nl>
13 * Jon A. Cruz <jon@joncruz.org>
14 * Abhishek Sharma
15 *
16 * Copyright (C) 2006-2008 Johan Engelen <johan@shouraizou.nl>
17 * Copyright (C) 2000 - 2008 Authors
18 *
19 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
20 */
21
22#include "document-properties.h"
23
24#include <algorithm>
25#include <array>
26#include <cmath>
27#include <iterator>
28#include <optional>
29#include <set>
30#include <string>
31#include <tuple>
32
33#include <giomm/themedicon.h>
34#include <glibmm/main.h>
35#include <gtkmm/adjustment.h>
36#include <gtkmm/box.h>
37#include <gtkmm/button.h>
38#include <gtkmm/entry.h>
39#include <gtkmm/enums.h>
40#include <gtkmm/grid.h>
41#include <gtkmm/label.h>
42#include <gtkmm/listboxrow.h>
43#include <gtkmm/menubutton.h>
44#include <gtkmm/object.h>
45#include <gtkmm/popover.h>
46#include <gtkmm/popovermenu.h>
47#include <gtkmm/spinbutton.h>
48#include <gtkmm/togglebutton.h>
49#include <gtkmm/widget.h>
50#include <glibmm/convert.h>
51#include <gtkmm/image.h>
52#include <gtkmm/liststore.h>
53#include <gtkmm/sizegroup.h>
54#include <sigc++/adaptors/bind.h>
55#include <sigc++/functors/mem_fun.h>
56
57#include <2geom/angle.h>
58#include "inkscape-window.h"
59#include "preferences.h"
60#include "rdf.h"
61#include "page-manager.h"
62#include "selection.h"
63
64#include "colors/cms/profile.h"
65#include "colors/document-cms.h"
66#include <sigc++/scoped_connection.h>
68#include "object/sp-grid.h"
69#include "object/sp-guide.h"
70#include "object/sp-root.h"
71#include "object/sp-script.h"
72#include "streq.h"
74#include "ui/icon-loader.h"
75#include "ui/icon-names.h"
76#include "ui/pack.h"
77#include "ui/popup-menu.h"
78#include "ui/util.h"
82#include "ui/widget/labelled.h"
87#include "ui/widget/scalar.h"
90
91namespace Inkscape::UI {
92
93namespace Widget {
94
95class GridWidget final : public Gtk::Box
96{
97public:
98 GridWidget(SPGrid *obj);
99
100 void update();
101 SPGrid *getGrid() { return _grid; }
102 XML::Node *getGridRepr() { return _repr; }
103
104private:
105 SPGrid *_grid = nullptr;
106 XML::Node *_repr = nullptr;
107
108 Gtk::Button* _delete = Gtk::make_managed<Gtk::Button>();
109 Gtk::MenuButton* _options = Gtk::make_managed<Gtk::MenuButton>();
110 Gtk::Popover* _opt_items = Gtk::make_managed<Gtk::Popover>();
111 Gtk::Image* _icon = Gtk::make_managed<Gtk::Image>();
112 Gtk::Label* _id = Gtk::make_managed<Gtk::Label>();
113 Gtk::MenuButton* _align = Gtk::make_managed<Gtk::MenuButton>();
114 Gtk::Popover* _align_popup = Gtk::make_managed<Gtk::Popover>();
115
116 UI::Widget::Registry _wr;
118 RegisteredSwitchButton *_enabled = nullptr;
119 RegisteredCheckButton *_snap_visible_only = nullptr;
120 RegisteredToggleButton *_visible = nullptr;
121 RegisteredCheckButton *_dotted = nullptr;
122 AlignmentSelector *_alignment = nullptr;
123
124 RegisteredUnitMenu *_units = nullptr;
125 RegisteredScalarUnit *_origin_x = nullptr;
126 RegisteredScalarUnit *_origin_y = nullptr;
127 RegisteredScalarUnit *_spacing_x = nullptr;
128 RegisteredScalarUnit *_spacing_y = nullptr;
129 RegisteredScalar *_angle_x = nullptr;
130 RegisteredScalar *_angle_z = nullptr;
131 RegisteredColorPicker *_grid_color = nullptr;
132 RegisteredInteger *_no_of_lines = nullptr;
133 RegisteredScalarUnit* _gap_x = nullptr;
134 RegisteredScalarUnit* _gap_y = nullptr;
135 RegisteredScalarUnit* _margin_x = nullptr;
136 RegisteredScalarUnit* _margin_y = nullptr;
137 Gtk::MenuButton* _angle_popup = Gtk::make_managed<Gtk::MenuButton>();
138 Gtk::Entry* _aspect_ratio = nullptr;
139
140 sigc::scoped_connection _modified_signal;
141};
142
143} // namespace Widget
144
145namespace Dialog {
146
147static constexpr int SPACE_SIZE_X = 15;
148static constexpr int SPACE_SIZE_Y = 10;
149
150static void docprops_style_button(Gtk::Button& btn, char const* iconName)
151{
152 GtkWidget *child = sp_get_icon_image(iconName, GTK_ICON_SIZE_NORMAL);
153 gtk_widget_set_visible(child, true);
154 btn.set_child(*Gtk::manage(Glib::wrap(child)));
155 btn.set_has_frame(false);
156}
157
159 Gtk::TreeView &tree_view, UI::Widget::PopoverBin &pb, sigc::slot<void ()> const &slot)
160{
161 auto const selection = tree_view.get_selection();
162 if (!selection) return false;
163
164 auto const it = selection->get_selected();
165 if (!it) return false;
166
167 auto const mi = Gtk::make_managed<UI::Widget::PopoverMenuItem>(_("_Remove"), true);
168 mi->signal_activate().connect(slot);
169 auto const menu = Gtk::make_managed<UI::Widget::PopoverMenu>(Gtk::PositionType::BOTTOM);
170 menu->append(*mi);
171
172 pb.setPopover(menu);
173
174 if (click) {
175 menu->popup_at(tree_view, click->x, click->y);
176 return true;
177 }
178
179 auto const column = tree_view.get_column(0);
180 g_return_val_if_fail(column, false);
181 auto rect = Gdk::Rectangle{};
182 tree_view.get_cell_area(Gtk::TreePath{it}, *column, rect);
183 menu->popup_at(tree_view, rect.get_x() + rect.get_width () / 2.0,
184 rect.get_y() + rect.get_height());
185 return true;
186}
187
188static void connect_remove_popup_menu(Gtk::TreeView &tree_view, UI::Widget::PopoverBin &pb, sigc::slot<void ()> slot)
189{
190 UI::on_popup_menu(tree_view, sigc::bind(&do_remove_popup_menu, std::ref(tree_view), std::ref(pb), std::move(slot)));
191}
192
194 : DialogBase("/dialogs/documentoptions", "DocumentProperties")
195 , _page_page(Gtk::make_managed<UI::Widget::NotebookPage>(1, 1, true))
196 , _page_guides(Gtk::make_managed<UI::Widget::NotebookPage>(1, 1, true))
197 , _page_cms(Gtk::make_managed<UI::Widget::NotebookPage>(1, 1, true))
198 , _page_scripting(Gtk::make_managed<UI::Widget::NotebookPage>(1, 1, true))
199 , _page_external_scripts(Gtk::make_managed<UI::Widget::NotebookPage>(1, 1))
200 , _page_embedded_scripts(Gtk::make_managed<UI::Widget::NotebookPage>(1, 1))
201 , _page_metadata1(Gtk::make_managed<UI::Widget::NotebookPage>(1, 1, true))
202 , _page_metadata2(Gtk::make_managed<UI::Widget::NotebookPage>(1, 1, true))
203 //---------------------------------------------------------------
204 // General guide options
205 , _rcb_sgui(_("Show _guides"), _("Show or hide guides"), "showguides", _wr)
206 , _rcb_lgui(_("Lock all guides"), _("Toggle lock of all guides in the document"), "inkscape:lockguides", _wr)
207 , _rcp_gui(_("Guide co_lor:"), _("Guideline color"), _("Color of guidelines"), "guidecolor", "guideopacity", _wr)
208 , _rcp_hgui(_("_Highlight color:"), _("Highlighted guideline color"),
209 _("Color of a guideline when it is under mouse"), "guidehicolor", "guidehiopacity", _wr)
210 , _create_guides_btn(_("Create guides around the current page"))
211 , _delete_guides_btn(_("Delete all guides"))
212 //---------------------------------------------------------------
213 , _grids_label_def("", Gtk::Align::START)
214 , _grids_vbox(Gtk::Orientation::VERTICAL)
215 , _grids_hbox_crea(Gtk::Orientation::HORIZONTAL)
216 // Attach nodeobservers to this document
217 , _namedview_connection(this)
218 , _root_connection(this)
219{
221 _popoverbin.set_expand();
223
224 _notebook.append_page(*_page_page, _("Display"));
225 _notebook.append_page(*_page_guides, _("Guides"));
226 _notebook.append_page(_grids_vbox, _("Grids"));
227 _notebook.append_page(*_page_cms, _("Color"));
228 _notebook.append_page(*_page_scripting, _("Scripting"));
229 _notebook.append_page(*_page_metadata1, _("Metadata"));
230 _notebook.append_page(*_page_metadata2, _("License"));
231 _notebook.signal_switch_page().connect([this](Gtk::Widget const *, unsigned const page){
232 // we cannot use widget argument, as this notification fires during destruction with all pages passed one by one
233 // page no 3 - cms
234 if (page == 3) {
235 // lazy-load color profiles; it can get prohibitively expensive when hundreds are installed
237 }
238 });
239
240 _wr.setUpdating (true);
241 build_page();
242 build_guides();
244 build_cms();
247 _wr.setUpdating (false);
248}
249
250//========================================================================
251
261void attach_all(Gtk::Grid &table, Gtk::Widget *const arr[], unsigned const n)
262{
263 for (unsigned i = 0, r = 0; i < n; i += 2) {
264 if (arr[i] && arr[i+1]) {
265 arr[i]->set_hexpand();
266 arr[i+1]->set_hexpand();
267 arr[i]->set_valign(Gtk::Align::CENTER);
268 arr[i+1]->set_valign(Gtk::Align::CENTER);
269 table.attach(*arr[i], 0, r, 1, 1);
270 table.attach(*arr[i+1], 1, r, 1, 1);
271 } else {
272 if (arr[i+1]) {
273 arr[i+1]->set_hexpand();
274 arr[i+1]->set_valign(Gtk::Align::CENTER);
275 table.attach(*arr[i+1], 0, r, 2, 1);
276 } else if (arr[i]) {
277 auto &label = dynamic_cast<Gtk::Label &>(*arr[i]);
278 label.set_hexpand();
279 label.set_halign(Gtk::Align::START);
280 label.set_valign(Gtk::Align::CENTER);
281 table.attach(label, 0, r, 2, 1);
282 } else {
283 auto const space = Gtk::make_managed<Gtk::Box>();
284 space->set_size_request (SPACE_SIZE_X, SPACE_SIZE_Y);
285 space->set_halign(Gtk::Align::CENTER);
286 space->set_valign(Gtk::Align::CENTER);
287 table.attach(*space, 0, r, 1, 1);
288 }
289 }
290 ++r;
291 }
292}
293
294void set_namedview_bool(SPDesktop* desktop, const Glib::ustring& operation, SPAttr key, bool on) {
295 if (!desktop || !desktop->getDocument()) return;
296
298
300 DocumentUndo::done(desktop->getDocument(), operation, "");
301}
302
303void set_color(SPDesktop* desktop, Glib::ustring operation, SPAttr color_key, SPAttr opacity_key, Colors::Color const &color) {
304 if (!desktop || !desktop->getDocument()) return;
305
306 desktop->getNamedView()->change_color(color_key, opacity_key, color);
308 DocumentUndo::maybeDone(desktop->getDocument(), ("document-color-" + operation).c_str(), operation, "");
309}
310
312 if (!desktop) return;
313
314 auto new_width_q = Inkscape::Util::Quantity(width, unit);
315 auto new_height_q = Inkscape::Util::Quantity(height, unit);
317 Inkscape::Util::Quantity const old_height_q = doc->getHeight();
318 auto rect = Geom::Rect(Geom::Point(0, 0), Geom::Point(new_width_q.value("px"), new_height_q.value("px")));
319 doc->fitToRect(rect, false);
320
321 // The origin for the user is in the lower left corner; this point should remain stationary when
322 // changing the page size. The SVG's origin however is in the upper left corner, so we must compensate for this
323 if (!doc->is_yaxisdown()) {
324 auto const vert_offset = Geom::Translate(Geom::Point(0, (old_height_q.value("px") - new_height_q.value("px"))));
325 doc->getRoot()->translateChildItems(vert_offset);
326 } else {
327 // when this is_yaxisdown is true, we need to translate just the guides
328 // the guides simply need their new converted positions
329 // in reference to: https://gitlab.com/inkscape/inkscape/-/issues/1230
330 for (auto guide : doc->getNamedView()->guides) {
331 guide->moveto(guide->getPoint() * Geom::Translate(0, 0), true);
332 }
333 }
334
335 // units: this is most likely not needed, units are part of document size attributes
336 // if (unit) {
337 // set_namedview_value(desktop, "", SPAttr::UNITS)
338 // write_str_to_xml(desktop, _("Set document unit"), "unit", unit->abbr.c_str());
339 // }
340 doc->setWidthAndHeight(new_width_q, new_height_q, true);
341
342 DocumentUndo::done(doc, _("Set page size"), "");
343}
344
346 if (!desktop) return;
347
348 auto document = desktop->getDocument();
349 if (!document) return;
350
351 auto box = document->getViewBox();
352 document->setViewBox(Geom::Rect::from_xywh(x, y, box.width(), box.height()));
353 DocumentUndo::done(document, _("Set viewbox position"), "");
355}
356
358 if (!desktop) return;
359
360 auto document = desktop->getDocument();
361 if (!document) return;
362
363 auto box = document->getViewBox();
365 DocumentUndo::done(document, _("Set viewbox size"), "");
367}
368
369// helper function to set document scale; uses magnitude of document width/height only, not computed (pixel) values
370void set_document_scale_helper(SPDocument& document, double scale) {
371 if (scale <= 0) return;
372
373 auto root = document.getRoot();
374 auto box = document.getViewBox();
376 box.min()[Geom::X], box.min()[Geom::Y],
377 root->width.value / scale, root->height.value / scale)
378 );
379}
380
382{
383 if (!desktop) return;
384
385 auto document = desktop->getDocument();
386 if (!document) return;
387
388 if (scale > 0) {
389 auto old_scale = document->getDocumentScale(false);
390 auto delta = old_scale * Geom::Scale(scale).inverse();
391
392 // Shapes in the document
394
395 // Pages, margins and bleeds
397
398 // Grids
399 if (auto nv = document->getNamedView()) {
400 for (auto grid : nv->grids) {
401 grid->scale(delta);
402 }
403 }
404 }
405}
406
408 if (!desktop) return;
409
410 auto document = desktop->getDocument();
411 if (!document) return;
412
413 if (scale > 0) {
417 DocumentUndo::done(document, _("Set page scale"), "");
418 }
419}
420
421// document scale as a ratio of document size and viewbox size
422// as described in Wiki: https://wiki.inkscape.org/wiki/index.php/Units_In_Inkscape
423// for example: <svg width="100mm" height="100mm" viewBox="0 0 100 100"> will report 1:1 scale
424std::optional<Geom::Scale> get_document_scale_helper(SPDocument& doc) {
425 auto root = doc.getRoot();
426 if (root &&
427 root->width._set && root->width.unit != SVGLength::PERCENT &&
428 root->height._set && root->height.unit != SVGLength::PERCENT) {
429 if (root->viewBox_set) {
430 // viewbox and document size present
431 auto vw = root->viewBox.width();
432 auto vh = root->viewBox.height();
433 if (vw > 0 && vh > 0) {
434 return Geom::Scale(root->width.value / vw, root->height.value / vh);
435 }
436 } else {
437 // no viewbox, use SVG size in pixels
438 auto w = root->width.computed;
439 auto h = root->height.computed;
440 if (w > 0 && h > 0) {
441 return Geom::Scale(root->width.value / w, root->height.value / h);
442 }
443 }
444 }
445
446 // there is no scale concept applicable in the current state
447 return std::optional<Geom::Scale>();
448}
449
451 if (!desktop) return;
452
453 auto document = desktop->getDocument();
454 if (!document) return;
455
458 auto sx = (*scale)[Geom::X];
459 auto sy = (*scale)[Geom::Y];
460 double eps = 0.0001; // TODO: tweak this value
461 bool uniform = fabs(sx - sy) < eps;
462 _page->set_dimension(PageProperties::Dimension::Scale, sx, sx); // only report one, only one "scale" is used
463 _page->set_check(PageProperties::Check::NonuniformScale, !uniform);
464 _page->set_check(PageProperties::Check::DisabledScale, false);
465 } else {
466 // no scale
467 _page->set_dimension(PageProperties::Dimension::Scale, 1, 1);
468 _page->set_check(PageProperties::Check::NonuniformScale, false);
469 _page->set_check(PageProperties::Check::DisabledScale, true);
470 }
471}
472
474 if (!desktop) return;
475
476 auto document = desktop->getDocument();
477 if (!document) return;
478
480 Geom::Rect viewBox = document->getViewBox();
481 _page->set_dimension(PageProperties::Dimension::ViewboxPosition, viewBox.min()[Geom::X], viewBox.min()[Geom::Y]);
482 _page->set_dimension(PageProperties::Dimension::ViewboxSize, viewBox.width(), viewBox.height());
483}
484
486{
488 _page = Gtk::manage(PageProperties::create());
489 _page_page->table().attach(*_page, 0, 0);
490
491 _page->signal_color_changed().connect([this](Colors::Color const &color, PageProperties::Color const element){
492 if (_wr.isUpdating() || !_wr.desktop()) return;
493
494 _wr.setUpdating(true);
495 switch (element) {
496 case PageProperties::Color::Desk:
498 break;
499 case PageProperties::Color::Background:
500 set_color(_wr.desktop(), _("Background color"), SPAttr::PAGECOLOR, SPAttr::INKSCAPE_PAGEOPACITY, color);
501 break;
502 case PageProperties::Color::Border:
503 set_color(_wr.desktop(), _("Border color"), SPAttr::BORDERCOLOR, SPAttr::BORDEROPACITY, color);
504 break;
505 }
506 _wr.setUpdating(false);
507 });
508
509 _page->signal_dimension_changed().connect([this](double const x, double const y,
510 auto const unit,
511 PageProperties::Dimension const element)
512 {
513 if (_wr.isUpdating() || !_wr.desktop()) return;
514
515 _wr.setUpdating(true);
516 switch (element) {
517 case PageProperties::Dimension::PageTemplate:
518 case PageProperties::Dimension::PageSize:
519 set_document_dimensions(_wr.desktop(), x, y, unit);
521 break;
522
523 case PageProperties::Dimension::ViewboxSize:
525 break;
526
527 case PageProperties::Dimension::ViewboxPosition:
528 set_viewbox_pos(_wr.desktop(), x, y);
529 break;
530
531 case PageProperties::Dimension::ScaleContent:
533 case PageProperties::Dimension::Scale:
534 set_document_scale(_wr.desktop(), x); // only uniform scale; there's no 'y' in the dialog
535 break;
536 }
537 _wr.setUpdating(false);
538 });
539
540 _page->signal_check_toggled().connect([this](bool const checked, PageProperties::Check const element){
541 if (_wr.isUpdating() || !_wr.desktop()) return;
542
543 _wr.setUpdating(true);
544 switch (element) {
545 case PageProperties::Check::Checkerboard:
546 set_namedview_bool(_wr.desktop(), _("Toggle checkerboard"), SPAttr::INKSCAPE_DESK_CHECKERBOARD, checked);
547 break;
548 case PageProperties::Check::Border:
549 set_namedview_bool(_wr.desktop(), _("Toggle page border"), SPAttr::SHOWBORDER, checked);
550 break;
551 case PageProperties::Check::BorderOnTop:
552 set_namedview_bool(_wr.desktop(), _("Toggle border on top"), SPAttr::BORDERLAYER, checked);
553 break;
554 case PageProperties::Check::Shadow:
555 set_namedview_bool(_wr.desktop(), _("Toggle page shadow"), SPAttr::SHOWPAGESHADOW, checked);
556 break;
557 case PageProperties::Check::AntiAlias:
558 set_namedview_bool(_wr.desktop(), _("Toggle anti-aliasing"), SPAttr::INKSCAPE_ANTIALIAS_RENDERING, checked);
559 break;
560 case PageProperties::Check::ClipToPage:
561 set_namedview_bool(_wr.desktop(), _("Toggle clip to page mode"), SPAttr::INKSCAPE_CLIP_TO_PAGE_RENDERING, checked);
562 break;
563 case PageProperties::Check::PageLabelStyle:
564 set_namedview_bool(_wr.desktop(), _("Toggle page label style"), SPAttr::PAGELABELSTYLE, checked);
565 break;
566 case PageProperties::Check::YAxisPointsDown:
567 set_namedview_bool(_wr.desktop(), _("Toggle system coordinate Y axis orientation"), SPAttr::INKSCAPE_Y_AXIS_DOWN, checked);
568 break;
569 case PageProperties::Check::OriginCurrentPage:
570 set_namedview_bool(_wr.desktop(), _("Toggle system coordinate origin correction"), SPAttr::INKSCAPE_ORIGIN_CORRECTION, checked);
571 break;
572 }
573 _wr.setUpdating(false);
574 });
575
576 _page->signal_unit_changed().connect([this](Inkscape::Util::Unit const * const unit, PageProperties::Units const element){
577 if (_wr.isUpdating() || !_wr.desktop()) return;
578
579 if (element == PageProperties::Units::Display) {
580 // display only units
582 }
583 else if (element == PageProperties::Units::Document) {
584 // not used, fired with page size
585 }
586 });
587
588 _page->signal_resize_to_fit().connect([this]{
589 if (_wr.isUpdating() || !_wr.desktop()) return;
590
591 if (auto document = getDocument()) {
592 auto &page_manager = document->getPageManager();
593 page_manager.selectPage(0);
594 // fit page to selection or content, if there's no selection
595 page_manager.fitToSelection(_wr.desktop()->getSelection());
596 DocumentUndo::done(document, _("Resize page to fit"), INKSCAPE_ICON("tool-pages"));
598 }
599 });
600}
601
603{
604 auto const label_gui = Gtk::make_managed<Gtk::Label>();
605 label_gui->set_markup (_("<b>Guides</b>"));
606
607 _rcp_gui.set_margin_start(0);
608 _rcp_hgui.set_margin_start(0);
609 _rcp_gui.set_hexpand();
610 _rcp_hgui.set_hexpand();
611 _rcb_sgui.set_hexpand();
612 auto const inner = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, 4);
613 inner->append(_rcb_sgui);
614 inner->append(_rcb_lgui);
615 inner->append(_rcp_gui);
616 inner->append(_rcp_hgui);
617 auto const spacer = Gtk::make_managed<Gtk::Label>();
618 Gtk::Widget *const widget_array[] =
619 {
620 label_gui, nullptr,
621 inner, spacer,
622 nullptr, nullptr,
623 nullptr, &_create_guides_btn,
624 nullptr, &_delete_guides_btn
625 };
626 attach_all(_page_guides->table(), widget_array, G_N_ELEMENTS(widget_array));
627 inner->set_hexpand(false);
628
629 _create_guides_btn.set_action_name("doc.create-guides-around-page");
630 _delete_guides_btn.set_action_name("doc.delete-all-guides");
631}
632
635 // scanning can be expensive; avoid if possible
636 if (!_AvailableProfilesListStore->children().empty()) return;
637
638 _AvailableProfilesListStore->clear(); // Clear any existing items in the combo box
639
640 // Iterate through the list of profiles and add the name to the combo box.
641 bool home = true; // initial value doesn't matter, it's just to avoid a compiler warning
642 bool first = true;
643 auto &cms_system = Inkscape::Colors::CMS::System::get();
644 cms_system.refreshProfiles();
645 for (auto const &profile: cms_system.getProfiles()) {
646 Gtk::TreeModel::Row row;
647
648 // add a separator between profiles from the user's home directory and system profiles
649 if (!first && profile->inHome() != home)
650 {
651 row = *(_AvailableProfilesListStore->append());
652 row[_AvailableProfilesListColumns.fileColumn] = "<separator>";
653 row[_AvailableProfilesListColumns.nameColumn] = "<separator>";
655 }
656 home = profile->inHome();
657 first = false;
658
659 row = *(_AvailableProfilesListStore->append());
663 }
664}
665
679void sanitizeName(std::string& str) {
680 if (str.empty()) return;
681
682 auto val = str.at(0);
683 if ((val < 'A' || val > 'Z') && (val < 'a' || val > 'z') && val != '_' && val != ':') {
684 str.insert(0, "_");
685 }
686 for (std::size_t i = 1; i < str.size(); i++) {
687 auto val = str.at(i);
688 if ((val < 'A' || val > 'Z') && (val < 'a' || val > 'z') && (val < '0' || val > '9') &&
689 val != '_' && val != ':' && val != '-' && val != '.') {
690 if (str.at(i - 1) == '-') {
691 str.erase(i, 1);
692 i--;
693 } else {
694 str.replace(i, 1, "-");
695 }
696 }
697 }
698 if (str.at(str.size() - 1) == '-') {
699 str.pop_back();
700 }
701}
702
705{
706 //store this profile in the SVG document (create <color-profile> element in the XML)
707 if (auto document = getDocument()){
708 // Find the index of the currently-selected row in the color profiles combobox
709 Gtk::TreeModel::iterator iter = _AvailableProfilesList.get_active();
710 if (!iter)
711 return;
712
713 // Read the filename and description from the list of available profiles
714 Glib::ustring file = (*iter)[_AvailableProfilesListColumns.fileColumn];
715 Glib::ustring name = (*iter)[_AvailableProfilesListColumns.nameColumn];
716
718 // inform the document, so we can undo
719 DocumentUndo::done(document, _("Link Color Profile"), "");
720
722 }
723}
724
725template <typename From, typename To>
726struct static_caster { To * operator () (From * value) const { return static_cast<To *>(value); } };
727
729{
731 if (auto document = getDocument()) {
732 std::vector<SPObject *> current = document->getResourceList( "iccprofile" );
733
734 std::set<Inkscape::ColorProfile *> _current;
735 std::transform(current.begin(),
736 current.end(),
737 std::inserter(_current, _current.begin()),
738 static_caster<SPObject, Inkscape::ColorProfile>());
739
740 for (auto const &profile: _current) {
741 Gtk::TreeModel::Row row = *(_LinkedProfilesListStore->append());
743 }
744 }
745}
746
748{
749 Glib::RefPtr<Gtk::TreeSelection> sel = _LinkedProfilesList.get_selection();
750 if (sel) {
751 _unlink_btn.set_sensitive(sel->count_selected_rows () > 0);
752 }
753}
754
756 Glib::ustring name;
757 if(_LinkedProfilesList.get_selection()) {
758 Gtk::TreeModel::iterator i = _LinkedProfilesList.get_selection()->get_selected();
759
760 if(i){
762 } else {
763 return;
764 }
765 }
766 if (auto document = getDocument()) {
767 if (auto colorprofile = document->getDocumentCMS().getColorProfileForSpace(name)) {
768 colorprofile->deleteObject(true, false);
769 DocumentUndo::done(document, _("Remove linked color profile"), "");
770 }
771 }
772
775}
776
777bool DocumentProperties::_AvailableProfilesList_separator(Glib::RefPtr<Gtk::TreeModel> const &model,
778 Gtk::TreeModel::const_iterator const &iter)
779{
780 bool separator = (*iter)[_AvailableProfilesListColumns.separatorColumn];
781 return separator;
782}
783
785{
786 Gtk::Label *label_link= Gtk::make_managed<Gtk::Label>("", Gtk::Align::START);
787 label_link->set_markup (_("<b>Linked Color Profiles:</b>"));
788 auto const label_avail = Gtk::make_managed<Gtk::Label>("", Gtk::Align::START);
789 label_avail->set_markup (_("<b>Available Color Profiles:</b>"));
790
791 _unlink_btn.set_tooltip_text(_("Unlink Profile"));
792 docprops_style_button(_unlink_btn, INKSCAPE_ICON("list-remove"));
793
794 int row = 0;
795
796 label_link->set_hexpand();
797 label_link->set_halign(Gtk::Align::START);
798 label_link->set_valign(Gtk::Align::CENTER);
799 _page_cms->table().attach(*label_link, 0, row, 3, 1);
800
801 row++;
802
803 _LinkedProfilesListScroller.set_hexpand();
804 _LinkedProfilesListScroller.set_valign(Gtk::Align::CENTER);
805 _page_cms->table().attach(_LinkedProfilesListScroller, 0, row, 3, 1);
806
807 row++;
808
809 auto const spacer = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
810 spacer->set_size_request(SPACE_SIZE_X, SPACE_SIZE_Y);
811
812 spacer->set_hexpand();
813 spacer->set_valign(Gtk::Align::CENTER);
814 _page_cms->table().attach(*spacer, 0, row, 3, 1);
815
816 row++;
817
818 label_avail->set_hexpand();
819 label_avail->set_halign(Gtk::Align::START);
820 label_avail->set_valign(Gtk::Align::CENTER);
821 _page_cms->table().attach(*label_avail, 0, row, 3, 1);
822
823 row++;
824
825 _AvailableProfilesList.set_hexpand();
826 _AvailableProfilesList.set_valign(Gtk::Align::CENTER);
827 _page_cms->table().attach(_AvailableProfilesList, 0, row, 1, 1);
828
829 _unlink_btn.set_halign(Gtk::Align::CENTER);
830 _unlink_btn.set_valign(Gtk::Align::CENTER);
831 _page_cms->table().attach(_unlink_btn, 2, row, 1, 1);
832
833 // Set up the Available Profiles combo box
837 _AvailableProfilesList.set_row_separator_func(sigc::mem_fun(*this, &DocumentProperties::_AvailableProfilesList_separator));
838 _AvailableProfilesList.signal_changed().connect( sigc::mem_fun(*this, &DocumentProperties::linkSelectedProfile) );
839
840 //# Set up the Linked Profiles combo box
843 _LinkedProfilesList.append_column(_("Profile Name"), _LinkedProfilesListColumns.nameColumn);
844// _LinkedProfilesList.append_column(_("Color Preview"), _LinkedProfilesListColumns.previewColumn);
845 _LinkedProfilesList.set_headers_visible(false);
846// TODO restore? _LinkedProfilesList.set_fixed_height_mode(true);
847
849
851 _LinkedProfilesListScroller.set_has_frame(true);
852 _LinkedProfilesListScroller.set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::ALWAYS);
853 _LinkedProfilesListScroller.set_size_request(-1, 90);
854
855 _unlink_btn.signal_clicked().connect(sigc::mem_fun(*this, &DocumentProperties::removeSelectedProfile));
856
857 _LinkedProfilesList.get_selection()->signal_changed().connect( sigc::mem_fun(*this, &DocumentProperties::onColorProfileSelectRow) );
858
860}
861
863{
864 _page_scripting->table().attach(_scripting_notebook, 0, 0, 1, 1);
865
866 _scripting_notebook.append_page(*_page_external_scripts, _("External scripts"));
867 _scripting_notebook.append_page(*_page_embedded_scripts, _("Embedded scripts"));
868
869 //# External scripts tab
870 Gtk::Label *label_external= Gtk::make_managed<Gtk::Label>("", Gtk::Align::START);
871 label_external->set_markup (_("<b>External script files:</b>"));
872
873 _external_add_btn.set_tooltip_text(_("Add the current file name or browse for a file"));
874 docprops_style_button(_external_add_btn, INKSCAPE_ICON("list-add"));
875
876 _external_remove_btn.set_tooltip_text(_("Remove"));
877 docprops_style_button(_external_remove_btn, INKSCAPE_ICON("list-remove"));
878
879 int row = 0;
880
881 label_external->set_hexpand();
882 label_external->set_halign(Gtk::Align::START);
883 label_external->set_valign(Gtk::Align::CENTER);
884 _page_external_scripts->table().attach(*label_external, 0, row, 3, 1);
885
886 row++;
887
888 _ExternalScriptsListScroller.set_hexpand();
889 _ExternalScriptsListScroller.set_valign(Gtk::Align::CENTER);
891
892 row++;
893
894 auto const spacer_external = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
895 spacer_external->set_size_request(SPACE_SIZE_X, SPACE_SIZE_Y);
896
897 spacer_external->set_hexpand();
898 spacer_external->set_valign(Gtk::Align::CENTER);
899 _page_external_scripts->table().attach(*spacer_external, 0, row, 3, 1);
900
901 row++;
902
903 _script_entry.set_hexpand();
904 _script_entry.set_valign(Gtk::Align::CENTER);
905 _page_external_scripts->table().attach(_script_entry, 0, row, 1, 1);
906
907 _external_add_btn.set_halign(Gtk::Align::CENTER);
908 _external_add_btn.set_valign(Gtk::Align::CENTER);
909 _external_add_btn.set_margin_start(2);
910 _external_add_btn.set_margin_end(2);
911
912 _page_external_scripts->table().attach(_external_add_btn, 1, row, 1, 1);
913
914 _external_remove_btn.set_halign(Gtk::Align::CENTER);
915 _external_remove_btn.set_valign(Gtk::Align::CENTER);
916 _page_external_scripts->table().attach(_external_remove_btn, 2, row, 1, 1);
917
918 //# Set up the External Scripts box
922 _ExternalScriptsList.set_headers_visible(true);
923// TODO restore? _ExternalScriptsList.set_fixed_height_mode(true);
924
925 //# Embedded scripts tab
926 Gtk::Label *label_embedded= Gtk::make_managed<Gtk::Label>("", Gtk::Align::START);
927 label_embedded->set_markup (_("<b>Embedded script files:</b>"));
928
929 _embed_new_btn.set_tooltip_text(_("New"));
930 docprops_style_button(_embed_new_btn, INKSCAPE_ICON("list-add"));
931
932 _embed_remove_btn.set_tooltip_text(_("Remove"));
933 docprops_style_button(_embed_remove_btn, INKSCAPE_ICON("list-remove"));
934
937 _embed_button_box.set_halign(Gtk::Align::END);
938
939 row = 0;
940
941 label_embedded->set_hexpand();
942 label_embedded->set_halign(Gtk::Align::START);
943 label_embedded->set_valign(Gtk::Align::CENTER);
944 _page_embedded_scripts->table().attach(*label_embedded, 0, row, 3, 1);
945
946 row++;
947
948 _EmbeddedScriptsListScroller.set_hexpand();
949 _EmbeddedScriptsListScroller.set_valign(Gtk::Align::CENTER);
951
952 row++;
953
954 _embed_button_box.set_hexpand();
955 _embed_button_box.set_valign(Gtk::Align::CENTER);
956 _page_embedded_scripts->table().attach(_embed_button_box, 0, row, 1, 1);
957
958 row++;
959
960 auto const spacer_embedded = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
961 spacer_embedded->set_size_request(SPACE_SIZE_X, SPACE_SIZE_Y);
962 spacer_embedded->set_hexpand();
963 spacer_embedded->set_valign(Gtk::Align::CENTER);
964 _page_embedded_scripts->table().attach(*spacer_embedded, 0, row, 3, 1);
965
966 row++;
967
968 //# Set up the Embedded Scripts box
971 _EmbeddedScriptsList.append_column(_("Script ID"), _EmbeddedScriptsListColumns.idColumn);
972 _EmbeddedScriptsList.set_headers_visible(true);
973// TODO restore? _EmbeddedScriptsList.set_fixed_height_mode(true);
974
975 //# Set up the Embedded Scripts content box
976 Gtk::Label *label_embedded_content= Gtk::make_managed<Gtk::Label>("", Gtk::Align::START);
977 label_embedded_content->set_markup (_("<b>Content:</b>"));
978
979 label_embedded_content->set_hexpand();
980 label_embedded_content->set_halign(Gtk::Align::START);
981 label_embedded_content->set_valign(Gtk::Align::CENTER);
982 _page_embedded_scripts->table().attach(*label_embedded_content, 0, row, 3, 1);
983
984 row++;
985
986 _EmbeddedContentScroller.set_hexpand();
987 _EmbeddedContentScroller.set_valign(Gtk::Align::CENTER);
989
991 _EmbeddedContentScroller.set_has_frame(true);
992 _EmbeddedContentScroller.set_policy(Gtk::PolicyType::AUTOMATIC, Gtk::PolicyType::AUTOMATIC);
993 _EmbeddedContentScroller.set_size_request(-1, 140);
994
995 _EmbeddedScriptsList.signal_cursor_changed().connect(sigc::mem_fun(*this, &DocumentProperties::changeEmbeddedScript));
996 _EmbeddedScriptsList.get_selection()->signal_changed().connect( sigc::mem_fun(*this, &DocumentProperties::onEmbeddedScriptSelectRow) );
997
998 _ExternalScriptsList.get_selection()->signal_changed().connect( sigc::mem_fun(*this, &DocumentProperties::onExternalScriptSelectRow) );
999
1000 _EmbeddedContent.get_buffer()->signal_changed().connect(sigc::mem_fun(*this, &DocumentProperties::editEmbeddedScript));
1001
1003
1005 _ExternalScriptsListScroller.set_has_frame(true);
1006 _ExternalScriptsListScroller.set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::ALWAYS);
1007 _ExternalScriptsListScroller.set_size_request(-1, 90);
1008
1009 _external_add_btn.signal_clicked().connect(sigc::mem_fun(*this, &DocumentProperties::addExternalScript));
1010
1012 _EmbeddedScriptsListScroller.set_has_frame(true);
1013 _EmbeddedScriptsListScroller.set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::ALWAYS);
1014 _EmbeddedScriptsListScroller.set_size_request(-1, 90);
1015
1016 _embed_new_btn.signal_clicked().connect(sigc::mem_fun(*this, &DocumentProperties::addEmbeddedScript));
1017
1018 _external_remove_btn.signal_clicked().connect(sigc::mem_fun(*this, &DocumentProperties::removeExternalScript));
1019 _embed_remove_btn.signal_clicked().connect(sigc::mem_fun(*this, &DocumentProperties::removeEmbeddedScript));
1020
1023
1024 //TODO: review this observers code:
1025 if (auto document = getDocument()) {
1026 std::vector<SPObject *> current = document->getResourceList( "script" );
1027 if (! current.empty()) {
1028 _scripts_observer.set((*(current.begin()))->parent);
1029 }
1033 }
1034}
1035
1037{
1039
1040 auto const label = Gtk::make_managed<Gtk::Label>();
1041 label->set_markup (_("<b>Dublin Core Entities</b>"));
1042 label->set_halign(Gtk::Align::START);
1043 label->set_valign(Gtk::Align::CENTER);
1044 _page_metadata1->table().attach (*label, 0,0,2,1);
1045
1046 /* add generic metadata entry areas */
1047 int row = 1;
1048 for (auto entity = rdf_work_entities; entity && entity->name; ++entity, ++row) {
1049 if (entity->editable == RDF_EDIT_GENERIC) {
1050 auto w = std::unique_ptr<EntityEntry>{EntityEntry::create(entity, _wr)};
1051
1052 w->_label.set_halign(Gtk::Align::START);
1053 w->_label.set_valign(Gtk::Align::CENTER);
1054 _page_metadata1->table().attach(w->_label, 0, row, 1, 1);
1055
1056 w->_packable->set_hexpand();
1057 w->_packable->set_valign(Gtk::Align::CENTER);
1058 if (streq(entity->name, "description")) {
1059 // expand description edit box if there is space
1060 w->_packable->set_valign(Gtk::Align::FILL);
1061 w->_packable->set_vexpand();
1062 }
1063 _page_metadata1->table().attach(*w->_packable, 1, row, 1, 1);
1064
1065 _rdflist.push_back(std::move(w));
1066 }
1067 }
1068
1069 auto const button_save = Gtk::make_managed<Gtk::Button>(_("_Save as default"),true);
1070 button_save->set_tooltip_text(_("Save this metadata as the default metadata"));
1071 auto const button_load = Gtk::make_managed<Gtk::Button>(_("Use _default"),true);
1072 button_load->set_tooltip_text(_("Use the previously saved default metadata here"));
1073
1074 auto const box_buttons = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 4);
1075 UI::pack_end(*box_buttons, *button_save, true, true, 6);
1076 UI::pack_end(*box_buttons, *button_load, true, true, 6);
1077 _page_metadata1->table().attach(*box_buttons, 0, row++, 2);
1078 box_buttons->set_halign(Gtk::Align::END);
1079 box_buttons->set_homogeneous();
1080
1081 button_save->signal_clicked().connect(sigc::mem_fun(*this, &DocumentProperties::save_default_metadata));
1082 button_load->signal_clicked().connect(sigc::mem_fun(*this, &DocumentProperties::load_default_metadata));
1083
1084 row = 0;
1085 auto const llabel = Gtk::make_managed<Gtk::Label>();
1086 llabel->set_markup (_("<b>License</b>"));
1087 llabel->set_halign(Gtk::Align::START);
1088 llabel->set_valign(Gtk::Align::CENTER);
1089 _page_metadata2->table().attach(*llabel, 0, row, 2, 1);
1090
1091 /* add license selector pull-down and URI */
1092 ++row;
1093 _licensor.init (_wr);
1094
1095 _licensor.set_hexpand();
1096 _licensor.set_valign(Gtk::Align::CENTER);
1097 _page_metadata2->table().attach(_licensor, 0, row, 2, 1);
1098 _page_metadata2->table().set_valign(Gtk::Align::START);
1099}
1100
1102
1103 auto document = getDocument();
1104 if (!document)
1105 return;
1106
1107 if (_script_entry.get_text().empty() ) {
1108 // Click Add button with no filename, show a Browse dialog
1110 }
1111
1112 if (!_script_entry.get_text().empty()) {
1114 Inkscape::XML::Node *scriptRepr = xml_doc->createElement("svg:script");
1115 scriptRepr->setAttributeOrRemoveIfEmpty("xlink:href", _script_entry.get_text());
1116 _script_entry.set_text("");
1117
1118 xml_doc->root()->addChild(scriptRepr, nullptr);
1119
1120 // inform the document, so we can undo
1121 DocumentUndo::done(document, _("Add external script..."), "");
1122
1124 }
1125}
1126
1128
1129 // Get the current directory for finding files.
1130 static std::string open_path;
1132
1133 // Create a dialog.
1134 static std::vector<std::pair<Glib::ustring, Glib::ustring>> const filters {
1135 {_("JavaScript Files"), "*.js"}
1136 };
1137
1138 auto window = getDesktop()->getInkscapeWindow();
1139 auto file = choose_file_open(_("Select a script to load"),
1140 window,
1141 filters,
1142 open_path);
1143
1144 if (!file) {
1145 return; // Cancel
1146 }
1147
1149 prefs->setString(_prefs_path, open_path);
1150
1151 _script_entry.set_text(file->get_parse_name());
1152}
1153
1155 if(auto document = getDocument()) {
1157 Inkscape::XML::Node *scriptRepr = xml_doc->createElement("svg:script");
1158
1159 xml_doc->root()->addChild(scriptRepr, nullptr);
1160
1161 // inform the document, so we can undo
1162 DocumentUndo::done(document, _("Add embedded script..."), "");
1164 }
1165}
1166
1168 Glib::ustring name;
1169 if(_ExternalScriptsList.get_selection()) {
1170 Gtk::TreeModel::iterator i = _ExternalScriptsList.get_selection()->get_selected();
1171
1172 if(i){
1174 } else {
1175 return;
1176 }
1177 }
1178
1179 auto document = getDocument();
1180 if (!document)
1181 return;
1182 std::vector<SPObject *> current = document->getResourceList( "script" );
1183 for (auto obj : current) {
1184 if (obj) {
1185 auto script = cast<SPScript>(obj);
1186 if (script && (name == script->xlinkhref)) {
1187
1188 //XML Tree being used directly here while it shouldn't be.
1189 Inkscape::XML::Node *repr = obj->getRepr();
1190 if (repr){
1191 sp_repr_unparent(repr);
1192
1193 // inform the document, so we can undo
1194 DocumentUndo::done(document, _("Remove external script"), "");
1195 }
1196 }
1197 }
1198 }
1199
1201}
1202
1204 Glib::ustring id;
1205 if(_EmbeddedScriptsList.get_selection()) {
1206 Gtk::TreeModel::iterator i = _EmbeddedScriptsList.get_selection()->get_selected();
1207
1208 if(i){
1210 } else {
1211 return;
1212 }
1213 }
1214
1215 if (auto document = getDocument()) {
1216 if (auto obj = document->getObjectById(id)) {
1217 //XML Tree being used directly here while it shouldn't be.
1218 if (auto repr = obj->getRepr()){
1219 sp_repr_unparent(repr);
1220
1221 // inform the document, so we can undo
1222 DocumentUndo::done(document, _("Remove embedded script"), "");
1223 }
1224 }
1225 }
1226
1228}
1229
1231{
1232 Glib::RefPtr<Gtk::TreeSelection> sel = _ExternalScriptsList.get_selection();
1233 if (sel) {
1234 _external_remove_btn.set_sensitive(sel->count_selected_rows () > 0);
1235 }
1236}
1237
1239{
1240 Glib::RefPtr<Gtk::TreeSelection> sel = _EmbeddedScriptsList.get_selection();
1241 if (sel) {
1242 _embed_remove_btn.set_sensitive(sel->count_selected_rows () > 0);
1243 }
1244}
1245
1247 Glib::ustring id;
1248 if(_EmbeddedScriptsList.get_selection()) {
1249 Gtk::TreeModel::iterator i = _EmbeddedScriptsList.get_selection()->get_selected();
1250
1251 if(i){
1253 } else {
1254 return;
1255 }
1256 }
1257
1258 auto document = getDocument();
1259 if (!document)
1260 return;
1261
1262 bool voidscript=true;
1263 std::vector<SPObject *> current = document->getResourceList( "script" );
1264 for (auto obj : current) {
1265 if (id == obj->getId()){
1266 int count = (int) obj->children.size();
1267
1268 if (count>1)
1269 g_warning("TODO: Found a script element with multiple (%d) child nodes! We must implement support for that!", count);
1270
1271 //XML Tree being used directly here while it shouldn't be.
1272 SPObject* child = obj->firstChild();
1273 //TODO: shouldn't we get all children instead of simply the first child?
1274
1275 if (child && child->getRepr()){
1276 if (auto const content = child->getRepr()->content()) {
1277 voidscript = false;
1278 _EmbeddedContent.get_buffer()->set_text(content);
1279 }
1280 }
1281 }
1282 }
1283
1284 if (voidscript)
1285 _EmbeddedContent.get_buffer()->set_text("");
1286}
1287
1289 Glib::ustring id;
1290 if(_EmbeddedScriptsList.get_selection()) {
1291 Gtk::TreeModel::iterator i = _EmbeddedScriptsList.get_selection()->get_selected();
1292
1293 if(i){
1295 } else {
1296 return;
1297 }
1298 }
1299
1300 auto document = getDocument();
1301 if (!document)
1302 return;
1303
1304 for (auto obj : document->getResourceList("script")) {
1305 if (id == obj->getId()) {
1306 //XML Tree being used directly here while it shouldn't be.
1307 Inkscape::XML::Node *repr = obj->getRepr();
1308 if (repr){
1309 auto tmp = obj->children | boost::adaptors::transformed([](SPObject& o) { return &o; });
1310 std::vector<SPObject*> vec(tmp.begin(), tmp.end());
1311 for (auto const child: vec) {
1312 child->deleteObject();
1313 }
1314 obj->appendChildRepr(document->getReprDoc()->createTextNode(_EmbeddedContent.get_buffer()->get_text().c_str()));
1315
1316 //TODO repr->set_content(_EmbeddedContent.get_buffer()->get_text());
1317
1318 // inform the document, so we can undo
1319 DocumentUndo::done(document, _("Edit embedded script"), "");
1320 }
1321 }
1322 }
1323}
1324
1328 auto document = getDocument();
1329 if (!document)
1330 return;
1331
1332 std::vector<SPObject *> current = getDocument()->getResourceList( "script" );
1333 if (!current.empty()) {
1334 SPObject *obj = *(current.begin());
1335 g_assert(obj != nullptr);
1337 }
1338 for (auto obj : current) {
1339 auto script = cast<SPScript>(obj);
1340 g_assert(script != nullptr);
1341 if (script->xlinkhref)
1342 {
1343 Gtk::TreeModel::Row row = *(_ExternalScriptsListStore->append());
1344 row[_ExternalScriptsListColumns.filenameColumn] = script->xlinkhref;
1345 }
1346 else // Embedded scripts
1347 {
1348 Gtk::TreeModel::Row row = *(_EmbeddedScriptsListStore->append());
1349 row[_EmbeddedScriptsListColumns.idColumn] = obj->getId();
1350 }
1351 }
1352}
1353
1359{
1360 _grids_list.remove_all();
1361 for (auto w : _grids_unified_size->get_widgets()) {
1362 _grids_unified_size->remove_widget(*w);
1363 }
1364
1365 for (auto grid : getDesktop()->getNamedView()->grids) {
1366 add_grid_widget(grid);
1367 }
1368
1370}
1371
1373 _no_grids.set_visible(_grids_list.get_first_child() == nullptr);
1374}
1375
1377{
1378 auto const widget = Gtk::make_managed<Inkscape::UI::Widget::GridWidget>(grid);
1379 _grids_list.append(*widget);
1380 _grids_unified_size->add_widget(*widget);
1381 // get rid of row highlight - they are not selectable (we just need to change the last one, but there's no API for that)
1382 int index = 0;
1383 for (auto row = _grids_list.get_row_at_index(index); row; row = _grids_list.get_row_at_index(++index)) {
1384 row->property_activatable() = false;
1385 }
1386
1388}
1389
1391{
1392 // The SPObject is already gone, so we're working from the xml node directly.
1393 int index = 0;
1394 for (auto row = _grids_list.get_row_at_index(index); row; row = _grids_list.get_row_at_index(++index)) {
1395 if (auto widget = dynamic_cast<Inkscape::UI::Widget::GridWidget*>(row->get_child())) {
1396 if (&node == widget->getGridRepr()) {
1397 _grids_unified_size->remove_widget(*widget);
1398 _grids_list.remove(*row);
1399 break;
1400 }
1401 }
1402 }
1403
1405}
1406
1411{
1414
1415 _grids_hbox_crea.set_spacing(5);
1416 _grids_hbox_crea.set_margin(8);
1417 _grids_hbox_crea.set_halign(Gtk::Align::CENTER);
1418
1419 {
1420 auto btn = Gtk::make_managed<Gtk::Button>();
1421 btn->set_size_request(120); // make it easier to hit
1422 auto hbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL, 5);
1423 hbox->set_halign(Gtk::Align::CENTER);
1424 hbox->set_valign(Gtk::Align::CENTER);
1425
1426 auto icon_image = Gtk::make_managed<Gtk::Image>();
1427 icon_image->set_from_icon_name("plus");
1428 icon_image->set_icon_size(Gtk::IconSize::NORMAL);
1429 hbox->append(*icon_image);
1430
1431 auto btn_label = Gtk::make_managed<Gtk::Label>(_("New Grid"));
1432 btn_label->set_valign(Gtk::Align::CENTER);
1433 hbox->append(*btn_label);
1434
1435 btn->set_child(*hbox);
1436
1437 UI::pack_start(_grids_hbox_crea, *btn, false, true);
1438 btn->signal_clicked().connect([this]{ onNewGrid(GridType::RECTANGULAR); });
1439 }
1440
1442 _no_grids.set_text(_("There are no grids defined."));
1443 _no_grids.set_halign(Gtk::Align::CENTER);
1444 _no_grids.set_hexpand();
1445 _no_grids.set_margin_top(40);
1446 _no_grids.add_css_class("informational-text");
1447 UI::pack_start(_grids_vbox, _no_grids, false, false);
1449 _grids_wnd.set_child(_grids_list);
1450 _grids_list.set_show_separators();
1451 _grids_list.set_selection_mode(Gtk::SelectionMode::NONE);
1452 _grids_wnd.set_policy(Gtk::PolicyType::NEVER, Gtk::PolicyType::AUTOMATIC);
1453 _grids_wnd.set_has_frame(false);
1454}
1455
1457 if (!desktop) return;
1458
1459 auto* document = desktop->getDocument();
1460 if (!document) return;
1461
1464 if (root->viewBox_set) {
1465 auto& vb = root->viewBox;
1466 _page->set_dimension(PageProperties::Dimension::ViewboxPosition, vb.min()[Geom::X], vb.min()[Geom::Y]);
1467 _page->set_dimension(PageProperties::Dimension::ViewboxSize, vb.width(), vb.height());
1468 }
1469
1471}
1472
1477{
1478 auto desktop = getDesktop();
1479 auto document = getDocument();
1480 if (_wr.isUpdating() || !document) return;
1481
1482 auto nv = desktop->getNamedView();
1483 auto &page_manager = document->getPageManager();
1484
1485 _wr.setUpdating(true);
1486
1488
1489 double doc_w = root->width.value;
1490 Glib::ustring doc_w_unit = Util::UnitTable::get().getUnit(root->width.unit)->abbr;
1491 bool percent = doc_w_unit == "%";
1492 if (doc_w_unit == "") {
1493 doc_w_unit = "px";
1494 } else if (doc_w_unit == "%" && root->viewBox_set) {
1495 doc_w_unit = "px";
1496 doc_w = root->viewBox.width();
1497 }
1498 double doc_h = root->height.value;
1499 Glib::ustring doc_h_unit = Util::UnitTable::get().getUnit(root->height.unit)->abbr;
1500 percent = percent || doc_h_unit == "%";
1501 if (doc_h_unit == "") {
1502 doc_h_unit = "px";
1503 } else if (doc_h_unit == "%" && root->viewBox_set) {
1504 doc_h_unit = "px";
1505 doc_h = root->viewBox.height();
1506 }
1508 // dialog's behavior is not entirely correct when document sizes are expressed in '%', so put up a disclaimer
1509 _page->set_check(PageProperties::Check::UnsupportedSize, percent);
1510
1511 _page->set_dimension(PageProperties::Dimension::PageSize, doc_w, doc_h);
1512 _page->set_unit(PageProperties::Units::Document, doc_w_unit);
1513
1516
1517 if (nv->display_units) {
1518 _page->set_unit(PageProperties::Units::Display, nv->display_units->abbr);
1519 }
1520 _page->set_check(PageProperties::Check::Checkerboard, nv->desk_checkerboard);
1521 _page->set_color(PageProperties::Color::Desk, nv->getDeskColor());
1522 _page->set_color(PageProperties::Color::Background, page_manager.getBackgroundColor());
1523 _page->set_check(PageProperties::Check::Border, page_manager.border_show);
1524 _page->set_check(PageProperties::Check::BorderOnTop, page_manager.border_on_top);
1525 _page->set_color(PageProperties::Color::Border, page_manager.getBorderColor());
1526 _page->set_check(PageProperties::Check::Shadow, page_manager.shadow_show);
1527 _page->set_check(PageProperties::Check::PageLabelStyle, page_manager.label_style != "default");
1528 _page->set_check(PageProperties::Check::AntiAlias, nv->antialias_rendering);
1529 _page->set_check(PageProperties::Check::ClipToPage, nv->clip_to_page);
1530 _page->set_check(PageProperties::Check::YAxisPointsDown, nv->is_y_axis_down());
1531 _page->set_check(PageProperties::Check::OriginCurrentPage, nv->get_origin_follows_page());
1532
1533 //-----------------------------------------------------------guide page
1534
1535 _rcb_sgui.setActive (nv->getShowGuides());
1536 _rcb_lgui.setActive (nv->getLockGuides());
1537 _rcp_gui.setColor(nv->getGuideColor());
1538 _rcp_hgui.setColor(nv->getGuideHiColor());
1539
1540 //-----------------------------------------------------------meta pages
1541 // update the RDF entities; note that this may modify document, maybe doc-undo should be called?
1542 if (auto document = getDocument()) {
1543 for (auto const &it : _rdflist) {
1544 bool read_only = false;
1545 it->update(document, read_only);
1546 }
1548 }
1549 _wr.setUpdating (false);
1550}
1551
1552//--------------------------------------------------------------------
1553
1555{
1556 if (id == Gtk::ResponseType::DELETE_EVENT || id == Gtk::ResponseType::CLOSE)
1557 {
1560 }
1561
1562 if (id == Gtk::ResponseType::CLOSE)
1563 set_visible(false);
1564}
1565
1567{
1568 /* Get the data RDF entities data from preferences*/
1569 for (auto const &it : _rdflist) {
1570 it->load_from_preferences ();
1571 }
1572}
1573
1575{
1576 /* Save these RDF entities to preferences*/
1577 if (auto document = getDocument()) {
1578 for (auto const &it : _rdflist) {
1579 it->save_to_preferences(document);
1580 }
1581 }
1582}
1583
1585{
1586 disconnect();
1587 if (!node) return;
1588
1589 _node = node;
1590 _node->addObserver(*this);
1591}
1592
1594 if (_node) {
1595 _node->removeObserver(*this);
1596 _node = nullptr;
1597 }
1598}
1599
1601{
1602 if (auto grid = cast<SPGrid>(_dialog->getDocument()->getObjectByRepr(&child))) {
1603 _dialog->add_grid_widget(grid);
1604 }
1605}
1606
1611
1616
1635
1640
1641/*########################################################################
1642# BUTTON CLICK HANDLERS (callbacks)
1643########################################################################*/
1644
1646{
1647 auto desktop = getDesktop();
1648 auto document = getDocument();
1649 if (!desktop || !document) return;
1650
1651 auto repr = desktop->getNamedView()->getRepr();
1652 SPGrid::create_new(document, repr, grid_type);
1653 // flip global switch, so snapping to grid works
1655
1656 DocumentUndo::done(document, _("Create new grid"), INKSCAPE_ICON("document-properties"));
1657
1658 // scroll to the last (newly added) grid, so we can see it; postponed till idle time, since scrolling
1659 // range is not yet updated, despite new grid UI being in place already
1660 _on_idle_scroll = Glib::signal_idle().connect([this](){
1661 if (auto adj = _grids_wnd.get_vadjustment()) {
1662 adj->set_value(adj->get_upper());
1663 }
1664 return false;
1665 });
1666}
1667
1668/* This should not effect anything in the SVG tree (other than "inkscape:document-units").
1669 This should only effect values displayed in the GUI. */
1671{
1673 // Don't execute when change is being undone
1675 return;
1676 }
1677 // Don't execute when initializing widgets
1678 if (_wr.isUpdating()) {
1679 return;
1680 }
1681
1682 auto action = document->getActionGroup()->lookup_action("set-display-unit");
1683 action->activate(doc_unit->abbr);
1684}
1685
1686} // namespace Dialog
1687
1688namespace Widget {
1689
1690static const auto grid_types = std::to_array({std::tuple
1691 {C_("Grid", "Rectangular"), GridType::RECTANGULAR, "grid-rectangular"},
1692 {C_("Grid", "Axonometric"), GridType::AXONOMETRIC, "grid-axonometric"},
1693 {C_("Grid", "Modular"), GridType::MODULAR, "grid-modular"}
1694});
1695
1696GridWidget::GridWidget(SPGrid *grid)
1697 : Gtk::Box(Gtk::Orientation::VERTICAL)
1698 , _grid(grid)
1699 , _repr(grid->getRepr())
1700{
1701 set_halign(Gtk::Align::CENTER);
1702 add_css_class("grid-row-definition");
1703
1704 Inkscape::XML::Node *repr = grid->getRepr();
1705 auto doc = grid->document;
1706
1707 constexpr int SPACE = 4;
1708 constexpr int POPUP_MARGIN = 8;
1709
1710 _wr.setUpdating(true);
1711
1712 for (auto const &[label, type, icon]: grid_types) {
1713 _grid_type.add_row(icon, label, static_cast<int>(type));
1714 }
1715 _grid_type.refilter();
1716
1717 _enabled = Gtk::make_managed<Inkscape::UI::Widget::RegisteredSwitchButton>("",
1718 _("Makes the grid available for working with on the canvas."),
1719 "enabled", _wr, false, repr, doc);
1720
1721 _snap_visible_only = Gtk::make_managed<Inkscape::UI::Widget::RegisteredCheckButton>(
1722 _("Snap to visible _grid lines only"),
1723 _("When zoomed out, not all grid lines will be displayed. Only the visible ones will be snapped to"),
1724 "snapvisiblegridlinesonly", _wr, false, repr, doc);
1725
1726 _visible = Gtk::make_managed<Inkscape::UI::Widget::RegisteredToggleButton>("",
1727 _("Determines whether the grid is displayed or not. Objects are still snapped to invisible grids."),
1728 "visible", _wr, false, repr, doc,
1729 "object-visible", "object-hidden");
1730 _visible->set_child(*Gtk::make_managed<Gtk::Image>(Gio::ThemedIcon::create("object-visible")));
1731
1732 _alignment = Gtk::make_managed<Inkscape::UI::Widget::AlignmentSelector>();
1733 _alignment->connectAlignmentClicked([this, grid](int const align) {
1734 auto dimensions = grid->document->getDimensions();
1735 dimensions[Geom::X] *= align % 3 * 0.5;
1736 dimensions[Geom::Y] *= align / 3 * 0.5;
1737 dimensions *= grid->document->doc2dt();
1738 dimensions *= grid->document->getDocumentScale().inverse();
1739 grid->setOrigin(dimensions);
1740 });
1741
1742 _dotted = Gtk::make_managed<Inkscape::UI::Widget::RegisteredCheckButton>(
1743 _("_Show dots instead of lines"), _("If set, displays dots at gridpoints instead of gridlines"),
1744 "dotted", _wr, false, repr, doc );
1745
1746 auto vbox = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL, SPACE);
1747 auto align = Gtk::make_managed<Gtk::Label>(_("Align to page:"));
1748 align->set_margin_top(8);
1749 vbox->append(*align);
1750 vbox->append(*_alignment);
1751 _align_popup->set_child(*vbox);
1752
1753 auto angle_popover = Gtk::make_managed<Gtk::Popover>();
1754 angle_popover->set_has_arrow(false);
1755 _angle_popup->set_popover(*angle_popover);
1756 _angle_popup->set_valign(Gtk::Align::FILL);
1757 // set grid angles from given width to height ratio
1758 auto angle = Gtk::make_managed<Gtk::Label>(_("Set angle from aspect ratio:"));
1759 angle->set_xalign(0);
1760 auto subgrid = Gtk::make_managed<Gtk::Grid>();
1761 subgrid->set_margin(POPUP_MARGIN);
1762 subgrid->set_row_spacing(SPACE);
1763 subgrid->set_column_spacing(SPACE);
1764 _aspect_ratio = Gtk::make_managed<Gtk::Entry>();
1765 _aspect_ratio->set_max_width_chars(9);
1766 subgrid->attach(*angle, 0, 0);
1767 subgrid->attach(*_aspect_ratio, 0, 1);
1768 auto apply = Gtk::make_managed<Gtk::Button>(_("Set"));
1769 apply->set_halign(Gtk::Align::CENTER);
1770 apply->set_size_request(100);
1771 subgrid->attach(*apply, 0, 2);
1772 // TRANSLATORS: Axonometric grid looks like a pattern of parallelograms. Their width to height proportions
1773 // can be manipulated by changing angles in the axonometric grid. This DX/DY ratio does just that.
1774 // Pressing "Set" button will calculate grid angles to produce parallelograms with requested widh to height ratio.
1775 apply->set_tooltip_text(_("Automatically calculate angles from width to height ratio\nof a single grid parallelogram"));
1776 apply->signal_clicked().connect([=, this](){
1777 try {
1778 auto const result = ExpressionEvaluator{get_text(*_aspect_ratio)}.evaluate().value;
1779 if (!std::isfinite(result) || result <= 0) return;
1780
1781 auto angle = Geom::deg_from_rad(std::atan(1.0 / result));
1782 if (angle > 0.0 && angle < 90.0) {
1783 _angle_x->setValue(angle, false);
1784 _angle_z->setValue(angle, false);
1785 }
1786 }
1787 catch (EvaluatorException& e) {
1788 // ignoring user input error for now
1789 }
1790 });
1791 angle_popover->set_child(*subgrid);
1792 angle_popover->signal_show().connect([this](){
1793 if (!_grid) return;
1794
1795 auto ax = _grid->getAngleX();
1796 auto az = _grid->getAngleZ();
1797 // try to guess ratio if angles are the same, otherwise leave ratio boxes intact
1798 if (az == ax) {
1799 auto ratio = std::tan(Geom::rad_from_deg(ax));
1800 if (ratio > 0) {
1801 _aspect_ratio->set_text(ratio > 1.0 ?
1802 Glib::ustring::format("1 : ", ratio) :
1803 Glib::ustring::format(1.0 / ratio, " : 1")
1804 );
1805 }
1806 }
1807 });
1808
1809 _units = Gtk::make_managed<RegisteredUnitMenu>(
1810 _("Grid _units:"), "units", _wr, repr, doc);
1811 _origin_x = Gtk::make_managed<RegisteredScalarUnit>(
1812 _("_Origin X:"), _("X coordinate of grid origin"), "originx",
1813 *_units, _wr, repr, doc, RSU_x);
1814 _origin_y = Gtk::make_managed<RegisteredScalarUnit>(
1815 _("O_rigin Y:"), _("Y coordinate of grid origin"), "originy",
1816 *_units, _wr, repr, doc, RSU_y);
1817 _spacing_x = Gtk::make_managed<RegisteredScalarUnit>(
1818 "-", _("Distance between horizontal grid lines"), "spacingx",
1819 *_units, _wr, repr, doc, RSU_x);
1820 _spacing_y = Gtk::make_managed<RegisteredScalarUnit>(
1821 "-", _("Distance between vertical grid lines"), "spacingy",
1822 *_units, _wr, repr, doc, RSU_y);
1823
1824 _gap_x = Gtk::make_managed<RegisteredScalarUnit>(
1825 _("Gap _X:"), _("Horizontal distance between blocks"), "gapx",
1826 *_units, _wr, repr, doc, RSU_x);
1827 _gap_y = Gtk::make_managed<RegisteredScalarUnit>(
1828 _("Gap _Y:"), _("Vertical distance between blocks"), "gapy",
1829 *_units, _wr, repr, doc, RSU_y);
1830 _margin_x = Gtk::make_managed<RegisteredScalarUnit>(
1831 _("_Margin X:"), _("Right and left margins"), "marginx",
1832 *_units, _wr, repr, doc, RSU_x);
1833 _margin_y = Gtk::make_managed<RegisteredScalarUnit>(
1834 _("M_argin Y:"), _("Top and bottom margins"), "marginy",
1835 *_units, _wr, repr, doc, RSU_y);
1836
1837 _angle_x = Gtk::make_managed<RegisteredScalar>(
1838 _("An_gle X:"), _("Angle of x-axis"), "gridanglex", _wr, repr, doc);
1839 _angle_z = Gtk::make_managed<RegisteredScalar>(
1840 _("Ang_le Z:"), _("Angle of z-axis"), "gridanglez", _wr, repr, doc);
1841 _grid_color = Gtk::make_managed<RegisteredColorPicker>(
1842 "", _("Grid color"),
1843 _("Color of the grid lines"),
1844 "empcolor", "empopacity", _wr, repr, doc);
1845 _grid_color->setCustomSetter([](Inkscape::XML::Node* node, Colors::Color color) {
1846 // major color
1847 node->setAttribute("empcolor", color.toString(false));
1848 node->setAttributeSvgDouble("empopacity", color.getOpacity());
1849
1850 // minor color at half opacity
1851 color.addOpacity(0.5);
1852 node->setAttribute("color", color.toString(false));
1853 node->setAttributeCssDouble("opacity", color.getOpacity());
1854 });
1855 _grid_color->set_spacing(0);
1856 _no_of_lines = Gtk::make_managed<RegisteredInteger>(
1857 _("Major grid line e_very:"), _("Number of lines"),
1858 "empspacing", _wr, repr, doc);
1859
1860 // All of these undo settings are the same, refactor this later if possible.
1861 _units->set_undo_parameters(_("Change grid units"), "show-grid", "grid-settings");
1862 _angle_x->set_undo_parameters(_("Change grid dimensions"), "show-grid", "grid-settings");
1863 _angle_z->set_undo_parameters(_("Change grid dimensions"), "show-grid", "grid-settings");
1864 _grid_color->set_undo_parameters(_("Change grid color"), "show-grid", "grid-settings");
1865 _no_of_lines->set_undo_parameters(_("Change grid number of lines"), "show-grid", "grid-settings");
1866 for (auto widget : {_origin_x, _origin_y, _spacing_x, _spacing_y, _gap_x, _gap_y, _margin_x, _margin_y}) {
1867 widget->set_undo_parameters(_("Change grid dimensions"), "show-grid", "grid-settings");
1868 }
1869
1870 for (auto labelled : std::to_array<Labelled*>(
1871 {_units, _origin_x, _origin_y, _spacing_x, _spacing_y, _gap_x, _gap_y, _margin_x, _margin_y,
1872 _angle_x, _angle_z, _no_of_lines})) {
1873 labelled->getLabel()->set_hexpand();
1874 }
1875
1876 _units->set_hexpand();
1877 _angle_x->set_hexpand();
1878 _angle_z->set_hexpand();
1879 _no_of_lines->set_hexpand();
1880 _no_of_lines->setWidthChars(5);
1881
1882 _origin_x->setProgrammatically = false;
1883 _origin_y->setProgrammatically = false;
1884
1885 auto main_grid = Gtk::make_managed<Gtk::Grid>();
1886 main_grid->set_column_homogeneous();
1887 main_grid->set_column_spacing(4 * SPACE);
1888
1889 auto buttons = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
1890 buttons->set_spacing(SPACE);
1891 buttons->append(*_visible);
1892 buttons->append(*_grid_color);
1893 _delete->set_child(*Gtk::make_managed<Gtk::Image>(Gio::ThemedIcon::create("edit-delete")));
1894 _delete->set_tooltip_text(_("Delete this grid"));
1895 _delete->signal_clicked().connect([this](){
1896 auto doc = getGrid()->document;
1897 getGrid()->deleteObject();
1898 DocumentUndo::done(doc, _("Remove grid"), INKSCAPE_ICON("document-properties"));
1899 });
1900 _delete->set_hexpand();
1901 _delete->set_halign(Gtk::Align::END);
1902 buttons->append(*_delete);
1903 buttons->append(*_options);
1904 _options->set_popover(*_opt_items);
1905 _options->set_icon_name("gear");
1906 auto items = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::VERTICAL);
1907 items->set_spacing(SPACE);
1908 items->set_margin(POPUP_MARGIN);
1909 items->append(*_snap_visible_only);
1910 items->append(*_dotted);
1911 _opt_items->set_child(*items);
1912 _opt_items->set_has_arrow(false);
1913
1914 _align->set_label(C_("popup-align-grid-origin", "Align"));
1915 _align->set_tooltip_text(_("Align grid origin relative to active page."));
1916 _align_popup->set_has_arrow(false);
1917 _align->set_popover(*_align_popup);
1918
1919 auto left_col = Gtk::make_managed<Gtk::Grid>();
1920 main_grid->attach(*left_col, 0, 1);
1921
1922 auto right_col = Gtk::make_managed<Gtk::Grid>();
1923 main_grid->attach(*right_col, 1, 1);
1924
1925 for (auto grid : {left_col, right_col}) {
1926 grid->set_column_spacing(SPACE);
1927 grid->set_row_spacing(SPACE);
1928 }
1929
1930 auto first_row_height = Gtk::SizeGroup::create(Gtk::SizeGroup::Mode::VERTICAL);
1931 int row = 0;
1932 auto box = Gtk::make_managed<Gtk::Box>(Gtk::Orientation::HORIZONTAL);
1933 box->set_spacing(SPACE);
1934 box->append(*_enabled);
1935 _id->set_ellipsize(Pango::EllipsizeMode::END);
1936 box->append(*_id);
1937 _grid_type.set_hexpand();
1938 _grid_type.set_halign(Gtk::Align::END);
1939 _grid_type.set_active_by_id(static_cast<int>(_grid->getType()));
1940 _grid_type.signal_changed().connect([&,this](int index){
1941 // change grid type
1942 if (index < 0) return;
1943
1944 // create grid of required type
1945 _grid->setType(std::get<1>(grid_types[index]));
1946 });
1947 _grid_type.set_tooltip_text(_("Change to a different grid type."));
1948 box->append(_grid_type);
1949 left_col->attach(*box, 0, row, 2);
1950 right_col->attach(*buttons, 0, row, 2);
1951 ++row;
1952 first_row_height->add_widget(*box);
1953 first_row_height->add_widget(*buttons);
1954 // add "separators"
1955 {
1956 auto lbox = Gtk::make_managed<Gtk::Box>();
1957 lbox->set_size_request(0, SPACE);
1958 left_col->attach(*lbox, 0, row);
1959 auto rbox = Gtk::make_managed<Gtk::Box>();
1960 rbox->set_size_request(0, SPACE);
1961 right_col->attach(*rbox, 0, row++);
1962 }
1963
1964 int first_row = row;
1965 left_col->attach(*_units, 0, row++, 2);
1966
1967 auto cur_grid = left_col;
1968 for (auto rs : std::to_array<Scalar*>({
1969 // left
1970 _spacing_x, _spacing_y, _angle_x, _angle_z, _gap_x, _gap_y,
1971 // right
1972 _origin_x, _origin_y, _margin_x, _margin_y})) {
1973 rs->setDigits(6);
1974 rs->set_hexpand();
1975 rs->setWidthChars(12);
1976 int width = 2;
1977 if (rs == _origin_x) {
1978 cur_grid = right_col;
1979 row = first_row;
1980 // attach "align" popup
1981 cur_grid->attach(*_align, 0, row++, width);
1982 _align->set_halign(Gtk::Align::END);
1983 }
1984 if (rs == _angle_x) {
1985 cur_grid->attach(*_angle_popup, 1, row, 1, 2);
1986 }
1987 if (rs == _angle_x || rs == _angle_z) {
1988 rs->setWidthChars(8);
1989 width = 1;
1990 }
1991 cur_grid->attach(*rs, 0, row++, width);
1992 }
1993
1994 left_col->attach(*_no_of_lines, 0, row++, 2);
1995
1996 _modified_signal = grid->connectModified([this, grid](SPObject const * /*obj*/, unsigned /*flags*/) {
1997 if (!_wr.isUpdating()) {
1998 _modified_signal.block();
1999 update();
2000 _modified_signal.unblock();
2001 }
2002 });
2003 update();
2004
2005 UI::pack_start(*this, *main_grid, false, false);
2006
2007 std::vector<Gtk::Widget*> widgets;
2008 for_each_descendant(*main_grid, [&](Gtk::Widget& w){
2009 if (dynamic_cast<Gtk::SpinButton*>(&w) ||
2010 dynamic_cast<Gtk::ToggleButton*>(&w) ||
2011 dynamic_cast<Gtk::MenuButton*>(&w) ||
2012 dynamic_cast<Gtk::Label*>(&w) ||
2013 dynamic_cast<LabelledColorPicker*>(&w)
2014 ) {
2015 widgets.push_back(&w);
2016 return ForEachResult::_skip;
2017 }
2018
2020 });
2021 _enabled->setSubordinateWidgets(std::move(widgets));
2022
2023 _wr.setUpdating(false);
2024}
2025
2029void GridWidget::update()
2030{
2031 _wr.setUpdating (true);
2032 auto scale = _grid->document->getDocumentScale();
2033
2034 const auto modular = _grid->getType() == GridType::MODULAR;
2035 const auto axonometric = _grid->getType() == GridType::AXONOMETRIC;
2036 const auto rectangular = _grid->getType() == GridType::RECTANGULAR;
2037
2038 _units->setUnit(_grid->getUnit()->abbr);
2039
2040 // Doc to px so unit is conserved in RegisteredScalerUnit
2041 auto origin = _grid->getOrigin() * scale;
2042 _origin_x->setValueKeepUnit(origin[Geom::X], "px");
2043 _origin_y->setValueKeepUnit(origin[Geom::Y], "px");
2044
2045 auto spacing = _grid->getSpacing() * scale;
2046 _spacing_x->setValueKeepUnit(spacing[Geom::X], "px");
2047 _spacing_y->setValueKeepUnit(spacing[Geom::Y], "px");
2048 _spacing_x->getLabel()->set_markup_with_mnemonic(modular ? _("Block _width:") : _("Spacing _X:"));
2049 _spacing_y->getLabel()->set_markup_with_mnemonic(modular ? _("Block _height:") : _("Spacing _Y:"));
2050
2051 auto show = [](Gtk::Widget* w, bool do_show){
2052 w->set_visible(do_show);
2053 };
2054
2055 show(_angle_x, axonometric);
2056 show(_angle_z, axonometric);
2057 show(_angle_popup, axonometric);
2058 if (axonometric) {
2059 _angle_x->setValue(_grid->getAngleX());
2060 _angle_z->setValue(_grid->getAngleZ());
2061 }
2062
2063 show(_gap_x, modular);
2064 show(_gap_y, modular);
2065 show(_margin_x, modular);
2066 show(_margin_y, modular);
2067 if (modular) {
2068 auto gap = _grid->get_gap() * scale;
2069 auto margin = _grid->get_margin() * scale;
2070 _gap_x->setValueKeepUnit(gap.x(), "px");
2071 _gap_y->setValueKeepUnit(gap.y(), "px");
2072 _margin_x->setValueKeepUnit(margin.x(), "px");
2073 _margin_y->setValueKeepUnit(margin.y(), "px");
2074 }
2075
2076 _grid_color->setColor(_grid->getMajorColor());
2077
2078 show(_no_of_lines, !modular);
2079 _no_of_lines->setValue(_grid->getMajorLineInterval());
2080
2081 _enabled->set_active(_grid->isEnabled());
2082 _visible->setActive(_grid->isVisible());
2083
2084 if (_dotted)
2085 _dotted->setActive(_grid->isDotted());
2086
2087 _snap_visible_only->setActive(_grid->getSnapToVisibleOnly());
2088 // which condition to use to call setActive?
2089 // _grid_rcb_snap_visible_only->setActive(grid->snapper()->getSnapVisibleOnly());
2090 _enabled->set_active(_grid->snapper()->getEnabled());
2091
2092 show(_dotted, rectangular);
2093 show(_spacing_x, !axonometric);
2094
2095 _icon->set_from_icon_name(_grid->typeName());
2096 auto id = _grid->getId() ? _grid->getId() : "-";
2097 _id->set_label(id);
2098 _id->set_tooltip_text(id);
2099
2100 _wr.setUpdating(false);
2101}
2102
2103} // namespace Widget
2104
2105} // namespace Inkscape::UI
2106
2107/*
2108 Local Variables:
2109 mode:c++
2110 c-file-style:"stroustrup"
2111 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
2112 indent-tabs-mode:nil
2113 fill-column:99
2114 End:
2115*/
2116// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
double scale
Definition aa.cpp:228
Point origin
Definition aa.cpp:227
Various trigoniometric helper functions.
SPAttr
Definition attributes.h:27
@ INKSCAPE_DESK_COLOR
@ BORDERCOLOR
@ INKSCAPE_PAGEOPACITY
@ INKSCAPE_ANTIALIAS_RENDERING
@ INKSCAPE_DESK_CHECKERBOARD
@ SHOWPAGESHADOW
@ BORDEROPACITY
@ INKSCAPE_DESK_OPACITY
@ INKSCAPE_CLIP_TO_PAGE_RENDERING
@ SHOWBORDER
@ BORDERLAYER
@ INKSCAPE_Y_AXIS_DOWN
@ PAGELABELSTYLE
@ INKSCAPE_ORIGIN_CORRECTION
uint64_t page
Definition canvas.cpp:171
int margin
Definition canvas.cpp:166
static CRect from_xywh(Coord x, Coord y, Coord w, Coord h)
Create rectangle from origin and dimensions.
C height() const
Get the vertical extent of the rectangle.
C width() const
Get the horizontal extent of the rectangle.
CPoint min() const
Get the corner of the rectangle with smallest coordinate values.
Two-dimensional point that doubles as a vector.
Definition point.h:66
Axis aligned, non-empty rectangle.
Definition rect.h:92
Scaling from the origin.
Definition transforms.h:150
Scale inverse() const
Definition transforms.h:172
Translation by a vector.
Definition transforms.h:115
static System & get()
Definition system.h:35
sigc::connection connectChanged(const sigc::slot< void()> &slot)
ColorProfile * getColorProfileForSpace(std::string const &name) const
Get the document color-profile SPObject for the named cms profile.
void attachProfileToDoc(std::string const &lookup, ColorProfileStorage storage, RenderingIntent intent, std::string name="")
Attach the named profile to the document.
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
static bool getUndoSensitive(SPDocument const *document)
static void maybeDone(SPDocument *document, const gchar *keyconst, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
void scalePages(Geom::Scale const &scale)
Change page size, margins and bleeds by a set amount.
bool selectPage(SPPage *page)
Set the given page as the selected page.
Preference storage class.
Definition preferences.h:66
static Preferences * get()
Access the singleton Preferences object.
void setString(Glib::ustring const &pref_path, Glib::ustring const &value)
Set an UTF-8 string value.
DialogBase is the base class for the dialog system.
Definition dialog-base.h:42
SPDocument * getDocument() const
Definition dialog-base.h:83
Glib::ustring const _prefs_path
Definition dialog-base.h:88
SPDesktop * getDesktop() const
Definition dialog-base.h:79
void notifyChildRemoved(XML::Node &node, XML::Node &child, XML::Node *prev) final
Child removal callback.
void notifyAttributeChanged(XML::Node &node, GQuark name, Util::ptr_shared old_value, Util::ptr_shared new_value) final
Attribute change callback.
void notifyChildAdded(XML::Node &node, XML::Node &child, XML::Node *prev) final
Child addition callback.
UI::Widget::NotebookPage * _page_external_scripts
void set_viewbox_pos(SPDesktop *desktop, double x, double y)
bool _AvailableProfilesList_separator(Glib::RefPtr< Gtk::TreeModel > const &model, Gtk::TreeModel::const_iterator const &iter)
UI::Widget::RegisteredColorPicker _rcp_hgui
void populate_available_profiles()
Populates the available color profiles combo box.
Glib::RefPtr< Gtk::ListStore > _LinkedProfilesListStore
Glib::RefPtr< Gtk::ListStore > _AvailableProfilesListStore
Glib::RefPtr< Gtk::ListStore > _ExternalScriptsListStore
void rebuild_gridspage()
Called for updating the dialog.
UI::Widget::RegisteredColorPicker _rcp_gui
UI::Widget::RegisteredCheckButton _rcb_lgui
void display_unit_change(const Inkscape::Util::Unit *unit)
void set_document_scale(SPDesktop *desktop, double scale_x)
Glib::RefPtr< Gtk::ListStore > _EmbeddedScriptsListStore
void set_viewbox_size(SPDesktop *desktop, double width, double height)
void build_gridspage()
Build grid page of dialog.
void linkSelectedProfile()
Links the selected color profile in the combo box to the document.
void update() override
The update() method is essential to Gtk state management.
AvailableProfilesColumns _AvailableProfilesListColumns
UI::Widget::NotebookPage * _page_embedded_scripts
Glib::RefPtr< Gtk::SizeGroup > _grids_unified_size
Inkscape::XML::SignalObserver _scripts_observer
void set_content_scale(SPDesktop *desktop, double scale_x)
void update_widgets()
Update dialog widgets from desktop.
UI::Widget::RegisteredCheckButton _rcb_sgui
static EntityEntry * create(rdf_work_entity_t *ent, Registry &wr)
void update(SPDocument *doc)
Definition licensor.cpp:123
A tabbed notebook page for dialogs.
virtual void set_unit(Units unit, const Glib::ustring &abbr)=0
virtual void set_check(Check element, bool checked)=0
virtual void set_dimension(Dimension dim, double x, double y)=0
virtual void set_color(Color element, Colors::Color const &)=0
Holds a single child widget while allowing a single popover to be displayed over it.
Definition popover-bin.h:19
void setPopover(Gtk::Popover *popover)
Definition popover-bin.h:24
void setChild(Gtk::Widget *child)
Definition popover-bin.h:23
SPDesktop * desktop() const
Definition registry.h:27
void setDesktop(SPDesktop *desktop)
Definition registry.cpp:37
double value(Unit const *u) const
Return the quantity's value in the specified unit.
Definition units.cpp:565
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
Interface for refcounted XML nodes.
Definition node.h:80
virtual void addChild(Node *child, Node *after)=0
Insert another node as a child of this node.
void setAttributeOrRemoveIfEmpty(Inkscape::Util::const_char_ptr key, Inkscape::Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:167
void setAttribute(Util::const_char_ptr key, Util::const_char_ptr value)
Change an attribute of this node.
Definition node.cpp:25
bool setAttributeCssDouble(Util::const_char_ptr key, double val)
Set a property attribute to val [slightly rounded], in the format required for CSS properties: in par...
Definition node.cpp:102
virtual void addObserver(NodeObserver &observer)=0
Add an object that will be notified of the changes to this node.
bool setAttributeSvgDouble(Util::const_char_ptr key, double val)
For attributes where an exponent is allowed.
Definition node.cpp:111
virtual Node * root()=0
Get the root node of this node's document.
sigc::signal< void()> & signal_changed()
To do: update description of desktop.
Definition desktop.h:149
SPDocument * getDocument() const
Definition desktop.h:189
InkscapeWindow const * getInkscapeWindow() const
Definition desktop.cpp:975
SPNamedView * getNamedView() const
Definition desktop.h:191
Inkscape::Selection * getSelection() const
Definition desktop.h:188
SVGLength width
Typed SVG document implementation.
Definition document.h:101
void scaleContentBy(Geom::Scale const &delta)
Scale the content, used by file-update and document properties when modifying the the document's view...
Definition document.cpp:780
Glib::RefPtr< Gio::SimpleActionGroup > getActionGroup()
Definition document.h:371
const Geom::Affine & doc2dt() const
Document to desktop coordinate transformation.
Definition document.cpp:920
void setWidthAndHeight(const Inkscape::Util::Quantity &width, const Inkscape::Util::Quantity &height, bool changeSize=true)
Definition document.cpp:804
SPRoot * getRoot()
Returns our SPRoot.
Definition document.h:200
SPObject * getObjectById(std::string const &id) const
void setModifiedSinceSave(bool const modified=true)
Indicate to the user if the document has been modified since the last save by displaying a "*" in fro...
void fitToRect(Geom::Rect const &rect, bool with_margins=false)
Given a Geom::Rect that may, for example, correspond to the bbox of an object, this function fits the...
Definition document.cpp:986
std::vector< SPObject * > const getResourceList(char const *key)
Geom::Point getDimensions() const
Definition document.cpp:958
Geom::Rect getViewBox() const
Definition document.cpp:929
Inkscape::PageManager & getPageManager()
Definition document.h:162
Inkscape::XML::Document * getReprDoc()
Our Inkscape::XML::Document.
Definition document.h:211
bool is_yaxisdown() const
True if the desktop Y-axis points down, false if it points up.
Definition document.h:274
SPNamedView * getNamedView()
Get the namedview for this document, creates it if it's not found.
Definition document.cpp:234
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
void setViewBox()
Set default viewbox calculated from document properties.
Definition document.cpp:943
Inkscape::Colors::DocumentCMS & getDocumentCMS()
Definition document.h:165
Inkscape::Util::Quantity getHeight() const
Definition document.cpp:881
static void create_new(SPDocument *doc, Inkscape::XML::Node *parent, GridType type)
Definition sp-grid.cpp:57
void setOrigin(Geom::Point const &new_origin)
Definition sp-grid.cpp:675
void translateChildItems(Geom::Translate const &tr)
void newGridCreated()
std::vector< SPGuide * > guides
void change_bool_setting(SPAttr key, bool value)
void change_color(SPAttr color_key, SPAttr opacity_key, Colors::Color const &color)
std::vector< SPGrid * > grids
SPObject is an abstract base class of all of the document nodes at the SVG document level.
Definition sp-object.h:160
SPDocument * document
Definition sp-object.h:188
SPObject * firstChild()
Definition sp-object.h:315
SPObject * parent
Definition sp-object.h:189
Inkscape::XML::Node * getRepr()
Returns the XML representation of tree.
sigc::connection connectModified(sigc::slot< void(SPObject *, unsigned int)> slot)
Connects to the modification notification signal.
Definition sp-object.h:705
<svg> element
Definition sp-root.h:33
Geom::Rect viewBox
Definition viewbox.h:35
float value
Definition svg-length.h:47
SPObject of the color-profile object found a direct child of defs.
const double w
Definition conic-4.cpp:19
double inner(valarray< double > const &x, valarray< double > const &y)
vector< vpsc::Rectangle * > rs
RootCluster root
Css & result
static char const *const current
Definition dir-util.cpp:71
Document properties dialog, Gtkmm-style.
TODO: insert short description here.
@ Y
Definition coord.h:48
@ X
Definition coord.h:48
Gtk::Image * sp_get_icon_image(Glib::ustring const &icon_name, int size)
Icon Loader.
Macro for icon names used in Inkscape.
Inkscape::XML::Node * node
Inkscape - An SVG editor.
Glib::ustring label
double angle(std::vector< Point > const &A)
Definition desktop.h:50
void get_start_directory(std::string &start_path, Glib::ustring const &prefs_path, bool try_document_dir)
Find the start directory for a file dialog.
void sanitizeName(std::string &str)
Cleans up name to remove disallowed characters.
static void connect_remove_popup_menu(Gtk::TreeView &tree_view, UI::Widget::PopoverBin &pb, sigc::slot< void()> slot)
void set_color(SPDesktop *desktop, Glib::ustring operation, SPAttr color_key, SPAttr opacity_key, Colors::Color const &color)
static bool do_remove_popup_menu(PopupMenuOptionalClick const click, Gtk::TreeView &tree_view, UI::Widget::PopoverBin &pb, sigc::slot< void()> const &slot)
void set_document_scale_helper(SPDocument &document, double scale)
static constexpr int SPACE_SIZE_Y
void set_document_dimensions(SPDesktop *desktop, double width, double height, const Inkscape::Util::Unit *unit)
static void docprops_style_button(Gtk::Button &btn, char const *iconName)
std::optional< Geom::Scale > get_document_scale_helper(SPDocument &doc)
void attach_all(Gtk::Grid &table, Gtk::Widget *const arr[], unsigned const n)
Helper function that sets widgets in a 2 by n table.
void set_namedview_bool(SPDesktop *desktop, const Glib::ustring &operation, SPAttr key, bool on)
static constexpr int SPACE_SIZE_X
static double profile(double r)
Definition canvas.cpp:846
static constexpr int height
static char const * get_text(Gtk::Editable const &editable)
User interface code.
Definition desktop.h:113
void pack_end(Gtk::Box &box, Gtk::Widget &child, bool const expand, bool const fill, unsigned const padding)
Adds child to box, packed with reference to the end of box.
Definition pack.cpp:153
Gtk::Widget * for_each_descendant(Gtk::Widget &widget, Func &&func)
Like for_each_child() but also tests the initial widget & recurses through childrenʼs children.
Definition util.h:132
void on_popup_menu(Gtk::Widget &widget, PopupMenuSlot slot)
Connect slot to a widgetʼs key and button events that traditionally trigger a popup menu,...
void pack_start(Gtk::Box &box, Gtk::Widget &child, bool const expand, bool const fill, unsigned const padding)
Adds child to box, packed with reference to the start of box.
Definition pack.cpp:141
std::optional< PopupMenuClick > PopupMenuOptionalClick
Optional: not present if popup wasnʼt triggered by click.
Definition popup-menu.h:41
ptr_shared format(char const *format,...) G_GNUC_PRINTF(1
Definition format.h:33
static void append(std::vector< T > &target, std::vector< T > &&source)
Glib::RefPtr< Gio::File > choose_file_open(Glib::ustring const &title, Gtk::Window *parent, Glib::RefPtr< Gio::ListStore< Gtk::FileFilter > > const &filters_model, std::string &current_folder, Glib::ustring const &accept)
Synchronously run a Gtk::FileDialog to open a single file for reading data.
STL namespace.
static cairo_user_data_key_t key
Helpers for using Gtk::Boxes, encapsulating large changes between GTK3 & GTK4.
Page properties widget.
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.
Singleton class to access the preferences file in a convenient way.
Authors: see git history.
unsigned long mi
Definition quantize.cpp:40
Ocnode * child[8]
Definition quantize.cpp:33
struct rdf_work_entity_t rdf_work_entities[]
Definition rdf.cpp:240
headers for RDF types
@ RDF_EDIT_GENERIC
Definition rdf.h:63
void sp_repr_unparent(Inkscape::XML::Node *repr)
Remove repr from children of its parent node.
Definition repr.h:107
GList * items
GridType
Definition sp-grid.h:42
SPGuide – a guideline.
guint32 GQuark
SPRoot: SVG <svg> implementation.
TODO: insert short description here.
bool streq(char const *a, char const *b)
Convenience/readability wrapper for strcmp(a,b)==0.
Definition streq.h:17
Interface for XML documents.
Definition document.h:43
virtual Node * createTextNode(char const *content)=0
virtual Node * createElement(char const *name)=0
char const * name
Definition rdf.h:72
int delta
int index
SPDesktop * desktop
double width
Glib::ustring name
Definition toolbars.cpp:55
double uniform()
Geom::IntPoint dimensions(const Cairo::RefPtr< Cairo::ImageSurface > &surface)
Definition util.cpp:368