Code

Move the task view changing to the "View" menu. Fixes bugs #170781 and #171663.
[inkscape.git] / src / interface.cpp
1 #define __SP_INTERFACE_C__
3 /** @file
4  * @brief Main UI stuff
5  */
6 /* Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   Frank Felfe <innerspace@iname.com>
9  *   bulia byak <buliabyak@users.sf.net>
10  *
11  * Copyright (C) 1999-2005 authors
12  * Copyright (C) 2001-2002 Ximian, Inc.
13  * Copyright (C) 2004 David Turner
14  *
15  * Released under GNU GPL, read the file 'COPYING' for more information
16  */
18 #ifdef HAVE_CONFIG_H
19 # include "config.h"
20 #endif
22 #include <gtk/gtk.h>
23 #include <glib.h>
25 #include "inkscape-private.h"
26 #include "extension/db.h"
27 #include "extension/effect.h"
28 #include "extension/input.h"
29 #include "widgets/icon.h"
30 #include "preferences.h"
31 #include "path-prefix.h"
32 #include "shortcuts.h"
33 #include "document.h"
34 #include "desktop-handles.h"
35 #include "file.h"
36 #include "interface.h"
37 #include "desktop.h"
38 #include "ui/context-menu.h"
39 #include "selection.h"
40 #include "selection-chemistry.h"
41 #include "svg-view-widget.h"
42 #include "widgets/desktop-widget.h"
43 #include "sp-item-group.h"
44 #include "sp-text.h"
45 #include "sp-gradient-fns.h"
46 #include "sp-gradient.h"
47 #include "sp-flowtext.h"
48 #include "sp-namedview.h"
49 #include "ui/view/view.h"
50 #include "helper/action.h"
51 #include "helper/gnome-utils.h"
52 #include "helper/window.h"
53 #include "io/sys.h"
54 #include "dialogs/dialog-events.h"
55 #include "message-context.h"
56 #include "ui/uxmanager.h"
58 // Added for color drag-n-drop
59 #if ENABLE_LCMS
60 #include "lcms.h"
61 #endif // ENABLE_LCMS
62 #include "display/sp-canvas.h"
63 #include "color.h"
64 #include "svg/svg-color.h"
65 #include "desktop-style.h"
66 #include "style.h"
67 #include "event-context.h"
68 #include "gradient-drag.h"
69 #include "widgets/ege-paint-def.h"
71 // Include Mac OS X menu synchronization on native OSX build
72 #ifdef GDK_WINDOWING_QUARTZ
73 #include "ige-mac-menu.h"
74 #endif
76 /* Drag and Drop */
77 typedef enum {
78     URI_LIST,
79     SVG_XML_DATA,
80     SVG_DATA,
81     PNG_DATA,
82     JPEG_DATA,
83     IMAGE_DATA,
84     APP_X_INKY_COLOR,
85     APP_X_COLOR,
86     APP_OSWB_COLOR,
87 } ui_drop_target_info;
89 static GtkTargetEntry ui_drop_target_entries [] = {
90     {(gchar *)"text/uri-list",                0, URI_LIST        },
91     {(gchar *)"image/svg+xml",                0, SVG_XML_DATA    },
92     {(gchar *)"image/svg",                    0, SVG_DATA        },
93     {(gchar *)"image/png",                    0, PNG_DATA        },
94     {(gchar *)"image/jpeg",                   0, JPEG_DATA       },
95 #if ENABLE_MAGIC_COLORS
96     {(gchar *)"application/x-inkscape-color", 0, APP_X_INKY_COLOR},
97 #endif // ENABLE_MAGIC_COLORS
98     {(gchar *)"application/x-oswb-color",     0, APP_OSWB_COLOR  },
99     {(gchar *)"application/x-color",          0, APP_X_COLOR     }
100 };
102 static GtkTargetEntry *completeDropTargets = 0;
103 static int completeDropTargetsCount = 0;
104 static bool temporarily_block_actions = false;
106 #define ENTRIES_SIZE(n) sizeof(n)/sizeof(n[0])
107 static guint nui_drop_target_entries = ENTRIES_SIZE(ui_drop_target_entries);
108 static void sp_ui_import_files(gchar *buffer);
109 static void sp_ui_import_one_file(char const *filename);
110 static void sp_ui_import_one_file_with_check(gpointer filename, gpointer unused);
111 static void sp_ui_drag_data_received(GtkWidget *widget,
112                                      GdkDragContext *drag_context,
113                                      gint x, gint y,
114                                      GtkSelectionData *data,
115                                      guint info,
116                                      guint event_time,
117                                      gpointer user_data);
118 static void sp_ui_drag_motion( GtkWidget *widget,
119                                GdkDragContext *drag_context,
120                                gint x, gint y,
121                                GtkSelectionData *data,
122                                guint info,
123                                guint event_time,
124                                gpointer user_data );
125 static void sp_ui_drag_leave( GtkWidget *widget,
126                               GdkDragContext *drag_context,
127                               guint event_time,
128                               gpointer user_data );
129 static void sp_ui_menu_item_set_sensitive(SPAction *action,
130                                           unsigned int sensitive,
131                                           void *data);
132 static void sp_ui_menu_item_set_name(SPAction *action,
133                                      Glib::ustring name,
134                                      void *data);
135 static void sp_recent_open(GtkRecentChooser *, gpointer);
137 SPActionEventVector menu_item_event_vector = {
138     {NULL},
139     NULL,
140     NULL, /* set_active */
141     sp_ui_menu_item_set_sensitive, /* set_sensitive */
142     NULL, /* set_shortcut */
143     sp_ui_menu_item_set_name /* set_name */
144 };
146 static const int MIN_ONSCREEN_DISTANCE = 50;
148 void
149 sp_create_window(SPViewWidget *vw, gboolean editable)
151     g_return_if_fail(vw != NULL);
152     g_return_if_fail(SP_IS_VIEW_WIDGET(vw));
154     Gtk::Window *win = Inkscape::UI::window_new("", TRUE);
156     gtk_container_add(GTK_CONTAINER(win->gobj()), GTK_WIDGET(vw));
157     gtk_widget_show(GTK_WIDGET(vw));
159     if (editable) {
160         g_object_set_data(G_OBJECT(vw), "window", win);
162         SPDesktopWidget *desktop_widget = reinterpret_cast<SPDesktopWidget*>(vw);
163         SPDesktop* desktop = desktop_widget->desktop;
165         desktop_widget->window = win;
167         win->set_data("desktop", desktop);
168         win->set_data("desktopwidget", desktop_widget);
170         win->signal_delete_event().connect(sigc::mem_fun(*(SPDesktop*)vw->view, &SPDesktop::onDeleteUI));
171         win->signal_window_state_event().connect(sigc::mem_fun(*desktop, &SPDesktop::onWindowStateEvent));
172         win->signal_focus_in_event().connect(sigc::mem_fun(*desktop_widget, &SPDesktopWidget::onFocusInEvent));
174         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
175         gint prefs_geometry =
176             (2==prefs->getInt("/options/savewindowgeometry/value", 0));
177         if (prefs_geometry) {
178             gint pw = prefs->getInt("/desktop/geometry/width", -1);
179             gint ph = prefs->getInt("/desktop/geometry/height", -1);
180             gint px = prefs->getInt("/desktop/geometry/x", -1);
181             gint py = prefs->getInt("/desktop/geometry/y", -1);
182             gint full = prefs->getBool("/desktop/geometry/fullscreen");
183             gint maxed = prefs->getBool("/desktop/geometry/maximized");
184             if (pw>0 && ph>0) {
185                 gint w = MIN(gdk_screen_width(), pw);
186                 gint h = MIN(gdk_screen_height(), ph);
187                 gint x = MIN(gdk_screen_width() - MIN_ONSCREEN_DISTANCE, px);
188                 gint y = MIN(gdk_screen_height() - MIN_ONSCREEN_DISTANCE, py);
189                 if (w>0 && h>0) {
190                     x = MIN(gdk_screen_width() - w, x);
191                     y = MIN(gdk_screen_height() - h, y);
192                     desktop->setWindowSize(w, h);
193                 }
195                 // Only restore xy for the first window so subsequent windows don't overlap exactly
196                 // with first.  (Maybe rule should be only restore xy if it's different from xy of
197                 // other desktops?)
199                 // Empirically it seems that active_desktop==this desktop only the first time a
200                 // desktop is created.
201                 SPDesktop *active_desktop = SP_ACTIVE_DESKTOP;
202                 if (active_desktop == desktop || active_desktop==NULL) {
203                     desktop->setWindowPosition(Geom::Point(x, y));
204                 }
205             }
206             if (maxed) {
207                 win->maximize();
208             }
209             if (full) {
210                 win->fullscreen();
211             }
212         }
214     } else {
215         gtk_window_set_policy(GTK_WINDOW(win->gobj()), TRUE, TRUE, TRUE);
216     }
218     if ( completeDropTargets == 0 || completeDropTargetsCount == 0 )
219     {
220         std::vector<gchar*> types;
222         GSList *list = gdk_pixbuf_get_formats();
223         while ( list ) {
224             int i = 0;
225             GdkPixbufFormat *one = (GdkPixbufFormat*)list->data;
226             gchar** typesXX = gdk_pixbuf_format_get_mime_types(one);
227             for ( i = 0; typesXX[i]; i++ ) {
228                 types.push_back(g_strdup(typesXX[i]));
229             }
230             g_strfreev(typesXX);
232             list = g_slist_next(list);
233         }
234         completeDropTargetsCount = nui_drop_target_entries + types.size();
235         completeDropTargets = new GtkTargetEntry[completeDropTargetsCount];
236         for ( int i = 0; i < (int)nui_drop_target_entries; i++ ) {
237             completeDropTargets[i] = ui_drop_target_entries[i];
238         }
239         int pos = nui_drop_target_entries;
241         for (std::vector<gchar*>::iterator it = types.begin() ; it != types.end() ; it++) {
242             completeDropTargets[pos].target = *it;
243             completeDropTargets[pos].flags = 0;
244             completeDropTargets[pos].info = IMAGE_DATA;
245             pos++;
246         }
247     }
249     gtk_drag_dest_set((GtkWidget*)win->gobj(),
250                       GTK_DEST_DEFAULT_ALL,
251                       completeDropTargets,
252                       completeDropTargetsCount,
253                       GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE));
256     g_signal_connect(G_OBJECT(win->gobj()),
257                      "drag_data_received",
258                      G_CALLBACK(sp_ui_drag_data_received),
259                      NULL);
260     g_signal_connect(G_OBJECT(win->gobj()),
261                      "drag_motion",
262                      G_CALLBACK(sp_ui_drag_motion),
263                      NULL);
264     g_signal_connect(G_OBJECT(win->gobj()),
265                      "drag_leave",
266                      G_CALLBACK(sp_ui_drag_leave),
267                      NULL);
268     win->show();
270     // needed because the first ACTIVATE_DESKTOP was sent when there was no window yet
271     inkscape_reactivate_desktop(SP_DESKTOP_WIDGET(vw)->desktop);
274 void
275 sp_ui_new_view()
277     SPDocument *document;
278     SPViewWidget *dtw;
280     document = SP_ACTIVE_DOCUMENT;
281     if (!document) return;
283     dtw = sp_desktop_widget_new(sp_document_namedview(document, NULL));
284     g_return_if_fail(dtw != NULL);
286     sp_create_window(dtw, TRUE);
287     sp_namedview_window_from_document(static_cast<SPDesktop*>(dtw->view));
288     sp_namedview_update_layers_from_document(static_cast<SPDesktop*>(dtw->view));
291 /* TODO: not yet working */
292 /* To be re-enabled (by adding to menu) once it works. */
293 void
294 sp_ui_new_view_preview()
296     SPDocument *document;
297     SPViewWidget *dtw;
299     document = SP_ACTIVE_DOCUMENT;
300     if (!document) return;
302     dtw = (SPViewWidget *) sp_svg_view_widget_new(document);
303     g_return_if_fail(dtw != NULL);
304     sp_svg_view_widget_set_resize(SP_SVG_VIEW_WIDGET(dtw), TRUE, 400.0, 400.0);
306     sp_create_window(dtw, FALSE);
309 /**
310  * \param widget unused
311  */
312 void
313 sp_ui_close_view(GtkWidget */*widget*/)
315         SPDesktop *dt = SP_ACTIVE_DESKTOP;
317         if (dt == NULL) {
318         return;
319     }
321     if (dt->shutdown()) {
322         return; // Shutdown operation has been canceled, so do nothing
323     }
325     // Shutdown can proceed; use the stored reference to the desktop here instead of the current SP_ACTIVE_DESKTOP,
326     // because the user might have changed the focus in the meantime (see bug #381357 on Launchpad)
327     dt->destroyWidget();
331 /**
332  *  sp_ui_close_all
333  *
334  *  This function is called to exit the program, and iterates through all
335  *  open document view windows, attempting to close each in turn.  If the
336  *  view has unsaved information, the user will be prompted to save,
337  *  discard, or cancel.
338  *
339  *  Returns FALSE if the user cancels the close_all operation, TRUE
340  *  otherwise.
341  */
342 unsigned int
343 sp_ui_close_all(void)
345     /* Iterate through all the windows, destroying each in the order they
346        become active */
347     while (SP_ACTIVE_DESKTOP) {
348         SPDesktop *dt = SP_ACTIVE_DESKTOP;
349         if (dt->shutdown()) {
350             /* The user canceled the operation, so end doing the close */
351             return FALSE;
352         }
353         // Shutdown can proceed; use the stored reference to the desktop here instead of the current SP_ACTIVE_DESKTOP,
354         // because the user might have changed the focus in the meantime (see bug #381357 on Launchpad)
355         dt->destroyWidget();
356     }
358     return TRUE;
361 /*
362  * Some day when the right-click menus are ready to start working
363  * smarter with the verbs, we'll need to change this NULL being
364  * sent to sp_action_perform to something useful, or set some kind
365  * of global "right-clicked position" variable for actions to
366  * investigate when they're called.
367  */
368 static void
369 sp_ui_menu_activate(void */*object*/, SPAction *action)
371     if (!temporarily_block_actions) {
372         sp_action_perform(action, NULL);
373     }
376 static void
377 sp_ui_menu_select_action(void */*object*/, SPAction *action)
379     action->view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, action->tip);
382 static void
383 sp_ui_menu_deselect_action(void */*object*/, SPAction *action)
385     action->view->tipsMessageContext()->clear();
388 static void
389 sp_ui_menu_select(gpointer object, gpointer tip)
391     Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view"));
392     view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, (gchar *)tip);
395 static void
396 sp_ui_menu_deselect(gpointer object)
398     Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*>  (g_object_get_data(G_OBJECT(object), "view"));
399     view->tipsMessageContext()->clear();
402 /**
403  * sp_ui_menuitem_add_icon
404  *
405  * Creates and attaches a scaled icon to the given menu item.
406  *
407  */
408 void
409 sp_ui_menuitem_add_icon( GtkWidget *item, gchar *icon_name )
411     GtkWidget *icon;
413     icon = sp_icon_new( Inkscape::ICON_SIZE_MENU, icon_name );
414     gtk_widget_show(icon);
415     gtk_image_menu_item_set_image((GtkImageMenuItem *) item, icon);
416 } // end of sp_ui_menu_add_icon
418 /**
419  * sp_ui_menu_append_item
420  *
421  * Appends a UI item with specific info for Inkscape/Sodipodi.
422  *
423  */
424 static GtkWidget *
425 sp_ui_menu_append_item( GtkMenu *menu, gchar const *stock,
426                         gchar const *label, gchar const *tip, Inkscape::UI::View::View *view, GCallback callback,
427                         gpointer data, gboolean with_mnemonic = TRUE )
429     GtkWidget *item;
431     if (stock) {
432         item = gtk_image_menu_item_new_from_stock(stock, NULL);
433     } else if (label) {
434         item = (with_mnemonic)
435             ? gtk_image_menu_item_new_with_mnemonic(label) :
436             gtk_image_menu_item_new_with_label(label);
437     } else {
438         item = gtk_separator_menu_item_new();
439     }
441     gtk_widget_show(item);
443     if (callback) {
444         g_signal_connect(G_OBJECT(item), "activate", callback, data);
445     }
447     if (tip && view) {
448         g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
449         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) tip );
450         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
451     }
453     gtk_menu_append(GTK_MENU(menu), item);
455     return item;
457 } // end of sp_ui_menu_append_item()
459 /**
460 \brief  a wrapper around gdk_keyval_name producing (when possible) characters, not names
461  */
462 static gchar const *
463 sp_key_name(guint keyval)
465     /* TODO: Compare with the definition of gtk_accel_label_refetch in gtk/gtkaccellabel.c (or
466        simply use GtkAccelLabel as the TODO comment in sp_ui_shortcut_string suggests). */
467     gchar const *n = gdk_keyval_name(gdk_keyval_to_upper(keyval));
469     if      (!strcmp(n, "asciicircum"))  return "^";
470     else if (!strcmp(n, "parenleft"  ))  return "(";
471     else if (!strcmp(n, "parenright" ))  return ")";
472     else if (!strcmp(n, "plus"       ))  return "+";
473     else if (!strcmp(n, "minus"      ))  return "-";
474     else if (!strcmp(n, "asterisk"   ))  return "*";
475     else if (!strcmp(n, "KP_Multiply"))  return "*";
476     else if (!strcmp(n, "Delete"     ))  return "Del";
477     else if (!strcmp(n, "Page_Up"    ))  return "PgUp";
478     else if (!strcmp(n, "Page_Down"  ))  return "PgDn";
479     else if (!strcmp(n, "grave"      ))  return "`";
480     else if (!strcmp(n, "numbersign" ))  return "#";
481     else if (!strcmp(n, "bar"        ))  return "|";
482     else if (!strcmp(n, "slash"      ))  return "/";
483     else if (!strcmp(n, "exclam"     ))  return "!";
484     else if (!strcmp(n, "percent"    ))  return "%";
485     else return n;
489 /**
490  * \param shortcut A GDK keyval OR'd with SP_SHORTCUT_blah_MASK values.
491  * \param c Points to a buffer at least 256 bytes long.
492  */
493 void
494 sp_ui_shortcut_string(unsigned const shortcut, gchar *const c)
496     /* TODO: This function shouldn't exist.  Our callers should use GtkAccelLabel instead of
497      * a generic GtkLabel containing this string, and should call gtk_widget_add_accelerator.
498      * Will probably need to change sp_shortcut_invoke callers.
499      *
500      * The existing gtk_label_new_with_mnemonic call can be replaced with
501      * g_object_new(GTK_TYPE_ACCEL_LABEL, NULL) followed by
502      * gtk_label_set_text_with_mnemonic(lbl, str).
503      */
504     static GtkAccelLabelClass const &accel_lbl_cls
505         = *(GtkAccelLabelClass const *) g_type_class_peek_static(GTK_TYPE_ACCEL_LABEL);
507     struct { unsigned test; char const *name; } const modifier_tbl[] = {
508         { SP_SHORTCUT_SHIFT_MASK,   accel_lbl_cls.mod_name_shift   },
509         { SP_SHORTCUT_CONTROL_MASK, accel_lbl_cls.mod_name_control },
510         { SP_SHORTCUT_ALT_MASK,     accel_lbl_cls.mod_name_alt     }
511     };
513     gchar *p = c;
514     gchar *end = p + 256;
516     for (unsigned i = 0; i < G_N_ELEMENTS(modifier_tbl); ++i) {
517         if ((shortcut & modifier_tbl[i].test)
518             && (p < end))
519         {
520             p += g_snprintf(p, end - p, "%s%s",
521                             modifier_tbl[i].name,
522                             accel_lbl_cls.mod_separator);
523         }
524     }
525     if (p < end) {
526         p += g_snprintf(p, end - p, "%s", sp_key_name(shortcut & 0xffffff));
527     }
528     end[-1] = '\0';  // snprintf doesn't guarantee to nul-terminate the string.
531 void
532 sp_ui_dialog_title_string(Inkscape::Verb *verb, gchar *c)
534     SPAction     *action;
535     unsigned int shortcut;
536     gchar        *s;
537     gchar        key[256];
538     gchar        *atitle;
540     action = verb->get_action(NULL);
541     if (!action)
542         return;
544     atitle = sp_action_get_title(action);
546     s = g_stpcpy(c, atitle);
548     g_free(atitle);
550     shortcut = sp_shortcut_get_primary(verb);
551     if (shortcut) {
552         s = g_stpcpy(s, " (");
553         sp_ui_shortcut_string(shortcut, key);
554         s = g_stpcpy(s, key);
555         s = g_stpcpy(s, ")");
556     }
560 /**
561  * sp_ui_menu_append_item_from_verb
562  *
563  * Appends a custom menu UI from a verb.
564  *
565  */
567 static GtkWidget *
568 sp_ui_menu_append_item_from_verb(GtkMenu *menu, Inkscape::Verb *verb, Inkscape::UI::View::View *view, bool radio = false, GSList *group = NULL)
570     SPAction *action;
571     GtkWidget *item;
573     if (verb->get_code() == SP_VERB_NONE) {
575         item = gtk_separator_menu_item_new();
577     } else {
578         unsigned int shortcut;
580         action = verb->get_action(view);
582         if (!action) return NULL;
584         shortcut = sp_shortcut_get_primary(verb);
585         if (shortcut) {
586             gchar c[256];
587             sp_ui_shortcut_string(shortcut, c);
588             GtkWidget *const hb = gtk_hbox_new(FALSE, 16);
589             GtkWidget *const name_lbl = gtk_label_new("");
590             gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
591             gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
592             gtk_box_pack_start((GtkBox *) hb, name_lbl, TRUE, TRUE, 0);
593             GtkWidget *const accel_lbl = gtk_label_new(c);
594             gtk_misc_set_alignment((GtkMisc *) accel_lbl, 1.0, 0.5);
595             gtk_box_pack_end((GtkBox *) hb, accel_lbl, FALSE, FALSE, 0);
596             gtk_widget_show_all(hb);
597             if (radio) {
598                 item = gtk_radio_menu_item_new (group);
599             } else {
600                 item = gtk_image_menu_item_new();
601             }
602             gtk_container_add((GtkContainer *) item, hb);
603         } else {
604             if (radio) {
605                 item = gtk_radio_menu_item_new (group);
606             } else {
607                 item = gtk_image_menu_item_new ();
608             }
609             GtkWidget *const name_lbl = gtk_label_new("");
610             gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
611             gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
612             gtk_container_add((GtkContainer *) item, name_lbl);
613         }
615         nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
616         if (!action->sensitive) {
617             gtk_widget_set_sensitive(item, FALSE);
618         }
620         if (action->image) {
621             sp_ui_menuitem_add_icon(item, action->image);
622         }
623         gtk_widget_set_events(item, GDK_KEY_PRESS_MASK);
624         g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
625         g_signal_connect( G_OBJECT(item), "activate", G_CALLBACK(sp_ui_menu_activate), action );
626         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select_action), action );
627         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect_action), action );
628     }
630     gtk_widget_show(item);
631     gtk_menu_append(GTK_MENU(menu), item);
633     return item;
635 } // end of sp_ui_menu_append_item_from_verb
638 static Glib::ustring getLayoutPrefPath( Inkscape::UI::View::View *view )
640     Glib::ustring prefPath;
642     if (reinterpret_cast<SPDesktop*>(view)->is_focusMode()) {
643         prefPath = "/focus/";
644     } else if (reinterpret_cast<SPDesktop*>(view)->is_fullscreen()) {
645         prefPath = "/fullscreen/";
646     } else {
647         prefPath = "/window/";
648     }
650     return prefPath;
653 static void
654 checkitem_toggled(GtkCheckMenuItem *menuitem, gpointer user_data)
656     gchar const *pref = (gchar const *) user_data;
657     Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
659     Glib::ustring pref_path = getLayoutPrefPath( view );
660     pref_path += pref;
661     pref_path += "/state";
663     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
664     gboolean checked = gtk_check_menu_item_get_active(menuitem);
665     prefs->setBool(pref_path, checked);
667     reinterpret_cast<SPDesktop*>(view)->layoutWidget();
670 static gboolean
671 checkitem_update(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_data)
673     GtkCheckMenuItem *menuitem=GTK_CHECK_MENU_ITEM(widget);
675     gchar const *pref = (gchar const *) user_data;
676     Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
678     Glib::ustring pref_path = getLayoutPrefPath( view );
679     pref_path += pref;
680     pref_path += "/state";
681     pref_path += pref;
683     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
684     bool ison = prefs->getBool(pref_path + "/state", true);
686     g_signal_handlers_block_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
687     gtk_check_menu_item_set_active(menuitem, ison);
688     g_signal_handlers_unblock_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
690     return FALSE;
693 static void taskToggled(GtkCheckMenuItem *menuitem, gpointer userData)
695     if ( gtk_check_menu_item_get_active(menuitem) ) {
696         gint taskNum = GPOINTER_TO_INT(userData);
697         taskNum = (taskNum < 0) ? 0 : (taskNum > 2) ? 2 : taskNum;
699         Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
701         // note: this will change once more options are in the task set support:
702         Inkscape::UI::UXManager::getInstance()->setTask( dynamic_cast<SPDesktop*>(view), taskNum );
703     }
707 /**
708  *  \brief Callback function to update the status of the radio buttons in the View -> Display mode menu (Normal, No Filters, Outline)
709  */
711 static gboolean
712 update_view_menu(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_data)
714         SPAction *action = (SPAction *) user_data;
715         g_assert(action->id != NULL);
717         Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(widget), "view");
718     SPDesktop *dt = static_cast<SPDesktop*>(view);
719         Inkscape::RenderMode mode = dt->getMode();
721         bool new_state = false;
722         if (!strcmp(action->id, "ViewModeNormal")) {
723         new_state = mode == Inkscape::RENDERMODE_NORMAL;
724         } else if (!strcmp(action->id, "ViewModeNoFilters")) {
725         new_state = mode == Inkscape::RENDERMODE_NO_FILTERS;
726     } else if (!strcmp(action->id, "ViewModeOutline")) {
727         new_state = mode == Inkscape::RENDERMODE_OUTLINE;
728     } else if (!strcmp(action->id, "ViewModePrintColorsPreview")) {
729         new_state = mode == Inkscape::RENDERMODE_PRINT_COLORS_PREVIEW;
730     } else {
731         g_warning("update_view_menu does not handle this verb");
732     }
734         if (new_state) { //only one of the radio buttons has to be activated; the others will automatically be deactivated
735                 if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) {
736                         // When the GtkMenuItem version of the "activate" signal has been emitted by a GtkRadioMenuItem, there is a second
737                         // emission as the most recently active item is toggled to inactive. This is dealt with before the original signal is handled.
738                         // This emission however should not invoke any actions, hence we block it here:
739                         temporarily_block_actions = true;
740                         gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (widget), TRUE);
741                         temporarily_block_actions = false;
742                 }
743         }
745         return FALSE;
748 void
749 sp_ui_menu_append_check_item_from_verb(GtkMenu *menu, Inkscape::UI::View::View *view, gchar const *label, gchar const *tip, gchar const *pref,
750                                        void (*callback_toggle)(GtkCheckMenuItem *, gpointer user_data),
751                                        gboolean (*callback_update)(GtkWidget *widget, GdkEventExpose *event, gpointer user_data),
752                                        Inkscape::Verb *verb)
754     unsigned int shortcut = (verb) ? sp_shortcut_get_primary(verb) : 0;
755     SPAction *action = (verb) ? verb->get_action(view) : 0;
756     GtkWidget *item = gtk_check_menu_item_new();
758     if (verb && shortcut) {
759         gchar c[256];
760         sp_ui_shortcut_string(shortcut, c);
762         GtkWidget *hb = gtk_hbox_new(FALSE, 16);
764         {
765             GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
766             gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
767             gtk_box_pack_start((GtkBox *) hb, l, TRUE, TRUE, 0);
768         }
770         {
771             GtkWidget *l = gtk_label_new(c);
772             gtk_misc_set_alignment((GtkMisc *) l, 1.0, 0.5);
773             gtk_box_pack_end((GtkBox *) hb, l, FALSE, FALSE, 0);
774         }
776         gtk_widget_show_all(hb);
778         gtk_container_add((GtkContainer *) item, hb);
779     } else {
780         GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
781         gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
782         gtk_container_add((GtkContainer *) item, l);
783     }
784 #if 0
785     nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
786     if (!action->sensitive) {
787         gtk_widget_set_sensitive(item, FALSE);
788     }
789 #endif
790     gtk_widget_show(item);
792     gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
794     g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
796     g_signal_connect( G_OBJECT(item), "toggled", (GCallback) callback_toggle, (void *) pref);
797     g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) callback_update, (void *) pref);
799     g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) (action ? action->tip : tip));
800     g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
803 static void
804 sp_recent_open(GtkRecentChooser *recent_menu, gpointer /*user_data*/)
806     // dealing with the bizarre filename convention in Inkscape for now
807     gchar *uri = gtk_recent_chooser_get_current_uri(GTK_RECENT_CHOOSER(recent_menu));
808     gchar *local_fn = g_filename_from_uri(uri, NULL, NULL);
809     gchar *utf8_fn = g_filename_to_utf8(local_fn, -1, NULL, NULL, NULL);
810     sp_file_open(utf8_fn, NULL);
811     g_free(utf8_fn);
812     g_free(local_fn);
813     g_free(uri);
816 static void
817 sp_file_new_from_template(GtkWidget */*widget*/, gchar const *uri)
819     sp_file_new(uri);
822 void
823 sp_menu_append_new_templates(GtkWidget *menu, Inkscape::UI::View::View *view)
825     std::list<gchar *> sources;
826     sources.push_back( profile_path("templates") ); // first try user's local dir
827     sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir
829     // Use this loop to iterate through a list of possible document locations.
830     while (!sources.empty()) {
831         gchar *dirname = sources.front();
833         if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) {
834             GError *err = 0;
835             GDir *dir = g_dir_open(dirname, 0, &err);
837             if (dir) {
838                 for (gchar const *file = g_dir_read_name(dir); file != NULL; file = g_dir_read_name(dir)) {
839                     if (!g_str_has_suffix(file, ".svg") && !g_str_has_suffix(file, ".svgz"))
840                         continue; // skip non-svg files
842                     gchar *basename = g_path_get_basename(file);
843                     if (g_str_has_suffix(basename, ".svg") && g_str_has_prefix(basename, "default."))
844                         continue; // skip default.*.svg (i.e. default.svg and translations) - it's in the menu already
846                     gchar const *filepath = g_build_filename(dirname, file, NULL);
847                     gchar *dupfile = g_strndup(file, strlen(file) - 4);
848                     gchar *filename =  g_filename_to_utf8(dupfile,  -1, NULL, NULL, NULL);
849                     g_free(dupfile);
850                     GtkWidget *item = gtk_menu_item_new_with_label(filename);
851                     g_free(filename);
853                     gtk_widget_show(item);
854                     // how does "filepath" ever get freed?
855                     g_signal_connect(G_OBJECT(item),
856                                      "activate",
857                                      G_CALLBACK(sp_file_new_from_template),
858                                      (gpointer) filepath);
860                     if (view) {
861                         // set null tip for now; later use a description from the template file
862                         g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
863                         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) NULL );
864                         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
865                     }
867                     gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
868                 }
869                 g_dir_close(dir);
870             }
871         }
873         // toss the dirname
874         g_free(dirname);
875         sources.pop_front();
876     }
879 void
880 sp_ui_checkboxes_menus(GtkMenu *m, Inkscape::UI::View::View *view)
882     //sp_ui_menu_append_check_item_from_verb(m, view, _("_Menu"), _("Show or hide the menu bar"), "menu",
883     //                                       checkitem_toggled, checkitem_update, 0);
884     sp_ui_menu_append_check_item_from_verb(m, view, _("Commands Bar"), _("Show or hide the Commands bar (under the menu)"), "commands",
885                                            checkitem_toggled, checkitem_update, 0);
886     sp_ui_menu_append_check_item_from_verb(m, view, _("Snap Controls Bar"), _("Show or hide the snapping controls"), "snaptoolbox",
887                                            checkitem_toggled, checkitem_update, 0);
888     sp_ui_menu_append_check_item_from_verb(m, view, _("Tool Controls Bar"), _("Show or hide the Tool Controls bar"), "toppanel",
889                                            checkitem_toggled, checkitem_update, 0);
890     sp_ui_menu_append_check_item_from_verb(m, view, _("_Toolbox"), _("Show or hide the main toolbox (on the left)"), "toolbox",
891                                            checkitem_toggled, checkitem_update, 0);
892     sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "rulers",
893                                            checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_RULERS));
894     sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "scrollbars",
895                                            checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_SCROLLBARS));
896     sp_ui_menu_append_check_item_from_verb(m, view, _("_Palette"), _("Show or hide the color palette"), "panels",
897                                            checkitem_toggled, checkitem_update, 0);
898     sp_ui_menu_append_check_item_from_verb(m, view, _("_Statusbar"), _("Show or hide the statusbar (at the bottom of the window)"), "statusbar",
899                                            checkitem_toggled, checkitem_update, 0);
903 void addTaskMenuItems(GtkMenu *menu, Inkscape::UI::View::View *view)
905     gchar const* data[] = {
906         _("Default"), _("Default interface setup"),
907         _("Custom"), _("Set the custom task"),
908         _("Wide"), _("Setup for widescreen work."),
909         0, 0
910     };
912     GSList *group = 0;
913     int count = 0;
914     gint active = Inkscape::UI::UXManager::getInstance()->getDefaultTask( dynamic_cast<SPDesktop*>(view) );
915     for (gchar const **strs = data; strs[0]; strs += 2, count++)
916     {
917         GtkWidget *item = gtk_radio_menu_item_new_with_label( group, strs[0] );
918         group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item) );
919         if ( count == active )
920         {
921             gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(item), TRUE );
922         }
924         g_object_set_data( G_OBJECT(item), "view", view );
925         g_signal_connect( G_OBJECT(item), "toggled", reinterpret_cast<GCallback>(taskToggled), GINT_TO_POINTER(count) );
926         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), const_cast<gchar*>(strs[1]) );
927         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), 0 );
929         gtk_widget_show( item );
930         gtk_menu_shell_append( GTK_MENU_SHELL(menu), item );
931     }
935 /** @brief Observer that updates the recent list's max document count */
936 class MaxRecentObserver : public Inkscape::Preferences::Observer {
937 public:
938     MaxRecentObserver(GtkWidget *recent_menu) :
939         Observer("/options/maxrecentdocuments/value"),
940         _rm(recent_menu)
941     {}
942     virtual void notify(Inkscape::Preferences::Entry const &e) {
943         gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(_rm), e.getInt());
944         // hack: the recent menu doesn't repopulate after changing the limit, so we force it
945         g_signal_emit_by_name((gpointer) gtk_recent_manager_get_default(), "changed");
946     }
947 private:
948     GtkWidget *_rm;
949 };
951 /** \brief  This function turns XML into a menu
952     \param  menus  This is the XML that defines the menu
953     \param  menu   Menu to be added to
954     \param  view   The View that this menu is being built for
956     This function is realitively simple as it just goes through the XML
957     and parses the individual elements.  In the case of a submenu, it
958     just calls itself recursively.  Because it is only reasonable to have
959     a couple of submenus, it is unlikely this will go more than two or
960     three times.
962     In the case of an unrecognized verb, a menu item is made to identify
963     the verb that is missing, and display that.  The menu item is also made
964     insensitive.
965 */
966 void
967 sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI::View::View *view)
969     if (menus == NULL) return;
970     if (menu == NULL)  return;
971     GSList *group = NULL;
973     for (Inkscape::XML::Node *menu_pntr = menus;
974          menu_pntr != NULL;
975          menu_pntr = menu_pntr->next()) {
976         if (!strcmp(menu_pntr->name(), "submenu")) {
977             GtkWidget *mitem = gtk_menu_item_new_with_mnemonic(_(menu_pntr->attribute("name")));
978             GtkWidget *submenu = gtk_menu_new();
979             sp_ui_build_dyn_menus(menu_pntr->firstChild(), submenu, view);
980             gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), GTK_WIDGET(submenu));
981             gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
982             continue;
983         }
984         if (!strcmp(menu_pntr->name(), "verb")) {
985             gchar const *verb_name = menu_pntr->attribute("verb-id");
986             Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name);
988             if (verb != NULL) {
989                 if (menu_pntr->attribute("radio") != NULL) {
990                     GtkWidget *item = sp_ui_menu_append_item_from_verb (GTK_MENU(menu), verb, view, true, group);
991                     group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item));
992                     if (menu_pntr->attribute("default") != NULL) {
993                         gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
994                     }
995                     if (verb->get_code() != SP_VERB_NONE) {
996                         SPAction *action = verb->get_action(view);
997                         g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) update_view_menu, (void *) action);
998                     }
999                 } else {
1000                     sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view);
1001                     group = NULL;
1002                 }
1003             } else {
1004                 gchar string[120];
1005                 g_snprintf(string, 120, _("Verb \"%s\" Unknown"), verb_name);
1006                 string[119] = '\0'; /* may not be terminated */
1007                 GtkWidget *item = gtk_menu_item_new_with_label(string);
1008                 gtk_widget_set_sensitive(item, false);
1009                 gtk_widget_show(item);
1010                 gtk_menu_append(GTK_MENU(menu), item);
1011             }
1012             continue;
1013         }
1014         if (!strcmp(menu_pntr->name(), "separator")
1015                 // This was spelt wrong in the original version
1016                 // and so this is for backward compatibility.  It can
1017                 // probably be dropped after the 0.44 release.
1018              || !strcmp(menu_pntr->name(), "seperator")) {
1019             GtkWidget *item = gtk_separator_menu_item_new();
1020             gtk_widget_show(item);
1021             gtk_menu_append(GTK_MENU(menu), item);
1022             continue;
1023         }
1024         if (!strcmp(menu_pntr->name(), "template-list")) {
1025             sp_menu_append_new_templates(menu, view);
1026             continue;
1027         }
1028         if (!strcmp(menu_pntr->name(), "recent-file-list")) {
1029             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1031             // create recent files menu
1032             int max_recent = prefs->getInt("/options/maxrecentdocuments/value");
1033             GtkWidget *recent_menu = gtk_recent_chooser_menu_new_for_manager(gtk_recent_manager_get_default());
1034             gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(recent_menu), max_recent);
1035             // sort most recently used documents first to preserve previous behavior
1036             gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(recent_menu), GTK_RECENT_SORT_MRU);
1037             g_signal_connect(G_OBJECT(recent_menu), "item-activated", G_CALLBACK(sp_recent_open), (gpointer) NULL);
1039             // add filter to only open files added by Inkscape
1040             GtkRecentFilter *inkscape_only_filter = gtk_recent_filter_new();
1041             gtk_recent_filter_add_application(inkscape_only_filter, g_get_prgname());
1042             gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_menu), inkscape_only_filter);
1044             gtk_recent_chooser_set_show_tips (GTK_RECENT_CHOOSER(recent_menu), TRUE);
1045             gtk_recent_chooser_set_show_not_found (GTK_RECENT_CHOOSER(recent_menu), FALSE);
1047             GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic(_("Open _Recent"));
1048             gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), recent_menu);
1050             gtk_menu_append(GTK_MENU(menu), GTK_WIDGET(recent_item));
1051             // this will just sit and update the list's item count
1052             static MaxRecentObserver *mro = new MaxRecentObserver(recent_menu);
1053             prefs->addObserver(*mro);
1054             continue;
1055         }
1056         if (!strcmp(menu_pntr->name(), "objects-checkboxes")) {
1057             sp_ui_checkboxes_menus(GTK_MENU(menu), view);
1058             continue;
1059         }
1060         if (!strcmp(menu_pntr->name(), "task-checkboxes")) {
1061             addTaskMenuItems(GTK_MENU(menu), view);
1062             continue;
1063         }
1064     }
1067 /** \brief  Build the main tool bar
1068     \param  view  View to build the bar for
1070     Currently the main tool bar is built as a dynamic XML menu using
1071     \c sp_ui_build_dyn_menus.  This function builds the bar, and then
1072     pass it to get items attached to it.
1073 */
1074 GtkWidget *
1075 sp_ui_main_menubar(Inkscape::UI::View::View *view)
1077     GtkWidget *mbar = gtk_menu_bar_new();
1079 #ifdef GDK_WINDOWING_QUARTZ
1080     ige_mac_menu_set_menu_bar(GTK_MENU_SHELL(mbar));
1081 #endif
1083     sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view);
1085 #ifdef GDK_WINDOWING_QUARTZ
1086     return NULL;
1087 #else
1088     return mbar;
1089 #endif
1092 static void leave_group(GtkMenuItem *, SPDesktop *desktop) {
1093     desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer()));
1096 static void enter_group(GtkMenuItem *mi, SPDesktop *desktop) {
1097     desktop->setCurrentLayer(reinterpret_cast<SPObject *>(g_object_get_data(G_OBJECT(mi), "group")));
1098     sp_desktop_selection(desktop)->clear();
1101 GtkWidget *
1102 sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item)
1104     GtkWidget *m;
1105     SPDesktop *dt;
1107     dt = static_cast<SPDesktop*>(view);
1109     m = gtk_menu_new();
1111     /* Undo and Redo */
1112     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_UNDO), view);
1113     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_REDO), view);
1115     /* Separator */
1116     sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1118     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_CUT), view);
1119     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_COPY), view);
1120     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_PASTE), view);
1122     /* Separator */
1123     sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1125     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DUPLICATE), view);
1126     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DELETE), view);
1128     /* Item menu */
1129     if (item) {
1130         sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1131         sp_object_menu((SPObject *) item, dt, GTK_MENU(m));
1132     }
1134     /* layer menu */
1135     SPGroup *group=NULL;
1136     if (item) {
1137         if (SP_IS_GROUP(item)) {
1138             group = SP_GROUP(item);
1139         } else if ( item != dt->currentRoot() && SP_IS_GROUP(SP_OBJECT_PARENT(item)) ) {
1140             group = SP_GROUP(SP_OBJECT_PARENT(item));
1141         }
1142     }
1144     if (( group && group != dt->currentLayer() ) ||
1145         ( dt->currentLayer() != dt->currentRoot() && SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) ) {
1146         /* Separator */
1147         sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1148     }
1150     if ( group && group != dt->currentLayer() ) {
1151         /* TRANSLATORS: #%s is the id of the group e.g. <g id="#g7">, not a number. */
1152         gchar *label=g_strdup_printf(_("Enter group #%s"), group->getId());
1153         GtkWidget *w = gtk_menu_item_new_with_label(label);
1154         g_free(label);
1155         g_object_set_data(G_OBJECT(w), "group", group);
1156         g_signal_connect(G_OBJECT(w), "activate", GCallback(enter_group), dt);
1157         gtk_widget_show(w);
1158         gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1159     }
1161     if ( dt->currentLayer() != dt->currentRoot() ) {
1162         if ( SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) {
1163             GtkWidget *w = gtk_menu_item_new_with_label(_("Go to parent"));
1164             g_signal_connect(G_OBJECT(w), "activate", GCallback(leave_group), dt);
1165             gtk_widget_show(w);
1166             gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1168         }
1169     }
1171     return m;
1174 /* Drag and Drop */
1175 void
1176 sp_ui_drag_data_received(GtkWidget *widget,
1177                          GdkDragContext *drag_context,
1178                          gint x, gint y,
1179                          GtkSelectionData *data,
1180                          guint info,
1181                          guint /*event_time*/,
1182                          gpointer /*user_data*/)
1184     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1185     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1187     switch (info) {
1188 #if ENABLE_MAGIC_COLORS
1189         case APP_X_INKY_COLOR:
1190         {
1191             int destX = 0;
1192             int destY = 0;
1193             gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1194             Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1196             SPItem *item = desktop->item_at_point( where, true );
1197             if ( item )
1198             {
1199                 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1201                 if ( data->length >= 8 ) {
1202                     cmsHPROFILE srgbProf = cmsCreate_sRGBProfile();
1204                     gchar c[64] = {0};
1205                     // Careful about endian issues.
1206                     guint16* dataVals = (guint16*)data->data;
1207                     sp_svg_write_color( c, sizeof(c),
1208                                         SP_RGBA32_U_COMPOSE(
1209                                             0x0ff & (dataVals[0] >> 8),
1210                                             0x0ff & (dataVals[1] >> 8),
1211                                             0x0ff & (dataVals[2] >> 8),
1212                                             0xff // can't have transparency in the color itself
1213                                             //0x0ff & (data->data[3] >> 8),
1214                                             ));
1215                     SPCSSAttr *css = sp_repr_css_attr_new();
1216                     bool updatePerformed = false;
1218                     if ( data->length > 14 ) {
1219                         int flags = dataVals[4];
1221                         // piggie-backed palette entry info
1222                         int index = dataVals[5];
1223                         Glib::ustring palName;
1224                         for ( int i = 0; i < dataVals[6]; i++ ) {
1225                             palName += (gunichar)dataVals[7+i];
1226                         }
1228                         // Now hook in a magic tag of some sort.
1229                         if ( !palName.empty() && (flags & 1) ) {
1230                             gchar* str = g_strdup_printf("%d|", index);
1231                             palName.insert( 0, str );
1232                             g_free(str);
1233                             str = 0;
1235                             sp_object_setAttribute( SP_OBJECT(item),
1236                                                     fillnotstroke ? "inkscape:x-fill-tag":"inkscape:x-stroke-tag",
1237                                                     palName.c_str(),
1238                                                     false );
1239                             item->updateRepr();
1241                             sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1242                             updatePerformed = true;
1243                         }
1244                     }
1246                     if ( !updatePerformed ) {
1247                         sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1248                     }
1250                     sp_desktop_apply_css_recursive( item, css, true );
1251                     item->updateRepr();
1253                     sp_document_done( doc , SP_VERB_NONE,
1254                                       _("Drop color"));
1256                     if ( srgbProf ) {
1257                         cmsCloseProfile( srgbProf );
1258                     }
1259                 }
1260             }
1261         }
1262         break;
1263 #endif // ENABLE_MAGIC_COLORS
1265         case APP_X_COLOR:
1266         {
1267             int destX = 0;
1268             int destY = 0;
1269             gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1270             Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1271             Geom::Point const button_dt(desktop->w2d(where));
1272             Geom::Point const button_doc(desktop->dt2doc(button_dt));
1274             if ( data->length == 8 ) {
1275                 gchar colorspec[64] = {0};
1276                 // Careful about endian issues.
1277                 guint16* dataVals = (guint16*)data->data;
1278                 sp_svg_write_color( colorspec, sizeof(colorspec),
1279                                     SP_RGBA32_U_COMPOSE(
1280                                         0x0ff & (dataVals[0] >> 8),
1281                                         0x0ff & (dataVals[1] >> 8),
1282                                         0x0ff & (dataVals[2] >> 8),
1283                                         0xff // can't have transparency in the color itself
1284                                         //0x0ff & (data->data[3] >> 8),
1285                                         ));
1287                 SPItem *item = desktop->item_at_point( where, true );
1289                 bool consumed = false;
1290                 if (desktop->event_context && desktop->event_context->get_drag()) {
1291                     consumed = desktop->event_context->get_drag()->dropColor(item, colorspec, button_dt);
1292                     if (consumed) {
1293                         sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1294                         desktop->event_context->get_drag()->updateDraggers();
1295                     }
1296                 }
1298                 //if (!consumed && tools_active(desktop, TOOLS_TEXT)) {
1299                 //    consumed = sp_text_context_drop_color(c, button_doc);
1300                 //    if (consumed) {
1301                 //        sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient stop"));
1302                 //    }
1303                 //}
1305                 if (!consumed && item) {
1306                     bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1307                     if (fillnotstroke &&
1308                         (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1309                         Path *livarot_path = Path_for_item(item, true, true);
1310                         livarot_path->ConvertWithBackData(0.04);
1312                         boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1313                         if (position) {
1314                             Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1315                             Geom::Point delta = nearest - button_doc;
1316                             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1317                             delta = desktop->d2w(delta);
1318                             double stroke_tolerance =
1319                                 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1320                                   desktop->current_zoom() *
1321                                   SP_OBJECT_STYLE (item)->stroke_width.computed *
1322                                   to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1323                                   : 0.0)
1324                                 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1326                             if (Geom::L2 (delta) < stroke_tolerance) {
1327                                 fillnotstroke = false;
1328                             }
1329                         }
1330                         delete livarot_path;
1331                     }
1333                     SPCSSAttr *css = sp_repr_css_attr_new();
1334                     sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec );
1336                     sp_desktop_apply_css_recursive( item, css, true );
1337                     item->updateRepr();
1339                     sp_document_done( doc , SP_VERB_NONE,
1340                                       _("Drop color"));
1341                 }
1342             }
1343         }
1344         break;
1346         case APP_OSWB_COLOR:
1347         {
1348             bool worked = false;
1349             Glib::ustring colorspec;
1350             if ( data->format == 8 ) {
1351                 ege::PaintDef color;
1352                 worked = color.fromMIMEData("application/x-oswb-color",
1353                                             reinterpret_cast<char*>(data->data),
1354                                             data->length,
1355                                             data->format);
1356                 if ( worked ) {
1357                     if ( color.getType() == ege::PaintDef::CLEAR ) {
1358                         colorspec = ""; // TODO check if this is sufficient
1359                     } else if ( color.getType() == ege::PaintDef::NONE ) {
1360                         colorspec = "none";
1361                     } else {
1362                         unsigned int r = color.getR();
1363                         unsigned int g = color.getG();
1364                         unsigned int b = color.getB();
1366                         SPGradient* matches = 0;
1367                         const GSList *gradients = sp_document_get_resource_list(doc, "gradient");
1368                         for (const GSList *item = gradients; item; item = item->next) {
1369                             SPGradient* grad = SP_GRADIENT(item->data);
1370                             if ( color.descr == grad->getId() ) {
1371                                 if ( grad->has_stops ) {
1372                                     matches = grad;
1373                                     break;
1374                                 }
1375                             }
1376                         }
1377                         if (matches) {
1378                             colorspec = "url(#";
1379                             colorspec += matches->getId();
1380                             colorspec += ")";
1381                         } else {
1382                             gchar* tmp = g_strdup_printf("#%02x%02x%02x", r, g, b);
1383                             colorspec = tmp;
1384                             g_free(tmp);
1385                         }
1386                     }
1387                 }
1388             }
1389             if ( worked ) {
1390                 int destX = 0;
1391                 int destY = 0;
1392                 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1393                 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1394                 Geom::Point const button_dt(desktop->w2d(where));
1395                 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1397                 SPItem *item = desktop->item_at_point( where, true );
1399                 bool consumed = false;
1400                 if (desktop->event_context && desktop->event_context->get_drag()) {
1401                     consumed = desktop->event_context->get_drag()->dropColor(item, colorspec.c_str(), button_dt);
1402                     if (consumed) {
1403                         sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1404                         desktop->event_context->get_drag()->updateDraggers();
1405                     }
1406                 }
1408                 if (!consumed && item) {
1409                     bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1410                     if (fillnotstroke &&
1411                         (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1412                         Path *livarot_path = Path_for_item(item, true, true);
1413                         livarot_path->ConvertWithBackData(0.04);
1415                         boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1416                         if (position) {
1417                             Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1418                             Geom::Point delta = nearest - button_doc;
1419                             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1420                             delta = desktop->d2w(delta);
1421                             double stroke_tolerance =
1422                                 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1423                                   desktop->current_zoom() *
1424                                   SP_OBJECT_STYLE (item)->stroke_width.computed *
1425                                   to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1426                                   : 0.0)
1427                                 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1429                             if (Geom::L2 (delta) < stroke_tolerance) {
1430                                 fillnotstroke = false;
1431                             }
1432                         }
1433                         delete livarot_path;
1434                     }
1436                     SPCSSAttr *css = sp_repr_css_attr_new();
1437                     sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec.c_str() );
1439                     sp_desktop_apply_css_recursive( item, css, true );
1440                     item->updateRepr();
1442                     sp_document_done( doc , SP_VERB_NONE,
1443                                       _("Drop color"));
1444                 }
1445             }
1446         }
1447         break;
1449         case SVG_DATA:
1450         case SVG_XML_DATA: {
1451             gchar *svgdata = (gchar *)data->data;
1453             Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI);
1455             if (rnewdoc == NULL) {
1456                 sp_ui_error_dialog(_("Could not parse SVG data"));
1457                 return;
1458             }
1460             Inkscape::XML::Node *repr = rnewdoc->root();
1461             gchar const *style = repr->attribute("style");
1463             Inkscape::XML::Node *newgroup = rnewdoc->createElement("svg:g");
1464             newgroup->setAttribute("style", style);
1466             Inkscape::XML::Document * xml_doc =  sp_document_repr_doc(doc);
1467             for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) {
1468                 Inkscape::XML::Node *newchild = child->duplicate(xml_doc);
1469                 newgroup->appendChild(newchild);
1470             }
1472             Inkscape::GC::release(rnewdoc);
1474             // Add it to the current layer
1476             // Greg's edits to add intelligent positioning of svg drops
1477             SPObject *new_obj = NULL;
1478             new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
1480             Inkscape::Selection *selection = sp_desktop_selection(desktop);
1481             selection->set(SP_ITEM(new_obj));
1483             // move to mouse pointer
1484             {
1485                 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
1486                 Geom::OptRect sel_bbox = selection->bounds();
1487                 if (sel_bbox) {
1488                     Geom::Point m( desktop->point() - sel_bbox->midpoint() );
1489                     sp_selection_move_relative(selection, m, false);
1490                 }
1491             }
1493             Inkscape::GC::release(newgroup);
1494             sp_document_done(doc, SP_VERB_NONE,
1495                              _("Drop SVG"));
1496             break;
1497         }
1499         case URI_LIST: {
1500             gchar *uri = (gchar *)data->data;
1501             sp_ui_import_files(uri);
1502             break;
1503         }
1505         case PNG_DATA:
1506         case JPEG_DATA:
1507         case IMAGE_DATA: {
1508             const char *mime = (info == JPEG_DATA ? "image/jpeg" : "image/png");
1510             Inkscape::Extension::DB::InputList o;
1511             Inkscape::Extension::db.get_input_list(o);
1512             Inkscape::Extension::DB::InputList::const_iterator i = o.begin();
1513             while (i != o.end() && strcmp( (*i)->get_mimetype(), mime ) != 0) {
1514                 ++i;
1515             }
1516             Inkscape::Extension::Extension *ext = *i;
1517             bool save = (strcmp(ext->get_param_optiongroup("link"), "embed") == 0);
1518             ext->set_param_optiongroup("link", "embed");
1519             ext->set_gui(false);
1521             gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-dnd-import", NULL );
1522             g_file_set_contents(filename, reinterpret_cast<gchar const *>(data->data), data->length, NULL);
1523             file_import(doc, filename, ext);
1524             g_free(filename);
1526             ext->set_param_optiongroup("link", save ? "embed" : "link");
1527             ext->set_gui(true);
1528             sp_document_done( doc , SP_VERB_NONE,
1529                               _("Drop bitmap image"));
1530             break;
1531         }
1532     }
1535 #include "gradient-context.h"
1537 void sp_ui_drag_motion( GtkWidget */*widget*/,
1538                         GdkDragContext */*drag_context*/,
1539                         gint /*x*/, gint /*y*/,
1540                         GtkSelectionData */*data*/,
1541                         guint /*info*/,
1542                         guint /*event_time*/,
1543                         gpointer /*user_data*/)
1545 //     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1546 //     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1549 //     g_message("drag-n-drop motion (%4d, %4d)  at %d", x, y, event_time);
1552 static void sp_ui_drag_leave( GtkWidget */*widget*/,
1553                               GdkDragContext */*drag_context*/,
1554                               guint /*event_time*/,
1555                               gpointer /*user_data*/ )
1557 //     g_message("drag-n-drop leave                at %d", event_time);
1560 static void
1561 sp_ui_import_files(gchar *buffer)
1563     GList *list = gnome_uri_list_extract_filenames(buffer);
1564     if (!list)
1565         return;
1566     g_list_foreach(list, sp_ui_import_one_file_with_check, NULL);
1567     g_list_foreach(list, (GFunc) g_free, NULL);
1568     g_list_free(list);
1571 static void
1572 sp_ui_import_one_file_with_check(gpointer filename, gpointer /*unused*/)
1574     if (filename) {
1575         if (strlen((char const *)filename) > 2)
1576             sp_ui_import_one_file((char const *)filename);
1577     }
1580 static void
1581 sp_ui_import_one_file(char const *filename)
1583     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1584     if (!doc) return;
1586     if (filename == NULL) return;
1588     // Pass off to common implementation
1589     // TODO might need to get the proper type of Inkscape::Extension::Extension
1590     file_import( doc, filename, NULL );
1593 void
1594 sp_ui_error_dialog(gchar const *message)
1596     GtkWidget *dlg;
1597     gchar *safeMsg = Inkscape::IO::sanitizeString(message);
1599     dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
1600                                  GTK_BUTTONS_CLOSE, "%s", safeMsg);
1601     sp_transientize(dlg);
1602     gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE);
1603     gtk_dialog_run(GTK_DIALOG(dlg));
1604     gtk_widget_destroy(dlg);
1605     g_free(safeMsg);
1608 bool
1609 sp_ui_overwrite_file(gchar const *filename)
1611     bool return_value = FALSE;
1613     if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) {
1614         Gtk::Window *window = SP_ACTIVE_DESKTOP->getToplevel();
1615         gchar* baseName = g_path_get_basename( filename );
1616         gchar* dirName = g_path_get_dirname( filename );
1617         GtkWidget* dialog = gtk_message_dialog_new_with_markup( window->gobj(),
1618                                                                 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
1619                                                                 GTK_MESSAGE_QUESTION,
1620                                                                 GTK_BUTTONS_NONE,
1621                                                                 _( "<span weight=\"bold\" size=\"larger\">A file named \"%s\" already exists. Do you want to replace it?</span>\n\n"
1622                                                                    "The file already exists in \"%s\". Replacing it will overwrite its contents." ),
1623                                                                 baseName,
1624                                                                 dirName
1625             );
1626         gtk_dialog_add_buttons( GTK_DIALOG(dialog),
1627                                 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1628                                 _("Replace"), GTK_RESPONSE_YES,
1629                                 NULL );
1630         gtk_dialog_set_default_response( GTK_DIALOG(dialog), GTK_RESPONSE_YES );
1632         if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_YES ) {
1633             return_value = TRUE;
1634         } else {
1635             return_value = FALSE;
1636         }
1637         gtk_widget_destroy(dialog);
1638         g_free( baseName );
1639         g_free( dirName );
1640     } else {
1641         return_value = TRUE;
1642     }
1644     return return_value;
1647 static void
1648 sp_ui_menu_item_set_sensitive(SPAction */*action*/, unsigned int sensitive, void *data)
1650     return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
1653 static void
1654 sp_ui_menu_item_set_name(SPAction */*action*/, Glib::ustring name, void *data)
1656     void *child = GTK_BIN (data)->child;
1657     //child is either
1658     //- a GtkHBox, whose first child is a label displaying name if the menu
1659     //item has an accel key
1660     //- a GtkLabel if the menu has no accel key
1661     if (GTK_IS_LABEL(child)) {
1662         gtk_label_set_markup_with_mnemonic(GTK_LABEL (child), name.c_str());
1663     } else if (GTK_IS_HBOX(child)) {
1664         gtk_label_set_markup_with_mnemonic(
1665         GTK_LABEL (gtk_container_get_children(GTK_CONTAINER (child))->data),
1666         name.c_str());
1667     }//else sp_ui_menu_append_item_from_verb has been modified and can set
1668     //a menu item in yet another way...
1672 /*
1673   Local Variables:
1674   mode:c++
1675   c-file-style:"stroustrup"
1676   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1677   indent-tabs-mode:nil
1678   fill-column:99
1679   End:
1680 */
1681 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :