Code

18610f613b6dcb37962d6a6331c14ee6d826a7eb
[inkscape.git] / src / ui / dialog / filter-effects-dialog.cpp
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();
104 Glib::SignalProxy0<void> FilterEffectsDialog::FilterModifier::signal_selection_changed()
106     return _list.get_selection()->signal_changed();
109 SPFilter* FilterEffectsDialog::FilterModifier::get_selected_filter()
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;
121 void FilterEffectsDialog::FilterModifier::select_filter(const SPFilter* filter)
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     }
134 void FilterEffectsDialog::FilterModifier::filter_list_button_release(GdkEventButton* event)
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     }
144 void FilterEffectsDialog::FilterModifier::add_filter()
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"));
156 void FilterEffectsDialog::FilterModifier::remove_filter()
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     }
170 void FilterEffectsDialog::FilterModifier::duplicate_filter()
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     }
183 void FilterEffectsDialog::FilterModifier::filter_name_edited(const Glib::ustring& path, const Glib::ustring& text)
185     Gtk::TreeModel::iterator i = _model->get_iter(path);
187     if(i)
188         try_id_change((SPObject*)(*i)[_columns.filter], text);
191 FilterEffectsDialog::CellRendererConnection::CellRendererConnection()
192     : Glib::ObjectBase(typeid(CellRendererConnection)),
193       _primitive(*this, "primitive", 0)
197 Glib::PropertyProxy<void*> FilterEffectsDialog::CellRendererConnection::property_primitive()
199     return _primitive.get_proxy();
202 int FilterEffectsDialog::CellRendererConnection::input_count(const SPFilterPrimitive* prim)
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;
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
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     }
238 /*** PrimitiveList ***/
239 FilterEffectsDialog::PrimitiveList::PrimitiveList(FilterEffectsDialog& d)
240     : _dialog(d), _in_drag(0)
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);
261 Glib::SignalProxy0<void> FilterEffectsDialog::PrimitiveList::signal_selection_changed()
263     return get_selection()->signal_changed();
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()
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     }
305 void FilterEffectsDialog::PrimitiveList::set_menu(Glib::RefPtr<Gtk::Menu> menu)
307     _primitive_menu = menu;
310 SPFilterPrimitive* FilterEffectsDialog::PrimitiveList::get_selected()
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;
321 void FilterEffectsDialog::PrimitiveList::select(SPFilterPrimitive* prim)
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     }
330 bool FilterEffectsDialog::PrimitiveList::on_expose_event(GdkEventExpose* e)
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;
420 void FilterEffectsDialog::PrimitiveList::draw_connection(const Gtk::TreeIter& input, const int x1, const int y1,
421                                                          const int row_count)
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     }
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)
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();
465 const Gtk::TreeIter FilterEffectsDialog::PrimitiveList::find_result(const Gtk::TreeIter& start,
466                                                                     const SPAttributeEnum attr)
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;
498 int FilterEffectsDialog::PrimitiveList::find_index(const Gtk::TreeIter& target)
500     int i = 0;
501     for(Gtk::TreeIter iter = _model->children().begin();
502         iter != target; ++iter, ++i);
503     return i;
506 bool FilterEffectsDialog::PrimitiveList::on_button_press_event(GdkEventButton* e)
508     Gtk::TreePath path;
509     Gtk::TreeViewColumn* col;
510     const int x = (int)e->x, y = (int)e->y;
511     int cx, cy;
512     
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);
529 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
531     queue_draw();
533     return Gtk::TreeView::on_motion_notify_event(e);
536 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
538     SPFilterPrimitive *prim = get_selected(), *target;
540     if(_in_drag && prim) {
541         Gtk::TreePath path;
542         Gtk::TreeViewColumn* col;
543         int cx, cy;
544         
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);
593 // Reorder the filter primitives to match the list order
594 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>&)
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"));
608 int FilterEffectsDialog::PrimitiveList::primitive_count() const
610     return _model->children().size();
613 bool FilterEffectsDialog::PrimitiveList::is_first(const SPFilterPrimitive* prim) const
615     return (*_model->children().begin())[_columns.primitive] == prim;
618 /*** SettingsGroup ***/
619 FilterEffectsDialog::SettingsGroup::SettingsGroup()
621     show();
624 void FilterEffectsDialog::SettingsGroup::init(FilterEffectsDialog* dlg, Glib::RefPtr<Gtk::SizeGroup> sg)
626     _dialog = dlg;
627     _dialog->_settings.pack_start(*this, false, false);
628     _sizegroup = sg;
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)
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();
650 /* For SpinSlider settings */
651 void FilterEffectsDialog::SettingsGroup::add_setting(SpinSlider& ss, const SPAttributeEnum attr,
652                                                      const Glib::ustring& label)
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));
659 /* For subgroups of settings */
660 void FilterEffectsDialog::SettingsGroup::add_setting(std::vector<Gtk::Widget*>& w, const Glib::ustring& label)
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);
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"))
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);
731     
732     show_all_children();
733     init_settings_widgets();
734     _primitive_list.update();
735     update_settings_view();
738 FilterEffectsDialog::~FilterEffectsDialog()
742 void FilterEffectsDialog::init_settings_widgets()
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);
771     
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);
782     
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);*/
808 void FilterEffectsDialog::add_primitive()
810     SPFilter* filter = _filter_modifier.get_selected_filter();
811     
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     }
822 void FilterEffectsDialog::remove_primitive()
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     }
836 void FilterEffectsDialog::duplicate_primitive()
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     }
852 void FilterEffectsDialog::set_attr_spinslider(const SPAttributeEnum attr, const SpinSlider* input)
854     if(input->is_sensitive()) {
855         std::ostringstream os;
856         os << input->get_value();
857         set_attr(attr, os.str().c_str());
858     }
861 void FilterEffectsDialog::set_attr_special(const SPAttributeEnum attr)
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());
891 void FilterEffectsDialog::set_attr(const SPAttributeEnum attr, const gchar* val)
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     }
906 FilterPrimitiveInput convert_fpinput(const gchar* in)
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;
919 void FilterEffectsDialog::update_settings_view()
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();
994 void FilterEffectsDialog::update_settings_sensitivity()
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);
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 :