1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /**
3  * @file
4  * Document properties dialog, Gtkmm-style.
5  */
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 #ifdef HAVE_CONFIG_H
23 # include "config.h"  // only include where actually required!
24 #endif
25 
26 #include <vector>
27 #include "style.h"
28 #include "rdf.h"
29 #include "verbs.h"
30 
31 #include "display/control/canvas-grid.h"
32 #include "document-properties.h"
33 #include "helper/action.h"
34 #include "include/gtkmm_version.h"
35 #include "io/sys.h"
36 #include "object/sp-root.h"
37 #include "object/sp-script.h"
38 #include "ui/dialog/filedialog.h"
39 #include "ui/icon-loader.h"
40 #include "ui/icon-names.h"
41 #include "ui/shape-editor.h"
42 #include "ui/tools-switch.h"
43 #include "ui/widget/entity-entry.h"
44 #include "ui/widget/notebook-page.h"
45 #include "xml/node-event-vector.h"
46 
47 #include "object/color-profile.h"
48 
49 namespace Inkscape {
50 namespace UI {
51 namespace Dialog {
52 
53 #define SPACE_SIZE_X 15
54 #define SPACE_SIZE_Y 10
55 
56 
57 //===================================================
58 
59 //---------------------------------------------------
60 
61 static void on_child_added(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, void * data);
62 static void on_child_removed(Inkscape::XML::Node *repr, Inkscape::XML::Node *child, Inkscape::XML::Node *ref, void * data);
63 static void on_repr_attr_changed (Inkscape::XML::Node *, gchar const *, gchar const *, gchar const *, bool, gpointer);
64 
65 static Inkscape::XML::NodeEventVector const _repr_events = {
66     on_child_added, // child_added
67     on_child_removed, // child_removed
68     on_repr_attr_changed,
69     nullptr, // content_changed
70     nullptr  // order_changed
71 };
72 
docprops_style_button(Gtk::Button & btn,char const * iconName)73 static void docprops_style_button(Gtk::Button& btn, char const* iconName)
74 {
75     GtkWidget *child = sp_get_icon_image(iconName, GTK_ICON_SIZE_SMALL_TOOLBAR);
76     gtk_widget_show( child );
77     btn.add(*Gtk::manage(Glib::wrap(child)));
78     btn.set_relief(Gtk::RELIEF_NONE);
79 }
80 
getInstance()81 DocumentProperties& DocumentProperties::getInstance()
82 {
83     DocumentProperties &instance = *new DocumentProperties();
84     instance.init();
85 
86     return instance;
87 }
88 
DocumentProperties()89 DocumentProperties::DocumentProperties()
90     : DialogBase("/dialogs/documentoptions", SP_VERB_DIALOG_DOCPROPERTIES)
91     , _page_page(Gtk::manage(new UI::Widget::NotebookPage(1, 1, true, true)))
92     , _page_guides(Gtk::manage(new UI::Widget::NotebookPage(1, 1)))
93     , _page_snap(Gtk::manage(new UI::Widget::NotebookPage(1, 1)))
94     , _page_cms(Gtk::manage(new UI::Widget::NotebookPage(1, 1)))
95     , _page_scripting(Gtk::manage(new UI::Widget::NotebookPage(1, 1)))
96     , _page_external_scripts(Gtk::manage(new UI::Widget::NotebookPage(1, 1)))
97     , _page_embedded_scripts(Gtk::manage(new UI::Widget::NotebookPage(1, 1)))
98     , _page_metadata1(Gtk::manage(new UI::Widget::NotebookPage(1, 1)))
99     , _page_metadata2(Gtk::manage(new UI::Widget::NotebookPage(1, 1)))
100     //---------------------------------------------------------------
101     , _rcb_antialias(_("Use antialiasing"), _("If unset, no antialiasing will be done on the drawing"), "shape-rendering", _wr, false, nullptr, nullptr, nullptr, "crispEdges")
102     , _rcb_checkerboard(_("Checkerboard background"), _("If set, use a colored checkerboard for the canvas background"), "inkscape:pagecheckerboard", _wr, false)
103     , _rcb_canb(_("Show page _border"), _("If set, rectangular page border is shown"), "showborder", _wr, false)
104     , _rcb_bord(_("Border on _top of drawing"), _("If set, border is always on top of the drawing"), "borderlayer", _wr, false)
105     , _rcb_shad(_("_Show border shadow"), _("If set, page border shows a shadow on its right and lower side"), "inkscape:showpageshadow", _wr, false)
106     , _rcp_bg(_("Back_ground color:"), _("Background color"), _("Color of the canvas background. Note: opacity is ignored except when exporting to bitmap."), "pagecolor", "inkscape:pageopacity", _wr)
107     , _rcp_bord(_("Border _color:"), _("Page border color"), _("Color of the page border"), "bordercolor", "borderopacity", _wr)
108     , _rum_deflt(_("Display _units:"), "inkscape:document-units", _wr)
109     , _page_sizer(_wr)
110     //---------------------------------------------------------------
111     //General snap options
112     , _rcb_sgui(_("Show _guides"), _("Show or hide guides"), "showguides", _wr)
113     , _rcb_lgui(_("Lock all guides"), _("Toggle lock of all guides in the document"), "inkscape:lockguides", _wr)
114     , _rcp_gui(_("Guide co_lor:"), _("Guideline color"), _("Color of guidelines"), "guidecolor", "guideopacity", _wr)
115     , _rcp_hgui(_("_Highlight color:"), _("Highlighted guideline color"), _("Color of a guideline when it is under mouse"), "guidehicolor", "guidehiopacity", _wr)
116     , _create_guides_btn(_("Create guides around the page"))
117     , _delete_guides_btn(_("Delete all guides"))
118     //---------------------------------------------------------------
119     , _rsu_sno(_("Snap _distance"), _("Snap only when _closer than:"), _("Always snap"),
120                _("Snapping distance, in screen pixels, for snapping to objects"), _("Always snap to objects, regardless of their distance"),
121                _("If set, objects only snap to another object when it's within the range specified below"),
122                "objecttolerance", _wr)
123     //Options for snapping to grids
124     , _rsu_sn(_("Snap d_istance"), _("Snap only when c_loser than:"), _("Always snap"),
125               _("Snapping distance, in screen pixels, for snapping to grid"), _("Always snap to grids, regardless of the distance"),
126               _("If set, objects only snap to a grid line when it's within the range specified below"),
127               "gridtolerance", _wr)
128     //Options for snapping to guides
129     , _rsu_gusn(_("Snap dist_ance"), _("Snap only when close_r than:"), _("Always snap"),
130                 _("Snapping distance, in screen pixels, for snapping to guides"), _("Always snap to guides, regardless of the distance"),
131                 _("If set, objects only snap to a guide when it's within the range specified below"),
132                 "guidetolerance", _wr)
133     //---------------------------------------------------------------
134     , _rcb_snclp(_("Snap to clip paths"), _("When snapping to paths, then also try snapping to clip paths"), "inkscape:snap-path-clip", _wr)
135     , _rcb_snmsk(_("Snap to mask paths"), _("When snapping to paths, then also try snapping to mask paths"), "inkscape:snap-path-mask", _wr)
136     , _rcb_perp(_("Snap perpendicularly"), _("When snapping to paths or guides, then also try snapping perpendicularly"), "inkscape:snap-perpendicular", _wr)
137     , _rcb_tang(_("Snap tangentially"), _("When snapping to paths or guides, then also try snapping tangentially"), "inkscape:snap-tangential", _wr)
138     //---------------------------------------------------------------
139     , _grids_label_crea("", Gtk::ALIGN_START)
140     , _grids_button_new(C_("Grid", "_New"), _("Create new grid."))
141     , _grids_button_remove(C_("Grid", "_Remove"), _("Remove selected grid."))
142     , _grids_label_def("", Gtk::ALIGN_START)
143     , _grids_vbox(Gtk::ORIENTATION_VERTICAL)
144     , _grids_hbox_crea(Gtk::ORIENTATION_HORIZONTAL)
145     , _grids_space(Gtk::ORIENTATION_HORIZONTAL)
146 {
147     set_spacing (4);
148     pack_start(_notebook, true, true);
149 
150     _notebook.append_page(*_page_page,      _("Page"));
151     _notebook.append_page(*_page_guides,    _("Guides"));
152     _notebook.append_page(_grids_vbox,      _("Grids"));
153     _notebook.append_page(*_page_snap,      _("Snap"));
154     _notebook.append_page(*_page_cms,       _("Color"));
155     _notebook.append_page(*_page_scripting, _("Scripting"));
156     _notebook.append_page(*_page_metadata1, _("Metadata"));
157     _notebook.append_page(*_page_metadata2, _("License"));
158 
159     _wr.setUpdating (true);
160     build_page();
161     build_guides();
162     build_gridspage();
163     build_snap();
164     build_cms();
165     build_scripting();
166     build_metadata();
167     _wr.setUpdating (false);
168 
169     _grids_button_new.signal_clicked().connect(sigc::mem_fun(*this, &DocumentProperties::onNewGrid));
170     _grids_button_remove.signal_clicked().connect(sigc::mem_fun(*this, &DocumentProperties::onRemoveGrid));
171 
172     _rum_deflt._changed_connection.block();
173     _rum_deflt.getUnitMenu()->signal_changed().connect(sigc::mem_fun(*this, &DocumentProperties::onDocUnitChange));
174 }
175 
init()176 void DocumentProperties::init()
177 {
178     show_all_children();
179     _grids_button_remove.hide();
180 }
181 
~DocumentProperties()182 DocumentProperties::~DocumentProperties()
183 {
184     for (auto & it : _rdflist)
185         delete it;
186     if (_repr_root) {
187         _document_replaced_connection.disconnect();
188         _repr_root->removeListenerByData(this);
189         _repr_root = nullptr;
190         _repr_namedview->removeListenerByData(this);
191         _repr_namedview = nullptr;
192     }
193 }
194 
195 //========================================================================
196 
197 /**
198  * Helper function that sets widgets in a 2 by n table.
199  * arr has two entries per table row. Each row is in the following form:
200  *     widget, widget -> function adds a widget in each column.
201  *     nullptr, widget -> function adds a widget that occupies the row.
202  *     label, nullptr -> function adds label that occupies the row.
203  *     nullptr, nullptr -> function adds an empty box that occupies the row.
204  * This used to be a helper function for a 3 by n table
205  */
attach_all(Gtk::Grid & table,Gtk::Widget * const arr[],unsigned const n)206 void attach_all(Gtk::Grid &table, Gtk::Widget *const arr[], unsigned const n)
207 {
208     for (unsigned i = 0, r = 0; i < n; i += 2) {
209         if (arr[i] && arr[i+1]) {
210             arr[i]->set_hexpand();
211             arr[i+1]->set_hexpand();
212             arr[i]->set_valign(Gtk::ALIGN_CENTER);
213             arr[i+1]->set_valign(Gtk::ALIGN_CENTER);
214             table.attach(*arr[i],   0, r, 1, 1);
215             table.attach(*arr[i+1], 1, r, 1, 1);
216         } else {
217             if (arr[i+1]) {
218                 Gtk::AttachOptions yoptions = (Gtk::AttachOptions)0;
219                 if (dynamic_cast<Inkscape::UI::Widget::PageSizer*>(arr[i+1])) {
220                     // only the PageSizer in Document Properties|Page should be stretched vertically
221                     yoptions = Gtk::FILL|Gtk::EXPAND;
222                 }
223                 arr[i+1]->set_hexpand();
224 
225                 if (yoptions & Gtk::EXPAND)
226                     arr[i+1]->set_vexpand();
227                 else
228                     arr[i+1]->set_valign(Gtk::ALIGN_CENTER);
229 
230                 table.attach(*arr[i+1], 0, r, 2, 1);
231             } else if (arr[i]) {
232                 Gtk::Label& label = reinterpret_cast<Gtk::Label&>(*arr[i]);
233 
234                 label.set_hexpand();
235                 label.set_halign(Gtk::ALIGN_START);
236                 label.set_valign(Gtk::ALIGN_CENTER);
237                 table.attach(label, 0, r, 2, 1);
238             } else {
239                 auto space = Gtk::manage (new Gtk::Box);
240                 space->set_size_request (SPACE_SIZE_X, SPACE_SIZE_Y);
241 
242                 space->set_halign(Gtk::ALIGN_CENTER);
243                 space->set_valign(Gtk::ALIGN_CENTER);
244                 table.attach(*space, 0, r, 1, 1);
245             }
246         }
247         ++r;
248     }
249 }
250 
build_page()251 void DocumentProperties::build_page()
252 {
253     _page_page->show();
254 
255     Gtk::Label* label_gen = Gtk::manage (new Gtk::Label);
256     label_gen->set_markup (_("<b>General</b>"));
257 
258     Gtk::Label *label_for = Gtk::manage (new Gtk::Label);
259     label_for->set_markup (_("<b>Page Size</b>"));
260 
261     Gtk::Label* label_bkg = Gtk::manage (new Gtk::Label);
262     label_bkg->set_markup (_("<b>Background</b>"));
263 
264     Gtk::Label* label_bdr = Gtk::manage (new Gtk::Label);
265     label_bdr->set_markup (_("<b>Border</b>"));
266 
267     Gtk::Label* label_dsp = Gtk::manage (new Gtk::Label);
268     label_dsp->set_markup (_("<b>Display</b>"));
269 
270     _page_sizer.init();
271 
272     _rcb_doc_props_left.set_border_width(4);
273     _rcb_doc_props_left.set_row_spacing(4);
274     _rcb_doc_props_left.set_column_spacing(4);
275     _rcb_doc_props_right.set_border_width(4);
276     _rcb_doc_props_right.set_row_spacing(4);
277     _rcb_doc_props_right.set_column_spacing(4);
278 
279     Gtk::Widget *const widget_array[] =
280     {
281         label_gen,            nullptr,
282         nullptr,              &_rum_deflt,
283         nullptr,              nullptr,
284         label_for,            nullptr,
285         nullptr,              &_page_sizer,
286         nullptr,              nullptr,
287         &_rcb_doc_props_left, &_rcb_doc_props_right,
288     };
289     attach_all(_page_page->table(), widget_array, G_N_ELEMENTS(widget_array));
290 
291     Gtk::Widget *const widget_array_left[] =
292     {
293         label_bkg,            nullptr,
294         nullptr,              &_rcb_checkerboard,
295         nullptr,              &_rcp_bg,
296         label_dsp,            nullptr,
297         nullptr,              &_rcb_antialias,
298     };
299     attach_all(_rcb_doc_props_left, widget_array_left, G_N_ELEMENTS(widget_array_left));
300 
301     Gtk::Widget *const widget_array_right[] =
302     {
303         label_bdr,            nullptr,
304         nullptr,              &_rcb_canb,
305         nullptr,              &_rcb_bord,
306         nullptr,              &_rcb_shad,
307         nullptr,              &_rcp_bord,
308     };
309     attach_all(_rcb_doc_props_right, widget_array_right, G_N_ELEMENTS(widget_array_right));
310 
311     std::list<Gtk::Widget*> _slaveList;
312     _slaveList.push_back(&_rcb_bord);
313     _slaveList.push_back(&_rcb_shad);
314     _slaveList.push_back(&_rcp_bord);
315     _rcb_canb.setSlaveWidgets(_slaveList);
316 }
317 
build_guides()318 void DocumentProperties::build_guides()
319 {
320     _page_guides->show();
321 
322     Gtk::Label *label_gui = Gtk::manage (new Gtk::Label);
323     label_gui->set_markup (_("<b>Guides</b>"));
324 
325     _rum_deflt.set_margin_start(0);
326     _rcp_bg.set_margin_start(0);
327     _rcp_bord.set_margin_start(0);
328     _rcp_gui.set_margin_start(0);
329     _rcp_hgui.set_margin_start(0);
330     _rcp_gui.set_hexpand();
331     _rcp_hgui.set_hexpand();
332     _rcb_sgui.set_hexpand();
333     auto inner = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL, 4));
334     inner->add(_rcb_sgui);
335     inner->add(_rcb_lgui);
336     inner->add(_rcp_gui);
337     inner->add(_rcp_hgui);
338     auto spacer = Gtk::manage(new Gtk::Label());
339     Gtk::Widget *const widget_array[] =
340     {
341         label_gui, nullptr,
342         inner,     spacer,
343         nullptr,   nullptr,
344         nullptr,   &_create_guides_btn,
345         nullptr,   &_delete_guides_btn
346     };
347     attach_all(_page_guides->table(), widget_array, G_N_ELEMENTS(widget_array));
348     inner->set_hexpand(false);
349 
350     _create_guides_btn.signal_clicked().connect(sigc::mem_fun(*this, &DocumentProperties::create_guides_around_page));
351     _delete_guides_btn.signal_clicked().connect(sigc::mem_fun(*this, &DocumentProperties::delete_all_guides));
352 }
353 
build_snap()354 void DocumentProperties::build_snap()
355 {
356     _page_snap->show();
357 
358     Gtk::Label *label_o = Gtk::manage (new Gtk::Label);
359     label_o->set_markup (_("<b>Snap to objects</b>"));
360     Gtk::Label *label_gr = Gtk::manage (new Gtk::Label);
361     label_gr->set_markup (_("<b>Snap to grids</b>"));
362     Gtk::Label *label_gu = Gtk::manage (new Gtk::Label);
363     label_gu->set_markup (_("<b>Snap to guides</b>"));
364     Gtk::Label *label_m = Gtk::manage (new Gtk::Label);
365     label_m->set_markup (_("<b>Miscellaneous</b>"));
366 
367     auto spacer = Gtk::manage(new Gtk::Label());
368 
369     Gtk::Widget *const array[] =
370     {
371         label_o,     nullptr,
372         nullptr,     _rsu_sno._vbox,
373         &_rcb_snclp, spacer,
374         nullptr,     &_rcb_snmsk,
375         nullptr,     nullptr,
376         label_gr,    nullptr,
377         nullptr,     _rsu_sn._vbox,
378         nullptr,     nullptr,
379         label_gu,    nullptr,
380         nullptr,     _rsu_gusn._vbox,
381         nullptr,     nullptr,
382         label_m,     nullptr,
383         nullptr,     &_rcb_perp,
384         nullptr,     &_rcb_tang
385     };
386     attach_all(_page_snap->table(), array, G_N_ELEMENTS(array));
387  }
388 
create_guides_around_page()389 void DocumentProperties::create_guides_around_page()
390 {
391     SPDesktop *dt = getDesktop();
392     Verb *verb = Verb::get( SP_VERB_EDIT_GUIDES_AROUND_PAGE );
393     if (verb) {
394         SPAction *action = verb->get_action(Inkscape::ActionContext(dt));
395         if (action) {
396             sp_action_perform(action, nullptr);
397         }
398     }
399 }
400 
delete_all_guides()401 void DocumentProperties::delete_all_guides()
402 {
403     SPDesktop *dt = getDesktop();
404     Verb *verb = Verb::get( SP_VERB_EDIT_DELETE_ALL_GUIDES );
405     if (verb) {
406         SPAction *action = verb->get_action(Inkscape::ActionContext(dt));
407         if (action) {
408             sp_action_perform(action, nullptr);
409         }
410     }
411 }
412 
413 /// Populates the available color profiles combo box
populate_available_profiles()414 void DocumentProperties::populate_available_profiles(){
415     _AvailableProfilesListStore->clear(); // Clear any existing items in the combo box
416 
417     // Iterate through the list of profiles and add the name to the combo box.
418     bool home = true; // initial value doesn't matter, it's just to avoid a compiler warning
419     bool first = true;
420     for (auto &profile: ColorProfile::getProfileFilesWithNames()) {
421         Gtk::TreeModel::Row row;
422 
423         // add a separator between profiles from the user's home directory and system profiles
424         if (!first && profile.isInHome != home)
425         {
426           row = *(_AvailableProfilesListStore->append());
427           row[_AvailableProfilesListColumns.fileColumn] = "<separator>";
428           row[_AvailableProfilesListColumns.nameColumn] = "<separator>";
429           row[_AvailableProfilesListColumns.separatorColumn] = true;
430         }
431         home = profile.isInHome;
432         first = false;
433 
434         row = *(_AvailableProfilesListStore->append());
435         row[_AvailableProfilesListColumns.fileColumn] = profile.filename;
436         row[_AvailableProfilesListColumns.nameColumn] = profile.name;
437         row[_AvailableProfilesListColumns.separatorColumn] = false;
438     }
439 }
440 
441 /**
442  * Cleans up name to remove disallowed characters.
443  * Some discussion at http://markmail.org/message/bhfvdfptt25kgtmj
444  * Allowed ASCII first characters:  ':', 'A'-'Z', '_', 'a'-'z'
445  * Allowed ASCII remaining chars add: '-', '.', '0'-'9',
446  *
447  * @param str the string to clean up.
448  */
sanitizeName(Glib::ustring & str)449 static void sanitizeName( Glib::ustring& str )
450 {
451     if (str.size() > 0) {
452         char val = str.at(0);
453         if (((val < 'A') || (val > 'Z'))
454             && ((val < 'a') || (val > 'z'))
455             && (val != '_')
456             && (val != ':')) {
457           str.insert(0, "_");
458         }
459         for (Glib::ustring::size_type i = 1; i < str.size(); i++) {
460             char val = str.at(i);
461             if (((val < 'A') || (val > 'Z'))
462                 && ((val < 'a') || (val > 'z'))
463                 && ((val < '0') || (val > '9'))
464                 && (val != '_')
465                 && (val != ':')
466                 && (val != '-')
467                 && (val != '.')) {
468                 str.replace(i, 1, "-");
469             }
470         }
471     }
472 }
473 
474 /// Links the selected color profile in the combo box to the document
linkSelectedProfile()475 void DocumentProperties::linkSelectedProfile()
476 {
477     //store this profile in the SVG document (create <color-profile> element in the XML)
478     // TODO remove use of 'active' desktop
479     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
480     if (!desktop){
481         g_warning("No active desktop");
482     } else {
483         // Find the index of the currently-selected row in the color profiles combobox
484         Gtk::TreeModel::iterator iter = _AvailableProfilesList.get_active();
485 
486         if (!iter) {
487             return;
488         }
489 
490         // Read the filename and description from the list of available profiles
491         Glib::ustring file = (*iter)[_AvailableProfilesListColumns.fileColumn];
492         Glib::ustring name = (*iter)[_AvailableProfilesListColumns.nameColumn];
493 
494         std::vector<SPObject *> current = SP_ACTIVE_DOCUMENT->getResourceList( "iccprofile" );
495         for (auto obj : current) {
496             Inkscape::ColorProfile* prof = reinterpret_cast<Inkscape::ColorProfile*>(obj);
497             if (!strcmp(prof->href, file.c_str()))
498                 return;
499         }
500         Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
501         Inkscape::XML::Node *cprofRepr = xml_doc->createElement("svg:color-profile");
502         gchar* tmp = g_strdup(name.c_str());
503         Glib::ustring nameStr = tmp ? tmp : "profile"; // TODO add some auto-numbering to avoid collisions
504         sanitizeName(nameStr);
505         cprofRepr->setAttribute("name", nameStr);
506         cprofRepr->setAttribute("xlink:href", Glib::filename_to_uri(Glib::filename_from_utf8(file)));
507         cprofRepr->setAttribute("id", file);
508 
509 
510         // Checks whether there is a defs element. Creates it when needed
511         Inkscape::XML::Node *defsRepr = sp_repr_lookup_name(xml_doc, "svg:defs");
512         if (!defsRepr) {
513             defsRepr = xml_doc->createElement("svg:defs");
514             xml_doc->root()->addChild(defsRepr, nullptr);
515         }
516 
517         g_assert(desktop->doc()->getDefs());
518         defsRepr->addChild(cprofRepr, nullptr);
519 
520         // TODO check if this next line was sometimes needed. It being there caused an assertion.
521         //Inkscape::GC::release(defsRepr);
522 
523         // inform the document, so we can undo
524         DocumentUndo::done(desktop->doc(), SP_VERB_EDIT_LINK_COLOR_PROFILE, _("Link Color Profile"));
525 
526         populate_linked_profiles_box();
527     }
528 }
529 
530 struct _cmp {
operator ()Inkscape::UI::Dialog::_cmp531   bool operator()(const SPObject * const & a, const SPObject * const & b)
532   {
533     const Inkscape::ColorProfile &a_prof = reinterpret_cast<const Inkscape::ColorProfile &>(*a);
534     const Inkscape::ColorProfile &b_prof = reinterpret_cast<const Inkscape::ColorProfile &>(*b);
535     gchar *a_name_casefold = g_utf8_casefold(a_prof.name, -1 );
536     gchar *b_name_casefold = g_utf8_casefold(b_prof.name, -1 );
537     int result = g_strcmp0(a_name_casefold, b_name_casefold);
538     g_free(a_name_casefold);
539     g_free(b_name_casefold);
540     return result < 0;
541   }
542 };
543 
544 template <typename From, typename To>
operator ()Inkscape::UI::Dialog::static_caster545 struct static_caster { To * operator () (From * value) const { return static_cast<To *>(value); } };
546 
populate_linked_profiles_box()547 void DocumentProperties::populate_linked_profiles_box()
548 {
549     _LinkedProfilesListStore->clear();
550     std::vector<SPObject *> current = SP_ACTIVE_DOCUMENT->getResourceList( "iccprofile" );
551     if (! current.empty()) {
552         _emb_profiles_observer.set((*(current.begin()))->parent);
553     }
554 
555     std::set<Inkscape::ColorProfile *> _current;
556     std::transform(current.begin(),
557                    current.end(),
558                    std::inserter(_current, _current.begin()),
559                    static_caster<SPObject, Inkscape::ColorProfile>());
560 
561     for (auto &profile: _current) {
562         Gtk::TreeModel::Row row = *(_LinkedProfilesListStore->append());
563         row[_LinkedProfilesListColumns.nameColumn] = profile->name;
564 //        row[_LinkedProfilesListColumns.previewColumn] = "Color Preview";
565     }
566 }
567 
external_scripts_list_button_release(GdkEventButton * event)568 void DocumentProperties::external_scripts_list_button_release(GdkEventButton* event)
569 {
570     if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
571         _ExternalScriptsContextMenu.popup_at_pointer(reinterpret_cast<GdkEvent *>(event));
572     }
573 }
574 
embedded_scripts_list_button_release(GdkEventButton * event)575 void DocumentProperties::embedded_scripts_list_button_release(GdkEventButton* event)
576 {
577     if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
578         _EmbeddedScriptsContextMenu.popup_at_pointer(reinterpret_cast<GdkEvent *>(event));
579     }
580 }
581 
linked_profiles_list_button_release(GdkEventButton * event)582 void DocumentProperties::linked_profiles_list_button_release(GdkEventButton* event)
583 {
584     if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
585         _EmbProfContextMenu.popup_at_pointer(reinterpret_cast<GdkEvent *>(event));
586     }
587 }
588 
cms_create_popup_menu(Gtk::Widget & parent,sigc::slot<void> rem)589 void DocumentProperties::cms_create_popup_menu(Gtk::Widget& parent, sigc::slot<void> rem)
590 {
591     Gtk::MenuItem* mi = Gtk::manage(new Gtk::MenuItem(_("_Remove"), true));
592     _EmbProfContextMenu.append(*mi);
593     mi->signal_activate().connect(rem);
594     mi->show();
595     _EmbProfContextMenu.accelerate(parent);
596 }
597 
598 
external_create_popup_menu(Gtk::Widget & parent,sigc::slot<void> rem)599 void DocumentProperties::external_create_popup_menu(Gtk::Widget& parent, sigc::slot<void> rem)
600 {
601     Gtk::MenuItem* mi = Gtk::manage(new Gtk::MenuItem(_("_Remove"), true));
602     _ExternalScriptsContextMenu.append(*mi);
603     mi->signal_activate().connect(rem);
604     mi->show();
605     _ExternalScriptsContextMenu.accelerate(parent);
606 }
607 
embedded_create_popup_menu(Gtk::Widget & parent,sigc::slot<void> rem)608 void DocumentProperties::embedded_create_popup_menu(Gtk::Widget& parent, sigc::slot<void> rem)
609 {
610     Gtk::MenuItem* mi = Gtk::manage(new Gtk::MenuItem(_("_Remove"), true));
611     _EmbeddedScriptsContextMenu.append(*mi);
612     mi->signal_activate().connect(rem);
613     mi->show();
614     _EmbeddedScriptsContextMenu.accelerate(parent);
615 }
616 
onColorProfileSelectRow()617 void DocumentProperties::onColorProfileSelectRow()
618 {
619     Glib::RefPtr<Gtk::TreeSelection> sel = _LinkedProfilesList.get_selection();
620     if (sel) {
621         _unlink_btn.set_sensitive(sel->count_selected_rows () > 0);
622     }
623 }
624 
625 
removeSelectedProfile()626 void DocumentProperties::removeSelectedProfile(){
627     Glib::ustring name;
628     if(_LinkedProfilesList.get_selection()) {
629         Gtk::TreeModel::iterator i = _LinkedProfilesList.get_selection()->get_selected();
630 
631         if(i){
632             name = (*i)[_LinkedProfilesListColumns.nameColumn];
633         } else {
634             return;
635         }
636     }
637     std::vector<SPObject *> current = SP_ACTIVE_DOCUMENT->getResourceList( "iccprofile" );
638     for (auto obj : current) {
639         Inkscape::ColorProfile* prof = reinterpret_cast<Inkscape::ColorProfile*>(obj);
640         if (!name.compare(prof->name)){
641             prof->deleteObject(true, false);
642             DocumentUndo::done(SP_ACTIVE_DOCUMENT, SP_VERB_EDIT_REMOVE_COLOR_PROFILE, _("Remove linked color profile"));
643             break; // removing the color profile likely invalidates part of the traversed list, stop traversing here.
644         }
645     }
646 
647     populate_linked_profiles_box();
648     onColorProfileSelectRow();
649 }
650 
_AvailableProfilesList_separator(const Glib::RefPtr<Gtk::TreeModel> & model,const Gtk::TreeModel::iterator & iter)651 bool DocumentProperties::_AvailableProfilesList_separator(const Glib::RefPtr<Gtk::TreeModel>& model, const Gtk::TreeModel::iterator& iter)
652 {
653     bool separator = (*iter)[_AvailableProfilesListColumns.separatorColumn];
654     return separator;
655 }
656 
build_cms()657 void DocumentProperties::build_cms()
658 {
659     _page_cms->show();
660     Gtk::Label *label_link= Gtk::manage (new Gtk::Label("", Gtk::ALIGN_START));
661     label_link->set_markup (_("<b>Linked Color Profiles:</b>"));
662     Gtk::Label *label_avail = Gtk::manage (new Gtk::Label("", Gtk::ALIGN_START));
663     label_avail->set_markup (_("<b>Available Color Profiles:</b>"));
664 
665     _unlink_btn.set_tooltip_text(_("Unlink Profile"));
666     docprops_style_button(_unlink_btn, INKSCAPE_ICON("list-remove"));
667 
668     gint row = 0;
669 
670     label_link->set_hexpand();
671     label_link->set_halign(Gtk::ALIGN_START);
672     label_link->set_valign(Gtk::ALIGN_CENTER);
673     _page_cms->table().attach(*label_link, 0, row, 3, 1);
674 
675     row++;
676 
677     _LinkedProfilesListScroller.set_hexpand();
678     _LinkedProfilesListScroller.set_valign(Gtk::ALIGN_CENTER);
679     _page_cms->table().attach(_LinkedProfilesListScroller, 0, row, 3, 1);
680 
681     row++;
682 
683     Gtk::Box* spacer = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
684     spacer->set_size_request(SPACE_SIZE_X, SPACE_SIZE_Y);
685 
686     spacer->set_hexpand();
687     spacer->set_valign(Gtk::ALIGN_CENTER);
688     _page_cms->table().attach(*spacer, 0, row, 3, 1);
689 
690     row++;
691 
692     label_avail->set_hexpand();
693     label_avail->set_halign(Gtk::ALIGN_START);
694     label_avail->set_valign(Gtk::ALIGN_CENTER);
695     _page_cms->table().attach(*label_avail, 0, row, 3, 1);
696 
697     row++;
698 
699     _AvailableProfilesList.set_hexpand();
700     _AvailableProfilesList.set_valign(Gtk::ALIGN_CENTER);
701     _page_cms->table().attach(_AvailableProfilesList, 0, row, 1, 1);
702 
703     _unlink_btn.set_halign(Gtk::ALIGN_CENTER);
704     _unlink_btn.set_valign(Gtk::ALIGN_CENTER);
705     _page_cms->table().attach(_unlink_btn, 2, row, 1, 1);
706 
707     // Set up the Available Profiles combo box
708     _AvailableProfilesListStore = Gtk::ListStore::create(_AvailableProfilesListColumns);
709     _AvailableProfilesList.set_model(_AvailableProfilesListStore);
710     _AvailableProfilesList.pack_start(_AvailableProfilesListColumns.nameColumn);
711     _AvailableProfilesList.set_row_separator_func(sigc::mem_fun(*this, &DocumentProperties::_AvailableProfilesList_separator));
712     _AvailableProfilesList.signal_changed().connect( sigc::mem_fun(*this, &DocumentProperties::linkSelectedProfile) );
713 
714     populate_available_profiles();
715 
716     //# Set up the Linked Profiles combo box
717     _LinkedProfilesListStore = Gtk::ListStore::create(_LinkedProfilesListColumns);
718     _LinkedProfilesList.set_model(_LinkedProfilesListStore);
719     _LinkedProfilesList.append_column(_("Profile Name"), _LinkedProfilesListColumns.nameColumn);
720 //    _LinkedProfilesList.append_column(_("Color Preview"), _LinkedProfilesListColumns.previewColumn);
721     _LinkedProfilesList.set_headers_visible(false);
722 // TODO restore?    _LinkedProfilesList.set_fixed_height_mode(true);
723 
724     populate_linked_profiles_box();
725 
726     _LinkedProfilesListScroller.add(_LinkedProfilesList);
727     _LinkedProfilesListScroller.set_shadow_type(Gtk::SHADOW_IN);
728     _LinkedProfilesListScroller.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS);
729     _LinkedProfilesListScroller.set_size_request(-1, 90);
730 
731     _unlink_btn.signal_clicked().connect(sigc::mem_fun(*this, &DocumentProperties::removeSelectedProfile));
732 
733     _LinkedProfilesList.get_selection()->signal_changed().connect( sigc::mem_fun(*this, &DocumentProperties::onColorProfileSelectRow) );
734 
735     _LinkedProfilesList.signal_button_release_event().connect_notify(sigc::mem_fun(*this, &DocumentProperties::linked_profiles_list_button_release));
736     cms_create_popup_menu(_LinkedProfilesList, sigc::mem_fun(*this, &DocumentProperties::removeSelectedProfile));
737 
738     std::vector<SPObject *> current = SP_ACTIVE_DOCUMENT->getResourceList( "defs" );
739     if (!current.empty()) {
740         _emb_profiles_observer.set((*(current.begin()))->parent);
741     }
742     _emb_profiles_observer.signal_changed().connect(sigc::mem_fun(*this, &DocumentProperties::populate_linked_profiles_box));
743     onColorProfileSelectRow();
744 }
745 
build_scripting()746 void DocumentProperties::build_scripting()
747 {
748     _page_scripting->show();
749 
750     _page_scripting->table().attach(_scripting_notebook, 0, 0, 1, 1);
751 
752     _scripting_notebook.append_page(*_page_external_scripts, _("External scripts"));
753     _scripting_notebook.append_page(*_page_embedded_scripts, _("Embedded scripts"));
754 
755     //# External scripts tab
756     _page_external_scripts->show();
757     Gtk::Label *label_external= Gtk::manage (new Gtk::Label("", Gtk::ALIGN_START));
758     label_external->set_markup (_("<b>External script files:</b>"));
759 
760     _external_add_btn.set_tooltip_text(_("Add the current file name or browse for a file"));
761     docprops_style_button(_external_add_btn, INKSCAPE_ICON("list-add"));
762 
763     _external_remove_btn.set_tooltip_text(_("Remove"));
764     docprops_style_button(_external_remove_btn, INKSCAPE_ICON("list-remove"));
765 
766     gint row = 0;
767 
768     label_external->set_hexpand();
769     label_external->set_halign(Gtk::ALIGN_START);
770     label_external->set_valign(Gtk::ALIGN_CENTER);
771     _page_external_scripts->table().attach(*label_external, 0, row, 3, 1);
772 
773     row++;
774 
775     _ExternalScriptsListScroller.set_hexpand();
776     _ExternalScriptsListScroller.set_valign(Gtk::ALIGN_CENTER);
777     _page_external_scripts->table().attach(_ExternalScriptsListScroller, 0, row, 3, 1);
778 
779     row++;
780 
781     Gtk::Box* spacer_external = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
782     spacer_external->set_size_request(SPACE_SIZE_X, SPACE_SIZE_Y);
783 
784     spacer_external->set_hexpand();
785     spacer_external->set_valign(Gtk::ALIGN_CENTER);
786     _page_external_scripts->table().attach(*spacer_external, 0, row, 3, 1);
787 
788     row++;
789 
790     _script_entry.set_hexpand();
791     _script_entry.set_valign(Gtk::ALIGN_CENTER);
792     _page_external_scripts->table().attach(_script_entry, 0, row, 1, 1);
793 
794     _external_add_btn.set_halign(Gtk::ALIGN_CENTER);
795     _external_add_btn.set_valign(Gtk::ALIGN_CENTER);
796     _external_add_btn.set_margin_start(2);
797     _external_add_btn.set_margin_end(2);
798 
799     _page_external_scripts->table().attach(_external_add_btn, 1, row, 1, 1);
800 
801     _external_remove_btn.set_halign(Gtk::ALIGN_CENTER);
802     _external_remove_btn.set_valign(Gtk::ALIGN_CENTER);
803     _page_external_scripts->table().attach(_external_remove_btn, 2, row, 1, 1);
804 
805     //# Set up the External Scripts box
806     _ExternalScriptsListStore = Gtk::ListStore::create(_ExternalScriptsListColumns);
807     _ExternalScriptsList.set_model(_ExternalScriptsListStore);
808     _ExternalScriptsList.append_column(_("Filename"), _ExternalScriptsListColumns.filenameColumn);
809     _ExternalScriptsList.set_headers_visible(true);
810 // TODO restore?    _ExternalScriptsList.set_fixed_height_mode(true);
811 
812 
813     //# Embedded scripts tab
814     _page_embedded_scripts->show();
815     Gtk::Label *label_embedded= Gtk::manage (new Gtk::Label("", Gtk::ALIGN_START));
816     label_embedded->set_markup (_("<b>Embedded script files:</b>"));
817 
818     _embed_new_btn.set_tooltip_text(_("New"));
819     docprops_style_button(_embed_new_btn, INKSCAPE_ICON("list-add"));
820 
821     _embed_remove_btn.set_tooltip_text(_("Remove"));
822     docprops_style_button(_embed_remove_btn, INKSCAPE_ICON("list-remove"));
823 
824     _embed_button_box.set_layout (Gtk::BUTTONBOX_START);
825     _embed_button_box.add(_embed_new_btn);
826     _embed_button_box.add(_embed_remove_btn);
827 
828     row = 0;
829 
830     label_embedded->set_hexpand();
831     label_embedded->set_halign(Gtk::ALIGN_START);
832     label_embedded->set_valign(Gtk::ALIGN_CENTER);
833     _page_embedded_scripts->table().attach(*label_embedded, 0, row, 3, 1);
834 
835     row++;
836 
837     _EmbeddedScriptsListScroller.set_hexpand();
838     _EmbeddedScriptsListScroller.set_valign(Gtk::ALIGN_CENTER);
839     _page_embedded_scripts->table().attach(_EmbeddedScriptsListScroller, 0, row, 3, 1);
840 
841     row++;
842 
843     _embed_button_box.set_hexpand();
844     _embed_button_box.set_valign(Gtk::ALIGN_CENTER);
845     _page_embedded_scripts->table().attach(_embed_button_box, 0, row, 1, 1);
846 
847     row++;
848 
849     Gtk::Box* spacer_embedded = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
850     spacer_embedded->set_size_request(SPACE_SIZE_X, SPACE_SIZE_Y);
851     spacer_embedded->set_hexpand();
852     spacer_embedded->set_valign(Gtk::ALIGN_CENTER);
853     _page_embedded_scripts->table().attach(*spacer_embedded, 0, row, 3, 1);
854 
855     row++;
856 
857     //# Set up the Embedded Scripts box
858     _EmbeddedScriptsListStore = Gtk::ListStore::create(_EmbeddedScriptsListColumns);
859     _EmbeddedScriptsList.set_model(_EmbeddedScriptsListStore);
860     _EmbeddedScriptsList.append_column(_("Script ID"), _EmbeddedScriptsListColumns.idColumn);
861     _EmbeddedScriptsList.set_headers_visible(true);
862 // TODO restore?    _EmbeddedScriptsList.set_fixed_height_mode(true);
863 
864     //# Set up the Embedded Scripts content box
865     Gtk::Label *label_embedded_content= Gtk::manage (new Gtk::Label("", Gtk::ALIGN_START));
866     label_embedded_content->set_markup (_("<b>Content:</b>"));
867 
868     label_embedded_content->set_hexpand();
869     label_embedded_content->set_halign(Gtk::ALIGN_START);
870     label_embedded_content->set_valign(Gtk::ALIGN_CENTER);
871     _page_embedded_scripts->table().attach(*label_embedded_content, 0, row, 3, 1);
872 
873     row++;
874 
875     _EmbeddedContentScroller.set_hexpand();
876     _EmbeddedContentScroller.set_valign(Gtk::ALIGN_CENTER);
877     _page_embedded_scripts->table().attach(_EmbeddedContentScroller, 0, row, 3, 1);
878 
879     _EmbeddedContentScroller.add(_EmbeddedContent);
880     _EmbeddedContentScroller.set_shadow_type(Gtk::SHADOW_IN);
881     _EmbeddedContentScroller.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
882     _EmbeddedContentScroller.set_size_request(-1, 140);
883 
884     _EmbeddedScriptsList.signal_cursor_changed().connect(sigc::mem_fun(*this, &DocumentProperties::changeEmbeddedScript));
885     _EmbeddedScriptsList.get_selection()->signal_changed().connect( sigc::mem_fun(*this, &DocumentProperties::onEmbeddedScriptSelectRow) );
886 
887     _ExternalScriptsList.get_selection()->signal_changed().connect( sigc::mem_fun(*this, &DocumentProperties::onExternalScriptSelectRow) );
888 
889     _EmbeddedContent.get_buffer()->signal_changed().connect(sigc::mem_fun(*this, &DocumentProperties::editEmbeddedScript));
890 
891     populate_script_lists();
892 
893     _ExternalScriptsListScroller.add(_ExternalScriptsList);
894     _ExternalScriptsListScroller.set_shadow_type(Gtk::SHADOW_IN);
895     _ExternalScriptsListScroller.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS);
896     _ExternalScriptsListScroller.set_size_request(-1, 90);
897 
898     _external_add_btn.signal_clicked().connect(sigc::mem_fun(*this, &DocumentProperties::addExternalScript));
899 
900     _EmbeddedScriptsListScroller.add(_EmbeddedScriptsList);
901     _EmbeddedScriptsListScroller.set_shadow_type(Gtk::SHADOW_IN);
902     _EmbeddedScriptsListScroller.set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_ALWAYS);
903     _EmbeddedScriptsListScroller.set_size_request(-1, 90);
904 
905     _embed_new_btn.signal_clicked().connect(sigc::mem_fun(*this, &DocumentProperties::addEmbeddedScript));
906 
907     _external_remove_btn.signal_clicked().connect(sigc::mem_fun(*this, &DocumentProperties::removeExternalScript));
908     _embed_remove_btn.signal_clicked().connect(sigc::mem_fun(*this, &DocumentProperties::removeEmbeddedScript));
909 
910     _ExternalScriptsList.signal_button_release_event().connect_notify(sigc::mem_fun(*this, &DocumentProperties::external_scripts_list_button_release));
911     external_create_popup_menu(_ExternalScriptsList, sigc::mem_fun(*this, &DocumentProperties::removeExternalScript));
912 
913     _EmbeddedScriptsList.signal_button_release_event().connect_notify(sigc::mem_fun(*this, &DocumentProperties::embedded_scripts_list_button_release));
914     embedded_create_popup_menu(_EmbeddedScriptsList, sigc::mem_fun(*this, &DocumentProperties::removeEmbeddedScript));
915 
916 //TODO: review this observers code:
917     std::vector<SPObject *> current = SP_ACTIVE_DOCUMENT->getResourceList( "script" );
918     if (! current.empty()) {
919         _scripts_observer.set((*(current.begin()))->parent);
920     }
921     _scripts_observer.signal_changed().connect(sigc::mem_fun(*this, &DocumentProperties::populate_script_lists));
922     onEmbeddedScriptSelectRow();
923     onExternalScriptSelectRow();
924 }
925 
build_metadata()926 void DocumentProperties::build_metadata()
927 {
928     using Inkscape::UI::Widget::EntityEntry;
929 
930     _page_metadata1->show();
931 
932     Gtk::Label *label = Gtk::manage (new Gtk::Label);
933     label->set_markup (_("<b>Dublin Core Entities</b>"));
934     label->set_halign(Gtk::ALIGN_START);
935     label->set_valign(Gtk::ALIGN_CENTER);
936     _page_metadata1->table().attach (*label, 0,0,2,1);
937 
938      /* add generic metadata entry areas */
939     struct rdf_work_entity_t * entity;
940     int row = 1;
941     for (entity = rdf_work_entities; entity && entity->name; entity++, row++) {
942         if ( entity->editable == RDF_EDIT_GENERIC ) {
943             EntityEntry *w = EntityEntry::create (entity, _wr);
944             _rdflist.push_back (w);
945 
946             w->_label.set_halign(Gtk::ALIGN_START);
947             w->_label.set_valign(Gtk::ALIGN_CENTER);
948             _page_metadata1->table().attach(w->_label, 0, row, 1, 1);
949 
950             w->_packable->set_hexpand();
951             w->_packable->set_valign(Gtk::ALIGN_CENTER);
952             _page_metadata1->table().attach(*w->_packable, 1, row, 1, 1);
953         }
954     }
955 
956     Gtk::Button *button_save = Gtk::manage (new Gtk::Button(_("_Save as default"),true));
957     button_save->set_tooltip_text(_("Save this metadata as the default metadata"));
958     Gtk::Button *button_load = Gtk::manage (new Gtk::Button(_("Use _default"),true));
959     button_load->set_tooltip_text(_("Use the previously saved default metadata here"));
960 
961     auto box_buttons = Gtk::manage (new Gtk::ButtonBox);
962 
963     box_buttons->set_layout(Gtk::BUTTONBOX_END);
964     box_buttons->set_spacing(4);
965     box_buttons->pack_start(*button_save, true, true, 6);
966     box_buttons->pack_start(*button_load, true, true, 6);
967     _page_metadata1->pack_end(*box_buttons, false, false, 0);
968 
969     button_save->signal_clicked().connect(sigc::mem_fun(*this, &DocumentProperties::save_default_metadata));
970     button_load->signal_clicked().connect(sigc::mem_fun(*this, &DocumentProperties::load_default_metadata));
971 
972     _page_metadata2->show();
973 
974     row = 0;
975     Gtk::Label *llabel = Gtk::manage (new Gtk::Label);
976     llabel->set_markup (_("<b>License</b>"));
977     llabel->set_halign(Gtk::ALIGN_START);
978     llabel->set_valign(Gtk::ALIGN_CENTER);
979     _page_metadata2->table().attach(*llabel, 0, row, 2, 1);
980 
981     /* add license selector pull-down and URI */
982     ++row;
983     _licensor.init (_wr);
984 
985     _licensor.set_hexpand();
986     _licensor.set_valign(Gtk::ALIGN_CENTER);
987     _page_metadata2->table().attach(_licensor, 0, row, 2, 1);
988 }
989 
addExternalScript()990 void DocumentProperties::addExternalScript(){
991 
992     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
993     if (!desktop) {
994         g_warning("No active desktop");
995         return;
996     }
997 
998     if (_script_entry.get_text().empty() ) {
999         // Click Add button with no filename, show a Browse dialog
1000         browseExternalScript();
1001     }
1002 
1003     if (!_script_entry.get_text().empty()) {
1004 
1005         Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
1006         Inkscape::XML::Node *scriptRepr = xml_doc->createElement("svg:script");
1007         scriptRepr->setAttributeOrRemoveIfEmpty("xlink:href", _script_entry.get_text());
1008         _script_entry.set_text("");
1009 
1010         xml_doc->root()->addChild(scriptRepr, nullptr);
1011 
1012         // inform the document, so we can undo
1013         DocumentUndo::done(desktop->doc(), SP_VERB_EDIT_ADD_EXTERNAL_SCRIPT, _("Add external script..."));
1014 
1015         populate_script_lists();
1016     }
1017 
1018 }
1019 
1020 static Inkscape::UI::Dialog::FileOpenDialog * selectPrefsFileInstance = nullptr;
1021 
browseExternalScript()1022 void  DocumentProperties::browseExternalScript() {
1023 
1024     //# Get the current directory for finding files
1025     static Glib::ustring open_path;
1026     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1027 
1028 
1029     Glib::ustring attr = prefs->getString(_prefs_path);
1030     if (!attr.empty()) open_path = attr;
1031 
1032     //# Test if the open_path directory exists
1033     if (!Inkscape::IO::file_test(open_path.c_str(),
1034               (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)))
1035         open_path = "";
1036 
1037     //# If no open path, default to our home directory
1038     if (open_path.empty())
1039     {
1040         open_path = g_get_home_dir();
1041         open_path.append(G_DIR_SEPARATOR_S);
1042     }
1043 
1044     //# Create a dialog
1045     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1046     if (!selectPrefsFileInstance) {
1047         selectPrefsFileInstance =
1048               Inkscape::UI::Dialog::FileOpenDialog::create(
1049                  *desktop->getToplevel(),
1050                  open_path,
1051                  Inkscape::UI::Dialog::CUSTOM_TYPE,
1052                  _("Select a script to load"));
1053         selectPrefsFileInstance->addFilterMenu("Javascript Files", "*.js");
1054     }
1055 
1056     //# Show the dialog
1057     bool const success = selectPrefsFileInstance->show();
1058 
1059     if (!success) {
1060         return;
1061     }
1062 
1063     //# User selected something.  Get name and type
1064     Glib::ustring fileName = selectPrefsFileInstance->getFilename();
1065 
1066     _script_entry.set_text(fileName);
1067 }
1068 
addEmbeddedScript()1069 void DocumentProperties::addEmbeddedScript(){
1070     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1071     if (!desktop){
1072         g_warning("No active desktop");
1073     } else {
1074         Inkscape::XML::Document *xml_doc = desktop->doc()->getReprDoc();
1075         Inkscape::XML::Node *scriptRepr = xml_doc->createElement("svg:script");
1076 
1077         xml_doc->root()->addChild(scriptRepr, nullptr);
1078 
1079         // inform the document, so we can undo
1080         DocumentUndo::done(desktop->doc(), SP_VERB_EDIT_ADD_EMBEDDED_SCRIPT, _("Add embedded script..."));
1081 
1082         populate_script_lists();
1083     }
1084 }
1085 
removeExternalScript()1086 void DocumentProperties::removeExternalScript(){
1087     Glib::ustring name;
1088     if(_ExternalScriptsList.get_selection()) {
1089         Gtk::TreeModel::iterator i = _ExternalScriptsList.get_selection()->get_selected();
1090 
1091         if(i){
1092             name = (*i)[_ExternalScriptsListColumns.filenameColumn];
1093         } else {
1094             return;
1095         }
1096     }
1097 
1098     std::vector<SPObject *> current = SP_ACTIVE_DOCUMENT->getResourceList( "script" );
1099     for (auto obj : current) {
1100         if (obj) {
1101             SPScript* script = dynamic_cast<SPScript *>(obj);
1102             if (script && (name == script->xlinkhref)) {
1103 
1104                 //XML Tree being used directly here while it shouldn't be.
1105                 Inkscape::XML::Node *repr = obj->getRepr();
1106                 if (repr){
1107                     sp_repr_unparent(repr);
1108 
1109                     // inform the document, so we can undo
1110                     DocumentUndo::done(SP_ACTIVE_DOCUMENT, SP_VERB_EDIT_REMOVE_EXTERNAL_SCRIPT, _("Remove external script"));
1111                 }
1112             }
1113         }
1114     }
1115 
1116     populate_script_lists();
1117 }
1118 
removeEmbeddedScript()1119 void DocumentProperties::removeEmbeddedScript(){
1120     Glib::ustring id;
1121     if(_EmbeddedScriptsList.get_selection()) {
1122         Gtk::TreeModel::iterator i = _EmbeddedScriptsList.get_selection()->get_selected();
1123 
1124         if(i){
1125             id = (*i)[_EmbeddedScriptsListColumns.idColumn];
1126         } else {
1127             return;
1128         }
1129     }
1130 
1131     SPObject* obj = SP_ACTIVE_DOCUMENT->getObjectById(id);
1132     if (obj) {
1133         //XML Tree being used directly here while it shouldn't be.
1134         Inkscape::XML::Node *repr = obj->getRepr();
1135         if (repr){
1136             sp_repr_unparent(repr);
1137 
1138             // inform the document, so we can undo
1139             DocumentUndo::done(SP_ACTIVE_DOCUMENT, SP_VERB_EDIT_REMOVE_EMBEDDED_SCRIPT, _("Remove embedded script"));
1140         }
1141     }
1142 
1143     populate_script_lists();
1144 }
1145 
onExternalScriptSelectRow()1146 void DocumentProperties::onExternalScriptSelectRow()
1147 {
1148     Glib::RefPtr<Gtk::TreeSelection> sel = _ExternalScriptsList.get_selection();
1149     if (sel) {
1150         _external_remove_btn.set_sensitive(sel->count_selected_rows () > 0);
1151     }
1152 }
1153 
onEmbeddedScriptSelectRow()1154 void DocumentProperties::onEmbeddedScriptSelectRow()
1155 {
1156     Glib::RefPtr<Gtk::TreeSelection> sel = _EmbeddedScriptsList.get_selection();
1157     if (sel) {
1158         _embed_remove_btn.set_sensitive(sel->count_selected_rows () > 0);
1159     }
1160 }
1161 
changeEmbeddedScript()1162 void DocumentProperties::changeEmbeddedScript(){
1163     Glib::ustring id;
1164     if(_EmbeddedScriptsList.get_selection()) {
1165         Gtk::TreeModel::iterator i = _EmbeddedScriptsList.get_selection()->get_selected();
1166 
1167         if(i){
1168             id = (*i)[_EmbeddedScriptsListColumns.idColumn];
1169         } else {
1170             return;
1171         }
1172     }
1173 
1174     bool voidscript=true;
1175     std::vector<SPObject *> current = SP_ACTIVE_DOCUMENT->getResourceList( "script" );
1176     for (auto obj : current) {
1177         if (id == obj->getId()){
1178             int count = (int) obj->children.size();
1179 
1180             if (count>1)
1181                 g_warning("TODO: Found a script element with multiple (%d) child nodes! We must implement support for that!", count);
1182 
1183             //XML Tree being used directly here while it shouldn't be.
1184             SPObject* child = obj->firstChild();
1185             //TODO: shouldn't we get all children instead of simply the first child?
1186 
1187             if (child && child->getRepr()){
1188                 const gchar* content = child->getRepr()->content();
1189                 if (content){
1190                     voidscript=false;
1191                     _EmbeddedContent.get_buffer()->set_text(content);
1192                 }
1193             }
1194         }
1195     }
1196 
1197     if (voidscript)
1198         _EmbeddedContent.get_buffer()->set_text("");
1199 }
1200 
editEmbeddedScript()1201 void DocumentProperties::editEmbeddedScript(){
1202     Glib::ustring id;
1203     if(_EmbeddedScriptsList.get_selection()) {
1204         Gtk::TreeModel::iterator i = _EmbeddedScriptsList.get_selection()->get_selected();
1205 
1206         if(i){
1207             id = (*i)[_EmbeddedScriptsListColumns.idColumn];
1208         } else {
1209             return;
1210         }
1211     }
1212 
1213     Inkscape::XML::Document *xml_doc = SP_ACTIVE_DOCUMENT->getReprDoc();
1214     std::vector<SPObject *> current = SP_ACTIVE_DOCUMENT->getResourceList( "script" );
1215     for (auto obj : current) {
1216         if (id == obj->getId()){
1217 
1218             //XML Tree being used directly here while it shouldn't be.
1219             Inkscape::XML::Node *repr = obj->getRepr();
1220             if (repr){
1221                 auto tmp = obj->children | boost::adaptors::transformed([](SPObject& o) { return &o; });
1222                 std::vector<SPObject*> vec(tmp.begin(), tmp.end());
1223                 for (auto &child: vec) {
1224                     child->deleteObject();
1225                 }
1226                 obj->appendChildRepr(xml_doc->createTextNode(_EmbeddedContent.get_buffer()->get_text().c_str()));
1227 
1228                 //TODO repr->set_content(_EmbeddedContent.get_buffer()->get_text());
1229 
1230                 // inform the document, so we can undo
1231                 DocumentUndo::done(SP_ACTIVE_DOCUMENT, SP_VERB_EDIT_EMBEDDED_SCRIPT, _("Edit embedded script"));
1232             }
1233         }
1234     }
1235 }
1236 
populate_script_lists()1237 void DocumentProperties::populate_script_lists(){
1238     _ExternalScriptsListStore->clear();
1239     _EmbeddedScriptsListStore->clear();
1240     std::vector<SPObject *> current = SP_ACTIVE_DOCUMENT->getResourceList( "script" );
1241     if (!current.empty()) {
1242         SPObject *obj = *(current.begin());
1243         g_assert(obj != nullptr);
1244         _scripts_observer.set(obj->parent);
1245     }
1246     for (auto obj : current) {
1247         SPScript* script = dynamic_cast<SPScript *>(obj);
1248         g_assert(script != nullptr);
1249         if (script->xlinkhref)
1250         {
1251             Gtk::TreeModel::Row row = *(_ExternalScriptsListStore->append());
1252             row[_ExternalScriptsListColumns.filenameColumn] = script->xlinkhref;
1253         }
1254         else // Embedded scripts
1255         {
1256             Gtk::TreeModel::Row row = *(_EmbeddedScriptsListStore->append());
1257             row[_EmbeddedScriptsListColumns.idColumn] = obj->getId();
1258         }
1259     }
1260 }
1261 
1262 /**
1263 * Called for _updating_ the dialog (e.g. when a new grid was manually added in XML)
1264 */
update_gridspage()1265 void DocumentProperties::update_gridspage()
1266 {
1267     SPDesktop *dt = getDesktop();
1268     SPNamedView *nv = dt->getNamedView();
1269 
1270     int prev_page_count = _grids_notebook.get_n_pages();
1271     int prev_page_pos = _grids_notebook.get_current_page();
1272 
1273     //remove all tabs
1274     while (_grids_notebook.get_n_pages() != 0) {
1275         _grids_notebook.remove_page(-1); // this also deletes the page.
1276     }
1277 
1278     //add tabs
1279     for(auto grid : nv->grids) {
1280         if (!grid->repr->attribute("id")) continue; // update_gridspage is called again when "id" is added
1281         Glib::ustring name(grid->repr->attribute("id"));
1282         const char *icon = nullptr;
1283         switch (grid->getGridType()) {
1284             case GRID_RECTANGULAR:
1285                 icon = "grid-rectangular";
1286                 break;
1287             case GRID_AXONOMETRIC:
1288                 icon = "grid-axonometric";
1289                 break;
1290             default:
1291                 break;
1292         }
1293         _grids_notebook.append_page(*grid->newWidget(), _createPageTabLabel(name, icon));
1294     }
1295     _grids_notebook.show_all();
1296 
1297     int cur_page_count = _grids_notebook.get_n_pages();
1298     if (cur_page_count > 0) {
1299         _grids_button_remove.set_sensitive(true);
1300 
1301         // The following is not correct if grid added/removed via XML
1302         if (cur_page_count == prev_page_count + 1) {
1303             _grids_notebook.set_current_page(cur_page_count - 1);
1304         } else if (cur_page_count == prev_page_count) {
1305             _grids_notebook.set_current_page(prev_page_pos);
1306         } else if (cur_page_count == prev_page_count - 1) {
1307             _grids_notebook.set_current_page(prev_page_pos < 1 ? 0 : prev_page_pos - 1);
1308         }
1309     } else {
1310         _grids_button_remove.set_sensitive(false);
1311     }
1312 }
1313 
1314 /**
1315  * Build grid page of dialog.
1316  */
build_gridspage()1317 void DocumentProperties::build_gridspage()
1318 {
1319     /// \todo FIXME: gray out snapping when grid is off.
1320     /// Dissenting view: you want snapping without grid.
1321 
1322     _grids_label_crea.set_markup(_("<b>Creation</b>"));
1323     _grids_label_def.set_markup(_("<b>Defined grids</b>"));
1324     _grids_hbox_crea.pack_start(_grids_combo_gridtype, true, true);
1325     _grids_hbox_crea.pack_start(_grids_button_new, true, true);
1326 
1327     for (gint t = 0; t <= GRID_MAXTYPENR; t++) {
1328         _grids_combo_gridtype.append( CanvasGrid::getName( (GridType) t ) );
1329     }
1330     _grids_combo_gridtype.set_active_text( CanvasGrid::getName(GRID_RECTANGULAR) );
1331 
1332     _grids_space.set_size_request (SPACE_SIZE_X, SPACE_SIZE_Y);
1333 
1334     _grids_vbox.set_border_width(4);
1335     _grids_vbox.set_spacing(4);
1336     _grids_vbox.pack_start(_grids_label_crea, false, false);
1337     _grids_vbox.pack_start(_grids_hbox_crea, false, false);
1338     _grids_vbox.pack_start(_grids_space, false, false);
1339     _grids_vbox.pack_start(_grids_label_def, false, false);
1340     _grids_vbox.pack_start(_grids_notebook, false, false);
1341     _grids_vbox.pack_start(_grids_button_remove, false, false);
1342 }
1343 
1344 
1345 
1346 /**
1347  * Update dialog widgets from desktop. Also call updateWidget routines of the grids.
1348  */
update_widgets()1349 void DocumentProperties::update_widgets()
1350 {
1351     if (_wr.isUpdating()) return;
1352 
1353     SPDesktop *dt = getDesktop();
1354     SPNamedView *nv = dt->getNamedView();
1355 
1356     _wr.setUpdating (true);
1357     set_sensitive (true);
1358 
1359     //-----------------------------------------------------------page page
1360     _rcb_checkerboard.setActive (nv->pagecheckerboard);
1361     _rcp_bg.setRgba32 (nv->pagecolor);
1362     _rcb_canb.setActive (nv->showborder);
1363     _rcb_bord.setActive (nv->borderlayer == SP_BORDER_LAYER_TOP);
1364     _rcp_bord.setRgba32 (nv->bordercolor);
1365     _rcb_shad.setActive (nv->showpageshadow);
1366 
1367     SPRoot *root = dt->getDocument()->getRoot();
1368     _rcb_antialias.set_xml_target(root->getRepr(), dt->getDocument());
1369     _rcb_antialias.setActive(root->style->shape_rendering.computed != SP_CSS_SHAPE_RENDERING_CRISPEDGES);
1370 
1371     if (nv->display_units) {
1372         _rum_deflt.setUnit (nv->display_units->abbr);
1373     }
1374 
1375     double doc_w = dt->getDocument()->getRoot()->width.value;
1376     Glib::ustring doc_w_unit = unit_table.getUnit(dt->getDocument()->getRoot()->width.unit)->abbr;
1377     if (doc_w_unit == "") {
1378         doc_w_unit = "px";
1379     } else if (doc_w_unit == "%" && dt->getDocument()->getRoot()->viewBox_set) {
1380         doc_w_unit = "px";
1381         doc_w = dt->getDocument()->getRoot()->viewBox.width();
1382     }
1383     double doc_h = dt->getDocument()->getRoot()->height.value;
1384     Glib::ustring doc_h_unit = unit_table.getUnit(dt->getDocument()->getRoot()->height.unit)->abbr;
1385     if (doc_h_unit == "") {
1386         doc_h_unit = "px";
1387     } else if (doc_h_unit == "%" && dt->getDocument()->getRoot()->viewBox_set) {
1388         doc_h_unit = "px";
1389         doc_h = dt->getDocument()->getRoot()->viewBox.height();
1390     }
1391     _page_sizer.setDim(Inkscape::Util::Quantity(doc_w, doc_w_unit), Inkscape::Util::Quantity(doc_h, doc_h_unit));
1392     _page_sizer.updateFitMarginsUI(nv->getRepr());
1393     _page_sizer.updateScaleUI();
1394 
1395     //-----------------------------------------------------------guide page
1396 
1397     _rcb_sgui.setActive (nv->showguides);
1398     _rcb_lgui.setActive (nv->lockguides);
1399     _rcp_gui.setRgba32 (nv->guidecolor);
1400     _rcp_hgui.setRgba32 (nv->guidehicolor);
1401 
1402     //-----------------------------------------------------------snap page
1403 
1404     _rsu_sno.setValue (nv->snap_manager.snapprefs.getObjectTolerance());
1405     _rsu_sn.setValue (nv->snap_manager.snapprefs.getGridTolerance());
1406     _rsu_gusn.setValue (nv->snap_manager.snapprefs.getGuideTolerance());
1407     _rcb_snclp.setActive (nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_PATH_CLIP));
1408     _rcb_snmsk.setActive (nv->snap_manager.snapprefs.isSnapButtonEnabled(Inkscape::SNAPTARGET_PATH_MASK));
1409     _rcb_perp.setActive (nv->snap_manager.snapprefs.getSnapPerp());
1410     _rcb_tang.setActive (nv->snap_manager.snapprefs.getSnapTang());
1411 
1412     //-----------------------------------------------------------grids page
1413 
1414     update_gridspage();
1415 
1416     //------------------------------------------------Color Management page
1417 
1418     populate_linked_profiles_box();
1419     populate_available_profiles();
1420 
1421     //-----------------------------------------------------------meta pages
1422     /* update the RDF entities */
1423     for (auto & it : _rdflist)
1424         it->update (SP_ACTIVE_DOCUMENT);
1425 
1426     _licensor.update (SP_ACTIVE_DOCUMENT);
1427 
1428 
1429     _wr.setUpdating (false);
1430 }
1431 
1432 // TODO: copied from fill-and-stroke.cpp factor out into new ui/widget file?
1433 Gtk::Box&
_createPageTabLabel(const Glib::ustring & label,const char * label_image)1434 DocumentProperties::_createPageTabLabel(const Glib::ustring& label, const char *label_image)
1435 {
1436     Gtk::Box *_tab_label_box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0));
1437     _tab_label_box->set_spacing(4);
1438 
1439     auto img = Gtk::manage(sp_get_icon_image(label_image, Gtk::ICON_SIZE_MENU));
1440     _tab_label_box->pack_start(*img);
1441 
1442     Gtk::Label *_tab_label = Gtk::manage(new Gtk::Label(label, true));
1443     _tab_label_box->pack_start(*_tab_label);
1444     _tab_label_box->show_all();
1445 
1446     return *_tab_label_box;
1447 }
1448 
1449 //--------------------------------------------------------------------
1450 
on_response(int id)1451 void DocumentProperties::on_response (int id)
1452 {
1453     if (id == Gtk::RESPONSE_DELETE_EVENT || id == Gtk::RESPONSE_CLOSE)
1454     {
1455         _rcp_bg.closeWindow();
1456         _rcp_bord.closeWindow();
1457         _rcp_gui.closeWindow();
1458         _rcp_hgui.closeWindow();
1459     }
1460 
1461     if (id == Gtk::RESPONSE_CLOSE)
1462         hide();
1463 }
1464 
load_default_metadata()1465 void DocumentProperties::load_default_metadata()
1466 {
1467     /* Get the data RDF entities data from preferences*/
1468     for (auto & it : _rdflist) {
1469         it->load_from_preferences ();
1470     }
1471 }
1472 
save_default_metadata()1473 void DocumentProperties::save_default_metadata()
1474 {
1475     /* Save these RDF entities to preferences*/
1476     for (auto & it : _rdflist) {
1477         it->save_to_preferences (SP_ACTIVE_DOCUMENT);
1478    }
1479 }
1480 
update()1481 void DocumentProperties::update()
1482 {
1483     if (!_app) {
1484         std::cerr << "UndoHistory::update(): _app is null" << std::endl;
1485         return;
1486     }
1487 
1488     SPDesktop *desktop = getDesktop();
1489 
1490     if (_repr_root) {
1491         _document_replaced_connection.disconnect();
1492         _repr_root->removeListenerByData(this);
1493         _repr_root = nullptr;
1494         _repr_namedview->removeListenerByData(this);
1495         _repr_namedview = nullptr;
1496     }
1497 
1498     if (!desktop) {
1499         return;
1500     }
1501 
1502     _wr.setDesktop(desktop);
1503 
1504     _repr_root = desktop->getNamedView()->getRepr();
1505     _repr_root->addListener(&_repr_events, this);
1506     _repr_namedview = desktop->getDocument()->getRoot()->getRepr();
1507     _repr_namedview->addListener(&_repr_events, this);
1508 
1509     update_widgets();
1510 }
1511 
on_child_added(Inkscape::XML::Node *,Inkscape::XML::Node *,Inkscape::XML::Node *,void * data)1512 static void on_child_added(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node */*child*/, Inkscape::XML::Node */*ref*/, void *data)
1513 {
1514     if (DocumentProperties *dialog = static_cast<DocumentProperties *>(data))
1515         dialog->update_gridspage();
1516 }
1517 
on_child_removed(Inkscape::XML::Node *,Inkscape::XML::Node *,Inkscape::XML::Node *,void * data)1518 static void on_child_removed(Inkscape::XML::Node */*repr*/, Inkscape::XML::Node */*child*/, Inkscape::XML::Node */*ref*/, void *data)
1519 {
1520     if (DocumentProperties *dialog = static_cast<DocumentProperties *>(data))
1521         dialog->update_gridspage();
1522 }
1523 
1524 
1525 
1526 /**
1527  * Called when XML node attribute changed; updates dialog widgets.
1528  */
on_repr_attr_changed(Inkscape::XML::Node *,gchar const *,gchar const *,gchar const *,bool,gpointer data)1529 static void on_repr_attr_changed(Inkscape::XML::Node *, gchar const *, gchar const *, gchar const *, bool, gpointer data)
1530 {
1531     if (DocumentProperties *dialog = static_cast<DocumentProperties *>(data))
1532         dialog->update_widgets();
1533 }
1534 
1535 
1536 /*########################################################################
1537 # BUTTON CLICK HANDLERS    (callbacks)
1538 ########################################################################*/
1539 
onNewGrid()1540 void DocumentProperties::onNewGrid()
1541 {
1542     SPDesktop *dt = getDesktop();
1543     Inkscape::XML::Node *repr = dt->getNamedView()->getRepr();
1544     SPDocument *doc = dt->getDocument();
1545 
1546     Glib::ustring typestring = _grids_combo_gridtype.get_active_text();
1547     CanvasGrid::writeNewGridToRepr(repr, doc, CanvasGrid::getGridTypeFromName(typestring.c_str()));
1548 
1549     // toggle grid showing to ON:
1550     dt->showGrids(true);
1551 }
1552 
1553 
onRemoveGrid()1554 void DocumentProperties::onRemoveGrid()
1555 {
1556     gint pagenum = _grids_notebook.get_current_page();
1557     if (pagenum == -1) // no pages
1558       return;
1559 
1560     SPDesktop *dt = getDesktop();
1561     SPNamedView *nv = dt->getNamedView();
1562     Inkscape::CanvasGrid * found_grid = nullptr;
1563     if( pagenum < (gint)nv->grids.size())
1564         found_grid = nv->grids[pagenum];
1565 
1566     if (found_grid) {
1567         // delete the grid that corresponds with the selected tab
1568         // when the grid is deleted from SVG, the SPNamedview handler automatically deletes the object, so found_grid becomes an invalid pointer!
1569         found_grid->repr->parent()->removeChild(found_grid->repr);
1570         DocumentUndo::done(dt->getDocument(), SP_VERB_DIALOG_DOCPROPERTIES, _("Remove grid"));
1571     }
1572 }
1573 
1574 /** Callback for document unit change. */
1575 /* This should not effect anything in the SVG tree (other than "inkscape:document-units").
1576    This should only effect values displayed in the GUI. */
onDocUnitChange()1577 void DocumentProperties::onDocUnitChange()
1578 {
1579     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1580     // Don't execute when change is being undone
1581     if (!DocumentUndo::getUndoSensitive(doc)) {
1582         return;
1583     }
1584     // Don't execute when initializing widgets
1585     if (_wr.isUpdating()) {
1586         return;
1587     }
1588 
1589 
1590     Inkscape::XML::Node *repr = getDesktop()->getNamedView()->getRepr();
1591     /*Inkscape::Util::Unit const *old_doc_unit = unit_table.getUnit("px");
1592     if(repr->attribute("inkscape:document-units")) {
1593         old_doc_unit = unit_table.getUnit(repr->attribute("inkscape:document-units"));
1594     }*/
1595     Inkscape::Util::Unit const *doc_unit = _rum_deflt.getUnit();
1596 
1597     // Set document unit
1598     Inkscape::SVGOStringStream os;
1599     os << doc_unit->abbr;
1600     repr->setAttribute("inkscape:document-units", os.str());
1601 
1602     _page_sizer.updateScaleUI();
1603 
1604     // Disable changing of SVG Units. The intent here is to change the units in the UI, not the units in SVG.
1605     // This code should be moved (and fixed) once we have an "SVG Units" setting that sets what units are used in SVG data.
1606 #if 0
1607     // Set viewBox
1608     if (doc->getRoot()->viewBox_set) {
1609         gdouble scale = Inkscape::Util::Quantity::convert(1, old_doc_unit, doc_unit);
1610         doc->setViewBox(doc->getRoot()->viewBox*Geom::Scale(scale));
1611     } else {
1612         Inkscape::Util::Quantity width = doc->getWidth();
1613         Inkscape::Util::Quantity height = doc->getHeight();
1614         doc->setViewBox(Geom::Rect::from_xywh(0, 0, width.value(doc_unit), height.value(doc_unit)));
1615     }
1616 
1617     // TODO: Fix bug in nodes tool instead of switching away from it
1618     if (tools_active(getDesktop()) == TOOLS_NODES) {
1619         tools_switch(getDesktop(), TOOLS_SELECT);
1620     }
1621 
1622     // Scale and translate objects
1623     // set transform options to scale all things with the transform, so all things scale properly after the viewbox change.
1624     /// \todo this "low-level" code of changing viewbox/unit should be moved somewhere else
1625 
1626     // save prefs
1627     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1628     bool transform_stroke      = prefs->getBool("/options/transform/stroke", true);
1629     bool transform_rectcorners = prefs->getBool("/options/transform/rectcorners", true);
1630     bool transform_pattern     = prefs->getBool("/options/transform/pattern", true);
1631     bool transform_gradient    = prefs->getBool("/options/transform/gradient", true);
1632 
1633     prefs->setBool("/options/transform/stroke", true);
1634     prefs->setBool("/options/transform/rectcorners", true);
1635     prefs->setBool("/options/transform/pattern", true);
1636     prefs->setBool("/options/transform/gradient", true);
1637     {
1638         ShapeEditor::blockSetItem(true);
1639         gdouble viewscale = 1.0;
1640         Geom::Rect vb = doc->getRoot()->viewBox;
1641         if ( !vb.hasZeroArea() ) {
1642             gdouble viewscale_w = doc->getWidth().value("px") / vb.width();
1643             gdouble viewscale_h = doc->getHeight().value("px")/ vb.height();
1644             viewscale = std::min(viewscale_h, viewscale_w);
1645         }
1646         gdouble scale = Inkscape::Util::Quantity::convert(1, old_doc_unit, doc_unit);
1647         doc->getRoot()->scaleChildItemsRec(Geom::Scale(scale), Geom::Point(-viewscale*doc->getRoot()->viewBox.min()[Geom::X] +
1648                                                                             (doc->getWidth().value("px") - viewscale*doc->getRoot()->viewBox.width())/2,
1649                                                                             viewscale*doc->getRoot()->viewBox.min()[Geom::Y] +
1650                                                                             (doc->getHeight().value("px") + viewscale*doc->getRoot()->viewBox.height())/2),
1651                                                                             false);
1652         ShapeEditor::blockSetItem(false);
1653     }
1654     prefs->setBool("/options/transform/stroke",      transform_stroke);
1655     prefs->setBool("/options/transform/rectcorners", transform_rectcorners);
1656     prefs->setBool("/options/transform/pattern",     transform_pattern);
1657     prefs->setBool("/options/transform/gradient",    transform_gradient);
1658 #endif
1659 
1660     doc->setModifiedSinceSave();
1661 
1662     DocumentUndo::done(doc, SP_VERB_NONE, _("Changed default display unit"));
1663 }
1664 
1665 } // namespace Dialog
1666 } // namespace UI
1667 } // namespace Inkscape
1668 
1669 /*
1670   Local Variables:
1671   mode:c++
1672   c-file-style:"stroustrup"
1673   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1674   indent-tabs-mode:nil
1675   fill-column:99
1676   End:
1677 */
1678 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
1679