f2f1a0295827e9837d0943b4abc8e9746bcec957
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);
419 if (controls == NULL) {
420 // std::cout << "No preferences for Effect" << std::endl;
421 return true;
422 }
424 ExecutionEnv executionEnv(this, doc, controls);
425 changeSignal.connect(sigc::mem_fun(executionEnv, &ExecutionEnv::preferencesChange));
427 timer->lock();
428 executionEnv.run();
429 timer->unlock();
431 return true;
432 }
434 /**
435 \brief The function that 'does' the effect itself
436 \param doc The Inkscape::UI::View::View to do the effect on
438 This function first insures that the extension is loaded, and if not,
439 loads it. It then calls the implemention to do the actual work. It
440 also resets the last effect pointer to be this effect. Finally, it
441 executes a \c sp_document_done to commit the changes to the undo
442 stack.
443 */
444 void
445 Effect::effect (Inkscape::UI::View::View * doc)
446 {
447 if (!loaded())
448 set_state(Extension::STATE_LOADED);
449 if (!loaded()) return;
452 ExecutionEnv executionEnv(this, doc, NULL);
453 executionEnv.run();
455 return;
456 }
458 /** \brief Sets which effect was called last
459 \param in_effect The effect that has been called
461 This function sets the static variable \c _last_effect and it
462 ensures that the last effect verb is sensitive.
464 If the \c in_effect variable is \c NULL then the last effect
465 verb is made insesitive.
466 */
467 void
468 Effect::set_last_effect (Effect * in_effect)
469 {
470 if (in_effect == NULL) {
471 Inkscape::Verb::get(SP_VERB_EFFECT_LAST)->sensitive(NULL, false);
472 Inkscape::Verb::get(SP_VERB_EFFECT_LAST_PREF)->sensitive(NULL, false);
473 } else if (_last_effect == NULL) {
474 Inkscape::Verb::get(SP_VERB_EFFECT_LAST)->sensitive(NULL, true);
475 Inkscape::Verb::get(SP_VERB_EFFECT_LAST_PREF)->sensitive(NULL, true);
476 }
478 _last_effect = in_effect;
479 return;
480 }
482 #define EFFECTS_LIST "effects-list"
484 bool
485 Effect::find_effects_list (Inkscape::XML::Node * menustruct)
486 {
487 if (menustruct == NULL) return false;
488 for (Inkscape::XML::Node * child = menustruct;
489 child != NULL;
490 child = child->next()) {
491 if (!strcmp(child->name(), EFFECTS_LIST)) {
492 _effects_list = child;
493 return true;
494 }
495 Inkscape::XML::Node * firstchild = child->firstChild();
496 if (firstchild != NULL)
497 if (find_effects_list(firstchild))
498 return true;
499 }
500 return false;
501 }
503 Gtk::VBox *
504 Effect::get_info_widget(void)
505 {
506 return Extension::get_info_widget();
507 }
509 /** \brief Create an action for a \c EffectVerb
510 \param view Which view the action should be created for
511 \return The built action.
513 Calls \c make_action_helper with the \c vector.
514 */
515 SPAction *
516 Effect::EffectVerb::make_action (Inkscape::UI::View::View * view)
517 {
518 return make_action_helper(view, &vector, static_cast<void *>(this));
519 }
521 /** \brief Decode the verb code and take appropriate action */
522 void
523 Effect::EffectVerb::perform (SPAction *action, void * data, void *pdata)
524 {
525 Inkscape::UI::View::View * current_view = sp_action_get_view(action);
526 // SPDocument * current_document = current_view->doc;
527 Effect::EffectVerb * ev = reinterpret_cast<Effect::EffectVerb *>(data);
528 Effect * effect = ev->_effect;
530 if (effect == NULL) return;
531 if (current_view == NULL) return;
533 if (ev->_showPrefs) {
534 effect->prefs(current_view);
535 } else {
536 effect->effect(current_view);
537 }
539 return;
540 }
542 /**
543 * Action vector to define functions called if a staticly defined file verb
544 * is called.
545 */
546 SPActionEventVector Effect::EffectVerb::vector =
547 {{NULL}, Effect::EffectVerb::perform, NULL, NULL, NULL, NULL};
550 } } /* namespace Inkscape, Extension */
552 /*
553 Local Variables:
554 mode:c++
555 c-file-style:"stroustrup"
556 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
557 indent-tabs-mode:nil
558 fill-column:99
559 End:
560 */
561 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :