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;
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;
186 }
188 Effect::~Effect (void)
189 {
190 if (get_last_effect() == this)
191 set_last_effect(NULL);
192 return;
193 }
195 bool
196 Effect::check (void)
197 {
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;
207 }
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 }
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;
406 }
408 bool
409 Effect::prefs (Inkscape::UI::View::View * doc)
410 {
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;
428 }
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)
442 {
443 if (!loaded())
444 set_state(Extension::STATE_LOADED);
445 if (!loaded()) return;
448 ExecutionEnv executionEnv(this, doc, NULL);
449 executionEnv.run();
451 return;
452 }
454 /** \brief Sets which effect was called last
455 \param in_effect The effect that has been called
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)
465 {
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;
476 }
478 #define EFFECTS_LIST "effects-list"
480 bool
481 Effect::find_effects_list (Inkscape::XML::Node * menustruct)
482 {
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;
497 }
499 Gtk::VBox *
500 Effect::get_info_widget(void)
501 {
502 return Extension::get_info_widget();
503 }
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)
513 {
514 return make_action_helper(view, &vector, static_cast<void *>(this));
515 }
517 /** \brief Decode the verb code and take appropriate action */
518 void
519 Effect::EffectVerb::perform (SPAction *action, void * data, void *pdata)
520 {
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;
536 }
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 :