Code

r15703@tres: ted | 2007-07-02 15:27:14 -0700
[inkscape.git] / src / extension / effect.cpp
1 /*
2  * Authors:
3  *   Ted Gould <ted@gould.cx>
4  *
5  * Copyright (C) 2002-2007 Authors
6  *
7  * Released under GNU GPL, read the file 'COPYING' for more information
8  */
10 #include "inkscape-private.h"
11 #include "helper/action.h"
12 #include "ui/view/view.h"
13 #include "desktop-handles.h"
14 #include "selection.h"
15 #include "sp-namedview.h"
16 #include "document.h"
17 #include "prefdialog.h"
18 #include "implementation/implementation.h"
19 #include "effect.h"
20 #include "timer.h"
21 #include "ui/view/view.h"
23 #include "gtkmm/messagedialog.h"
25 #include "util/glib-list-iterators.h"
27 /* Inkscape::Extension::Effect */
29 namespace Inkscape {
30 namespace Extension {
32 Effect * Effect::_last_effect = NULL;
33 Inkscape::XML::Node * Effect::_effects_list = NULL;
35 Effect::Effect (Inkscape::XML::Node * in_repr, Implementation::Implementation * in_imp)
36     : Extension(in_repr, in_imp),
37       _id_noprefs(Glib::ustring(get_id()) + ".noprefs"),
38       _name_noprefs(Glib::ustring(get_name()) + _(" (No preferences)")),
39       _verb(get_id(), get_name(), NULL, NULL, this, true),
40       _verb_nopref(_id_noprefs.c_str(), _name_noprefs.c_str(), NULL, NULL, this, false),
41       _menu_node(NULL), _workingDialog(true)
42 {
43     Inkscape::XML::Node * local_effects_menu = NULL;
45     // This is a weird hack
46     if (!strcmp(this->get_id(), "org.inkscape.filter.dropshadow"))
47         return;
49     bool hidden = false;
51     no_doc = false;
53     if (repr != NULL) {
55         for (Inkscape::XML::Node *child = sp_repr_children(repr); child != NULL; child = child->next()) {
56             if (!strcmp(child->name(), "effect")) {
57                 if (child->attribute("needs-document") && !strcmp(child->attribute("needs-document"), "no")) {
58                   no_doc = true;
59                 }
60                 for (Inkscape::XML::Node *effect_child = sp_repr_children(child); effect_child != NULL; effect_child = effect_child->next()) {
61                     if (!strcmp(effect_child->name(), "effects-menu")) {
62                         // printf("Found local effects menu in %s\n", this->get_name());
63                         local_effects_menu = sp_repr_children(effect_child);
64                         if (effect_child->attribute("hidden") && !strcmp(effect_child->attribute("hidden"), "yes")) {
65                             hidden = true;
66                         }
67                     }
68                     if (!strcmp(effect_child->name(), "menu-name") ||
69                             !strcmp(effect_child->name(), "_menu-name")) {
70                         // printf("Found local effects menu in %s\n", this->get_name());
71                         _verb.set_name(sp_repr_children(effect_child)->content());
72                     }
73                     if (!strcmp(effect_child->name(), "menu-tip") ||
74                             !strcmp(effect_child->name(), "_menu-tip")) {
75                         // printf("Found local effects menu in %s\n", this->get_name());
76                         _verb.set_tip(sp_repr_children(effect_child)->content());
77                     }
78                 } // children of "effect"
79                 break; // there can only be one effect
80             } // find "effect"
81         } // children of "inkscape-extension"
82     } // if we have an XML file
84     if (_effects_list == NULL && INKSCAPE != NULL) {
85         find_effects_list(inkscape_get_menus(INKSCAPE));
86     }
88     if (_effects_list != NULL) {
89         Inkscape::XML::Document *xml_doc;
90         xml_doc = _effects_list->document();
91         _menu_node = xml_doc->createElement("verb");
92         _menu_node->setAttribute("verb-id", this->get_id(), false);
94         if (!hidden)
95             merge_menu(_effects_list->parent(), _effects_list, local_effects_menu, _menu_node);
96     }
98     return;
99 }
101 void
102 Effect::merge_menu (Inkscape::XML::Node * base,
103                     Inkscape::XML::Node * start,
104                     Inkscape::XML::Node * patern,
105                     Inkscape::XML::Node * mergee) {
106     Glib::ustring mergename;
107     Inkscape::XML::Node * tomerge = NULL;
108     Inkscape::XML::Node * submenu = NULL;
110     /* printf("Merge menu with '%s' '%s' '%s'\n",
111             base != NULL ? base->name() : "NULL",
112             patern != NULL ? patern->name() : "NULL",
113             mergee != NULL ? mergee->name() : "NULL"); */
115     if (patern == NULL) {
116         // Merge the verb name
117         tomerge = mergee;
118         mergename = _(this->get_name());
119     } else {
120         gchar const * menuname = patern->attribute("name");
121         if (menuname == NULL) menuname = patern->attribute("_name");
122         if (menuname == NULL) return;
123         
124         Inkscape::XML::Document *xml_doc;
125         xml_doc = base->document();
126         tomerge = xml_doc->createElement("submenu");
127         tomerge->setAttribute("name", menuname, false);
129         mergename = _(menuname);
130     }
132     int position = -1;
134     if (start != NULL) {
135         Inkscape::XML::Node * menupass;
136         for (menupass = start->next(); menupass != NULL; menupass = menupass->next()) {
137             gchar const * compare_char = NULL;
138             if (!strcmp(menupass->name(), "verb")) {
139                 gchar const * verbid = menupass->attribute("verb-id");
140                 Inkscape::Verb * verb = Inkscape::Verb::getbyid(verbid);
141                 if (verb == NULL) {
142                     continue;
143                 }
144                 compare_char = verb->get_name();
145             } else if (!strcmp(menupass->name(), "submenu")) {
146                 compare_char = menupass->attribute("name");
147                 if (compare_char == NULL)
148                     compare_char = menupass->attribute("_name");
149             }
151             /* This will cause us to skip tags we don't understand */
152             if (compare_char == NULL) {
153                 continue;
154             }
156             Glib::ustring compare(_(compare_char));
158             if (mergename == compare) {
159                 Inkscape::GC::release(tomerge);
160                 tomerge = NULL;
161                 submenu = menupass;
162                 break;
163             }
165             if (mergename < compare) {
166                 position = menupass->position();
167                 break;
168             }
169         } // for menu items
170     } // start != NULL
172     if (tomerge != NULL) {
173         base->appendChild(tomerge);
174         Inkscape::GC::release(tomerge);
175         if (position != -1)
176             tomerge->setPosition(position);
177     }
179     if (patern != NULL) {
180         if (submenu == NULL)
181             submenu = tomerge;
182         merge_menu(submenu, submenu->firstChild(), patern->firstChild(), mergee);
183     }
185     return;
188 Effect::~Effect (void)
190     if (get_last_effect() == this)
191         set_last_effect(NULL);
192     return;
195 bool
196 Effect::check (void)
198     if (!Extension::check()) {
199         /** \todo  Check to see if parent has this as its only child,
200                    if so, delete it too */
201         if (_menu_node != NULL)
202             sp_repr_unparent(_menu_node);
203         _menu_node = NULL;
204         return false;
205     }
206     return true;
209 class ExecutionEnv {
210 private:
211     Effect * _effect;
212     Gtk::Dialog * _visibleDialog;
213     bool _prefsVisible;
214     bool _finished;
215     bool _humanWait;
216     bool _canceled;
217     bool _prefsChanged;
218     Glib::RefPtr<Glib::MainLoop> _mainloop;
219     Inkscape::UI::View::View * _doc;
220     std::list<Glib::ustring> _selected;
222 public:
223     void run (void);
225     ExecutionEnv (Effect * effect, Inkscape::UI::View::View * doc, Gtk::Widget * controls = NULL) :
226         _effect(effect),
227         _visibleDialog(NULL),
228         _prefsVisible(false),
229         _finished(false),
230         _humanWait(false),
231         _canceled(false),
232         _prefsChanged(false),
233         _doc(doc) {
235         SPDesktop *desktop = (SPDesktop *)_doc;
236         sp_namedview_document_from_window(desktop);
238         if (desktop != NULL) {
239             Inkscape::Util::GSListConstIterator<SPItem *> selected =
240                  sp_desktop_selection(desktop)->itemList();
241             while ( selected != NULL ) {
242                 Glib::ustring selected_id;
243                 selected_id = SP_OBJECT_ID(*selected);
244                 _selected.insert(_selected.end(), selected_id);
245                 //std::cout << "Selected: " << selected_id << std::endl;
246                 ++selected;
247             }
248         }
250         _mainloop = Glib::MainLoop::create(false);
252         if (controls != NULL) {
253             createPrefsDialog(controls);
254         } else {
255             createWorkingDialog();
256         }
258         return;
259     }
261     ~ExecutionEnv (void) {
262         if (_visibleDialog != NULL) {
263             delete _visibleDialog;
264         }
265         return;
266     }
268     void preferencesChange (void) {
269         //std::cout << "Preferences are a changin'" << std::endl;
270         _prefsChanged = true;
271         if (_humanWait) {
272             _mainloop->quit();
273             documentCancel();
274             _humanWait = false;
275         } else {
276             processingCancel();
277             documentCancel();
278         }
279         return;
280     }
282 private:
283     void createPrefsDialog (Gtk::Widget * controls) {
284         if (_visibleDialog != NULL) {
285             delete _visibleDialog;
286         }
288         _visibleDialog = new PrefDialog(_effect->get_name(), _effect->get_help(), controls);
289         _visibleDialog->signal_response().connect(sigc::mem_fun(this, &ExecutionEnv::preferencesResponse));
290         _visibleDialog->show();
292         _prefsVisible = true;
293         return;
294     }
296     void createWorkingDialog (void) {
297         if (_visibleDialog != NULL) {
298             delete _visibleDialog;
299         }
301         gchar * dlgmessage = g_strdup_printf(_("The effect '%s' is working on your document.  Please wait."), _effect->get_name());
302         _visibleDialog = new Gtk::MessageDialog(dlgmessage,
303                                    false, // use markup
304                                    Gtk::MESSAGE_INFO,
305                                    Gtk::BUTTONS_CANCEL,
306                                    true); // modal
307         _visibleDialog->signal_response().connect(sigc::mem_fun(this, &ExecutionEnv::workingCanceled));
308         g_free(dlgmessage);
309         _visibleDialog->show();
311         _prefsVisible = false;
312         return;
313     }
315     void workingCanceled (const int resp) {
316         processingCancel();
317         documentCancel();
318         _finished = true;
319         return;
320     }
322     void preferencesResponse (const int resp) {
323         if (resp == Gtk::RESPONSE_OK) {
324             if (_humanWait) {
325                 documentCommit();
326                 _mainloop->quit();
327                 _finished = true;
328             } else {
329                 createWorkingDialog();
330             }
331         } else {
332             if (_humanWait) {
333                 _mainloop->quit();
334             } else {
335                 processingCancel();
336             }
337             documentCancel();
338             _finished = true;
339         }
340         return;
341     }
343     void processingComplete(void) {
344         //std::cout << "Processing Complete" << std::endl;
345         if (_prefsChanged) { return; } // do it all again
346         if (_prefsVisible) {
347             _humanWait = true;
348         } else {
349             documentCommit();
350             _finished = true;
351         }
352         return;
353     }
355     void processingCancel (void) {
356         _effect->get_imp()->cancelProcessing();
357         return;
358     }
360     void documentCancel (void) {
361         _canceled = true;
362         return;
363     }
365     void documentCommit (void) {
366         sp_document_done(_doc->doc(), SP_VERB_NONE, _(_effect->get_name()));
367         Effect::set_last_effect(_effect);
368         return;
369     }
370     
371     void reselect (void) {
372         SPDocument * doc = _doc->doc();
374         SPDesktop *desktop = (SPDesktop *)_doc;
375         sp_namedview_document_from_window(desktop);
377         if (desktop == NULL) { return; }
379         Inkscape::Selection * selection = sp_desktop_selection(desktop);
381         for (std::list<Glib::ustring>::iterator i = _selected.begin(); i != _selected.end(); i++) {
382             selection->add(doc->getObjectById(i->c_str()));
383         }
385         return;
386     }
387 };
389 void
390 ExecutionEnv::run (void) {
391     while (!_finished) {
392         _canceled = false;
393         if (_humanWait) {
394             _mainloop->run();
395         } else {
396             _prefsChanged = false;
397             _effect->get_imp()->effect(_effect, _doc);
398             processingComplete();
399         }
400         if (_canceled) {
401             sp_document_cancel(_doc->doc());
402             reselect();
403         }
404     }
405     return;
408 bool
409 Effect::prefs (Inkscape::UI::View::View * doc)
411     if (!loaded())
412         set_state(Extension::STATE_LOADED);
413     if (!loaded()) return false;
415     sigc::signal<void> changeSignal;
417     Gtk::Widget * controls;
418     controls = imp->prefs_effect(this, doc, &changeSignal);
420     ExecutionEnv executionEnv(this, doc, controls);
421     changeSignal.connect(sigc::mem_fun(executionEnv, &ExecutionEnv::preferencesChange));
423     timer->lock();
424     executionEnv.run();
425     timer->unlock();
427     return true;
430 /**
431     \brief  The function that 'does' the effect itself
432     \param  doc  The Inkscape::UI::View::View to do the effect on
434     This function first insures that the extension is loaded, and if not,
435     loads it.  It then calls the implemention to do the actual work.  It
436     also resets the last effect pointer to be this effect.  Finally, it
437     executes a \c sp_document_done to commit the changes to the undo
438     stack.
439 */
440 void
441 Effect::effect (Inkscape::UI::View::View * doc)
443     if (!loaded())
444         set_state(Extension::STATE_LOADED);
445     if (!loaded()) return;
448     ExecutionEnv executionEnv(this, doc, NULL);
449     executionEnv.run();
451     return;
454 /** \brief  Sets which effect was called last
455     \param in_effect  The effect that has been called
456     
457     This function sets the static variable \c _last_effect and it
458     ensures that the last effect verb is sensitive.
460     If the \c in_effect variable is \c NULL then the last effect
461     verb is made insesitive.
462 */
463 void
464 Effect::set_last_effect (Effect * in_effect)
466     if (in_effect == NULL) {
467         Inkscape::Verb::get(SP_VERB_EFFECT_LAST)->sensitive(NULL, false);
468         Inkscape::Verb::get(SP_VERB_EFFECT_LAST_PREF)->sensitive(NULL, false);
469     } else if (_last_effect == NULL) {
470         Inkscape::Verb::get(SP_VERB_EFFECT_LAST)->sensitive(NULL, true);
471         Inkscape::Verb::get(SP_VERB_EFFECT_LAST_PREF)->sensitive(NULL, true);
472     }
474     _last_effect = in_effect;
475     return;
478 #define  EFFECTS_LIST  "effects-list"
480 bool
481 Effect::find_effects_list (Inkscape::XML::Node * menustruct)
483     if (menustruct == NULL) return false;
484     for (Inkscape::XML::Node * child = menustruct;
485             child != NULL;
486             child = child->next()) {
487         if (!strcmp(child->name(), EFFECTS_LIST)) {
488             _effects_list = child;
489             return true;
490         }
491         Inkscape::XML::Node * firstchild = child->firstChild();
492         if (firstchild != NULL)
493             if (find_effects_list(firstchild))
494                 return true;
495     }
496     return false;
499 Gtk::VBox *
500 Effect::get_info_widget(void)
502     return Extension::get_info_widget();
505 /** \brief  Create an action for a \c EffectVerb
506     \param  view  Which view the action should be created for
507     \return The built action.
509     Calls \c make_action_helper with the \c vector.
510 */
511 SPAction *
512 Effect::EffectVerb::make_action (Inkscape::UI::View::View * view)
514     return make_action_helper(view, &vector, static_cast<void *>(this));
517 /** \brief  Decode the verb code and take appropriate action */
518 void
519 Effect::EffectVerb::perform (SPAction *action, void * data, void *pdata)
521     Inkscape::UI::View::View * current_view = sp_action_get_view(action);
522 //  SPDocument * current_document = current_view->doc;
523     Effect::EffectVerb * ev = reinterpret_cast<Effect::EffectVerb *>(data);
524     Effect * effect = ev->_effect;
526     if (effect == NULL) return;
527     if (current_view == NULL) return;
529     if (ev->_showPrefs) {
530         effect->prefs(current_view);
531     } else {
532         effect->effect(current_view);
533     }
535     return;
538 /**
539  * Action vector to define functions called if a staticly defined file verb
540  * is called.
541  */
542 SPActionEventVector Effect::EffectVerb::vector =
543             {{NULL}, Effect::EffectVerb::perform, NULL, NULL, NULL, NULL};
546 } }  /* namespace Inkscape, Extension */
548 /*
549   Local Variables:
550   mode:c++
551   c-file-style:"stroustrup"
552   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
553   indent-tabs-mode:nil
554   fill-column:99
555   End:
556 */
557 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :