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