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 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);
524 }
526 bool FilterEffectsDialog::PrimitiveList::on_motion_notify_event(GdkEventMotion* e)
527 {
528 queue_draw();
530 return Gtk::TreeView::on_motion_notify_event(e);
531 }
533 bool FilterEffectsDialog::PrimitiveList::on_button_release_event(GdkEventButton* e)
534 {
535 SPFilterPrimitive *prim = get_selected(), *target;
537 if(_in_drag && prim) {
538 Gtk::TreePath path;
539 Gtk::TreeViewColumn* col;
540 int cx, cy;
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);
575 }
577 // Reorder the filter primitives to match the list order
578 void FilterEffectsDialog::PrimitiveList::on_drag_end(const Glib::RefPtr<Gdk::DragContext>&)
579 {
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"));
590 }
592 int FilterEffectsDialog::PrimitiveList::primitive_count() const
593 {
594 return _model->children().size();
595 }
597 bool FilterEffectsDialog::PrimitiveList::is_first(const SPFilterPrimitive* prim) const
598 {
599 return (*_model->children().begin())[_columns.primitive] == prim;
600 }
602 /*** SettingsGroup ***/
603 FilterEffectsDialog::SettingsGroup::SettingsGroup()
604 {
605 show();
606 }
608 void FilterEffectsDialog::SettingsGroup::init(FilterEffectsDialog* dlg, Glib::RefPtr<Gtk::SizeGroup> sg)
609 {
610 _dialog = dlg;
611 _dialog->_settings.pack_start(*this, false, false);
612 _sizegroup = sg;
613 }
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)
618 {
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();
632 }
634 /* For SpinSlider settings */
635 void FilterEffectsDialog::SettingsGroup::add_setting(SpinSlider& ss, const SPAttributeEnum attr,
636 const Glib::ustring& label)
637 {
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));
641 }
643 /* For subgroups of settings */
644 void FilterEffectsDialog::SettingsGroup::add_setting(std::vector<Gtk::Widget*>& w, const Glib::ustring& label)
645 {
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);
651 }
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"))
682 {
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);
716 show_all_children();
717 init_settings_widgets();
718 _primitive_list.update();
719 update_settings_view();
720 }
722 FilterEffectsDialog::~FilterEffectsDialog()
723 {
724 }
726 void FilterEffectsDialog::init_settings_widgets()
727 {
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);
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);
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);*/
790 }
792 void FilterEffectsDialog::add_primitive()
793 {
794 SPFilter* filter = _filter_modifier.get_selected_filter();
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 }
804 }
806 void FilterEffectsDialog::remove_primitive()
807 {
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 }
818 }
820 void FilterEffectsDialog::duplicate_primitive()
821 {
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 }
834 }
836 void FilterEffectsDialog::set_attr_spinslider(const SPAttributeEnum attr, const SpinSlider* input)
837 {
838 if(input->is_sensitive()) {
839 std::ostringstream os;
840 os << input->get_value();
841 set_attr(attr, os.str().c_str());
842 }
843 }
845 void FilterEffectsDialog::set_attr_special(const SPAttributeEnum attr)
846 {
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());
873 }
875 void FilterEffectsDialog::set_attr(const SPAttributeEnum attr, const gchar* val)
876 {
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 }
888 }
890 FilterPrimitiveInput convert_fpinput(const gchar* in)
891 {
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;
901 }
903 void FilterEffectsDialog::update_settings_view()
904 {
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();
976 }
978 void FilterEffectsDialog::update_settings_sensitivity()
979 {
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);
989 }
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 :