Code

Filter effects dialog:
[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         std::vector<Gdk::Point> points;
515         if(do_connection_node(_model->get_iter(path), 0, points, x, y))
516             _in_drag = 1;
517         else if(do_connection_node(_model->get_iter(path), 1, points, x, y))
518             _in_drag = 2;
520         queue_draw();
521     }
523     return Gtk::TreeView::on_button_press_event(e);
526 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
528     queue_draw();
530     return Gtk::TreeView::on_motion_notify_event(e);
533 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
535     SPFilterPrimitive *prim = get_selected(), *target;
537     if(_in_drag && prim) {
538         Gtk::TreePath path;
539         Gtk::TreeViewColumn* col;
540         int cx, cy;
541         
542         if(get_path_at_pos((int)e->x, (int)e->y, path, col, cx, cy)) {
543             target = (*_model->get_iter(path))[_columns.primitive];
544             Inkscape::XML::Node *repr = SP_OBJECT_REPR(target);
545             // Make sure the target has a result
546             const gchar *gres = repr->attribute("result");
547             Glib::ustring result = gres ? gres : "";
548             if(!gres) {
549                 result = "result" + Glib::Ascii::dtostr(SP_FILTER(prim->parent)->_image_number_next);
550                 repr->setAttribute("result", result.c_str());
551             }
553             if(_in_drag == 1)
554                 SP_OBJECT_REPR(prim)->setAttribute("in", result.c_str());
555             else if(_in_drag == 2)
556                 SP_OBJECT_REPR(prim)->setAttribute("in2", result.c_str());
557         }
559         _in_drag = 0;
560         queue_draw();
562         _dialog.update_settings_view();
563     }
565     if((e->type == GDK_BUTTON_RELEASE) && (e->button == 3)) {
566         const bool sensitive = get_selected() != NULL;
567         _primitive_menu->items()[0].set_sensitive(sensitive);
568         _primitive_menu->items()[1].set_sensitive(sensitive);
569         _primitive_menu->popup(e->button, e->time);
571         return true;
572     }
573     else
574         return Gtk::TreeView::on_button_release_event(e);
577 // Reorder the filter primitives to match the list order
578 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>&)
580     SPFilter* filter = _dialog._filter_modifier.get_selected_filter();
582     for(Gtk::TreeModel::iterator iter = _model->children().begin();
583         iter != _model->children().end(); ++iter) {
584         SPFilterPrimitive* prim = (*iter)[_columns.primitive];
585         if(prim)
586             ;//reorder_primitive(filter, prim->repr->position(), ndx); /* FIXME */
587     }
589     sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Reorder filter primitive"));
592 int FilterEffectsDialog::PrimitiveList::primitive_count() const
594     return _model->children().size();
597 bool FilterEffectsDialog::PrimitiveList::is_first(const SPFilterPrimitive* prim) const
599     return (*_model->children().begin())[_columns.primitive] == prim;
602 /*** SettingsGroup ***/
603 FilterEffectsDialog::SettingsGroup::SettingsGroup()
605     show();
608 void FilterEffectsDialog::SettingsGroup::init(FilterEffectsDialog* dlg, Glib::RefPtr<Gtk::SizeGroup> sg)
610     _dialog = dlg;
611     _dialog->_settings.pack_start(*this, false, false);
612     _sizegroup = sg;
615 /* Adds a new settings widget using the specified label. The label will be formatted with a colon
616    and all widgets within the setting group are aligned automatically. */
617 void FilterEffectsDialog::SettingsGroup::add_setting_generic(Gtk::Widget& w, const Glib::ustring& label)
619     Gtk::Label *lbl = Gtk::manage(new Gtk::Label(label + (label == "" ? "" : ":"), Gtk::ALIGN_LEFT));
620     Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
621     hb->set_spacing(12);
622     hb->pack_start(*lbl, false, false);
623     hb->pack_start(w);
624     pack_start(*hb);
626     _sizegroup->add_widget(*lbl);
628     hb->show();
629     lbl->show();
631     w.show();
634 /* For SpinSlider settings */
635 void FilterEffectsDialog::SettingsGroup::add_setting(SpinSlider& ss, const SPAttributeEnum attr,
636                                                      const Glib::ustring& label)
638     add_setting_generic(ss, label);
639     ss.signal_value_changed().connect(
640         sigc::bind(sigc::mem_fun(_dialog, &FilterEffectsDialog::set_attr_spinslider), attr, &ss));
643 /* For subgroups of settings */
644 void FilterEffectsDialog::SettingsGroup::add_setting(std::vector<Gtk::Widget*>& w, const Glib::ustring& label)
646     Gtk::HBox *hb = Gtk::manage(new Gtk::HBox);
647     for(unsigned int i = 0; i < w.size(); ++i)
648         hb->pack_start(*w[i]);
649     hb->set_spacing(12);
650     add_setting_generic(*hb, label);
653 /*** FilterEffectsDialog ***/
655 FilterEffectsDialog::FilterEffectsDialog() 
656     : Dialog ("dialogs.filtereffects", SP_VERB_DIALOG_FILTER_EFFECTS),
657       _primitive_list(*this),
658       _add_primitive_type(FPConverter),
659       _add_primitive(Gtk::Stock::ADD),
660       _settings_labels(Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL)),
661       _empty_settings(_("No primitive selected"), Gtk::ALIGN_LEFT),
662       // TODO: Find better range/climb-rate/digits values for the SpinSliders,
663       //       many of the current values are just guesses
664       _primitive_input1(FPInputConverter),
665       _primitive_input2(FPInputConverter),
666       _blend_mode(BlendModeConverter),
667       _composite_operator(CompositeOperatorConverter),
668       _composite_k1(0, -10, 10, 1, 0.01, 1),
669       _composite_k2(0, -10, 10, 1, 0.01, 1),
670       _composite_k3(0, -10, 10, 1, 0.01, 1),
671       _composite_k4(0, -10, 10, 1, 0.01, 1),
672       _gaussianblur_stddeviation(1, 0, 100, 1, 0.01, 1),
673       _morphology_radius(1, 0, 100, 1, 0.01, 1),
674       _offset_dx(0, -100, 100, 1, 0.01, 1),
675       _offset_dy(0, -100, 100, 1, 0.01, 1),
676       _turbulence_basefrequency(1, 0, 100, 1, 0.01, 1),
677       _turbulence_numoctaves(1, 1, 10, 1, 1, 0),
678       _turbulence_seed(1, 0, 100, 1, 0.01, 1),
679       _turbulence_stitchtiles(_("Stitch Tiles")),
680       _turbulence_fractalnoise(_turbulence_type, _("Fractal Noise")),
681       _turbulence_turbulence(_turbulence_type, _("Turbulence"))
683     // Initialize widget hierarchy
684     Gtk::HPaned* hpaned = Gtk::manage(new Gtk::HPaned);
685     Gtk::ScrolledWindow* sw_prims = Gtk::manage(new Gtk::ScrolledWindow);
686     Gtk::HBox* hb_prims = Gtk::manage(new Gtk::HBox);
687     Gtk::Frame* fr_settings = Gtk::manage(new Gtk::Frame(_("<b>Settings</b>")));
688     Gtk::Alignment* al_settings = Gtk::manage(new Gtk::Alignment);
689     get_vbox()->add(*hpaned);
690     hpaned->pack1(_filter_modifier);
691     hpaned->pack2(_primitive_box);
692     _primitive_box.pack_start(*sw_prims);
693     _primitive_box.pack_start(*hb_prims, false, false);
694     sw_prims->add(_primitive_list);
695     hb_prims->pack_end(_add_primitive, false, false);
696     hb_prims->pack_end(_add_primitive_type, false, false);
697     get_vbox()->pack_start(*fr_settings, false, false);
698     fr_settings->add(*al_settings);
699     al_settings->add(_settings);
701     _primitive_list.signal_selection_changed().connect(
702         sigc::mem_fun(*this, &FilterEffectsDialog::update_settings_view));
703     _filter_modifier.signal_selection_changed().connect(
704         sigc::mem_fun(_primitive_list, &PrimitiveList::update));
706     sw_prims->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
707     sw_prims->set_shadow_type(Gtk::SHADOW_IN);
708     al_settings->set_padding(0, 0, 12, 0);
709     fr_settings->set_shadow_type(Gtk::SHADOW_NONE);
710     ((Gtk::Label*)fr_settings->get_label_widget())->set_use_markup();
711     _add_primitive.signal_clicked().connect(sigc::mem_fun(*this, &FilterEffectsDialog::add_primitive));
712     _primitive_list.set_menu(create_popup_menu(*this, sigc::mem_fun(*this, &FilterEffectsDialog::duplicate_primitive),
713                                                sigc::mem_fun(*this, &FilterEffectsDialog::remove_primitive)));
714     _settings_labels->set_ignore_hidden(true);
715     
716     show_all_children();
717     init_settings_widgets();
718     _primitive_list.update();
719     update_settings_view();
722 FilterEffectsDialog::~FilterEffectsDialog()
726 void FilterEffectsDialog::init_settings_widgets()
728     _empty_settings.set_sensitive(false);
729     _settings.pack_start(_empty_settings);
731     _generic_settings.init(this, _settings_labels);
732     _generic_settings.add_setting_generic(_primitive_input1, _("Input"));
733     _primitive_input1.signal_changed().connect(
734         sigc::bind(sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_special), SP_ATTR_IN));
735     _generic_settings.add_setting_generic(_primitive_input2, _("Input 2"));
736     _primitive_input2.signal_changed().connect(
737         sigc::bind(sigc::mem_fun(*this, &FilterEffectsDialog::set_attr_special), SP_ATTR_IN2));
739     _blend.init(this, _settings_labels);
740     _blend.add_setting(_blend_mode, SP_ATTR_MODE, _("Mode"));
742     _colormatrix.init(this, _settings_labels);
743     //_colormatrix.add_setting(_colormatrix_type, _("Type"));
745     _componenttransfer.init(this, _settings_labels);
747     _composite.init(this, _settings_labels);
748     _composite.add_setting(_composite_operator, SP_ATTR_OPERATOR, _("Operator"));
749     _composite.add_setting(_composite_k1, SP_ATTR_K1, _("K1"));
750     _composite.add_setting(_composite_k2, SP_ATTR_K2, _("K2"));
751     _composite.add_setting(_composite_k3, SP_ATTR_K3, _("K3"));
752     _composite.add_setting(_composite_k4, SP_ATTR_K4, _("K4"));
754     _convolvematrix.init(this, _settings_labels);
755     
756     _diffuselighting.init(this, _settings_labels);
758     _displacementmap.init(this, _settings_labels);
760     _flood.init(this, _settings_labels);
762     _gaussianblur.init(this, _settings_labels);
763     _gaussianblur.add_setting(_gaussianblur_stddeviation, SP_ATTR_STDDEVIATION, _("Standard Deviation"));
765     _image.init(this, _settings_labels);
766     
767     _merge.init(this, _settings_labels);
769     _morphology.init(this, _settings_labels);
770     //_morphology.add_setting(_morphology_operator, _("Operator"));
771     //_morphology.add_setting(_morphology_radius, _("Radius"));
773     _offset.init(this, _settings_labels);
774     _offset.add_setting(_offset_dx, SP_ATTR_DX, _("Delta X"));
775     _offset.add_setting(_offset_dy, SP_ATTR_DY, _("Delta Y"));
777     _specularlighting.init(this, _settings_labels);
779     _tile.init(this, _settings_labels);
781     _turbulence.init(this, _settings_labels);
782     std::vector<Gtk::Widget*> trb_grp;
783     trb_grp.push_back(&_turbulence_fractalnoise);
784     trb_grp.push_back(&_turbulence_turbulence);
785     _turbulence.add_setting(trb_grp);
786     /*_turbulence.add_setting(_turbulence_numoctaves, _("Octaves"));
787     _turbulence.add_setting(_turbulence_basefrequency, _("Base Frequency"));
788     _turbulence.add_setting(_turbulence_seed, _("Seed"));
789     _turbulence.add_setting(_turbulence_stitchtiles);*/
792 void FilterEffectsDialog::add_primitive()
794     SPFilter* filter = _filter_modifier.get_selected_filter();
795     
796     if(filter) {
797         SPFilterPrimitive* prim = filter_add_primitive(filter, _add_primitive_type.get_active_data()->id);
799         _primitive_list.update();
800         _primitive_list.select(prim);
802         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Add filter primitive"));
803     }
806 void FilterEffectsDialog::remove_primitive()
808     SPFilterPrimitive* prim = _primitive_list.get_selected();
810     if(prim) {
811         sp_repr_unparent(prim->repr);
813         sp_document_done(sp_desktop_document(SP_ACTIVE_DESKTOP), SP_VERB_DIALOG_FILTER_EFFECTS,
814                          _("Remove filter primitive"));
816         _primitive_list.update();
817     }
820 void FilterEffectsDialog::duplicate_primitive()
822     SPFilter* filter = _filter_modifier.get_selected_filter();
823     SPFilterPrimitive* origprim = _primitive_list.get_selected();
825     if(filter && origprim) {
826         Inkscape::XML::Node *repr;
827         repr = SP_OBJECT_REPR(origprim)->duplicate(SP_OBJECT_REPR(origprim)->document());
828         SP_OBJECT_REPR(filter)->appendChild(repr);
830         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Duplicate filter primitive"));
832         _primitive_list.update();
833     }
836 void FilterEffectsDialog::set_attr_spinslider(const SPAttributeEnum attr, const SpinSlider* input)
838     if(input->is_sensitive()) {
839         std::ostringstream os;
840         os << input->get_value();
841         set_attr(attr, os.str().c_str());
842     }
845 void FilterEffectsDialog::set_attr_special(const SPAttributeEnum attr)
847     Glib::ustring val;
848     FilterPrimitiveInput input_id;
850     switch(attr) {
851         case SP_ATTR_IN:
852             input_id = _primitive_input1.get_active_data()->id;
853         case SP_ATTR_IN2:
854             if(attr == SP_ATTR_IN2)
855                 input_id = _primitive_input2.get_active_data()->id;
856             if(input_id == FPINPUT_DEFAULT) {
857                 // Remove the setting rather then set it
858                 set_attr(attr, 0);
859                 return;
860             }
861             else if(input_id == FPINPUT_CONNECTION) {
862                 return;
863             }
864             else {
865                 val = FPInputConverter.get_key(input_id);
866             }
867             break;
868         default:
869             return;
870     }
872     set_attr(attr, val.c_str());
875 void FilterEffectsDialog::set_attr(const SPAttributeEnum attr, const gchar* val)
877     SPFilter *filter = _filter_modifier.get_selected_filter();
878     SPFilterPrimitive* prim = _primitive_list.get_selected();
880     if(filter && prim) {
881         update_settings_sensitivity();
883         SP_OBJECT_REPR(prim)->setAttribute((gchar*)sp_attribute_name(attr), val);
884         filter->requestModified(SP_OBJECT_MODIFIED_FLAG);
886         sp_document_done(filter->document, SP_VERB_DIALOG_FILTER_EFFECTS, _("Set filter primitive attribute"));
887     }
890 FilterPrimitiveInput convert_fpinput(const gchar* in)
892     if(in) {
893         const Glib::ustring val(in);
894         if(FPInputConverter.is_valid_key(val))
895             return FPInputConverter.get_id_from_key(val);
896         else
897             return FPINPUT_CONNECTION;
898     }
899     else
900         return FPINPUT_DEFAULT;
903 void FilterEffectsDialog::update_settings_view()
905     SPFilterPrimitive* prim = _primitive_list.get_selected();
907     // Hide all the settings
908     _settings.hide_all();
909     _settings.show();
911     _settings.set_sensitive(false);
912     _empty_settings.show();
914     if(prim) {
915         const NR::FilterPrimitiveType tid = FPConverter.get_id_from_key(prim->repr->name());
917         _generic_settings.show_all();
918         _primitive_input1.set_active(convert_fpinput(SP_OBJECT_REPR(prim)->attribute("in")));
919         _primitive_input2.set_active(convert_fpinput(SP_OBJECT_REPR(prim)->attribute("in2")));
921         if(tid == NR::NR_FILTER_BLEND) {
922             _blend.show_all();
923             const gchar* val = prim->repr->attribute("mode");
924             if(val)
925                 _blend_mode.set_active(BlendModeConverter.get_id_from_key(val));
926         }
927         else if(tid == NR::NR_FILTER_COLORMATRIX)
928             _colormatrix.show_all();
929         else if(tid == NR::NR_FILTER_COMPONENTTRANSFER)
930             _componenttransfer.show_all();
931         else if(tid == NR::NR_FILTER_COMPOSITE) {
932             _composite.show_all();
933             SPFeComposite* comp = SP_FECOMPOSITE(prim);
934             _composite_operator.set_active(comp->composite_operator);
935             _composite_k1.set_value(comp->k1);
936             _composite_k2.set_value(comp->k2);
937             _composite_k3.set_value(comp->k3);
938             _composite_k4.set_value(comp->k4);
939         }
940         else if(tid == NR::NR_FILTER_CONVOLVEMATRIX)
941             _convolvematrix.show_all();
942         else if(tid == NR::NR_FILTER_DIFFUSELIGHTING)
943             _diffuselighting.show_all();
944         else if(tid == NR::NR_FILTER_DISPLACEMENTMAP) {
945             _displacementmap.show_all();
946         }
947         else if(tid == NR::NR_FILTER_FLOOD)
948             _flood.show_all();
949         else if(tid == NR::NR_FILTER_GAUSSIANBLUR) {
950             _gaussianblur.show_all();
951             _gaussianblur_stddeviation.set_value(((SPGaussianBlur*)prim)->stdDeviation.getNumber());
952         }
953         else if(tid == NR::NR_FILTER_IMAGE)
954             _image.show_all();
955         else if(tid == NR::NR_FILTER_MERGE)
956             _merge.show_all();
957         else if(tid == NR::NR_FILTER_MORPHOLOGY)
958             _morphology.show_all();
959         else if(tid == NR::NR_FILTER_OFFSET) {
960             _offset.show_all();
961             _offset_dx.set_value(((SPFeOffset*)prim)->dx);
962             _offset_dy.set_value(((SPFeOffset*)prim)->dy);
963         }
964         else if(tid == NR::NR_FILTER_SPECULARLIGHTING)
965             _specularlighting.show_all();
966         else if(tid == NR::NR_FILTER_TILE)
967             _tile.show_all();
968         else if(tid == NR::NR_FILTER_TURBULENCE)
969             _turbulence.show_all();
971         _settings.set_sensitive(true);
972         _empty_settings.hide();
973     }
975     update_settings_sensitivity();
978 void FilterEffectsDialog::update_settings_sensitivity()
980     SPFilterPrimitive* prim = _primitive_list.get_selected();
982     _primitive_input2.set_sensitive(SP_IS_FEBLEND(prim) || SP_IS_FECOMPOSITE(prim) || SP_IS_FEDISPLACEMENTMAP(prim));
984     const bool use_k = _composite_operator.get_active_data()->id == COMPOSITE_ARITHMETIC;
985     _composite_k1.set_sensitive(use_k);
986     _composite_k2.set_sensitive(use_k);
987     _composite_k3.set_sensitive(use_k);
988     _composite_k4.set_sensitive(use_k);
991 } // namespace Dialog
992 } // namespace UI
993 } // namespace Inkscape
995 /*
996   Local Variables:
997   mode:c++
998   c-file-style:"stroustrup"
999   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1000   indent-tabs-mode:nil
1001   fill-column:99
1002   End:
1003 */
1004 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :