1 /**
2 * \brief LivePathEffect dialog
3 *
4 * Authors:
5 * Johan Engelen <j.b.c.engelen@utwente.nl>
6 * Steren Giannini <steren.giannini@gmail.com>
7 * Bastien Bouclet <bgkweb@gmail.com>
8 *
9 * Copyright (C) 2007 Author
10 *
11 * Released under GNU GPL. Read the file 'COPYING' for more information.
12 */
14 #ifdef HAVE_CONFIG_H
15 # include <config.h>
16 #endif
18 #include <glibmm/i18n.h>
19 #include "livepatheffect-editor.h"
20 #include "ui/widget/imagetoggler.h"
21 #include "verbs.h"
22 #include "selection.h"
23 #include "sp-shape.h"
24 #include "sp-item-group.h"
25 #include "sp-path.h"
26 #include "sp-rect.h"
27 #include "sp-lpe-item.h"
28 #include "path-chemistry.h"
29 #include "live_effects/effect.h"
30 #include "live_effects/lpeobject.h"
31 #include "gtkmm/widget.h"
32 #include <vector>
33 #include "inkscape.h"
34 #include "desktop-handles.h"
35 #include "desktop.h"
36 #include "document.h"
37 #include "xml/node.h"
38 #include <gtkmm/stock.h>
39 #include <gtkmm/toolbar.h>
41 #include "live_effects/lpeobject-reference.h"
43 namespace Inkscape {
44 class Application;
46 namespace UI {
47 namespace Dialog {
50 /*####################
51 * Callback functions
52 */
53 static void lpeeditor_selection_changed (Inkscape::Selection * selection, gpointer data)
54 {
55 LivePathEffectEditor *lpeeditor = static_cast<LivePathEffectEditor *>(data);
56 lpeeditor->onSelectionChanged(selection);
57 }
59 static void lpeeditor_selection_modified (Inkscape::Selection * selection, guint /*flags*/, gpointer data)
60 {
61 LivePathEffectEditor *lpeeditor = static_cast<LivePathEffectEditor *>(data);
62 lpeeditor->onSelectionChanged(selection);
63 }
66 /*#######################
67 * LivePathEffectEditor
68 */
70 LivePathEffectEditor::LivePathEffectEditor()
71 : UI::Widget::Panel("", "dialogs.livepatheffect", SP_VERB_DIALOG_LIVE_PATH_EFFECT),
72 lpe_list_locked(false),
73 combo_effecttype(Inkscape::LivePathEffect::LPETypeConverter),
74 effectwidget(NULL),
75 explain_label("", Gtk::ALIGN_CENTER),
76 effectapplication_frame(_("Apply new effect")),
77 effectcontrol_frame(_("Current effect")),
78 effectlist_frame(_("Effect list")),
79 button_up(Gtk::Stock::GO_UP),
80 button_down(Gtk::Stock::GO_DOWN),
81 button_apply(Gtk::Stock::ADD),
82 button_remove(Gtk::Stock::REMOVE),
83 current_desktop(NULL),
84 current_lpeitem(NULL)
85 {
86 Gtk::Box *contents = _getContents();
87 contents->set_spacing(4);
89 //Add the TreeView, inside a ScrolledWindow, with the button underneath:
90 scrolled_window.add(effectlist_view);
91 //Only show the scrollbars when they are necessary:
92 scrolled_window.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC);
94 effectapplication_hbox.set_spacing(4);
95 effectcontrol_vbox.set_spacing(4);
96 effectlist_vbox.set_spacing(4);
98 effectapplication_hbox.pack_start(combo_effecttype, true, true);
99 effectapplication_hbox.pack_start(button_apply, true, true);
100 effectapplication_frame.add(effectapplication_hbox);
102 effectlist_vbox.pack_start(scrolled_window, Gtk::PACK_EXPAND_WIDGET);
103 effectlist_vbox.pack_end(toolbar, Gtk::PACK_SHRINK);
104 // effectlist_vbox.pack_end(button_hbox, Gtk::PACK_SHRINK);
105 effectlist_frame.add(effectlist_vbox);
107 effectcontrol_vbox.pack_start(explain_label, true, true);
108 effectcontrol_frame.add(effectcontrol_vbox);
110 // button_hbox.pack_start(button_up, true, true);
111 // button_hbox.pack_start(button_down, true, true);
112 // button_hbox.pack_end(button_remove, true, true);
113 toolbar.set_toolbar_style(Gtk::TOOLBAR_ICONS);
114 // Add toolbar items to toolbar
115 toolbar.append(button_up);
116 toolbar.append(button_down);
117 toolbar.append(button_remove);
120 // Add toolbar
121 //add_toolbar(toolbar);
122 toolbar.show_all(); //Show the toolbar and all its child widgets.
125 //Create the Tree model:
126 effectlist_store = Gtk::ListStore::create(columns);
127 effectlist_view.set_model(effectlist_store);
129 effectlist_view.set_headers_visible(false);
131 // Handle tree selections
132 effectlist_selection = effectlist_view.get_selection();
133 effectlist_selection->signal_changed().connect( sigc::mem_fun(*this, &LivePathEffectEditor::on_effect_selection_changed) );
135 //Add the visibility icon column:
136 Inkscape::UI::Widget::ImageToggler *eyeRenderer = manage( new Inkscape::UI::Widget::ImageToggler("visible", "hidden") );
137 int visibleColNum = effectlist_view.append_column("is_visible", *eyeRenderer) - 1;
138 eyeRenderer->signal_toggled().connect( sigc::mem_fun(*this, &LivePathEffectEditor::on_visibility_toggled) );
139 eyeRenderer->property_activatable() = true;
140 Gtk::TreeViewColumn* col = effectlist_view.get_column(visibleColNum);
141 if ( col ) {
142 col->add_attribute( eyeRenderer->property_active(), columns.col_visible );
143 }
145 //Add the effect name column:
146 effectlist_view.append_column("Effect", columns.col_name);
148 contents->pack_start(effectapplication_frame, false, false);
149 contents->pack_start(effectlist_frame, true, true);
150 contents->pack_start(effectcontrol_frame, false, false);
152 // connect callback functions to buttons
153 button_apply.signal_clicked().connect(sigc::mem_fun(*this, &LivePathEffectEditor::onApply));
154 button_remove.signal_clicked().connect(sigc::mem_fun(*this, &LivePathEffectEditor::onRemove));
156 button_up.signal_clicked().connect(sigc::mem_fun(*this, &LivePathEffectEditor::onUp));
157 button_down.signal_clicked().connect(sigc::mem_fun(*this, &LivePathEffectEditor::onDown));
159 show_all_children();
161 //button_remove.hide();
162 }
164 LivePathEffectEditor::~LivePathEffectEditor()
165 {
166 if (effectwidget) {
167 effectcontrol_vbox.remove(*effectwidget);
168 delete effectwidget;
169 effectwidget = NULL;
170 }
172 if (current_desktop) {
173 selection_changed_connection.disconnect();
174 selection_modified_connection.disconnect();
175 }
176 }
178 void
179 LivePathEffectEditor::showParams(LivePathEffect::Effect* effect)
180 {
181 if (effectwidget) {
182 effectcontrol_vbox.remove(*effectwidget);
183 delete effectwidget;
184 effectwidget = NULL;
185 }
187 explain_label.set_markup("<b>" + effect->getName() + "</b>");
188 effectwidget = effect->newWidget(&tooltips);
189 if (effectwidget) {
190 effectcontrol_vbox.pack_start(*effectwidget, true, true);
191 }
192 button_remove.show();
194 effectcontrol_vbox.show_all_children();
195 // fixme: add resizing of dialog
196 }
198 void
199 LivePathEffectEditor::selectInList(LivePathEffect::Effect* effect)
200 {
201 Gtk::TreeNodeChildren chi = effectlist_view.get_model()->children();
202 for (Gtk::TreeIter ci = chi.begin() ; ci != chi.end(); ci++) {
203 if (ci->get_value(columns.lperef)->lpeobject->lpe == effect)
204 effectlist_view.get_selection()->select(ci);
205 }
206 }
209 void
210 LivePathEffectEditor::showText(Glib::ustring const &str)
211 {
212 if (effectwidget) {
213 effectcontrol_vbox.remove(*effectwidget);
214 delete effectwidget;
215 effectwidget = NULL;
216 }
218 explain_label.set_label(str);
219 //button_remove.hide();
221 // fixme: do resizing of dialog ?
222 }
224 void
225 LivePathEffectEditor::set_sensitize_all(bool sensitive)
226 {
227 combo_effecttype.set_sensitive(sensitive);
228 button_apply.set_sensitive(sensitive);
229 button_remove.set_sensitive(sensitive);
230 effectlist_view.set_sensitive(sensitive);
231 button_up.set_sensitive(sensitive);
232 button_down.set_sensitive(sensitive);
233 }
236 void
237 LivePathEffectEditor::onSelectionChanged(Inkscape::Selection *sel)
238 {
239 if (lpe_list_locked) {
240 // this was triggered by selecting a row in the list, so skip reloading
241 lpe_list_locked = false;
242 return;
243 }
245 effectlist_store->clear();
246 current_lpeitem = NULL;
248 if ( sel && !sel->isEmpty() ) {
249 SPItem *item = sel->singleItem();
250 if ( item ) {
251 if ( SP_IS_LPE_ITEM(item) ) {
252 SPLPEItem *lpeitem = SP_LPE_ITEM(item);
254 effect_list_reload(lpeitem);
256 current_lpeitem = lpeitem;
258 set_sensitize_all(true);
259 if ( sp_lpe_item_has_path_effect(lpeitem) ) {
260 Inkscape::LivePathEffect::Effect *lpe = sp_lpe_item_get_current_lpe(lpeitem);
261 if (lpe) {
262 showParams(lpe);
263 lpe_list_locked = true;
264 selectInList(lpe);
265 } else {
266 showText(_("Unknown effect is applied"));
267 }
268 } else {
269 showText(_("No effect applied"));
270 button_remove.set_sensitive(false);
271 }
272 } else {
273 showText(_("Item is not a path or shape"));
274 set_sensitize_all(false);
275 }
276 } else {
277 showText(_("Only one item can be selected"));
278 set_sensitize_all(false);
279 }
280 } else {
281 showText(_("Empty selection"));
282 set_sensitize_all(false);
283 }
284 }
286 /*
287 * First clears the effectlist_store, then appends all effects from the effectlist.
288 */
289 void
290 LivePathEffectEditor::effect_list_reload(SPLPEItem *lpeitem)
291 {
292 effectlist_store->clear();
294 PathEffectList effectlist = sp_lpe_item_get_effect_list(lpeitem);
295 PathEffectList::iterator it;
296 for( it = effectlist.begin() ; it!=effectlist.end(); it++ )
297 {
298 Gtk::TreeModel::Row row = *(effectlist_store->append());
299 row[columns.col_name] = (*it)->lpeobject->lpe->getName();
300 row[columns.lperef] = *it;
301 row[columns.col_visible] = (*it)->lpeobject->lpe->isVisible();
302 }
303 }
306 void
307 LivePathEffectEditor::setDesktop(SPDesktop *desktop)
308 {
309 Panel::setDesktop(desktop);
311 if ( desktop == current_desktop ) {
312 return;
313 }
315 if (current_desktop) {
316 selection_changed_connection.disconnect();
317 selection_modified_connection.disconnect();
318 }
320 current_desktop = desktop;
321 if (desktop) {
322 Inkscape::Selection *selection = sp_desktop_selection(desktop);
323 selection_changed_connection = selection->connectChanged(
324 sigc::bind (sigc::ptr_fun(&lpeeditor_selection_changed), this ) );
325 selection_modified_connection = selection->connectModified(
326 sigc::bind (sigc::ptr_fun(&lpeeditor_selection_modified), this ) );
328 onSelectionChanged(selection);
329 } else {
330 onSelectionChanged(NULL);
331 }
332 }
337 /*########################################################################
338 # BUTTON CLICK HANDLERS (callbacks)
339 ########################################################################*/
341 // TODO: factor out the effect applying code which can be called from anywhere. (selection-chemistry.cpp also needs it)
343 void
344 LivePathEffectEditor::onApply()
345 {
346 Inkscape::Selection *sel = _getSelection();
347 if ( sel && !sel->isEmpty() ) {
348 SPItem *item = sel->singleItem();
349 if ( item && SP_IS_LPE_ITEM(item) ) {
350 SPDocument *doc = current_desktop->doc();
352 const Util::EnumData<LivePathEffect::EffectType>* data = combo_effecttype.get_active_data();
353 if (!data) return;
355 // If item is a SPRect, convert it to path first:
356 if ( SP_IS_RECT(item) ) {
357 sp_selected_path_to_curves(false);
358 item = sel->singleItem(); // get new item
359 }
361 LivePathEffect::Effect::createAndApply(data->key.c_str(), doc, item);
363 sp_document_done(doc, SP_VERB_DIALOG_LIVE_PATH_EFFECT,
364 _("Create and apply path effect"));
366 onSelectionChanged(sel);
367 }
368 }
369 }
371 void
372 LivePathEffectEditor::onRemove()
373 {
374 Inkscape::Selection *sel = _getSelection();
375 if ( sel && !sel->isEmpty() ) {
376 SPItem *item = sel->singleItem();
377 if ( item && SP_IS_LPE_ITEM(item) ) {
378 sp_lpe_item_remove_current_path_effect(SP_LPE_ITEM(item), false);
380 sp_document_done ( sp_desktop_document (current_desktop), SP_VERB_DIALOG_LIVE_PATH_EFFECT,
381 _("Remove path effect") );
383 effect_list_reload(SP_LPE_ITEM(item));
384 }
385 }
386 }
388 void LivePathEffectEditor::onUp()
389 {
390 Inkscape::Selection *sel = _getSelection();
391 if ( sel && !sel->isEmpty() ) {
392 SPItem *item = sel->singleItem();
393 if ( item && SP_IS_LPE_ITEM(item) ) {
394 sp_lpe_item_up_current_path_effect(SP_LPE_ITEM(item));
396 sp_document_done ( sp_desktop_document (current_desktop), SP_VERB_DIALOG_LIVE_PATH_EFFECT,
397 _("Move path effect up") );
399 effect_list_reload(SP_LPE_ITEM(item));
400 }
401 }
402 }
404 void LivePathEffectEditor::onDown()
405 {
406 Inkscape::Selection *sel = _getSelection();
407 if ( sel && !sel->isEmpty() ) {
408 SPItem *item = sel->singleItem();
409 if ( item && SP_IS_LPE_ITEM(item) ) {
410 sp_lpe_item_down_current_path_effect(SP_LPE_ITEM(item));
412 sp_document_done ( sp_desktop_document (current_desktop), SP_VERB_DIALOG_LIVE_PATH_EFFECT,
413 _("Move path effect down") );
415 effect_list_reload(SP_LPE_ITEM(item));
416 }
417 }
418 }
420 void LivePathEffectEditor::on_effect_selection_changed()
421 {
422 Glib::RefPtr<Gtk::TreeSelection> sel = effectlist_view.get_selection();
423 if (sel->count_selected_rows () == 0)
424 return;
426 Gtk::TreeModel::iterator it = sel->get_selected();
427 LivePathEffect::LPEObjectReference * lperef = (*it)[columns.lperef];
429 if (lperef && current_lpeitem) {
430 lpe_list_locked = true; // prevent reload of the list which would lose selection
431 sp_lpe_item_set_current_path_effect(current_lpeitem, lperef);
432 showParams(lperef->lpeobject->lpe);
433 }
434 }
436 void LivePathEffectEditor::on_visibility_toggled( Glib::ustring const& str )
437 {
438 Gtk::TreeModel::Children::iterator iter = effectlist_view.get_model()->get_iter(str);
439 Gtk::TreeModel::Row row = *iter;
441 LivePathEffect::LPEObjectReference * lpeobjref = row[columns.lperef];
443 if ( lpeobjref ) {
444 bool newValue = !row[columns.col_visible];
445 row[columns.col_visible] = newValue;
446 /* FIXME: this explicit writing to SVG is wrong. The lpe_item should have a method to disable/enable an effect within its stack.
447 * So one can call: lpe_item->setActive(lpeobjref->lpeobject); */
448 lpeobjref->lpeobject->lpe->getRepr()->setAttribute("is_visible", newValue ? "true" : "false");
449 sp_document_done( sp_desktop_document(current_desktop), SP_VERB_DIALOG_LIVE_PATH_EFFECT,
450 newValue ? _("Activate path effect") : _("Deactivate path effect"));
451 }
452 }
454 } // namespace Dialog
455 } // namespace UI
456 } // namespace Inkscape
458 /*
459 Local Variables:
460 mode:c++
461 c-file-style:"stroustrup"
462 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
463 indent-tabs-mode:nil
464 fill-column:99
465 End:
466 */
467 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :