18610f613b6dcb37962d6a6331c14ee6d826a7eb
1 /**
2 * \brief Filter Effects dialog
3 *
4 * Authors:
5 * Nicholas Bishop <nicholasbishop@gmail.org>
6 *
7 * Copyright (C) 2007 Authors
8 *
9 * Released under GNU GPL. Read the file 'COPYING' for more information.
10 */
12 #ifdef HAVE_CONFIG_H
13 # include <config.h>
14 #endif
16 #include <gtk/gtktreeview.h>
17 #include <gtkmm/cellrenderertext.h>
18 #include <gtkmm/paned.h>
19 #include <gtkmm/scale.h>
20 #include <gtkmm/scrolledwindow.h>
21 #include <gtkmm/spinbutton.h>
22 #include <gtkmm/stock.h>
23 #include <glibmm/i18n.h>
25 #include "application/application.h"
26 #include "application/editor.h"
27 #include "desktop.h"
28 #include "desktop-handles.h"
29 #include "dialog-manager.h"
30 #include "document.h"
31 #include "filter-chemistry.h"
32 #include "filter-effects-dialog.h"
33 #include "inkscape.h"
34 #include "sp-feblend.h"
35 #include "sp-fecomposite.h"
36 #include "sp-fedisplacementmap.h"
37 #include "sp-femerge.h"
38 #include "sp-filter-primitive.h"
39 #include "sp-gaussian-blur.h"
40 #include "sp-feoffset.h"
41 #include "verbs.h"
42 #include "xml/node.h"
43 #include "xml/repr.h"
44 #include <sstream>
46 #include <iostream>
48 namespace Inkscape {
49 namespace UI {
50 namespace Dialog {
52 Glib::RefPtr<Gtk::Menu> create_popup_menu(Gtk::Widget& parent, sigc::slot<void> dup,
53 sigc::slot<void> rem)
54 {
55 Glib::RefPtr<Gtk::Menu> menu(new Gtk::Menu);
57 menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("_Duplicate"), dup));
58 Gtk::MenuItem* mi = Gtk::manage(new Gtk::ImageMenuItem(Gtk::Stock::REMOVE));
59 menu->append(*mi);
60 mi->signal_activate().connect(rem);
61 mi->show();
62 menu->accelerate(parent);
64 return menu;
65 }
67 static void try_id_change(SPObject* ob, const Glib::ustring& text)
68 {
69 // FIXME: this needs more serious error checking...
70 if(ob && !SP_ACTIVE_DOCUMENT->getObjectById(text.c_str())) {
71 SPException ex;
72 SP_EXCEPTION_INIT(&ex);
73 sp_object_setAttribute(ob, "id", text.c_str(), &ex);
74 sp_document_done(SP_ACTIVE_DOCUMENT, SP_VERB_DIALOG_FILTER_EFFECTS, _("Set object ID"));
75 }
76 }
78 /*** FilterModifier ***/
79 FilterEffectsDialog::FilterModifier::FilterModifier()
80 : _add(Gtk::Stock::ADD)
81 {
82 Gtk::ScrolledWindow* sw = Gtk::manage(new Gtk::ScrolledWindow);
83 pack_start(*sw);
84 pack_start(_add, false, false);
85 sw->add(_list);
87 _list.set_model(_model);
88 _list.append_column_editable(_("_Filter"), _columns.id);
89 ((Gtk::CellRendererText*)_list.get_column(0)->get_first_cell_renderer())->
90 signal_edited().connect(sigc::mem_fun(*this, &FilterEffectsDialog::FilterModifier::filter_name_edited));
92 sw->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
93 sw->set_shadow_type(Gtk::SHADOW_IN);
94 show_all_children();
95 _add.signal_clicked().connect(sigc::mem_fun(*this, &FilterModifier::add_filter));
96 _list.signal_button_release_event().connect_notify(
97 sigc::mem_fun(*this, &FilterModifier::filter_list_button_release));
98 _menu = create_popup_menu(*this, sigc::mem_fun(*this, &FilterModifier::duplicate_filter),
99 sigc::mem_fun(*this, &FilterModifier::remove_filter));
101 update_filters();
102 }
104 Glib::SignalProxy0<void> FilterEffectsDialog::FilterModifier::signal_selection_changed()
105 {
106 return _list.get_selection()->signal_changed();
107 }
109 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
110 {
111 if(_list.get_selection()) {
112 Gtk::TreeModel::iterator i = _list.get_selection()->get_selected();
114 if(i)
115 return (*i)[_columns.filter];
116 }
118 return 0;
119 }
121 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
122 {
123 if(filter) {
124 for(Gtk::TreeModel::iterator i = _model->children().begin();
125 i != _model->children().end(); ++i) {
126 if((*i)[_columns.filter] == filter) {
127 _list.get_selection()->select(i);
128 break;
129 }
130 }
131 }
132 }
134 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
135 {
136 if((event->type == GDK_BUTTON_RELEASE) && (event->button == 3)) {
137 const bool sensitive = get_selected_filter() != NULL;
138 _menu->items()[0].set_sensitive(sensitive);
139 _menu->items()[1].set_sensitive(sensitive);
140 _menu->popup(event->button, event->time);
141 }
142 }
144 void FilterEffectsDialog::FilterModifier::add_filter()
145 {
146 SPDocument* doc = sp_desktop_document(SP_ACTIVE_DESKTOP);
147 SPFilter* filter = new_filter(doc);
149 update_filters();
151 select_filter(filter);
153 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter"));
154 }
156 void FilterEffectsDialog::FilterModifier::remove_filter()
157 {
158 SPFilter *filter = get_selected_filter();
160 if(filter) {
161 SPDocument* doc = filter->document;
162 sp_repr_unparent(filter->repr);
164 sp_document_done(doc, SP_VERB_DIALOG_FILTER_EFFECTS, _("Remove filter"));
166 update_filters();
167 }
168 }
170 void FilterEffectsDialog::FilterModifier::duplicate_filter()
171 {
172 SPFilter *filter = get_selected_filter();
174 if(filter) {
175 //SPFilter *dupfilter = filter_duplicate(sp_desktop_document(SP_ACTIVE_DESKTOP), filter);
177 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter"));
179 update_filters();
180 }
181 }
183 void FilterEffectsDialog::FilterModifier::filter_name_edited(const Glib::ustring& path, const Glib::ustring& text)
184 {
185 Gtk::TreeModel::iterator i = _model->get_iter(path);
187 if(i)
188 try_id_change((SPObject*)(*i)[_columns.filter], text);
189 }
191 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
192 : Glib::ObjectBase(typeid(CellRendererConnection)),
193 _primitive(*this, "primitive", 0)
194 {
195 }
197 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
198 {
199 return _primitive.get_proxy();
200 }
202 int FilterEffectsDialog::CellRendererConnection::input_count(const SPFilterPrimitive* prim)
203 {
204 if(!prim)
205 return 0;
206 else if(SP_IS_FEBLEND(prim) || SP_IS_FECOMPOSITE(prim) || SP_IS_FEDISPLACEMENTMAP(prim))
207 return 2;
208 else if(SP_IS_FEMERGE(prim)) {
209 // Return the number of feMergeNode connections plus an extra one for adding a new input
210 int count = 1;
211 for(const SPObject* o = prim->firstChild(); o; o = o->next, ++count);
212 return count;
213 }
214 else
215 return 1;
216 }
218 void FilterEffectsDialog::CellRendererConnection::get_size_vfunc(
219 Gtk::Widget& widget, const Gdk::Rectangle* cell_area,
220 int* x_offset, int* y_offset, int* width, int* height) const
221 {
222 PrimitiveList& primlist = dynamic_cast<PrimitiveList&>(widget);
224 if(x_offset)
225 (*x_offset) = 0;
226 if(y_offset)
227 (*y_offset) = 0;
228 if(width)
229 (*width) = size * primlist.primitive_count();
230 if(height) {
231 // Scale the height depending on the number of inputs, unless it's
232 // the first primitive, in which case their are no connections
233 SPFilterPrimitive* prim = (SPFilterPrimitive*)_primitive.get_value();
234 (*height) = primlist.is_first(prim) ? size : size * input_count(prim);
235 }
236 }
238 /*** PrimitiveList ***/
239 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
240 : _dialog(d), _in_drag(0)
241 {
242 add_events(Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK);
244 _model = Gtk::ListStore::create(_columns);
246 // TODO: reenable this once it is possible to modify the order in the backend
247 //set_reorderable(true);
249 set_model(_model);
250 append_column(_("_Type"), _columns.type);
252 signal_selection_changed().connect(sigc::mem_fun(*this, &PrimitiveList::queue_draw));
254 CellRendererConnection* cell = new CellRendererConnection;
255 int cols_count = append_column(_("Connections"), *cell);
256 Gtk::TreeViewColumn* col = get_column(cols_count - 1);
257 if(col)
258 col->add_attribute(cell->property_primitive(), _columns.primitive);
259 }
261 Glib::SignalProxy0<void> FilterEffectsDialog::PrimitiveList::signal_selection_changed()
262 {
263 return get_selection()->signal_changed();
264 }
266 /* Add all filter primitives in the current to the list.
267 Keeps the same selection if possible, otherwise selects the first element */
268 void FilterEffectsDialog::PrimitiveList::update()
269 {
270 SPFilter* f = _dialog._filter_modifier.get_selected_filter();
271 const SPFilterPrimitive* active_prim = get_selected();
272 bool active_found = false;
274 _model->clear();
276 if(f) {
277 _dialog._primitive_box.set_sensitive(true);
279 for(SPObject *prim_obj = f->children;
280 prim_obj && SP_IS_FILTER_PRIMITIVE(prim_obj);
281 prim_obj = prim_obj->next) {
282 SPFilterPrimitive *prim = SP_FILTER_PRIMITIVE(prim_obj);
283 if(prim) {
284 Gtk::TreeModel::Row row = *_model->append();
285 row[_columns.primitive] = prim;
286 row[_columns.type_id] = FPConverter.get_id_from_key(prim->repr->name());
287 row[_columns.type] = FPConverter.get_label(row[_columns.type_id]);
288 row[_columns.id] = SP_OBJECT_ID(prim);
290 if(prim == active_prim) {
291 get_selection()->select(row);
292 active_found = true;
293 }
294 }
295 }
297 if(!active_found && _model->children().begin())
298 get_selection()->select(_model->children().begin());
299 }
300 else {
301 _dialog._primitive_box.set_sensitive(false);
302 }
303 }
305 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
306 {
307 _primitive_menu = menu;
308 }
310 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
311 {
312 if(_dialog._filter_modifier.get_selected_filter()) {
313 Gtk::TreeModel::iterator i = get_selection()->get_selected();
314 if(i)
315 return (*i)[_columns.primitive];
316 }
318 return 0;
319 }
321 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
322 {
323 for(Gtk::TreeIter i = _model->children().begin();
324 i != _model->children().end(); ++i) {
325 if((*i)[_columns.primitive] == prim)
326 get_selection()->select(i);
327 }
328 }
330 bool FilterEffectsDialog::PrimitiveList::on_expose_event(GdkEventExpose* e)
331 {
332 Gtk::TreeView::on_expose_event(e);
334 SPFilterPrimitive* prim = get_selected();
336 int row_index = 0;
337 int row_count = get_model()->children().size();
338 int fheight = 0;
339 for(Gtk::TreeIter row = get_model()->children().begin();
340 row != get_model()->children().end(); ++row, ++row_index) {
341 Gdk::Rectangle rct, clip(&e->area);
342 get_cell_area(get_model()->get_path(row), *get_column(1), rct);
343 const int x = rct.get_x(), y = rct.get_y(), h = rct.get_height();
345 // For calculating the width of cells, the height of the first row is used
346 if(row_index == 0)
347 fheight = h;
349 // Check mouse state
350 int mx, my;
351 Gdk::ModifierType mask;
352 get_bin_window()->get_pointer(mx, my, mask);
354 // Outline the bottom of the connection area
355 const int outline_x = x + fheight * (row_count - row_index);
356 get_bin_window()->draw_line(get_style()->get_dark_gc(Gtk::STATE_NORMAL),
357 x, y + h, outline_x, y + h);
359 // The first row can't have any inputs
360 if(row_index == 0)
361 continue;
363 // Side outline
364 get_bin_window()->draw_line(get_style()->get_dark_gc(Gtk::STATE_NORMAL),
365 outline_x, y, outline_x, y + h);
367 std::vector<Gdk::Point> con_poly;
368 int con_drag_y;
369 bool inside;
370 const SPFilterPrimitive* row_prim = (*row)[_columns.primitive];
371 const int inputs = CellRendererConnection::input_count(row_prim);
373 if(SP_IS_FEMERGE(row_prim)) {
374 for(int i = 0; i < inputs; ++i) {
375 inside = do_connection_node(row, i, con_poly, mx, my);
376 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
377 get_style()->get_dark_gc(Gtk::STATE_NORMAL) :
378 get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
379 inside, con_poly);
381 // TODO: draw connections for each of the feMergeNodes
382 }
383 }
384 else {
385 // Draw "in" shape
386 inside = do_connection_node(row, 0, con_poly, mx, my);
387 con_drag_y = con_poly[2].get_y();
388 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
389 get_style()->get_dark_gc(Gtk::STATE_NORMAL) :
390 get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
391 inside, con_poly);
392 // Draw "in" connection
393 draw_connection(find_result(row, SP_ATTR_IN), outline_x, con_poly[2].get_y(), row_count);
395 if(inputs == 2) {
396 // Draw "in2" shape
397 inside = do_connection_node(row, 1, con_poly, mx, my);
398 if(_in_drag == 2)
399 con_drag_y = con_poly[2].get_y();
400 get_bin_window()->draw_polygon(inside && mask & GDK_BUTTON1_MASK ?
401 get_style()->get_dark_gc(Gtk::STATE_NORMAL) :
402 get_style()->get_dark_gc(Gtk::STATE_ACTIVE),
403 inside, con_poly);
404 // Draw "in2" connection
405 draw_connection(find_result(row, SP_ATTR_IN2), outline_x, con_poly[2].get_y(), row_count);
406 }
408 // Draw drag connection
409 if(row_prim == prim && _in_drag) {
410 get_bin_window()->draw_line(get_style()->get_black_gc(), outline_x, con_drag_y,
411 mx, con_drag_y);
412 get_bin_window()->draw_line(get_style()->get_black_gc(), mx, con_drag_y, mx, my);
413 }
414 }
415 }
417 return true;
418 }
420 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int x1, const int y1,
421 const int row_count)
422 {
423 if(input != _model->children().end()) {
424 Gdk::Rectangle rct;
426 get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
427 const int fheight = rct.get_height();
429 get_cell_area(get_model()->get_path(input), *get_column(1), rct);
430 const int row_index = find_index(input);
431 const int x2 = rct.get_x() + fheight * (row_count - row_index) - fheight / 2;
432 const int y2 = rct.get_y() + rct.get_height();
434 // Draw an 'L'-shaped connection
435 get_bin_window()->draw_line(get_style()->get_black_gc(), x1, y1, x2, y1);
436 get_bin_window()->draw_line(get_style()->get_black_gc(), x2, y1, x2, y2);
437 }
438 }
440 // Creates a triangle outline of the connection node and returns true if (x,y) is inside the node
441 bool FilterEffectsDialog::PrimitiveList::do_connection_node(const Gtk::TreeIter& row, const int input,
442 std::vector<Gdk::Point>& points,
443 const int ix, const int iy)
444 {
445 Gdk::Rectangle rct;
446 const int input_count = CellRendererConnection::input_count((*row)[_columns.primitive]);
448 get_cell_area(get_model()->get_path(_model->children().begin()), *get_column(1), rct);
449 const int fheight = rct.get_height();
451 get_cell_area(_model->get_path(row), *get_column(1), rct);
452 const float h = rct.get_height() / input_count;
454 const int x = rct.get_x() + fheight * (_model->children().size() - find_index(row));
455 const int con_w = (int)(fheight * 0.35f);
456 const int con_y = (int)(rct.get_y() + (h / 2) - con_w + (input * h));
457 points.clear();
458 points.push_back(Gdk::Point(x, con_y));
459 points.push_back(Gdk::Point(x, con_y + con_w * 2));
460 points.push_back(Gdk::Point(x - con_w, con_y + con_w));
462 return ix >= x - h && iy >= con_y && ix <= x && iy <= points[1].get_y();
463 }
465 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
466 const SPAttributeEnum attr)
467 {
468 SPFilterPrimitive* prim = (*start)[_columns.primitive];
469 Gtk::TreeIter target = _model->children().end();
470 int image;
472 if(attr == SP_ATTR_IN)
473 image = prim->image_in;
474 else if(attr == SP_ATTR_IN2) {
475 if(SP_IS_FEBLEND(prim))
476 image = SP_FEBLEND(prim)->in2;
477 else if(SP_IS_FECOMPOSITE(prim))
478 image = SP_FECOMPOSITE(prim)->in2;
479 /*else if(SP_IS_FEDISPLACEMENTMAP(prim))
480 image = SP_FEDISPLACEMENTMAP(prim)->in2;*/
481 else
482 return target;
483 }
484 else
485 return target;
487 if(image >= 0) {
488 for(Gtk::TreeIter i = _model->children().begin();
489 i != start; ++i) {
490 if(((SPFilterPrimitive*)(*i)[_columns.primitive])->image_out == image)
491 target = i;
492 }
493 }
495 return target;
496 }
498 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
499 {
500 int i = 0;
501 for(Gtk::TreeIter iter = _model->children().begin();
502 iter != target; ++iter, ++i);
503 return i;
504 }
506 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
507 {
508 Gtk::TreePath path;
509 Gtk::TreeViewColumn* col;
510 const int x = (int)e->x, y = (int)e->y;
511 int cx, cy;
513 if(get_path_at_pos(x, y, path, col, cx, cy)) {
514 Gtk::TreeIter iter = _model->get_iter(path);
515 if(iter != _model->children().begin()) {
516 std::vector<Gdk::Point> points;
517 if(do_connection_node(_model->get_iter(path), 0, points, x, y))
518 _in_drag = 1;
519 else if(do_connection_node(_model->get_iter(path), 1, points, x, y))
520 _in_drag = 2;
522 queue_draw();
523 }
524 }
526 return Gtk::TreeView::on_button_press_event(e);
527 }
529 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
530 {
531 queue_draw();
533 return Gtk::TreeView::on_motion_notify_event(e);
534 }
536 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
537 {
538 SPFilterPrimitive *prim = get_selected(), *target;
540 if(_in_drag && prim) {
541 Gtk::TreePath path;
542 Gtk::TreeViewColumn* col;
543 int cx, cy;
545 if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
546 const gchar *in_val = 0;
547 Gtk::TreeIter target_iter = _model->get_iter(path);
548 target = (*target_iter)[_columns.primitive];
550 // Ensure that the target comes before the selected primitive
551 for(Gtk::TreeIter iter = _model->children().begin();
552 iter != get_selection()->get_selected(); ++iter) {
553 if(iter == target_iter) {
554 Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
555 // Make sure the target has a result
556 const gchar *gres = repr->attribute("result");
557 if(!gres) {
558 const Glib::ustring result = "result" +
559 Glib::Ascii::dtostr(SP_FILTER(prim->parent)->_image_number_next);
560 repr->setAttribute("result", result.c_str());
561 in_val = result.c_str();
562 }
563 else
564 in_val = gres;
565 break;
566 }
567 }
569 if(_in_drag == 1)
570 SP_OBJECT_REPR(prim)->setAttribute("in", in_val);
571 else if(_in_drag == 2)
572 SP_OBJECT_REPR(prim)->setAttribute("in2", in_val);
573 }
575 _in_drag = 0;
576 queue_draw();
578 _dialog.update_settings_view();
579 }
581 if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
582 const bool sensitive = get_selected() != NULL;
583 _primitive_menu->items()[0].set_sensitive(sensitive);
584 _primitive_menu->items()[1].set_sensitive(sensitive);
585 _primitive_menu->popup(e->button, e->time);
587 return true;
588 }
589 else
590 return Gtk::TreeView::on_button_release_event(e);
591 }
593 // Reorder the filter primitives to match the list order
594 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>&)
595 {
596 SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
598 for(Gtk::TreeModel::iterator iter = _model->children().begin();
599 iter != _model->children().end(); ++iter) {
600 SPFilterPrimitive* prim = (*iter)[_columns.primitive];
601 if(prim)
602 ;//reorder_primitive(filter, prim->repr->position(), ndx); /* FIXME */
603 }
605 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
606 }
608 int FilterEffectsDialog::PrimitiveList::primitive_count() const
609 {
610 return _model->children().size();
611 }
613 bool FilterEffectsDialog::PrimitiveList::is_first(const SPFilterPrimitive* prim) const
614 {
615 return (*_model->children().begin())[_columns.primitive] == prim;
616 }
618 /*** SettingsGroup ***/
619 FilterEffectsDialog::SettingsGroup::SettingsGroup()
620 {
621 show();
622 }
624 void FilterEffectsDialog::SettingsGroup::init(FilterEffectsDialog* dlg, Glib::RefPtr<Gtk::SizeGroup> sg)
625 {
626 _dialog = dlg;
627 _dialog->_settings.pack_start(*this, false, false);
628 _sizegroup = sg;
629 }
631 /* Adds a new settings widget using the specified label. The label will be formatted with a colon
632 and all widgets within the setting group are aligned automatically. */
633 void FilterEffectsDialog::SettingsGroup::add_setting_generic(Gtk::Widget& w, const Glib::ustring& label)
634 {
635 Gtk::Label *lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT));
636 Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
637 hb->set_spacing(12);
638 hb->pack_start(*lbl, false, false);
639 hb->pack_start(w);
640 pack_start(*hb);
642 _sizegroup->add_widget(*lbl);
644 hb->show();
645 lbl->show();
647 w.show();
648 }
650 /* For SpinSlider settings */
651 void FilterEffectsDialog::SettingsGroup::add_setting(SpinSlider& ss, const SPAttributeEnum attr,
652 const Glib::ustring& label)
653 {
654 add_setting_generic(ss, label);
655 ss.signal_value_changed().connect(
656 sigc::bind(sigc::mem_fun(_dialog, &FilterEffectsDialog::set_attr_spinslider), attr, &ss));
657 }
659 /* For subgroups of settings */
660 void FilterEffectsDialog::SettingsGroup::add_setting(std::vector<Gtk::Widget*>& w, const Glib::ustring& label)
661 {
662 Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
663 for(unsigned int i = 0; i < w.size(); ++i)
664 hb->pack_start(*w[i]);
665 hb->set_spacing(12);
666 add_setting_generic(*hb, label);
667 }
669 /*** FilterEffectsDialog ***/
671 FilterEffectsDialog::FilterEffectsDialog()
672 : Dialog ("dialogs.filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
673 _primitive_list(*this),
674 _add_primitive_type(FPConverter),
675 _add_primitive(Gtk::Stock::ADD),
676 _settings_labels(Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL)),
677 _empty_settings(_("No primitive selected"), Gtk::ALIGN_LEFT),
678 // TODO: Find better range/climb-rate/digits values for the SpinSliders,
679 // many of the current values are just guesses
680 _primitive_input1(FPInputConverter),
681 _primitive_input2(FPInputConverter),
682 _blend_mode(BlendModeConverter),
683 _composite_operator(CompositeOperatorConverter),
684 _composite_k1(0, -10, 10, 1, 0.01, 1),
685 _composite_k2(0, -10, 10, 1, 0.01, 1),
686 _composite_k3(0, -10, 10, 1, 0.01, 1),
687 _composite_k4(0, -10, 10, 1, 0.01, 1),
688 _gaussianblur_stddeviation(1, 0, 100, 1, 0.01, 1),
689 _morphology_radius(1, 0, 100, 1, 0.01, 1),
690 _offset_dx(0, -100, 100, 1, 0.01, 1),
691 _offset_dy(0, -100, 100, 1, 0.01, 1),
692 _turbulence_basefrequency(1, 0, 100, 1, 0.01, 1),
693 _turbulence_numoctaves(1, 1, 10, 1, 1, 0),
694 _turbulence_seed(1, 0, 100, 1, 0.01, 1),
695 _turbulence_stitchtiles(_("Stitch Tiles")),
696 _turbulence_fractalnoise(_turbulence_type, _("Fractal Noise")),
697 _turbulence_turbulence(_turbulence_type, _("Turbulence"))
698 {
699 // Initialize widget hierarchy
700 Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
701 Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
702 Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
703 Gtk::Frame* fr_settings = Gtk::manage(new Gtk::Frame(_("<b>Settings</b>")));
704 Gtk::Alignment* al_settings = Gtk::manage(new Gtk::Alignment);
705 get_vbox()->add(*hpaned);
706 hpaned->pack1(_filter_modifier);
707 hpaned->pack2(_primitive_box);
708 _primitive_box.pack_start(*sw_prims);
709 _primitive_box.pack_start(*hb_prims, false, false);
710 sw_prims->add(_primitive_list);
711 hb_prims->pack_end(_add_primitive, false, false);
712 hb_prims->pack_end(_add_primitive_type, false, false);
713 get_vbox()->pack_start(*fr_settings, false, false);
714 fr_settings->add(*al_settings);
715 al_settings->add(_settings);
717 _primitive_list.signal_selection_changed().connect(
718 sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
719 _filter_modifier.signal_selection_changed().connect(
720 sigc::mem_fun(_primitive_list, &PrimitiveList::update));
722 sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
723 sw_prims->set_shadow_type(Gtk::SHADOW_IN);
724 al_settings->set_padding(0, 0, 12, 0);
725 fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
726 ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
727 _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
728 _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
729 sigc::mem_fun(*this, &FilterEffectsDialog::remove_primitive)));
730 _settings_labels->set_ignore_hidden(true);
732 show_all_children();
733 init_settings_widgets();
734 _primitive_list.update();
735 update_settings_view();
736 }
738 FilterEffectsDialog::~FilterEffectsDialog()
739 {
740 }
742 void FilterEffectsDialog::init_settings_widgets()
743 {
744 _empty_settings.set_sensitive(false);
745 _settings.pack_start(_empty_settings);
747 _generic_settings.init(this, _settings_labels);
748 _generic_settings.add_setting_generic(_primitive_input1, _("Input"));
749 _primitive_input1.signal_changed().connect(
750 sigc::bind(sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_special), SP_ATTR_IN));
751 _generic_settings.add_setting_generic(_primitive_input2, _("Input 2"));
752 _primitive_input2.signal_changed().connect(
753 sigc::bind(sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_special), SP_ATTR_IN2));
755 _blend.init(this, _settings_labels);
756 _blend.add_setting(_blend_mode, SP_ATTR_MODE, _("Mode"));
758 _colormatrix.init(this, _settings_labels);
759 //_colormatrix.add_setting(_colormatrix_type, _("Type"));
761 _componenttransfer.init(this, _settings_labels);
763 _composite.init(this, _settings_labels);
764 _composite.add_setting(_composite_operator, SP_ATTR_OPERATOR, _("Operator"));
765 _composite.add_setting(_composite_k1, SP_ATTR_K1, _("K1"));
766 _composite.add_setting(_composite_k2, SP_ATTR_K2, _("K2"));
767 _composite.add_setting(_composite_k3, SP_ATTR_K3, _("K3"));
768 _composite.add_setting(_composite_k4, SP_ATTR_K4, _("K4"));
770 _convolvematrix.init(this, _settings_labels);
772 _diffuselighting.init(this, _settings_labels);
774 _displacementmap.init(this, _settings_labels);
776 _flood.init(this, _settings_labels);
778 _gaussianblur.init(this, _settings_labels);
779 _gaussianblur.add_setting(_gaussianblur_stddeviation, SP_ATTR_STDDEVIATION, _("Standard Deviation"));
781 _image.init(this, _settings_labels);
783 _merge.init(this, _settings_labels);
785 _morphology.init(this, _settings_labels);
786 //_morphology.add_setting(_morphology_operator, _("Operator"));
787 //_morphology.add_setting(_morphology_radius, _("Radius"));
789 _offset.init(this, _settings_labels);
790 _offset.add_setting(_offset_dx, SP_ATTR_DX, _("Delta X"));
791 _offset.add_setting(_offset_dy, SP_ATTR_DY, _("Delta Y"));
793 _specularlighting.init(this, _settings_labels);
795 _tile.init(this, _settings_labels);
797 _turbulence.init(this, _settings_labels);
798 std::vector<Gtk::Widget*> trb_grp;
799 trb_grp.push_back(&_turbulence_fractalnoise);
800 trb_grp.push_back(&_turbulence_turbulence);
801 _turbulence.add_setting(trb_grp);
802 /*_turbulence.add_setting(_turbulence_numoctaves, _("Octaves"));
803 _turbulence.add_setting(_turbulence_basefrequency, _("Base Frequency"));
804 _turbulence.add_setting(_turbulence_seed, _("Seed"));
805 _turbulence.add_setting(_turbulence_stitchtiles);*/
806 }
808 void FilterEffectsDialog::add_primitive()
809 {
810 SPFilter* filter = _filter_modifier.get_selected_filter();
812 if(filter) {
813 SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
815 _primitive_list.update();
816 _primitive_list.select(prim);
818 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
819 }
820 }
822 void FilterEffectsDialog::remove_primitive()
823 {
824 SPFilterPrimitive* prim = _primitive_list.get_selected();
826 if(prim) {
827 sp_repr_unparent(prim->repr);
829 sp_document_done(sp_desktop_document(SP_ACTIVE_DESKTOP), SP_VERB_DIALOG_FILTER_EFFECTS,
830 _("Remove filter primitive"));
832 _primitive_list.update();
833 }
834 }
836 void FilterEffectsDialog::duplicate_primitive()
837 {
838 SPFilter* filter = _filter_modifier.get_selected_filter();
839 SPFilterPrimitive* origprim = _primitive_list.get_selected();
841 if(filter && origprim) {
842 Inkscape::XML::Node *repr;
843 repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
844 SP_OBJECT_REPR(filter)->appendChild(repr);
846 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
848 _primitive_list.update();
849 }
850 }
852 void FilterEffectsDialog::set_attr_spinslider(const SPAttributeEnum attr, const SpinSlider* input)
853 {
854 if(input->is_sensitive()) {
855 std::ostringstream os;
856 os << input->get_value();
857 set_attr(attr, os.str().c_str());
858 }
859 }
861 void FilterEffectsDialog::set_attr_special(const SPAttributeEnum attr)
862 {
863 Glib::ustring val;
864 FilterPrimitiveInput input_id;
866 switch(attr) {
867 case SP_ATTR_IN:
868 input_id = _primitive_input1.get_active_data()->id;
869 case SP_ATTR_IN2:
870 if(attr == SP_ATTR_IN2)
871 input_id = _primitive_input2.get_active_data()->id;
872 if(input_id == FPINPUT_DEFAULT) {
873 // Remove the setting rather then set it
874 set_attr(attr, 0);
875 return;
876 }
877 else if(input_id == FPINPUT_CONNECTION) {
878 return;
879 }
880 else {
881 val = FPInputConverter.get_key(input_id);
882 }
883 break;
884 default:
885 return;
886 }
888 set_attr(attr, val.c_str());
889 }
891 void FilterEffectsDialog::set_attr(const SPAttributeEnum attr, const gchar* val)
892 {
893 SPFilter *filter = _filter_modifier.get_selected_filter();
894 SPFilterPrimitive* prim = _primitive_list.get_selected();
896 if(filter && prim) {
897 update_settings_sensitivity();
899 SP_OBJECT_REPR(prim)->setAttribute((gchar*)sp_attribute_name(attr), val);
900 filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
902 sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Set filter primitive attribute"));
903 }
904 }
906 FilterPrimitiveInput convert_fpinput(const gchar* in)
907 {
908 if(in) {
909 const Glib::ustring val(in);
910 if(FPInputConverter.is_valid_key(val))
911 return FPInputConverter.get_id_from_key(val);
912 else
913 return FPINPUT_CONNECTION;
914 }
915 else
916 return FPINPUT_DEFAULT;
917 }
919 void FilterEffectsDialog::update_settings_view()
920 {
921 SPFilterPrimitive* prim = _primitive_list.get_selected();
923 // Hide all the settings
924 _settings.hide_all();
925 _settings.show();
927 _settings.set_sensitive(false);
928 _empty_settings.show();
930 if(prim) {
931 const NR::FilterPrimitiveType tid = FPConverter.get_id_from_key(prim->repr->name());
933 _generic_settings.show_all();
934 _primitive_input1.set_active(convert_fpinput(SP_OBJECT_REPR(prim)->attribute("in")));
935 _primitive_input2.set_active(convert_fpinput(SP_OBJECT_REPR(prim)->attribute("in2")));
937 if(tid == NR::NR_FILTER_BLEND) {
938 _blend.show_all();
939 const gchar* val = prim->repr->attribute("mode");
940 if(val)
941 _blend_mode.set_active(BlendModeConverter.get_id_from_key(val));
942 }
943 else if(tid == NR::NR_FILTER_COLORMATRIX)
944 _colormatrix.show_all();
945 else if(tid == NR::NR_FILTER_COMPONENTTRANSFER)
946 _componenttransfer.show_all();
947 else if(tid == NR::NR_FILTER_COMPOSITE) {
948 _composite.show_all();
949 SPFeComposite* comp = SP_FECOMPOSITE(prim);
950 _composite_operator.set_active(comp->composite_operator);
951 _composite_k1.set_value(comp->k1);
952 _composite_k2.set_value(comp->k2);
953 _composite_k3.set_value(comp->k3);
954 _composite_k4.set_value(comp->k4);
955 }
956 else if(tid == NR::NR_FILTER_CONVOLVEMATRIX)
957 _convolvematrix.show_all();
958 else if(tid == NR::NR_FILTER_DIFFUSELIGHTING)
959 _diffuselighting.show_all();
960 else if(tid == NR::NR_FILTER_DISPLACEMENTMAP) {
961 _displacementmap.show_all();
962 }
963 else if(tid == NR::NR_FILTER_FLOOD)
964 _flood.show_all();
965 else if(tid == NR::NR_FILTER_GAUSSIANBLUR) {
966 _gaussianblur.show_all();
967 _gaussianblur_stddeviation.set_value(((SPGaussianBlur*)prim)->stdDeviation.getNumber());
968 }
969 else if(tid == NR::NR_FILTER_IMAGE)
970 _image.show_all();
971 else if(tid == NR::NR_FILTER_MERGE)
972 _merge.show_all();
973 else if(tid == NR::NR_FILTER_MORPHOLOGY)
974 _morphology.show_all();
975 else if(tid == NR::NR_FILTER_OFFSET) {
976 _offset.show_all();
977 _offset_dx.set_value(((SPFeOffset*)prim)->dx);
978 _offset_dy.set_value(((SPFeOffset*)prim)->dy);
979 }
980 else if(tid == NR::NR_FILTER_SPECULARLIGHTING)
981 _specularlighting.show_all();
982 else if(tid == NR::NR_FILTER_TILE)
983 _tile.show_all();
984 else if(tid == NR::NR_FILTER_TURBULENCE)
985 _turbulence.show_all();
987 _settings.set_sensitive(true);
988 _empty_settings.hide();
989 }
991 update_settings_sensitivity();
992 }
994 void FilterEffectsDialog::update_settings_sensitivity()
995 {
996 SPFilterPrimitive* prim = _primitive_list.get_selected();
998 _primitive_input2.set_sensitive(SP_IS_FEBLEND(prim) || SP_IS_FECOMPOSITE(prim) || SP_IS_FEDISPLACEMENTMAP(prim));
1000 const bool use_k = _composite_operator.get_active_data()->id == COMPOSITE_ARITHMETIC;
1001 _composite_k1.set_sensitive(use_k);
1002 _composite_k2.set_sensitive(use_k);
1003 _composite_k3.set_sensitive(use_k);
1004 _composite_k4.set_sensitive(use_k);
1005 }
1007 } // namespace Dialog
1008 } // namespace UI
1009 } // namespace Inkscape
1011 /*
1012 Local Variables:
1013 mode:c++
1014 c-file-style:"stroustrup"
1015 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1016 indent-tabs-mode:nil
1017 fill-column:99
1018 End:
1019 */
1020 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :