1 /**
2 * \brief Inkscape Preferences dialog
3 *
4 * Authors:
5 * Marco Scholten
6 * Bruno Dilly <bruno.dilly@gmail.com>
7 *
8 * Copyright (C) 2004, 2006, 2007 Authors
9 *
10 * Released under GNU GPL. Read the file 'COPYING' for more information.
11 */
13 #ifdef HAVE_CONFIG_H
14 # include <config.h>
15 #endif
17 #include <gtkmm/frame.h>
18 #include <gtkmm/alignment.h>
19 #include <gtkmm/box.h>
21 #include "prefs-utils.h"
22 #include "ui/widget/preferences-widget.h"
23 #include "verbs.h"
24 #include "selcue.h"
25 #include <iostream>
26 #include "enums.h"
27 #include "inkscape.h"
28 #include "desktop-handles.h"
29 #include "message-stack.h"
30 #include "style.h"
31 #include "selection.h"
32 #include "selection-chemistry.h"
33 #include "xml/repr.h"
35 using namespace Inkscape::UI::Widget;
37 namespace Inkscape {
38 namespace UI {
39 namespace Widget {
41 DialogPage::DialogPage()
42 {
43 this->set_border_width(12);
44 this->set_col_spacings(12);
45 this->set_row_spacings(6);
46 }
48 void DialogPage::add_line(bool indent, const Glib::ustring label, Gtk::Widget& widget, const Glib::ustring suffix, const Glib::ustring& tip, bool expand_widget)
49 {
50 int start_col;
51 int row = this->property_n_rows();
52 Gtk::Widget* w;
53 if (expand_widget)
54 {
55 w = &widget;
56 }
57 else
58 {
59 Gtk::HBox* hb = Gtk::manage(new Gtk::HBox());
60 hb->set_spacing(12);
61 hb->pack_start(widget,false,false);
62 w = (Gtk::Widget*) hb;
63 }
64 if (label != "")
65 {
66 Gtk::Label* label_widget;
67 label_widget = Gtk::manage(new Gtk::Label(label , Gtk::ALIGN_LEFT , Gtk::ALIGN_CENTER, true));
68 label_widget->set_mnemonic_widget(widget);
69 if (indent)
70 {
71 Gtk::Alignment* alignment = Gtk::manage(new Gtk::Alignment());
72 alignment->set_padding(0, 0, 12, 0);
73 alignment->add(*label_widget);
74 this->attach(*alignment , 0, 1, row, row + 1, Gtk::FILL, Gtk::AttachOptions(), 0, 0);
75 }
76 else
77 this->attach(*label_widget , 0, 1, row, row + 1, Gtk::FILL, Gtk::AttachOptions(), 0, 0);
78 start_col = 1;
79 }
80 else
81 start_col = 0;
83 if (start_col == 0 && indent) //indent this widget
84 {
85 Gtk::Alignment* alignment = Gtk::manage(new Gtk::Alignment());
86 alignment->set_padding(0, 0, 12, 0);
87 alignment->add(*w);
88 this->attach(*alignment, start_col, 2, row, row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::AttachOptions(), 0, 0);
89 }
90 else
91 {
92 this->attach(*w, start_col, 2, row, row + 1, Gtk::FILL | Gtk::EXPAND, Gtk::AttachOptions(), 0, 0);
93 }
95 if (suffix != "")
96 {
97 Gtk::Label* suffix_widget = Gtk::manage(new Gtk::Label(suffix , Gtk::ALIGN_LEFT , Gtk::ALIGN_CENTER, true));
98 if (expand_widget)
99 this->attach(*suffix_widget, 2, 3, row, row + 1, Gtk::FILL, Gtk::AttachOptions(), 0, 0);
100 else
101 ((Gtk::HBox*)w)->pack_start(*suffix_widget,false,false);
102 }
104 if (tip != "")
105 {
106 _tooltips.set_tip (widget, tip);
107 }
109 }
111 void DialogPage::add_group_header(Glib::ustring name)
112 {
113 int row = this->property_n_rows();
114 if (name != "")
115 {
116 Gtk::Label* label_widget = Gtk::manage(new Gtk::Label(Glib::ustring(/*"<span size='large'>*/"<b>") + name +
117 Glib::ustring("</b>"/*</span>"*/) , Gtk::ALIGN_LEFT , Gtk::ALIGN_CENTER, true));
118 label_widget->set_use_markup(true);
119 this->attach(*label_widget , 0, 4, row, row + 1, Gtk::FILL, Gtk::AttachOptions(), 0, 0);
120 if (row != 1)
121 this->set_row_spacing(row - 1, 18);
122 }
123 }
125 void DialogPage::set_tip(Gtk::Widget& widget, const Glib::ustring& tip)
126 {
127 _tooltips.set_tip (widget, tip);
128 }
130 void PrefCheckButton::init(const Glib::ustring& label, const std::string& prefs_path, const std::string& attr,
131 bool default_value)
132 {
133 _prefs_path = prefs_path;
134 _attr = attr;
135 this->set_label(label);
136 this->set_active( prefs_get_int_attribute (_prefs_path.c_str(), _attr.c_str(), (int)default_value) );
137 }
139 void PrefCheckButton::on_toggled()
140 {
141 if (this->is_visible()) //only take action if the user toggled it
142 {
143 prefs_set_int_attribute (_prefs_path.c_str(), _attr.c_str(), (int) this->get_active());
144 }
145 }
147 void PrefRadioButton::init(const Glib::ustring& label, const std::string& prefs_path, const std::string& attr,
148 const std::string& string_value, bool default_value, PrefRadioButton* group_member)
149 {
150 (void)default_value;
151 _value_type = VAL_STRING;
152 _prefs_path = prefs_path;
153 _attr = attr;
154 _string_value = string_value;
155 this->set_label(label);
156 if (group_member)
157 {
158 Gtk::RadioButtonGroup rbg = group_member->get_group();
159 this->set_group(rbg);
160 }
161 const gchar* val = prefs_get_string_attribute( _prefs_path.c_str(), _attr.c_str() );
162 if ( val )
163 this->set_active( std::string( val ) == _string_value);
164 else
165 this->set_active( false );
166 }
168 void PrefRadioButton::init(const Glib::ustring& label, const std::string& prefs_path, const std::string& attr,
169 int int_value, bool default_value, PrefRadioButton* group_member)
170 {
171 _value_type = VAL_INT;
172 _prefs_path = prefs_path;
173 _attr = attr;
174 _int_value = int_value;
175 this->set_label(label);
176 if (group_member)
177 {
178 Gtk::RadioButtonGroup rbg = group_member->get_group();
179 this->set_group(rbg);
180 }
181 if (default_value)
182 this->set_active( prefs_get_int_attribute( _prefs_path.c_str(), _attr.c_str(), int_value ) == _int_value);
183 else
184 this->set_active( prefs_get_int_attribute( _prefs_path.c_str(), _attr.c_str(), int_value + 1 )== _int_value);
185 }
187 void PrefRadioButton::on_toggled()
188 {
189 this->changed_signal.emit(this->get_active());
190 if (this->is_visible() && this->get_active() ) //only take action if toggled by user (to active)
191 {
192 if ( _value_type == VAL_STRING )
193 prefs_set_string_attribute ( _prefs_path.c_str(), _attr.c_str(), _string_value.c_str());
194 else if ( _value_type == VAL_INT )
195 prefs_set_int_attribute ( _prefs_path.c_str(), _attr.c_str(), _int_value);
196 }
197 }
199 void PrefSpinButton::init(const std::string& prefs_path, const std::string& attr,
200 double lower, double upper, double step_increment, double page_increment,
201 double default_value, bool is_int, bool is_percent)
202 {
203 _prefs_path = prefs_path;
204 _attr = attr;
205 _is_int = is_int;
206 _is_percent = is_percent;
208 double value;
209 if (is_int)
210 if (is_percent)
211 value = 100 * prefs_get_double_attribute_limited (prefs_path.c_str(), attr.c_str(), default_value, lower/100.0, upper/100.0);
212 else
213 value = (double) prefs_get_int_attribute_limited (prefs_path.c_str(), attr.c_str(), (int) default_value, (int) lower, (int) upper);
214 else
215 value = prefs_get_double_attribute_limited (prefs_path.c_str(), attr.c_str(), default_value, lower, upper);
217 this->set_range (lower, upper);
218 this->set_increments (step_increment, page_increment);
219 this->set_numeric();
220 this->set_value (value);
221 this->set_width_chars(6);
222 if (is_int)
223 this->set_digits(0);
224 else if (step_increment < 0.1)
225 this->set_digits(4);
226 else
227 this->set_digits(2);
229 }
231 void PrefSpinButton::on_value_changed()
232 {
233 if (this->is_visible()) //only take action if user changed value
234 {
235 if (_is_int)
236 if (_is_percent)
237 prefs_set_double_attribute(_prefs_path.c_str(), _attr.c_str(), this->get_value()/100.0);
238 else
239 prefs_set_int_attribute(_prefs_path.c_str(), _attr.c_str(), (int) this->get_value());
240 else
241 prefs_set_double_attribute (_prefs_path.c_str(), _attr.c_str(), this->get_value());
242 }
243 }
245 const double ZoomCorrRuler::textsize = 7;
246 const double ZoomCorrRuler::textpadding = 5;
248 ZoomCorrRuler::ZoomCorrRuler(int width, int height) :
249 _unitconv(1.0),
250 _border(5)
251 {
252 set_size(width, height);
253 }
255 void ZoomCorrRuler::set_size(int x, int y)
256 {
257 _min_width = x;
258 _height = y;
259 set_size_request(x + _border*2, y + _border*2);
260 }
262 // The following two functions are borrowed from 2geom's toy-framework-2; if they are useful in
263 // other locations, we should perhaps make them (or adapted versions of them) publicly available
264 static void
265 draw_text(cairo_t *cr, Geom::Point loc, const char* txt, bool bottom = "false",
266 double fontsize = ZoomCorrRuler::textsize, std::string fontdesc = "Sans") {
267 PangoLayout* layout = pango_cairo_create_layout (cr);
268 pango_layout_set_text(layout, txt, -1);
270 // set font and size
271 std::ostringstream sizestr;
272 sizestr << fontsize;
273 fontdesc = fontdesc + " " + sizestr.str();
274 PangoFontDescription *font_desc = pango_font_description_from_string(fontdesc.c_str());
275 pango_layout_set_font_description(layout, font_desc);
276 pango_font_description_free (font_desc);
278 PangoRectangle logical_extent;
279 pango_layout_get_pixel_extents(layout, NULL, &logical_extent);
280 cairo_move_to(cr, loc[Geom::X], loc[Geom::Y] - (bottom ? logical_extent.height : 0));
281 pango_cairo_show_layout(cr, layout);
282 }
284 static void
285 draw_number(cairo_t *cr, Geom::Point pos, double num) {
286 std::ostringstream number;
287 number << num;
288 draw_text(cr, pos, number.str().c_str(), true);
289 }
291 /*
292 * \arg dist The distance between consecutive minor marks
293 * \arg major_interval Number of marks after which to draw a major mark
294 */
295 void
296 ZoomCorrRuler::draw_marks(Cairo::RefPtr<Cairo::Context> cr, double dist, int major_interval) {
297 const double zoomcorr = prefs_get_double_attribute("options.zoomcorrection", "value", 1.0);
298 double mark = 0;
299 int i = 0;
300 while (mark <= _drawing_width) {
301 cr->move_to(mark, _height);
302 if ((i % major_interval) == 0) {
303 // major mark
304 cr->line_to(mark, 0);
305 Geom::Point textpos(mark + 3, ZoomCorrRuler::textsize + ZoomCorrRuler::textpadding);
306 draw_number(cr->cobj(), textpos, dist * i);
307 } else {
308 // minor mark
309 cr->line_to(mark, ZoomCorrRuler::textsize + 2 * ZoomCorrRuler::textpadding);
310 }
311 mark += dist * zoomcorr / _unitconv;
312 ++i;
313 }
314 }
316 void
317 ZoomCorrRuler::redraw() {
318 Glib::RefPtr<Gdk::Window> window = get_window();
319 Cairo::RefPtr<Cairo::Context> cr = window->create_cairo_context();
321 int w, h;
322 window->get_size(w, h);
323 _drawing_width = w - _border * 2;
325 cr->set_source_rgb(1.0, 1.0, 1.0);
326 cr->set_fill_rule(Cairo::FILL_RULE_WINDING);
327 cr->rectangle(0, 0, w, _height + _border*2);
328 cr->fill();
330 cr->set_source_rgb(0.0, 0.0, 0.0);
331 cr->set_line_width(0.5);
333 cr->translate(_border, _border); // so that we have a small white border around the ruler
334 cr->move_to (0, _height);
335 cr->line_to (_drawing_width, _height);
337 const char *abbr = prefs_get_string_attribute("options.zoomcorrection", "unit");
338 if (!strcmp(abbr, "cm")) {
339 draw_marks(cr, 0.1, 10);
340 } else if (!strcmp(abbr, "ft")) {
341 draw_marks(cr, 1/12.0, 12);
342 } else if (!strcmp(abbr, "in")) {
343 draw_marks(cr, 0.25, 4);
344 } else if (!strcmp(abbr, "m")) {
345 draw_marks(cr, 1/10.0, 10);
346 } else if (!strcmp(abbr, "mm")) {
347 draw_marks(cr, 10, 10);
348 } else if (!strcmp(abbr, "pc")) {
349 draw_marks(cr, 1, 10);
350 } else if (!strcmp(abbr, "pt")) {
351 draw_marks(cr, 10, 10);
352 } else if (!strcmp(abbr, "px")) {
353 draw_marks(cr, 10, 10);
354 } else {
355 draw_marks(cr, 1, 1);
356 }
357 cr->stroke();
358 }
360 bool
361 ZoomCorrRuler::on_expose_event(GdkEventExpose *event) {
362 this->redraw();
363 return true;
364 }
366 void
367 ZoomCorrRulerSlider::on_slider_value_changed()
368 {
369 if (this->is_visible() || freeze) //only take action if user changed value
370 {
371 freeze = true;
372 prefs_set_double_attribute ("options.zoomcorrection", "value", _slider.get_value() / 100.0);
373 _sb.set_value(_slider.get_value());
374 _ruler.redraw();
375 freeze = false;
376 }
377 }
379 void
380 ZoomCorrRulerSlider::on_spinbutton_value_changed()
381 {
382 if (this->is_visible() || freeze) //only take action if user changed value
383 {
384 freeze = true;
385 prefs_set_double_attribute ("options.zoomcorrection", "value", _sb.get_value() / 100.0);
386 _slider.set_value(_sb.get_value());
387 _ruler.redraw();
388 freeze = false;
389 }
390 }
392 void
393 ZoomCorrRulerSlider::on_unit_changed() {
394 if (GPOINTER_TO_INT(_unit.get_data("sensitive")) == 0) {
395 // when the unit menu is initialized, the unit is set to the default but
396 // it needs to be reset later so we don't perform the change in this case
397 return;
398 }
399 prefs_set_string_attribute ("options.zoomcorrection", "unit", _unit.getUnitAbbr().c_str());
400 double conv = _unit.getConversion(_unit.getUnitAbbr(), "px");
401 _ruler.set_unit_conversion(conv);
402 if (_ruler.is_visible()) {
403 _ruler.redraw();
404 }
405 }
407 void
408 ZoomCorrRulerSlider::init(int ruler_width, int ruler_height, double lower, double upper,
409 double step_increment, double page_increment, double default_value)
410 {
411 double value = prefs_get_double_attribute_limited ("options.zoomcorrection", "value", default_value, lower, upper) * 100.0;
413 freeze = false;
415 _ruler.set_size(ruler_width, ruler_height);
417 _slider.set_size_request(_ruler.width(), -1);
418 _slider.set_range (lower, upper);
419 _slider.set_increments (step_increment, page_increment);
420 _slider.set_value (value);
421 _slider.set_digits(2);
423 _slider.signal_value_changed().connect(sigc::mem_fun(*this, &ZoomCorrRulerSlider::on_slider_value_changed));
424 _sb.signal_value_changed().connect(sigc::mem_fun(*this, &ZoomCorrRulerSlider::on_spinbutton_value_changed));
425 _unit.signal_changed().connect(sigc::mem_fun(*this, &ZoomCorrRulerSlider::on_unit_changed));
427 _sb.set_range (lower, upper);
428 _sb.set_increments (step_increment, page_increment);
429 _sb.set_value (value);
430 _sb.set_digits(2);
432 _unit.set_data("sensitive", GINT_TO_POINTER(0));
433 _unit.setUnitType(UNIT_TYPE_LINEAR);
434 _unit.set_data("sensitive", GINT_TO_POINTER(1));
435 _unit.setUnit(prefs_get_string_attribute ("options.zoomcorrection", "unit"));
437 Gtk::Table *table = Gtk::manage(new Gtk::Table());
438 Gtk::Alignment *alignment1 = Gtk::manage(new Gtk::Alignment(0.5,1,0,0));
439 Gtk::Alignment *alignment2 = Gtk::manage(new Gtk::Alignment(0.5,1,0,0));
440 alignment1->add(_sb);
441 alignment2->add(_unit);
443 table->attach(_slider, 0, 1, 0, 1);
444 table->attach(*alignment1, 1, 2, 0, 1, static_cast<Gtk::AttachOptions>(0));
445 table->attach(_ruler, 0, 1, 1, 2);
446 table->attach(*alignment2, 1, 2, 1, 2, static_cast<Gtk::AttachOptions>(0));
448 this->pack_start(*table, Gtk::PACK_EXPAND_WIDGET);
449 }
451 void PrefCombo::init(const std::string& prefs_path, const std::string& attr,
452 Glib::ustring labels[], int values[], int num_items, int default_value)
453 {
454 _prefs_path = prefs_path;
455 _attr = attr;
456 int row = 0;
457 int value = prefs_get_int_attribute (_prefs_path.c_str(), _attr.c_str(), default_value);
459 for (int i = 0 ; i < num_items; ++i)
460 {
461 this->append_text(labels[i]);
462 _values.push_back(values[i]);
463 if (value == values[i])
464 row = i;
465 }
466 this->set_active(row);
467 }
469 void PrefCombo::on_changed()
470 {
471 if (this->is_visible()) //only take action if user changed value
472 {
473 prefs_set_int_attribute (_prefs_path.c_str(), _attr.c_str(), _values[this->get_active_row_number()]);
474 }
475 }
477 void PrefEntryButtonHBox::init(const std::string& prefs_path, const std::string& attr,
478 bool visibility, gchar* default_string)
479 {
480 _prefs_path = prefs_path;
481 _attr = attr;
482 _default_string = default_string;
483 relatedEntry = new Gtk::Entry();
484 relatedButton = new Gtk::Button(_("Reset"));
485 relatedEntry->set_invisible_char('*');
486 relatedEntry->set_visibility(visibility);
487 relatedEntry->set_text(prefs_get_string_attribute(_prefs_path.c_str(), _attr.c_str()));
488 this->pack_start(*relatedEntry);
489 this->pack_start(*relatedButton);
490 relatedButton->signal_clicked().connect(
491 sigc::mem_fun(*this, &PrefEntryButtonHBox::onRelatedButtonClickedCallback));
492 relatedEntry->signal_changed().connect(
493 sigc::mem_fun(*this, &PrefEntryButtonHBox::onRelatedEntryChangedCallback));
494 }
496 void PrefEntryButtonHBox::onRelatedEntryChangedCallback()
497 {
498 if (this->is_visible()) //only take action if user changed value
499 {
500 prefs_set_string_attribute(_prefs_path.c_str(), _attr.c_str(),
501 relatedEntry->get_text().c_str());
502 }
503 }
505 void PrefEntryButtonHBox::onRelatedButtonClickedCallback()
506 {
507 if (this->is_visible()) //only take action if user changed value
508 {
509 prefs_set_string_attribute(_prefs_path.c_str(), _attr.c_str(),
510 _default_string);
511 relatedEntry->set_text(_default_string);
512 }
513 }
516 void PrefFileButton::init(const std::string& prefs_path, const std::string& attr)
517 {
518 _prefs_path = prefs_path;
519 _attr = attr;
520 select_filename(Glib::filename_from_utf8(prefs_get_string_attribute(_prefs_path.c_str(), _attr.c_str())));
522 signal_selection_changed().connect(sigc::mem_fun(*this, &PrefFileButton::onFileChanged));
523 }
525 void PrefFileButton::onFileChanged()
526 {
527 prefs_set_string_attribute(_prefs_path.c_str(), _attr.c_str(), Glib::filename_to_utf8(get_filename()).c_str());
528 }
530 void PrefEntry::init(const std::string& prefs_path, const std::string& attr,
531 bool visibility)
532 {
533 _prefs_path = prefs_path;
534 _attr = attr;
535 this->set_invisible_char('*');
536 this->set_visibility(visibility);
537 this->set_text(prefs_get_string_attribute(_prefs_path.c_str(), _attr.c_str()));
538 }
540 void PrefEntry::on_changed()
541 {
542 if (this->is_visible()) //only take action if user changed value
543 {
544 prefs_set_string_attribute(_prefs_path.c_str(), _attr.c_str(), this->get_text().c_str());
545 }
546 }
548 void PrefColorPicker::init(const Glib::ustring& label, const std::string& prefs_path, const std::string& attr,
549 guint32 default_rgba)
550 {
551 _prefs_path = prefs_path;
552 _attr = attr;
553 _title = label;
554 this->setRgba32( prefs_get_int_attribute (_prefs_path.c_str(), _attr.c_str(), (int)default_rgba) );
555 }
557 void PrefColorPicker::on_changed (guint32 rgba)
558 {
559 if (this->is_visible()) //only take action if the user toggled it
560 {
561 prefs_set_int_attribute (_prefs_path.c_str(), _attr.c_str(), (int) rgba);
562 }
563 }
565 void PrefUnit::init(const std::string& prefs_path, const std::string& attr)
566 {
567 _prefs_path = prefs_path;
568 _attr = attr;
569 setUnitType(UNIT_TYPE_LINEAR);
570 gchar const * prefval = prefs_get_string_attribute(_prefs_path.c_str(), _attr.c_str());
571 setUnit(prefval);
572 }
574 void PrefUnit::on_changed()
575 {
576 if (this->is_visible()) //only take action if user changed value
577 {
578 prefs_set_string_attribute(_prefs_path.c_str(), _attr.c_str(), getUnitAbbr().c_str());
579 }
580 }
582 } // namespace Widget
583 } // namespace UI
584 } // namespace Inkscape
586 /*
587 Local Variables:
588 mode:c++
589 c-file-style:"stroustrup"
590 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
591 indent-tabs-mode:nil
592 fill-column:99
593 End:
594 */
595 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :