Code

A simple layout document as to what, why and how is cppification.
[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";
682     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
683     bool ison = prefs->getBool(pref_path, true);
685     g_signal_handlers_block_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
686     gtk_check_menu_item_set_active(menuitem, ison);
687     g_signal_handlers_unblock_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
689     return FALSE;
692 static void taskToggled(GtkCheckMenuItem *menuitem, gpointer userData)
694     if ( gtk_check_menu_item_get_active(menuitem) ) {
695         gint taskNum = GPOINTER_TO_INT(userData);
696         taskNum = (taskNum < 0) ? 0 : (taskNum > 2) ? 2 : taskNum;
698         Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
700         // note: this will change once more options are in the task set support:
701         Inkscape::UI::UXManager::getInstance()->setTask( dynamic_cast<SPDesktop*>(view), taskNum );
702     }
706 /**
707  *  \brief Callback function to update the status of the radio buttons in the View -> Display mode menu (Normal, No Filters, Outline)
708  */
710 static gboolean
711 update_view_menu(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_data)
713         SPAction *action = (SPAction *) user_data;
714         g_assert(action->id != NULL);
716         Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(widget), "view");
717     SPDesktop *dt = static_cast<SPDesktop*>(view);
718         Inkscape::RenderMode mode = dt->getMode();
720         bool new_state = false;
721         if (!strcmp(action->id, "ViewModeNormal")) {
722         new_state = mode == Inkscape::RENDERMODE_NORMAL;
723         } else if (!strcmp(action->id, "ViewModeNoFilters")) {
724         new_state = mode == Inkscape::RENDERMODE_NO_FILTERS;
725     } else if (!strcmp(action->id, "ViewModeOutline")) {
726         new_state = mode == Inkscape::RENDERMODE_OUTLINE;
727     } else if (!strcmp(action->id, "ViewModePrintColorsPreview")) {
728         new_state = mode == Inkscape::RENDERMODE_PRINT_COLORS_PREVIEW;
729     } else {
730         g_warning("update_view_menu does not handle this verb");
731     }
733         if (new_state) { //only one of the radio buttons has to be activated; the others will automatically be deactivated
734                 if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) {
735                         // When the GtkMenuItem version of the "activate" signal has been emitted by a GtkRadioMenuItem, there is a second
736                         // emission as the most recently active item is toggled to inactive. This is dealt with before the original signal is handled.
737                         // This emission however should not invoke any actions, hence we block it here:
738                         temporarily_block_actions = true;
739                         gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (widget), TRUE);
740                         temporarily_block_actions = false;
741                 }
742         }
744         return FALSE;
747 void
748 sp_ui_menu_append_check_item_from_verb(GtkMenu *menu, Inkscape::UI::View::View *view, gchar const *label, gchar const *tip, gchar const *pref,
749                                        void (*callback_toggle)(GtkCheckMenuItem *, gpointer user_data),
750                                        gboolean (*callback_update)(GtkWidget *widget, GdkEventExpose *event, gpointer user_data),
751                                        Inkscape::Verb *verb)
753     unsigned int shortcut = (verb) ? sp_shortcut_get_primary(verb) : 0;
754     SPAction *action = (verb) ? verb->get_action(view) : 0;
755     GtkWidget *item = gtk_check_menu_item_new();
757     if (verb && shortcut) {
758         gchar c[256];
759         sp_ui_shortcut_string(shortcut, c);
761         GtkWidget *hb = gtk_hbox_new(FALSE, 16);
763         {
764             GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
765             gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
766             gtk_box_pack_start((GtkBox *) hb, l, TRUE, TRUE, 0);
767         }
769         {
770             GtkWidget *l = gtk_label_new(c);
771             gtk_misc_set_alignment((GtkMisc *) l, 1.0, 0.5);
772             gtk_box_pack_end((GtkBox *) hb, l, FALSE, FALSE, 0);
773         }
775         gtk_widget_show_all(hb);
777         gtk_container_add((GtkContainer *) item, hb);
778     } else {
779         GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
780         gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
781         gtk_container_add((GtkContainer *) item, l);
782     }
783 #if 0
784     nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
785     if (!action->sensitive) {
786         gtk_widget_set_sensitive(item, FALSE);
787     }
788 #endif
789     gtk_widget_show(item);
791     gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
793     g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
795     g_signal_connect( G_OBJECT(item), "toggled", (GCallback) callback_toggle, (void *) pref);
796     g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) callback_update, (void *) pref);
798     g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) (action ? action->tip : tip));
799     g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
802 static void
803 sp_recent_open(GtkRecentChooser *recent_menu, gpointer /*user_data*/)
805     // dealing with the bizarre filename convention in Inkscape for now
806     gchar *uri = gtk_recent_chooser_get_current_uri(GTK_RECENT_CHOOSER(recent_menu));
807     gchar *local_fn = g_filename_from_uri(uri, NULL, NULL);
808     gchar *utf8_fn = g_filename_to_utf8(local_fn, -1, NULL, NULL, NULL);
809     sp_file_open(utf8_fn, NULL);
810     g_free(utf8_fn);
811     g_free(local_fn);
812     g_free(uri);
815 static void
816 sp_file_new_from_template(GtkWidget */*widget*/, gchar const *uri)
818     sp_file_new(uri);
821 void
822 sp_menu_append_new_templates(GtkWidget *menu, Inkscape::UI::View::View *view)
824     std::list<gchar *> sources;
825     sources.push_back( profile_path("templates") ); // first try user's local dir
826     sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir
828     // Use this loop to iterate through a list of possible document locations.
829     while (!sources.empty()) {
830         gchar *dirname = sources.front();
832         if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) {
833             GError *err = 0;
834             GDir *dir = g_dir_open(dirname, 0, &err);
836             if (dir) {
837                 for (gchar const *file = g_dir_read_name(dir); file != NULL; file = g_dir_read_name(dir)) {
838                     if (!g_str_has_suffix(file, ".svg") && !g_str_has_suffix(file, ".svgz"))
839                         continue; // skip non-svg files
841                     gchar *basename = g_path_get_basename(file);
842                     if (g_str_has_suffix(basename, ".svg") && g_str_has_prefix(basename, "default."))
843                         continue; // skip default.*.svg (i.e. default.svg and translations) - it's in the menu already
845                     gchar const *filepath = g_build_filename(dirname, file, NULL);
846                     gchar *dupfile = g_strndup(file, strlen(file) - 4);
847                     gchar *filename =  g_filename_to_utf8(dupfile,  -1, NULL, NULL, NULL);
848                     g_free(dupfile);
849                     GtkWidget *item = gtk_menu_item_new_with_label(filename);
850                     g_free(filename);
852                     gtk_widget_show(item);
853                     // how does "filepath" ever get freed?
854                     g_signal_connect(G_OBJECT(item),
855                                      "activate",
856                                      G_CALLBACK(sp_file_new_from_template),
857                                      (gpointer) filepath);
859                     if (view) {
860                         // set null tip for now; later use a description from the template file
861                         g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
862                         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) NULL );
863                         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
864                     }
866                     gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
867                 }
868                 g_dir_close(dir);
869             }
870         }
872         // toss the dirname
873         g_free(dirname);
874         sources.pop_front();
875     }
878 void
879 sp_ui_checkboxes_menus(GtkMenu *m, Inkscape::UI::View::View *view)
881     //sp_ui_menu_append_check_item_from_verb(m, view, _("_Menu"), _("Show or hide the menu bar"), "menu",
882     //                                       checkitem_toggled, checkitem_update, 0);
883     sp_ui_menu_append_check_item_from_verb(m, view, _("Commands Bar"), _("Show or hide the Commands bar (under the menu)"), "commands",
884                                            checkitem_toggled, checkitem_update, 0);
885     sp_ui_menu_append_check_item_from_verb(m, view, _("Snap Controls Bar"), _("Show or hide the snapping controls"), "snaptoolbox",
886                                            checkitem_toggled, checkitem_update, 0);
887     sp_ui_menu_append_check_item_from_verb(m, view, _("Tool Controls Bar"), _("Show or hide the Tool Controls bar"), "toppanel",
888                                            checkitem_toggled, checkitem_update, 0);
889     sp_ui_menu_append_check_item_from_verb(m, view, _("_Toolbox"), _("Show or hide the main toolbox (on the left)"), "toolbox",
890                                            checkitem_toggled, checkitem_update, 0);
891     sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "rulers",
892                                            checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_RULERS));
893     sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "scrollbars",
894                                            checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_SCROLLBARS));
895     sp_ui_menu_append_check_item_from_verb(m, view, _("_Palette"), _("Show or hide the color palette"), "panels",
896                                            checkitem_toggled, checkitem_update, 0);
897     sp_ui_menu_append_check_item_from_verb(m, view, _("_Statusbar"), _("Show or hide the statusbar (at the bottom of the window)"), "statusbar",
898                                            checkitem_toggled, checkitem_update, 0);
902 void addTaskMenuItems(GtkMenu *menu, Inkscape::UI::View::View *view)
904     gchar const* data[] = {
905         _("Default"), _("Default interface setup"),
906         _("Custom"), _("Set the custom task"),
907         _("Wide"), _("Setup for widescreen work."),
908         0, 0
909     };
911     GSList *group = 0;
912     int count = 0;
913     gint active = Inkscape::UI::UXManager::getInstance()->getDefaultTask( dynamic_cast<SPDesktop*>(view) );
914     for (gchar const **strs = data; strs[0]; strs += 2, count++)
915     {
916         GtkWidget *item = gtk_radio_menu_item_new_with_label( group, strs[0] );
917         group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item) );
918         if ( count == active )
919         {
920             gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(item), TRUE );
921         }
923         g_object_set_data( G_OBJECT(item), "view", view );
924         g_signal_connect( G_OBJECT(item), "toggled", reinterpret_cast<GCallback>(taskToggled), GINT_TO_POINTER(count) );
925         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), const_cast<gchar*>(strs[1]) );
926         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), 0 );
928         gtk_widget_show( item );
929         gtk_menu_shell_append( GTK_MENU_SHELL(menu), item );
930     }
934 /** @brief Observer that updates the recent list's max document count */
935 class MaxRecentObserver : public Inkscape::Preferences::Observer {
936 public:
937     MaxRecentObserver(GtkWidget *recent_menu) :
938         Observer("/options/maxrecentdocuments/value"),
939         _rm(recent_menu)
940     {}
941     virtual void notify(Inkscape::Preferences::Entry const &e) {
942         gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(_rm), e.getInt());
943         // hack: the recent menu doesn't repopulate after changing the limit, so we force it
944         g_signal_emit_by_name((gpointer) gtk_recent_manager_get_default(), "changed");
945     }
946 private:
947     GtkWidget *_rm;
948 };
950 /** \brief  This function turns XML into a menu
951     \param  menus  This is the XML that defines the menu
952     \param  menu   Menu to be added to
953     \param  view   The View that this menu is being built for
955     This function is realitively simple as it just goes through the XML
956     and parses the individual elements.  In the case of a submenu, it
957     just calls itself recursively.  Because it is only reasonable to have
958     a couple of submenus, it is unlikely this will go more than two or
959     three times.
961     In the case of an unrecognized verb, a menu item is made to identify
962     the verb that is missing, and display that.  The menu item is also made
963     insensitive.
964 */
965 void
966 sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI::View::View *view)
968     if (menus == NULL) return;
969     if (menu == NULL)  return;
970     GSList *group = NULL;
972     for (Inkscape::XML::Node *menu_pntr = menus;
973          menu_pntr != NULL;
974          menu_pntr = menu_pntr->next()) {
975         if (!strcmp(menu_pntr->name(), "submenu")) {
976             GtkWidget *mitem = gtk_menu_item_new_with_mnemonic(_(menu_pntr->attribute("name")));
977             GtkWidget *submenu = gtk_menu_new();
978             sp_ui_build_dyn_menus(menu_pntr->firstChild(), submenu, view);
979             gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), GTK_WIDGET(submenu));
980             gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
981             continue;
982         }
983         if (!strcmp(menu_pntr->name(), "verb")) {
984             gchar const *verb_name = menu_pntr->attribute("verb-id");
985             Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name);
987             if (verb != NULL) {
988                 if (menu_pntr->attribute("radio") != NULL) {
989                     GtkWidget *item = sp_ui_menu_append_item_from_verb (GTK_MENU(menu), verb, view, true, group);
990                     group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item));
991                     if (menu_pntr->attribute("default") != NULL) {
992                         gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
993                     }
994                     if (verb->get_code() != SP_VERB_NONE) {
995                         SPAction *action = verb->get_action(view);
996                         g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) update_view_menu, (void *) action);
997                     }
998                 } else {
999                     sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view);
1000                     group = NULL;
1001                 }
1002             } else {
1003                 gchar string[120];
1004                 g_snprintf(string, 120, _("Verb \"%s\" Unknown"), verb_name);
1005                 string[119] = '\0'; /* may not be terminated */
1006                 GtkWidget *item = gtk_menu_item_new_with_label(string);
1007                 gtk_widget_set_sensitive(item, false);
1008                 gtk_widget_show(item);
1009                 gtk_menu_append(GTK_MENU(menu), item);
1010             }
1011             continue;
1012         }
1013         if (!strcmp(menu_pntr->name(), "separator")
1014                 // This was spelt wrong in the original version
1015                 // and so this is for backward compatibility.  It can
1016                 // probably be dropped after the 0.44 release.
1017              || !strcmp(menu_pntr->name(), "seperator")) {
1018             GtkWidget *item = gtk_separator_menu_item_new();
1019             gtk_widget_show(item);
1020             gtk_menu_append(GTK_MENU(menu), item);
1021             continue;
1022         }
1023         if (!strcmp(menu_pntr->name(), "template-list")) {
1024             sp_menu_append_new_templates(menu, view);
1025             continue;
1026         }
1027         if (!strcmp(menu_pntr->name(), "recent-file-list")) {
1028             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1030             // create recent files menu
1031             int max_recent = prefs->getInt("/options/maxrecentdocuments/value");
1032             GtkWidget *recent_menu = gtk_recent_chooser_menu_new_for_manager(gtk_recent_manager_get_default());
1033             gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(recent_menu), max_recent);
1034             // sort most recently used documents first to preserve previous behavior
1035             gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(recent_menu), GTK_RECENT_SORT_MRU);
1036             g_signal_connect(G_OBJECT(recent_menu), "item-activated", G_CALLBACK(sp_recent_open), (gpointer) NULL);
1038             // add filter to only open files added by Inkscape
1039             GtkRecentFilter *inkscape_only_filter = gtk_recent_filter_new();
1040             gtk_recent_filter_add_application(inkscape_only_filter, g_get_prgname());
1041             gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_menu), inkscape_only_filter);
1043             gtk_recent_chooser_set_show_tips (GTK_RECENT_CHOOSER(recent_menu), TRUE);
1044             gtk_recent_chooser_set_show_not_found (GTK_RECENT_CHOOSER(recent_menu), FALSE);
1046             GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic(_("Open _Recent"));
1047             gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), recent_menu);
1049             gtk_menu_append(GTK_MENU(menu), GTK_WIDGET(recent_item));
1050             // this will just sit and update the list's item count
1051             static MaxRecentObserver *mro = new MaxRecentObserver(recent_menu);
1052             prefs->addObserver(*mro);
1053             continue;
1054         }
1055         if (!strcmp(menu_pntr->name(), "objects-checkboxes")) {
1056             sp_ui_checkboxes_menus(GTK_MENU(menu), view);
1057             continue;
1058         }
1059         if (!strcmp(menu_pntr->name(), "task-checkboxes")) {
1060             addTaskMenuItems(GTK_MENU(menu), view);
1061             continue;
1062         }
1063     }
1066 /** \brief  Build the main tool bar
1067     \param  view  View to build the bar for
1069     Currently the main tool bar is built as a dynamic XML menu using
1070     \c sp_ui_build_dyn_menus.  This function builds the bar, and then
1071     pass it to get items attached to it.
1072 */
1073 GtkWidget *
1074 sp_ui_main_menubar(Inkscape::UI::View::View *view)
1076     GtkWidget *mbar = gtk_menu_bar_new();
1078 #ifdef GDK_WINDOWING_QUARTZ
1079     ige_mac_menu_set_menu_bar(GTK_MENU_SHELL(mbar));
1080 #endif
1082     sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view);
1084 #ifdef GDK_WINDOWING_QUARTZ
1085     return NULL;
1086 #else
1087     return mbar;
1088 #endif
1091 static void leave_group(GtkMenuItem *, SPDesktop *desktop) {
1092     desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer()));
1095 static void enter_group(GtkMenuItem *mi, SPDesktop *desktop) {
1096     desktop->setCurrentLayer(reinterpret_cast<SPObject *>(g_object_get_data(G_OBJECT(mi), "group")));
1097     sp_desktop_selection(desktop)->clear();
1100 GtkWidget *
1101 sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item)
1103     GtkWidget *m;
1104     SPDesktop *dt;
1106     dt = static_cast<SPDesktop*>(view);
1108     m = gtk_menu_new();
1110     /* Undo and Redo */
1111     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_UNDO), view);
1112     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_REDO), view);
1114     /* Separator */
1115     sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1117     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_CUT), view);
1118     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_COPY), view);
1119     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_PASTE), view);
1121     /* Separator */
1122     sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1124     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DUPLICATE), view);
1125     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DELETE), view);
1127     /* Item menu */
1128     if (item) {
1129         sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1130         sp_object_menu((SPObject *) item, dt, GTK_MENU(m));
1131     }
1133     /* layer menu */
1134     SPGroup *group=NULL;
1135     if (item) {
1136         if (SP_IS_GROUP(item)) {
1137             group = SP_GROUP(item);
1138         } else if ( item != dt->currentRoot() && SP_IS_GROUP(SP_OBJECT_PARENT(item)) ) {
1139             group = SP_GROUP(SP_OBJECT_PARENT(item));
1140         }
1141     }
1143     if (( group && group != dt->currentLayer() ) ||
1144         ( dt->currentLayer() != dt->currentRoot() && SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) ) {
1145         /* Separator */
1146         sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1147     }
1149     if ( group && group != dt->currentLayer() ) {
1150         /* TRANSLATORS: #%s is the id of the group e.g. <g id="#g7">, not a number. */
1151         gchar *label=g_strdup_printf(_("Enter group #%s"), group->getId());
1152         GtkWidget *w = gtk_menu_item_new_with_label(label);
1153         g_free(label);
1154         g_object_set_data(G_OBJECT(w), "group", group);
1155         g_signal_connect(G_OBJECT(w), "activate", GCallback(enter_group), dt);
1156         gtk_widget_show(w);
1157         gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1158     }
1160     if ( dt->currentLayer() != dt->currentRoot() ) {
1161         if ( SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) {
1162             GtkWidget *w = gtk_menu_item_new_with_label(_("Go to parent"));
1163             g_signal_connect(G_OBJECT(w), "activate", GCallback(leave_group), dt);
1164             gtk_widget_show(w);
1165             gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1167         }
1168     }
1170     return m;
1173 /* Drag and Drop */
1174 void
1175 sp_ui_drag_data_received(GtkWidget *widget,
1176                          GdkDragContext *drag_context,
1177                          gint x, gint y,
1178                          GtkSelectionData *data,
1179                          guint info,
1180                          guint /*event_time*/,
1181                          gpointer /*user_data*/)
1183     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1184     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1186     switch (info) {
1187 #if ENABLE_MAGIC_COLORS
1188         case APP_X_INKY_COLOR:
1189         {
1190             int destX = 0;
1191             int destY = 0;
1192             gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1193             Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1195             SPItem *item = desktop->item_at_point( where, true );
1196             if ( item )
1197             {
1198                 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1200                 if ( data->length >= 8 ) {
1201                     cmsHPROFILE srgbProf = cmsCreate_sRGBProfile();
1203                     gchar c[64] = {0};
1204                     // Careful about endian issues.
1205                     guint16* dataVals = (guint16*)data->data;
1206                     sp_svg_write_color( c, sizeof(c),
1207                                         SP_RGBA32_U_COMPOSE(
1208                                             0x0ff & (dataVals[0] >> 8),
1209                                             0x0ff & (dataVals[1] >> 8),
1210                                             0x0ff & (dataVals[2] >> 8),
1211                                             0xff // can't have transparency in the color itself
1212                                             //0x0ff & (data->data[3] >> 8),
1213                                             ));
1214                     SPCSSAttr *css = sp_repr_css_attr_new();
1215                     bool updatePerformed = false;
1217                     if ( data->length > 14 ) {
1218                         int flags = dataVals[4];
1220                         // piggie-backed palette entry info
1221                         int index = dataVals[5];
1222                         Glib::ustring palName;
1223                         for ( int i = 0; i < dataVals[6]; i++ ) {
1224                             palName += (gunichar)dataVals[7+i];
1225                         }
1227                         // Now hook in a magic tag of some sort.
1228                         if ( !palName.empty() && (flags & 1) ) {
1229                             gchar* str = g_strdup_printf("%d|", index);
1230                             palName.insert( 0, str );
1231                             g_free(str);
1232                             str = 0;
1234                             SP_OBJECT(item)->setAttribute( 
1235                                                     fillnotstroke ? "inkscape:x-fill-tag":"inkscape:x-stroke-tag",
1236                                                     palName.c_str(),
1237                                                     false );
1238                             item->updateRepr();
1240                             sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1241                             updatePerformed = true;
1242                         }
1243                     }
1245                     if ( !updatePerformed ) {
1246                         sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1247                     }
1249                     sp_desktop_apply_css_recursive( item, css, true );
1250                     item->updateRepr();
1252                     SPDocumentUndo::done( doc , SP_VERB_NONE,
1253                                       _("Drop color"));
1255                     if ( srgbProf ) {
1256                         cmsCloseProfile( srgbProf );
1257                     }
1258                 }
1259             }
1260         }
1261         break;
1262 #endif // ENABLE_MAGIC_COLORS
1264         case APP_X_COLOR:
1265         {
1266             int destX = 0;
1267             int destY = 0;
1268             gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1269             Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1270             Geom::Point const button_dt(desktop->w2d(where));
1271             Geom::Point const button_doc(desktop->dt2doc(button_dt));
1273             if ( data->length == 8 ) {
1274                 gchar colorspec[64] = {0};
1275                 // Careful about endian issues.
1276                 guint16* dataVals = (guint16*)data->data;
1277                 sp_svg_write_color( colorspec, sizeof(colorspec),
1278                                     SP_RGBA32_U_COMPOSE(
1279                                         0x0ff & (dataVals[0] >> 8),
1280                                         0x0ff & (dataVals[1] >> 8),
1281                                         0x0ff & (dataVals[2] >> 8),
1282                                         0xff // can't have transparency in the color itself
1283                                         //0x0ff & (data->data[3] >> 8),
1284                                         ));
1286                 SPItem *item = desktop->item_at_point( where, true );
1288                 bool consumed = false;
1289                 if (desktop->event_context && desktop->event_context->get_drag()) {
1290                     consumed = desktop->event_context->get_drag()->dropColor(item, colorspec, button_dt);
1291                     if (consumed) {
1292                         SPDocumentUndo::done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1293                         desktop->event_context->get_drag()->updateDraggers();
1294                     }
1295                 }
1297                 //if (!consumed && tools_active(desktop, TOOLS_TEXT)) {
1298                 //    consumed = sp_text_context_drop_color(c, button_doc);
1299                 //    if (consumed) {
1300                 //        SPDocumentUndo::done( doc , SP_VERB_NONE, _("Drop color on gradient stop"));
1301                 //    }
1302                 //}
1304                 if (!consumed && item) {
1305                     bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1306                     if (fillnotstroke &&
1307                         (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1308                         Path *livarot_path = Path_for_item(item, true, true);
1309                         livarot_path->ConvertWithBackData(0.04);
1311                         boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1312                         if (position) {
1313                             Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1314                             Geom::Point delta = nearest - button_doc;
1315                             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1316                             delta = desktop->d2w(delta);
1317                             double stroke_tolerance =
1318                                 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1319                                   desktop->current_zoom() *
1320                                   SP_OBJECT_STYLE (item)->stroke_width.computed *
1321                                   to_2geom(item->i2d_affine()).descrim() * 0.5
1322                                   : 0.0)
1323                                 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1325                             if (Geom::L2 (delta) < stroke_tolerance) {
1326                                 fillnotstroke = false;
1327                             }
1328                         }
1329                         delete livarot_path;
1330                     }
1332                     SPCSSAttr *css = sp_repr_css_attr_new();
1333                     sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec );
1335                     sp_desktop_apply_css_recursive( item, css, true );
1336                     item->updateRepr();
1338                     SPDocumentUndo::done( doc , SP_VERB_NONE,
1339                                       _("Drop color"));
1340                 }
1341             }
1342         }
1343         break;
1345         case APP_OSWB_COLOR:
1346         {
1347             bool worked = false;
1348             Glib::ustring colorspec;
1349             if ( data->format == 8 ) {
1350                 ege::PaintDef color;
1351                 worked = color.fromMIMEData("application/x-oswb-color",
1352                                             reinterpret_cast<char*>(data->data),
1353                                             data->length,
1354                                             data->format);
1355                 if ( worked ) {
1356                     if ( color.getType() == ege::PaintDef::CLEAR ) {
1357                         colorspec = ""; // TODO check if this is sufficient
1358                     } else if ( color.getType() == ege::PaintDef::NONE ) {
1359                         colorspec = "none";
1360                     } else {
1361                         unsigned int r = color.getR();
1362                         unsigned int g = color.getG();
1363                         unsigned int b = color.getB();
1365                         SPGradient* matches = 0;
1366                         const GSList *gradients = doc->get_resource_list("gradient");
1367                         for (const GSList *item = gradients; item; item = item->next) {
1368                             SPGradient* grad = SP_GRADIENT(item->data);
1369                             if ( color.descr == grad->getId() ) {
1370                                 if ( grad->hasStops() ) {
1371                                     matches = grad;
1372                                     break;
1373                                 }
1374                             }
1375                         }
1376                         if (matches) {
1377                             colorspec = "url(#";
1378                             colorspec += matches->getId();
1379                             colorspec += ")";
1380                         } else {
1381                             gchar* tmp = g_strdup_printf("#%02x%02x%02x", r, g, b);
1382                             colorspec = tmp;
1383                             g_free(tmp);
1384                         }
1385                     }
1386                 }
1387             }
1388             if ( worked ) {
1389                 int destX = 0;
1390                 int destY = 0;
1391                 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1392                 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1393                 Geom::Point const button_dt(desktop->w2d(where));
1394                 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1396                 SPItem *item = desktop->item_at_point( where, true );
1398                 bool consumed = false;
1399                 if (desktop->event_context && desktop->event_context->get_drag()) {
1400                     consumed = desktop->event_context->get_drag()->dropColor(item, colorspec.c_str(), button_dt);
1401                     if (consumed) {
1402                         SPDocumentUndo::done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1403                         desktop->event_context->get_drag()->updateDraggers();
1404                     }
1405                 }
1407                 if (!consumed && item) {
1408                     bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1409                     if (fillnotstroke &&
1410                         (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1411                         Path *livarot_path = Path_for_item(item, true, true);
1412                         livarot_path->ConvertWithBackData(0.04);
1414                         boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1415                         if (position) {
1416                             Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1417                             Geom::Point delta = nearest - button_doc;
1418                             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1419                             delta = desktop->d2w(delta);
1420                             double stroke_tolerance =
1421                                 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1422                                   desktop->current_zoom() *
1423                                   SP_OBJECT_STYLE (item)->stroke_width.computed *
1424                                   to_2geom(item->i2d_affine()).descrim() * 0.5
1425                                   : 0.0)
1426                                 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1428                             if (Geom::L2 (delta) < stroke_tolerance) {
1429                                 fillnotstroke = false;
1430                             }
1431                         }
1432                         delete livarot_path;
1433                     }
1435                     SPCSSAttr *css = sp_repr_css_attr_new();
1436                     sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec.c_str() );
1438                     sp_desktop_apply_css_recursive( item, css, true );
1439                     item->updateRepr();
1441                     SPDocumentUndo::done( doc , SP_VERB_NONE,
1442                                       _("Drop color"));
1443                 }
1444             }
1445         }
1446         break;
1448         case SVG_DATA:
1449         case SVG_XML_DATA: {
1450             gchar *svgdata = (gchar *)data->data;
1452             Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI);
1454             if (rnewdoc == NULL) {
1455                 sp_ui_error_dialog(_("Could not parse SVG data"));
1456                 return;
1457             }
1459             Inkscape::XML::Node *repr = rnewdoc->root();
1460             gchar const *style = repr->attribute("style");
1462             Inkscape::XML::Node *newgroup = rnewdoc->createElement("svg:g");
1463             newgroup->setAttribute("style", style);
1465             Inkscape::XML::Document * xml_doc =  sp_document_repr_doc(doc);
1466             for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) {
1467                 Inkscape::XML::Node *newchild = child->duplicate(xml_doc);
1468                 newgroup->appendChild(newchild);
1469             }
1471             Inkscape::GC::release(rnewdoc);
1473             // Add it to the current layer
1475             // Greg's edits to add intelligent positioning of svg drops
1476             SPObject *new_obj = NULL;
1477             new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
1479             Inkscape::Selection *selection = sp_desktop_selection(desktop);
1480             selection->set(SP_ITEM(new_obj));
1482             // move to mouse pointer
1483             {
1484                 sp_desktop_document(desktop)->ensure_up_to_date();
1485                 Geom::OptRect sel_bbox = selection->bounds();
1486                 if (sel_bbox) {
1487                     Geom::Point m( desktop->point() - sel_bbox->midpoint() );
1488                     sp_selection_move_relative(selection, m, false);
1489                 }
1490             }
1492             Inkscape::GC::release(newgroup);
1493             SPDocumentUndo::done(doc, SP_VERB_NONE,
1494                              _("Drop SVG"));
1495             break;
1496         }
1498         case URI_LIST: {
1499             gchar *uri = (gchar *)data->data;
1500             sp_ui_import_files(uri);
1501             break;
1502         }
1504         case PNG_DATA:
1505         case JPEG_DATA:
1506         case IMAGE_DATA: {
1507             const char *mime = (info == JPEG_DATA ? "image/jpeg" : "image/png");
1509             Inkscape::Extension::DB::InputList o;
1510             Inkscape::Extension::db.get_input_list(o);
1511             Inkscape::Extension::DB::InputList::const_iterator i = o.begin();
1512             while (i != o.end() && strcmp( (*i)->get_mimetype(), mime ) != 0) {
1513                 ++i;
1514             }
1515             Inkscape::Extension::Extension *ext = *i;
1516             bool save = (strcmp(ext->get_param_optiongroup("link"), "embed") == 0);
1517             ext->set_param_optiongroup("link", "embed");
1518             ext->set_gui(false);
1520             gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-dnd-import", NULL );
1521             g_file_set_contents(filename, reinterpret_cast<gchar const *>(data->data), data->length, NULL);
1522             file_import(doc, filename, ext);
1523             g_free(filename);
1525             ext->set_param_optiongroup("link", save ? "embed" : "link");
1526             ext->set_gui(true);
1527             SPDocumentUndo::done( doc , SP_VERB_NONE,
1528                               _("Drop bitmap image"));
1529             break;
1530         }
1531     }
1534 #include "gradient-context.h"
1536 void sp_ui_drag_motion( GtkWidget */*widget*/,
1537                         GdkDragContext */*drag_context*/,
1538                         gint /*x*/, gint /*y*/,
1539                         GtkSelectionData */*data*/,
1540                         guint /*info*/,
1541                         guint /*event_time*/,
1542                         gpointer /*user_data*/)
1544 //     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1545 //     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1548 //     g_message("drag-n-drop motion (%4d, %4d)  at %d", x, y, event_time);
1551 static void sp_ui_drag_leave( GtkWidget */*widget*/,
1552                               GdkDragContext */*drag_context*/,
1553                               guint /*event_time*/,
1554                               gpointer /*user_data*/ )
1556 //     g_message("drag-n-drop leave                at %d", event_time);
1559 static void
1560 sp_ui_import_files(gchar *buffer)
1562     GList *list = gnome_uri_list_extract_filenames(buffer);
1563     if (!list)
1564         return;
1565     g_list_foreach(list, sp_ui_import_one_file_with_check, NULL);
1566     g_list_foreach(list, (GFunc) g_free, NULL);
1567     g_list_free(list);
1570 static void
1571 sp_ui_import_one_file_with_check(gpointer filename, gpointer /*unused*/)
1573     if (filename) {
1574         if (strlen((char const *)filename) > 2)
1575             sp_ui_import_one_file((char const *)filename);
1576     }
1579 static void
1580 sp_ui_import_one_file(char const *filename)
1582     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1583     if (!doc) return;
1585     if (filename == NULL) return;
1587     // Pass off to common implementation
1588     // TODO might need to get the proper type of Inkscape::Extension::Extension
1589     file_import( doc, filename, NULL );
1592 void
1593 sp_ui_error_dialog(gchar const *message)
1595     GtkWidget *dlg;
1596     gchar *safeMsg = Inkscape::IO::sanitizeString(message);
1598     dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
1599                                  GTK_BUTTONS_CLOSE, "%s", safeMsg);
1600     sp_transientize(dlg);
1601     gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE);
1602     gtk_dialog_run(GTK_DIALOG(dlg));
1603     gtk_widget_destroy(dlg);
1604     g_free(safeMsg);
1607 bool
1608 sp_ui_overwrite_file(gchar const *filename)
1610     bool return_value = FALSE;
1612     if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) {
1613         Gtk::Window *window = SP_ACTIVE_DESKTOP->getToplevel();
1614         gchar* baseName = g_path_get_basename( filename );
1615         gchar* dirName = g_path_get_dirname( filename );
1616         GtkWidget* dialog = gtk_message_dialog_new_with_markup( window->gobj(),
1617                                                                 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
1618                                                                 GTK_MESSAGE_QUESTION,
1619                                                                 GTK_BUTTONS_NONE,
1620                                                                 _( "<span weight=\"bold\" size=\"larger\">A file named \"%s\" already exists. Do you want to replace it?</span>\n\n"
1621                                                                    "The file already exists in \"%s\". Replacing it will overwrite its contents." ),
1622                                                                 baseName,
1623                                                                 dirName
1624             );
1625         gtk_dialog_add_buttons( GTK_DIALOG(dialog),
1626                                 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1627                                 _("Replace"), GTK_RESPONSE_YES,
1628                                 NULL );
1629         gtk_dialog_set_default_response( GTK_DIALOG(dialog), GTK_RESPONSE_YES );
1631         if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_YES ) {
1632             return_value = TRUE;
1633         } else {
1634             return_value = FALSE;
1635         }
1636         gtk_widget_destroy(dialog);
1637         g_free( baseName );
1638         g_free( dirName );
1639     } else {
1640         return_value = TRUE;
1641     }
1643     return return_value;
1646 static void
1647 sp_ui_menu_item_set_sensitive(SPAction */*action*/, unsigned int sensitive, void *data)
1649     return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
1652 static void
1653 sp_ui_menu_item_set_name(SPAction */*action*/, Glib::ustring name, void *data)
1655     void *child = GTK_BIN (data)->child;
1656     //child is either
1657     //- a GtkHBox, whose first child is a label displaying name if the menu
1658     //item has an accel key
1659     //- a GtkLabel if the menu has no accel key
1660     if (GTK_IS_LABEL(child)) {
1661         gtk_label_set_markup_with_mnemonic(GTK_LABEL (child), name.c_str());
1662     } else if (GTK_IS_HBOX(child)) {
1663         gtk_label_set_markup_with_mnemonic(
1664         GTK_LABEL (gtk_container_get_children(GTK_CONTAINER (child))->data),
1665         name.c_str());
1666     }//else sp_ui_menu_append_item_from_verb has been modified and can set
1667     //a menu item in yet another way...
1671 /*
1672   Local Variables:
1673   mode:c++
1674   c-file-style:"stroustrup"
1675   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1676   indent-tabs-mode:nil
1677   fill-column:99
1678   End:
1679 */
1680 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :