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 "desktop.h"
17 #include "implementation/implementation.h"
18 #include "effect.h"
19 #include "execution-env.h"
20 #include "timer.h"
24 /* Inkscape::Extension::Effect */
26 namespace Inkscape {
27 namespace Extension {
29 Effect * Effect::_last_effect = NULL;
30 Inkscape::XML::Node * Effect::_effects_list = NULL;
31 Inkscape::XML::Node * Effect::_filters_list = NULL;
33 #define EFFECTS_LIST "effects-list"
34 #define FILTERS_LIST "filters-list"
36 Effect::Effect (Inkscape::XML::Node * in_repr, Implementation::Implementation * in_imp)
37 : Extension(in_repr, in_imp),
38 _id_noprefs(Glib::ustring(get_id()) + ".noprefs"),
39 _name_noprefs(Glib::ustring(get_name()) + _(" (No preferences)")),
40 _verb(get_id(), get_name(), NULL, NULL, this, true),
41 _verb_nopref(_id_noprefs.c_str(), _name_noprefs.c_str(), NULL, NULL, this, false),
42 _menu_node(NULL), _workingDialog(true),
43 _prefDialog(NULL)
44 {
45 Inkscape::XML::Node * local_effects_menu = NULL;
47 // This is a weird hack
48 if (!strcmp(this->get_id(), "org.inkscape.filter.dropshadow"))
49 return;
51 bool hidden = false;
53 no_doc = false;
54 no_live_preview = false;
56 if (repr != NULL) {
58 for (Inkscape::XML::Node *child = sp_repr_children(repr); child != NULL; child = child->next()) {
59 if (!strcmp(child->name(), INKSCAPE_EXTENSION_NS "effect")) {
60 if (child->attribute("needs-document") && !strcmp(child->attribute("needs-document"), "false")) {
61 no_doc = true;
62 }
63 if (child->attribute("needs-live-preview") && !strcmp(child->attribute("needs-live-preview"), "false")) {
64 no_live_preview = true;
65 }
66 for (Inkscape::XML::Node *effect_child = sp_repr_children(child); effect_child != NULL; effect_child = effect_child->next()) {
67 if (!strcmp(effect_child->name(), INKSCAPE_EXTENSION_NS "effects-menu")) {
68 // printf("Found local effects menu in %s\n", this->get_name());
69 local_effects_menu = sp_repr_children(effect_child);
70 if (effect_child->attribute("hidden") && !strcmp(effect_child->attribute("hidden"), "true")) {
71 hidden = true;
72 }
73 }
74 if (!strcmp(effect_child->name(), INKSCAPE_EXTENSION_NS "menu-name") ||
75 !strcmp(effect_child->name(), INKSCAPE_EXTENSION_NS "_menu-name")) {
76 // printf("Found local effects menu in %s\n", this->get_name());
77 _verb.set_name(sp_repr_children(effect_child)->content());
78 }
79 if (!strcmp(effect_child->name(), INKSCAPE_EXTENSION_NS "menu-tip") ||
80 !strcmp(effect_child->name(), INKSCAPE_EXTENSION_NS "_menu-tip")) {
81 // printf("Found local effects menu in %s\n", this->get_name());
82 _verb.set_tip(sp_repr_children(effect_child)->content());
83 }
84 } // children of "effect"
85 break; // there can only be one effect
86 } // find "effect"
87 } // children of "inkscape-extension"
88 } // if we have an XML file
90 if (INKSCAPE != NULL) {
91 if (_effects_list == NULL)
92 _effects_list = find_menu(inkscape_get_menus(INKSCAPE), EFFECTS_LIST);
93 if (_filters_list == NULL)
94 _filters_list = find_menu(inkscape_get_menus(INKSCAPE), FILTERS_LIST);
95 }
97 if ((_effects_list != NULL || _filters_list != NULL)) {
98 Inkscape::XML::Document *xml_doc;
99 xml_doc = _effects_list->document();
100 _menu_node = xml_doc->createElement("verb");
101 _menu_node->setAttribute("verb-id", this->get_id(), false);
103 if (!hidden) {
104 if (_filters_list &&
105 local_effects_menu &&
106 local_effects_menu->attribute("name") &&
107 !strcmp(local_effects_menu->attribute("name"), ("Filters"))) {
108 merge_menu(_filters_list->parent(), _filters_list, sp_repr_children(local_effects_menu), _menu_node);
109 } else if (_effects_list) {
110 merge_menu(_effects_list->parent(), _effects_list, local_effects_menu, _menu_node);
111 }
112 }
113 }
115 return;
116 }
118 void
119 Effect::merge_menu (Inkscape::XML::Node * base,
120 Inkscape::XML::Node * start,
121 Inkscape::XML::Node * patern,
122 Inkscape::XML::Node * mergee) {
123 Glib::ustring mergename;
124 Inkscape::XML::Node * tomerge = NULL;
125 Inkscape::XML::Node * submenu = NULL;
127 /* printf("Merge menu with '%s' '%s' '%s'\n",
128 base != NULL ? base->name() : "NULL",
129 patern != NULL ? patern->name() : "NULL",
130 mergee != NULL ? mergee->name() : "NULL"); */
132 if (patern == NULL) {
133 // Merge the verb name
134 tomerge = mergee;
135 mergename = _(this->get_name());
136 } else {
137 gchar const * menuname = patern->attribute("name");
138 if (menuname == NULL) menuname = patern->attribute("_name");
139 if (menuname == NULL) return;
141 Inkscape::XML::Document *xml_doc;
142 xml_doc = base->document();
143 tomerge = xml_doc->createElement("submenu");
144 tomerge->setAttribute("name", menuname, false);
146 mergename = _(menuname);
147 }
149 int position = -1;
151 if (start != NULL) {
152 Inkscape::XML::Node * menupass;
153 for (menupass = start; menupass != NULL && strcmp(menupass->name(), "separator"); menupass = menupass->next()) {
154 gchar const * compare_char = NULL;
155 if (!strcmp(menupass->name(), "verb")) {
156 gchar const * verbid = menupass->attribute("verb-id");
157 Inkscape::Verb * verb = Inkscape::Verb::getbyid(verbid);
158 if (verb == NULL) {
159 g_warning("Unable to find verb '%s' which is referred to in the menus.", verbid);
160 continue;
161 }
162 compare_char = verb->get_name();
163 } else if (!strcmp(menupass->name(), "submenu")) {
164 compare_char = menupass->attribute("name");
165 if (compare_char == NULL)
166 compare_char = menupass->attribute("_name");
167 }
169 position = menupass->position() + 1;
171 /* This will cause us to skip tags we don't understand */
172 if (compare_char == NULL) {
173 continue;
174 }
176 Glib::ustring compare(_(compare_char));
178 if (mergename == compare) {
179 Inkscape::GC::release(tomerge);
180 tomerge = NULL;
181 submenu = menupass;
182 break;
183 }
185 if (mergename < compare) {
186 position = menupass->position();
187 break;
188 }
189 } // for menu items
190 } // start != NULL
192 if (tomerge != NULL) {
193 base->appendChild(tomerge);
194 Inkscape::GC::release(tomerge);
195 if (position != -1)
196 tomerge->setPosition(position);
197 }
199 if (patern != NULL) {
200 if (submenu == NULL)
201 submenu = tomerge;
202 merge_menu(submenu, submenu->firstChild(), patern->firstChild(), mergee);
203 }
205 return;
206 }
208 Effect::~Effect (void)
209 {
210 if (get_last_effect() == this)
211 set_last_effect(NULL);
212 if (_menu_node)
213 Inkscape::GC::release(_menu_node);
214 return;
215 }
217 bool
218 Effect::check (void)
219 {
220 if (!Extension::check()) {
221 /** \todo Check to see if parent has this as its only child,
222 if so, delete it too */
223 if (_menu_node != NULL)
224 sp_repr_unparent(_menu_node);
225 _menu_node = NULL;
226 return false;
227 }
228 return true;
229 }
231 bool
232 Effect::prefs (Inkscape::UI::View::View * doc)
233 {
234 if (_prefDialog != NULL) {
235 _prefDialog->raise();
236 return true;
237 }
239 if (param_visible_count() == 0) {
240 effect(doc);
241 return true;
242 }
244 if (!loaded())
245 set_state(Extension::STATE_LOADED);
246 if (!loaded()) return false;
248 _prefDialog = new PrefDialog(this->get_name(), this->get_help(), NULL, this);
249 _prefDialog->show();
251 return true;
252 }
254 /**
255 \brief The function that 'does' the effect itself
256 \param doc The Inkscape::UI::View::View to do the effect on
258 This function first insures that the extension is loaded, and if not,
259 loads it. It then calls the implemention to do the actual work. It
260 also resets the last effect pointer to be this effect. Finally, it
261 executes a \c sp_document_done to commit the changes to the undo
262 stack.
263 */
264 void
265 Effect::effect (Inkscape::UI::View::View * doc)
266 {
267 //printf("Execute effect\n");
268 if (!loaded())
269 set_state(Extension::STATE_LOADED);
270 if (!loaded()) return;
273 ExecutionEnv executionEnv(this, doc);
274 timer->lock();
275 executionEnv.run();
276 if (executionEnv.wait()) {
277 executionEnv.commit();
278 } else {
279 executionEnv.cancel();
280 }
281 timer->unlock();
283 return;
284 }
286 /** \brief Sets which effect was called last
287 \param in_effect The effect that has been called
289 This function sets the static variable \c _last_effect and it
290 ensures that the last effect verb is sensitive.
292 If the \c in_effect variable is \c NULL then the last effect
293 verb is made insesitive.
294 */
295 void
296 Effect::set_last_effect (Effect * in_effect)
297 {
298 gchar const * verb_id = in_effect->get_verb()->get_id();
299 gchar const * help_id_prefix = "org.inkscape.help.";
301 // We don't want these "effects" to register as the last effect,
302 // this wouldn't be helpful to the user who selects a real effect,
303 // then goes to the help file (implemented as an effect), then goes
304 // back to the effect, only to see it written over by the help file
305 // selection.
307 // This snippet should fix this bug:
308 // https://bugs.launchpad.net/inkscape/+bug/600671
309 if (strncmp(verb_id, help_id_prefix, strlen(help_id_prefix)) == 0) return;
311 if (in_effect == NULL) {
312 Inkscape::Verb::get(SP_VERB_EFFECT_LAST)->sensitive(NULL, false);
313 Inkscape::Verb::get(SP_VERB_EFFECT_LAST_PREF)->sensitive(NULL, false);
314 } else if (_last_effect == NULL) {
315 Inkscape::Verb::get(SP_VERB_EFFECT_LAST)->sensitive(NULL, true);
316 Inkscape::Verb::get(SP_VERB_EFFECT_LAST_PREF)->sensitive(NULL, true);
317 }
319 _last_effect = in_effect;
320 return;
321 }
323 Inkscape::XML::Node *
324 Effect::find_menu (Inkscape::XML::Node * menustruct, const gchar *name)
325 {
326 if (menustruct == NULL) return false;
327 for (Inkscape::XML::Node * child = menustruct;
328 child != NULL;
329 child = child->next()) {
330 if (!strcmp(child->name(), name)) {
331 return child;
332 }
333 Inkscape::XML::Node * firstchild = child->firstChild();
334 if (firstchild != NULL) {
335 Inkscape::XML::Node *found = find_menu (firstchild, name);
336 if (found)
337 return found;
338 }
339 }
340 return NULL;
341 }
344 Gtk::VBox *
345 Effect::get_info_widget(void)
346 {
347 return Extension::get_info_widget();
348 }
350 void
351 Effect::set_pref_dialog (PrefDialog * prefdialog)
352 {
353 _prefDialog = prefdialog;
354 return;
355 }
357 /** \brief Create an action for a \c EffectVerb
358 \param view Which view the action should be created for
359 \return The built action.
361 Calls \c make_action_helper with the \c vector.
362 */
363 SPAction *
364 Effect::EffectVerb::make_action (Inkscape::UI::View::View * view)
365 {
366 return make_action_helper(view, &vector, static_cast<void *>(this));
367 }
369 /** \brief Decode the verb code and take appropriate action */
370 void
371 Effect::EffectVerb::perform( SPAction *action, void * data, void */*pdata*/ )
372 {
373 Inkscape::UI::View::View * current_view = sp_action_get_view(action);
374 // SPDocument * current_document = current_view->doc;
375 Effect::EffectVerb * ev = reinterpret_cast<Effect::EffectVerb *>(data);
376 Effect * effect = ev->_effect;
378 if (effect == NULL) return;
379 if (current_view == NULL) return;
381 if (ev->_showPrefs) {
382 effect->prefs(current_view);
383 } else {
384 effect->effect(current_view);
385 }
387 return;
388 }
390 /**
391 * Action vector to define functions called if a staticly defined file verb
392 * is called.
393 */
394 SPActionEventVector Effect::EffectVerb::vector =
395 {{NULL}, Effect::EffectVerb::perform, NULL, NULL, NULL, NULL};
398 } } /* namespace Inkscape, Extension */
400 /*
401 Local Variables:
402 mode:c++
403 c-file-style:"stroustrup"
404 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
405 indent-tabs-mode:nil
406 fill-column:99
407 End:
408 */
409 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :