Code

Improve behavior when pasting, DnDing and importing bitmap images
[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"
57 // Added for color drag-n-drop
58 #if ENABLE_LCMS
59 #include "lcms.h"
60 #endif // ENABLE_LCMS
61 #include "display/sp-canvas.h"
62 #include "color.h"
63 #include "svg/svg-color.h"
64 #include "desktop-style.h"
65 #include "style.h"
66 #include "event-context.h"
67 #include "gradient-drag.h"
68 #include "widgets/ege-paint-def.h"
70 // Include Mac OS X menu synchronization on native OSX build
71 #ifdef GDK_WINDOWING_QUARTZ
72 #include "ige-mac-menu.h"
73 #endif
75 /* Drag and Drop */
76 typedef enum {
77     URI_LIST,
78     SVG_XML_DATA,
79     SVG_DATA,
80     PNG_DATA,
81     JPEG_DATA,
82     IMAGE_DATA,
83     APP_X_INKY_COLOR,
84     APP_X_COLOR,
85     APP_OSWB_COLOR,
86 } ui_drop_target_info;
88 static GtkTargetEntry ui_drop_target_entries [] = {
89     {(gchar *)"text/uri-list",                0, URI_LIST        },
90     {(gchar *)"image/svg+xml",                0, SVG_XML_DATA    },
91     {(gchar *)"image/svg",                    0, SVG_DATA        },
92     {(gchar *)"image/png",                    0, PNG_DATA        },
93     {(gchar *)"image/jpeg",                   0, JPEG_DATA       },
94 #if ENABLE_MAGIC_COLORS
95     {(gchar *)"application/x-inkscape-color", 0, APP_X_INKY_COLOR},
96 #endif // ENABLE_MAGIC_COLORS
97     {(gchar *)"application/x-oswb-color",     0, APP_OSWB_COLOR  },
98     {(gchar *)"application/x-color",          0, APP_X_COLOR     }
99 };
101 static GtkTargetEntry *completeDropTargets = 0;
102 static int completeDropTargetsCount = 0;
103 static bool temporarily_block_actions = false;
105 #define ENTRIES_SIZE(n) sizeof(n)/sizeof(n[0])
106 static guint nui_drop_target_entries = ENTRIES_SIZE(ui_drop_target_entries);
107 static void sp_ui_import_files(gchar *buffer);
108 static void sp_ui_import_one_file(char const *filename);
109 static void sp_ui_import_one_file_with_check(gpointer filename, gpointer unused);
110 static void sp_ui_drag_data_received(GtkWidget *widget,
111                                      GdkDragContext *drag_context,
112                                      gint x, gint y,
113                                      GtkSelectionData *data,
114                                      guint info,
115                                      guint event_time,
116                                      gpointer user_data);
117 static void sp_ui_drag_motion( GtkWidget *widget,
118                                GdkDragContext *drag_context,
119                                gint x, gint y,
120                                GtkSelectionData *data,
121                                guint info,
122                                guint event_time,
123                                gpointer user_data );
124 static void sp_ui_drag_leave( GtkWidget *widget,
125                               GdkDragContext *drag_context,
126                               guint event_time,
127                               gpointer user_data );
128 static void sp_ui_menu_item_set_sensitive(SPAction *action,
129                                           unsigned int sensitive,
130                                           void *data);
131 static void sp_ui_menu_item_set_name(SPAction *action,
132                                      Glib::ustring name,
133                                      void *data);
134 static void sp_recent_open(GtkRecentChooser *, gpointer);
136 SPActionEventVector menu_item_event_vector = {
137     {NULL},
138     NULL,
139     NULL, /* set_active */
140     sp_ui_menu_item_set_sensitive, /* set_sensitive */
141     NULL, /* set_shortcut */
142     sp_ui_menu_item_set_name /* set_name */
143 };
145 static const int MIN_ONSCREEN_DISTANCE = 50;
147 void
148 sp_create_window(SPViewWidget *vw, gboolean editable)
150     g_return_if_fail(vw != NULL);
151     g_return_if_fail(SP_IS_VIEW_WIDGET(vw));
153     Gtk::Window *win = Inkscape::UI::window_new("", TRUE);
155     gtk_container_add(GTK_CONTAINER(win->gobj()), GTK_WIDGET(vw));
156     gtk_widget_show(GTK_WIDGET(vw));
158     if (editable) {
159         g_object_set_data(G_OBJECT(vw), "window", win);
161         SPDesktopWidget *desktop_widget = reinterpret_cast<SPDesktopWidget*>(vw);
162         SPDesktop* desktop = desktop_widget->desktop;
164         desktop_widget->window = win;
166         win->set_data("desktop", desktop);
167         win->set_data("desktopwidget", desktop_widget);
169         win->signal_delete_event().connect(sigc::mem_fun(*(SPDesktop*)vw->view, &SPDesktop::onDeleteUI));
170         win->signal_window_state_event().connect(sigc::mem_fun(*desktop, &SPDesktop::onWindowStateEvent));
171         win->signal_focus_in_event().connect(sigc::mem_fun(*desktop_widget, &SPDesktopWidget::onFocusInEvent));
173         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
174         gint prefs_geometry =
175             (2==prefs->getInt("/options/savewindowgeometry/value", 0));
176         if (prefs_geometry) {
177             gint pw = prefs->getInt("/desktop/geometry/width", -1);
178             gint ph = prefs->getInt("/desktop/geometry/height", -1);
179             gint px = prefs->getInt("/desktop/geometry/x", -1);
180             gint py = prefs->getInt("/desktop/geometry/y", -1);
181             gint full = prefs->getBool("/desktop/geometry/fullscreen");
182             gint maxed = prefs->getBool("/desktop/geometry/maximized");
183             if (pw>0 && ph>0) {
184                 gint w = MIN(gdk_screen_width(), pw);
185                 gint h = MIN(gdk_screen_height(), ph);
186                 gint x = MIN(gdk_screen_width() - MIN_ONSCREEN_DISTANCE, px);
187                 gint y = MIN(gdk_screen_height() - MIN_ONSCREEN_DISTANCE, py);
188                 if (w>0 && h>0) {
189                     x = MIN(gdk_screen_width() - w, x);
190                     y = MIN(gdk_screen_height() - h, y);
191                     desktop->setWindowSize(w, h);
192                 }
194                 // Only restore xy for the first window so subsequent windows don't overlap exactly
195                 // with first.  (Maybe rule should be only restore xy if it's different from xy of
196                 // other desktops?)
198                 // Empirically it seems that active_desktop==this desktop only the first time a
199                 // desktop is created.
200                 SPDesktop *active_desktop = SP_ACTIVE_DESKTOP;
201                 if (active_desktop == desktop || active_desktop==NULL) {
202                     desktop->setWindowPosition(Geom::Point(x, y));
203                 }
204             }
205             if (maxed) {
206                 win->maximize();
207             }
208             if (full) {
209                 win->fullscreen();
210             }
211         }
213     } else {
214         gtk_window_set_policy(GTK_WINDOW(win->gobj()), TRUE, TRUE, TRUE);
215     }
217     if ( completeDropTargets == 0 || completeDropTargetsCount == 0 )
218     {
219         std::vector<gchar*> types;
221         GSList *list = gdk_pixbuf_get_formats();
222         while ( list ) {
223             int i = 0;
224             GdkPixbufFormat *one = (GdkPixbufFormat*)list->data;
225             gchar** typesXX = gdk_pixbuf_format_get_mime_types(one);
226             for ( i = 0; typesXX[i]; i++ ) {
227                 types.push_back(g_strdup(typesXX[i]));
228             }
229             g_strfreev(typesXX);
231             list = g_slist_next(list);
232         }
233         completeDropTargetsCount = nui_drop_target_entries + types.size();
234         completeDropTargets = new GtkTargetEntry[completeDropTargetsCount];
235         for ( int i = 0; i < (int)nui_drop_target_entries; i++ ) {
236             completeDropTargets[i] = ui_drop_target_entries[i];
237         }
238         int pos = nui_drop_target_entries;
240         for (std::vector<gchar*>::iterator it = types.begin() ; it != types.end() ; it++) {
241             completeDropTargets[pos].target = *it;
242             completeDropTargets[pos].flags = 0;
243             completeDropTargets[pos].info = IMAGE_DATA;
244             pos++;
245         }
246     }
248     gtk_drag_dest_set((GtkWidget*)win->gobj(),
249                       GTK_DEST_DEFAULT_ALL,
250                       completeDropTargets,
251                       completeDropTargetsCount,
252                       GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE));
255     g_signal_connect(G_OBJECT(win->gobj()),
256                      "drag_data_received",
257                      G_CALLBACK(sp_ui_drag_data_received),
258                      NULL);
259     g_signal_connect(G_OBJECT(win->gobj()),
260                      "drag_motion",
261                      G_CALLBACK(sp_ui_drag_motion),
262                      NULL);
263     g_signal_connect(G_OBJECT(win->gobj()),
264                      "drag_leave",
265                      G_CALLBACK(sp_ui_drag_leave),
266                      NULL);
267     win->show();
269     // needed because the first ACTIVATE_DESKTOP was sent when there was no window yet
270     inkscape_reactivate_desktop(SP_DESKTOP_WIDGET(vw)->desktop);
273 void
274 sp_ui_new_view()
276     SPDocument *document;
277     SPViewWidget *dtw;
279     document = SP_ACTIVE_DOCUMENT;
280     if (!document) return;
282     dtw = sp_desktop_widget_new(sp_document_namedview(document, NULL));
283     g_return_if_fail(dtw != NULL);
285     sp_create_window(dtw, TRUE);
286     sp_namedview_window_from_document(static_cast<SPDesktop*>(dtw->view));
287     sp_namedview_update_layers_from_document(static_cast<SPDesktop*>(dtw->view));
290 /* TODO: not yet working */
291 /* To be re-enabled (by adding to menu) once it works. */
292 void
293 sp_ui_new_view_preview()
295     SPDocument *document;
296     SPViewWidget *dtw;
298     document = SP_ACTIVE_DOCUMENT;
299     if (!document) return;
301     dtw = (SPViewWidget *) sp_svg_view_widget_new(document);
302     g_return_if_fail(dtw != NULL);
303     sp_svg_view_widget_set_resize(SP_SVG_VIEW_WIDGET(dtw), TRUE, 400.0, 400.0);
305     sp_create_window(dtw, FALSE);
308 /**
309  * \param widget unused
310  */
311 void
312 sp_ui_close_view(GtkWidget */*widget*/)
314         SPDesktop *dt = SP_ACTIVE_DESKTOP;
316         if (dt == NULL) {
317         return;
318     }
320     if (dt->shutdown()) {
321         return; // Shutdown operation has been canceled, so do nothing
322     }
324     // Shutdown can proceed; use the stored reference to the desktop here instead of the current SP_ACTIVE_DESKTOP,
325     // because the user might have changed the focus in the meantime (see bug #381357 on Launchpad)
326     dt->destroyWidget();
330 /**
331  *  sp_ui_close_all
332  *
333  *  This function is called to exit the program, and iterates through all
334  *  open document view windows, attempting to close each in turn.  If the
335  *  view has unsaved information, the user will be prompted to save,
336  *  discard, or cancel.
337  *
338  *  Returns FALSE if the user cancels the close_all operation, TRUE
339  *  otherwise.
340  */
341 unsigned int
342 sp_ui_close_all(void)
344     /* Iterate through all the windows, destroying each in the order they
345        become active */
346     while (SP_ACTIVE_DESKTOP) {
347         SPDesktop *dt = SP_ACTIVE_DESKTOP;
348         if (dt->shutdown()) {
349             /* The user canceled the operation, so end doing the close */
350             return FALSE;
351         }
352         // Shutdown can proceed; use the stored reference to the desktop here instead of the current SP_ACTIVE_DESKTOP,
353         // because the user might have changed the focus in the meantime (see bug #381357 on Launchpad)
354         dt->destroyWidget();
355     }
357     return TRUE;
360 /*
361  * Some day when the right-click menus are ready to start working
362  * smarter with the verbs, we'll need to change this NULL being
363  * sent to sp_action_perform to something useful, or set some kind
364  * of global "right-clicked position" variable for actions to
365  * investigate when they're called.
366  */
367 static void
368 sp_ui_menu_activate(void */*object*/, SPAction *action)
370     if (!temporarily_block_actions) {
371         sp_action_perform(action, NULL);
372     }
375 static void
376 sp_ui_menu_select_action(void */*object*/, SPAction *action)
378     action->view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, action->tip);
381 static void
382 sp_ui_menu_deselect_action(void */*object*/, SPAction *action)
384     action->view->tipsMessageContext()->clear();
387 static void
388 sp_ui_menu_select(gpointer object, gpointer tip)
390     Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view"));
391     view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, (gchar *)tip);
394 static void
395 sp_ui_menu_deselect(gpointer object)
397     Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*>  (g_object_get_data(G_OBJECT(object), "view"));
398     view->tipsMessageContext()->clear();
401 /**
402  * sp_ui_menuitem_add_icon
403  *
404  * Creates and attaches a scaled icon to the given menu item.
405  *
406  */
407 void
408 sp_ui_menuitem_add_icon( GtkWidget *item, gchar *icon_name )
410     GtkWidget *icon;
412     icon = sp_icon_new( Inkscape::ICON_SIZE_MENU, icon_name );
413     gtk_widget_show(icon);
414     gtk_image_menu_item_set_image((GtkImageMenuItem *) item, icon);
415 } // end of sp_ui_menu_add_icon
417 /**
418  * sp_ui_menu_append_item
419  *
420  * Appends a UI item with specific info for Inkscape/Sodipodi.
421  *
422  */
423 static GtkWidget *
424 sp_ui_menu_append_item( GtkMenu *menu, gchar const *stock,
425                         gchar const *label, gchar const *tip, Inkscape::UI::View::View *view, GCallback callback,
426                         gpointer data, gboolean with_mnemonic = TRUE )
428     GtkWidget *item;
430     if (stock) {
431         item = gtk_image_menu_item_new_from_stock(stock, NULL);
432     } else if (label) {
433         item = (with_mnemonic)
434             ? gtk_image_menu_item_new_with_mnemonic(label) :
435             gtk_image_menu_item_new_with_label(label);
436     } else {
437         item = gtk_separator_menu_item_new();
438     }
440     gtk_widget_show(item);
442     if (callback) {
443         g_signal_connect(G_OBJECT(item), "activate", callback, data);
444     }
446     if (tip && view) {
447         g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
448         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) tip );
449         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
450     }
452     gtk_menu_append(GTK_MENU(menu), item);
454     return item;
456 } // end of sp_ui_menu_append_item()
458 /**
459 \brief  a wrapper around gdk_keyval_name producing (when possible) characters, not names
460  */
461 static gchar const *
462 sp_key_name(guint keyval)
464     /* TODO: Compare with the definition of gtk_accel_label_refetch in gtk/gtkaccellabel.c (or
465        simply use GtkAccelLabel as the TODO comment in sp_ui_shortcut_string suggests). */
466     gchar const *n = gdk_keyval_name(gdk_keyval_to_upper(keyval));
468     if      (!strcmp(n, "asciicircum"))  return "^";
469     else if (!strcmp(n, "parenleft"  ))  return "(";
470     else if (!strcmp(n, "parenright" ))  return ")";
471     else if (!strcmp(n, "plus"       ))  return "+";
472     else if (!strcmp(n, "minus"      ))  return "-";
473     else if (!strcmp(n, "asterisk"   ))  return "*";
474     else if (!strcmp(n, "KP_Multiply"))  return "*";
475     else if (!strcmp(n, "Delete"     ))  return "Del";
476     else if (!strcmp(n, "Page_Up"    ))  return "PgUp";
477     else if (!strcmp(n, "Page_Down"  ))  return "PgDn";
478     else if (!strcmp(n, "grave"      ))  return "`";
479     else if (!strcmp(n, "numbersign" ))  return "#";
480     else if (!strcmp(n, "bar"        ))  return "|";
481     else if (!strcmp(n, "slash"      ))  return "/";
482     else if (!strcmp(n, "exclam"     ))  return "!";
483     else if (!strcmp(n, "percent"    ))  return "%";
484     else return n;
488 /**
489  * \param shortcut A GDK keyval OR'd with SP_SHORTCUT_blah_MASK values.
490  * \param c Points to a buffer at least 256 bytes long.
491  */
492 void
493 sp_ui_shortcut_string(unsigned const shortcut, gchar *const c)
495     /* TODO: This function shouldn't exist.  Our callers should use GtkAccelLabel instead of
496      * a generic GtkLabel containing this string, and should call gtk_widget_add_accelerator.
497      * Will probably need to change sp_shortcut_invoke callers.
498      *
499      * The existing gtk_label_new_with_mnemonic call can be replaced with
500      * g_object_new(GTK_TYPE_ACCEL_LABEL, NULL) followed by
501      * gtk_label_set_text_with_mnemonic(lbl, str).
502      */
503     static GtkAccelLabelClass const &accel_lbl_cls
504         = *(GtkAccelLabelClass const *) g_type_class_peek_static(GTK_TYPE_ACCEL_LABEL);
506     struct { unsigned test; char const *name; } const modifier_tbl[] = {
507         { SP_SHORTCUT_SHIFT_MASK,   accel_lbl_cls.mod_name_shift   },
508         { SP_SHORTCUT_CONTROL_MASK, accel_lbl_cls.mod_name_control },
509         { SP_SHORTCUT_ALT_MASK,     accel_lbl_cls.mod_name_alt     }
510     };
512     gchar *p = c;
513     gchar *end = p + 256;
515     for (unsigned i = 0; i < G_N_ELEMENTS(modifier_tbl); ++i) {
516         if ((shortcut & modifier_tbl[i].test)
517             && (p < end))
518         {
519             p += g_snprintf(p, end - p, "%s%s",
520                             modifier_tbl[i].name,
521                             accel_lbl_cls.mod_separator);
522         }
523     }
524     if (p < end) {
525         p += g_snprintf(p, end - p, "%s", sp_key_name(shortcut & 0xffffff));
526     }
527     end[-1] = '\0';  // snprintf doesn't guarantee to nul-terminate the string.
530 void
531 sp_ui_dialog_title_string(Inkscape::Verb *verb, gchar *c)
533     SPAction     *action;
534     unsigned int shortcut;
535     gchar        *s;
536     gchar        key[256];
537     gchar        *atitle;
539     action = verb->get_action(NULL);
540     if (!action)
541         return;
543     atitle = sp_action_get_title(action);
545     s = g_stpcpy(c, atitle);
547     g_free(atitle);
549     shortcut = sp_shortcut_get_primary(verb);
550     if (shortcut) {
551         s = g_stpcpy(s, " (");
552         sp_ui_shortcut_string(shortcut, key);
553         s = g_stpcpy(s, key);
554         s = g_stpcpy(s, ")");
555     }
559 /**
560  * sp_ui_menu_append_item_from_verb
561  *
562  * Appends a custom menu UI from a verb.
563  *
564  */
566 static GtkWidget *
567 sp_ui_menu_append_item_from_verb(GtkMenu *menu, Inkscape::Verb *verb, Inkscape::UI::View::View *view, bool radio = false, GSList *group = NULL)
569     SPAction *action;
570     GtkWidget *item;
572     if (verb->get_code() == SP_VERB_NONE) {
574         item = gtk_separator_menu_item_new();
576     } else {
577         unsigned int shortcut;
579         action = verb->get_action(view);
581         if (!action) return NULL;
583         shortcut = sp_shortcut_get_primary(verb);
584         if (shortcut) {
585             gchar c[256];
586             sp_ui_shortcut_string(shortcut, c);
587             GtkWidget *const hb = gtk_hbox_new(FALSE, 16);
588             GtkWidget *const name_lbl = gtk_label_new("");
589             gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
590             gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
591             gtk_box_pack_start((GtkBox *) hb, name_lbl, TRUE, TRUE, 0);
592             GtkWidget *const accel_lbl = gtk_label_new(c);
593             gtk_misc_set_alignment((GtkMisc *) accel_lbl, 1.0, 0.5);
594             gtk_box_pack_end((GtkBox *) hb, accel_lbl, FALSE, FALSE, 0);
595             gtk_widget_show_all(hb);
596             if (radio) {
597                 item = gtk_radio_menu_item_new (group);
598             } else {
599                 item = gtk_image_menu_item_new();
600             }
601             gtk_container_add((GtkContainer *) item, hb);
602         } else {
603             if (radio) {
604                 item = gtk_radio_menu_item_new (group);
605             } else {
606                 item = gtk_image_menu_item_new ();
607             }
608             GtkWidget *const name_lbl = gtk_label_new("");
609             gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
610             gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
611             gtk_container_add((GtkContainer *) item, name_lbl);
612         }
614         nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
615         if (!action->sensitive) {
616             gtk_widget_set_sensitive(item, FALSE);
617         }
619         if (action->image) {
620             sp_ui_menuitem_add_icon(item, action->image);
621         }
622         gtk_widget_set_events(item, GDK_KEY_PRESS_MASK);
623         g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
624         g_signal_connect( G_OBJECT(item), "activate", G_CALLBACK(sp_ui_menu_activate), action );
625         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select_action), action );
626         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect_action), action );
627     }
629     gtk_widget_show(item);
630     gtk_menu_append(GTK_MENU(menu), item);
632     return item;
634 } // end of sp_ui_menu_append_item_from_verb
637 static void
638 checkitem_toggled(GtkCheckMenuItem *menuitem, gpointer user_data)
640     gchar const *pref = (gchar const *) user_data;
641     Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
643     Glib::ustring pref_path;
644     if (reinterpret_cast<SPDesktop*>(view)->is_focusMode()) {
645         pref_path = "/focus/";
646     } else if (reinterpret_cast<SPDesktop*>(view)->is_fullscreen()) {
647         pref_path = "/fullscreen/";
648     } else {
649         pref_path = "/window/";
650     }
651     pref_path += pref;
652     pref_path += "/state";
654     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
655     gboolean checked = gtk_check_menu_item_get_active(menuitem);
656     prefs->setBool(pref_path, checked);
658     reinterpret_cast<SPDesktop*>(view)->layoutWidget();
661 static gboolean
662 checkitem_update(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_data)
664     GtkCheckMenuItem *menuitem=GTK_CHECK_MENU_ITEM(widget);
666     gchar const *pref = (gchar const *) user_data;
667     Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
669     Glib::ustring pref_path;
670     if ((static_cast<SPDesktop*>(view))->is_fullscreen()) {
671         pref_path = "/fullscreen/";
672     } else {
673         pref_path = "/window/";
674     }
675     pref_path += pref;
677     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
678     bool ison = prefs->getBool(pref_path + "/state", true);
680     g_signal_handlers_block_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
681     gtk_check_menu_item_set_active(menuitem, ison);
682     g_signal_handlers_unblock_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
684     return FALSE;
687 /**
688  *  \brief Callback function to update the status of the radio buttons in the View -> Display mode menu (Normal, No Filters, Outline)
689  */
691 static gboolean
692 update_view_menu(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_data)
694         SPAction *action = (SPAction *) user_data;
695         g_assert(action->id != NULL);
697         Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(widget), "view");
698     SPDesktop *dt = static_cast<SPDesktop*>(view);
699         Inkscape::RenderMode mode = dt->getMode();
701         bool new_state = false;
702         if (!strcmp(action->id, "ViewModeNormal")) {
703         new_state = mode == Inkscape::RENDERMODE_NORMAL;
704         } else if (!strcmp(action->id, "ViewModeNoFilters")) {
705         new_state = mode == Inkscape::RENDERMODE_NO_FILTERS;
706     } else if (!strcmp(action->id, "ViewModeOutline")) {
707         new_state = mode == Inkscape::RENDERMODE_OUTLINE;
708     } else if (!strcmp(action->id, "ViewModePrintColorsPreview")) {
709         new_state = mode == Inkscape::RENDERMODE_PRINT_COLORS_PREVIEW;
710     } else {
711         g_warning("update_view_menu does not handle this verb");
712     }
714         if (new_state) { //only one of the radio buttons has to be activated; the others will automatically be deactivated
715                 if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) {
716                         // When the GtkMenuItem version of the "activate" signal has been emitted by a GtkRadioMenuItem, there is a second
717                         // emission as the most recently active item is toggled to inactive. This is dealt with before the original signal is handled.
718                         // This emission however should not invoke any actions, hence we block it here:
719                         temporarily_block_actions = true;
720                         gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (widget), TRUE);
721                         temporarily_block_actions = false;
722                 }
723         }
725         return FALSE;
728 void
729 sp_ui_menu_append_check_item_from_verb(GtkMenu *menu, Inkscape::UI::View::View *view, gchar const *label, gchar const *tip, gchar const *pref,
730                                        void (*callback_toggle)(GtkCheckMenuItem *, gpointer user_data),
731                                        gboolean (*callback_update)(GtkWidget *widget, GdkEventExpose *event, gpointer user_data),
732                                        Inkscape::Verb *verb)
734     GtkWidget *item;
736     unsigned int shortcut = 0;
737     SPAction *action = NULL;
739     if (verb) {
740         shortcut = sp_shortcut_get_primary(verb);
741         action = verb->get_action(view);
742     }
744     if (verb && shortcut) {
745         gchar c[256];
746         sp_ui_shortcut_string(shortcut, c);
748         GtkWidget *hb = gtk_hbox_new(FALSE, 16);
750         {
751             GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
752             gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
753             gtk_box_pack_start((GtkBox *) hb, l, TRUE, TRUE, 0);
754         }
756         {
757             GtkWidget *l = gtk_label_new(c);
758             gtk_misc_set_alignment((GtkMisc *) l, 1.0, 0.5);
759             gtk_box_pack_end((GtkBox *) hb, l, FALSE, FALSE, 0);
760         }
762         gtk_widget_show_all(hb);
764         item = gtk_check_menu_item_new();
765         gtk_container_add((GtkContainer *) item, hb);
766     } else {
767         GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
768         gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
769         item = gtk_check_menu_item_new();
770         gtk_container_add((GtkContainer *) item, l);
771     }
772 #if 0
773     nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
774     if (!action->sensitive) {
775         gtk_widget_set_sensitive(item, FALSE);
776     }
777 #endif
778     gtk_widget_show(item);
780     gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
782     g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
784     g_signal_connect( G_OBJECT(item), "toggled", (GCallback) callback_toggle, (void *) pref);
785     g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) callback_update, (void *) pref);
787     g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) (action ? action->tip : tip));
788     g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
791 static void
792 sp_recent_open(GtkRecentChooser *recent_menu, gpointer /*user_data*/)
794     // dealing with the bizarre filename convention in Inkscape for now
795     gchar *uri = gtk_recent_chooser_get_current_uri(GTK_RECENT_CHOOSER(recent_menu));
796     gchar *local_fn = g_filename_from_uri(uri, NULL, NULL);
797     gchar *utf8_fn = g_filename_to_utf8(local_fn, -1, NULL, NULL, NULL);
798     sp_file_open(utf8_fn, NULL);
799     g_free(utf8_fn);
800     g_free(local_fn);
801     g_free(uri);
804 static void
805 sp_file_new_from_template(GtkWidget */*widget*/, gchar const *uri)
807     sp_file_new(uri);
810 void
811 sp_menu_append_new_templates(GtkWidget *menu, Inkscape::UI::View::View *view)
813     std::list<gchar *> sources;
814     sources.push_back( profile_path("templates") ); // first try user's local dir
815     sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir
817     // Use this loop to iterate through a list of possible document locations.
818     while (!sources.empty()) {
819         gchar *dirname = sources.front();
821         if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) {
822             GError *err = 0;
823             GDir *dir = g_dir_open(dirname, 0, &err);
825             if (dir) {
826                 for (gchar const *file = g_dir_read_name(dir); file != NULL; file = g_dir_read_name(dir)) {
827                     if (!g_str_has_suffix(file, ".svg") && !g_str_has_suffix(file, ".svgz"))
828                         continue; // skip non-svg files
830                     gchar *basename = g_path_get_basename(file);
831                     if (g_str_has_suffix(basename, ".svg") && g_str_has_prefix(basename, "default."))
832                         continue; // skip default.*.svg (i.e. default.svg and translations) - it's in the menu already
834                     gchar const *filepath = g_build_filename(dirname, file, NULL);
835                     gchar *dupfile = g_strndup(file, strlen(file) - 4);
836                     gchar *filename =  g_filename_to_utf8(dupfile,  -1, NULL, NULL, NULL);
837                     g_free(dupfile);
838                     GtkWidget *item = gtk_menu_item_new_with_label(filename);
839                     g_free(filename);
841                     gtk_widget_show(item);
842                     // how does "filepath" ever get freed?
843                     g_signal_connect(G_OBJECT(item),
844                                      "activate",
845                                      G_CALLBACK(sp_file_new_from_template),
846                                      (gpointer) filepath);
848                     if (view) {
849                         // set null tip for now; later use a description from the template file
850                         g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
851                         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) NULL );
852                         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
853                     }
855                     gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
856                 }
857                 g_dir_close(dir);
858             }
859         }
861         // toss the dirname
862         g_free(dirname);
863         sources.pop_front();
864     }
867 void
868 sp_ui_checkboxes_menus(GtkMenu *m, Inkscape::UI::View::View *view)
870     //sp_ui_menu_append_check_item_from_verb(m, view, _("_Menu"), _("Show or hide the menu bar"), "menu",
871     //                                       checkitem_toggled, checkitem_update, 0);
872     sp_ui_menu_append_check_item_from_verb(m, view, _("Commands Bar"), _("Show or hide the Commands bar (under the menu)"), "commands",
873                                            checkitem_toggled, checkitem_update, 0);
874     sp_ui_menu_append_check_item_from_verb(m, view, _("Snap Controls Bar"), _("Show or hide the snapping controls"), "snaptoolbox",
875                                            checkitem_toggled, checkitem_update, 0);
876     sp_ui_menu_append_check_item_from_verb(m, view, _("Tool Controls Bar"), _("Show or hide the Tool Controls bar"), "toppanel",
877                                            checkitem_toggled, checkitem_update, 0);
878     sp_ui_menu_append_check_item_from_verb(m, view, _("_Toolbox"), _("Show or hide the main toolbox (on the left)"), "toolbox",
879                                            checkitem_toggled, checkitem_update, 0);
880     sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "rulers",
881                                            checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_RULERS));
882     sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "scrollbars",
883                                            checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_SCROLLBARS));
884     sp_ui_menu_append_check_item_from_verb(m, view, _("_Palette"), _("Show or hide the color palette"), "panels",
885                                            checkitem_toggled, checkitem_update, 0);
886     sp_ui_menu_append_check_item_from_verb(m, view, _("_Statusbar"), _("Show or hide the statusbar (at the bottom of the window)"), "statusbar",
887                                            checkitem_toggled, checkitem_update, 0);
890 /** @brief Observer that updates the recent list's max document count */
891 class MaxRecentObserver : public Inkscape::Preferences::Observer {
892 public:
893     MaxRecentObserver(GtkWidget *recent_menu) :
894         Observer("/options/maxrecentdocuments/value"),
895         _rm(recent_menu)
896     {}
897     virtual void notify(Inkscape::Preferences::Entry const &e) {
898         gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(_rm), e.getInt());
899         // hack: the recent menu doesn't repopulate after changing the limit, so we force it
900         g_signal_emit_by_name((gpointer) gtk_recent_manager_get_default(), "changed");
901     }
902 private:
903     GtkWidget *_rm;
904 };
906 /** \brief  This function turns XML into a menu
907     \param  menus  This is the XML that defines the menu
908     \param  menu   Menu to be added to
909     \param  view   The View that this menu is being built for
911     This function is realitively simple as it just goes through the XML
912     and parses the individual elements.  In the case of a submenu, it
913     just calls itself recursively.  Because it is only reasonable to have
914     a couple of submenus, it is unlikely this will go more than two or
915     three times.
917     In the case of an unrecognized verb, a menu item is made to identify
918     the verb that is missing, and display that.  The menu item is also made
919     insensitive.
920 */
921 void
922 sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI::View::View *view)
924     if (menus == NULL) return;
925     if (menu == NULL)  return;
926     GSList *group = NULL;
928     for (Inkscape::XML::Node *menu_pntr = menus;
929          menu_pntr != NULL;
930          menu_pntr = menu_pntr->next()) {
931         if (!strcmp(menu_pntr->name(), "submenu")) {
932             GtkWidget *mitem = gtk_menu_item_new_with_mnemonic(_(menu_pntr->attribute("name")));
933             GtkWidget *submenu = gtk_menu_new();
934             sp_ui_build_dyn_menus(menu_pntr->firstChild(), submenu, view);
935             gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), GTK_WIDGET(submenu));
936             gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
937             continue;
938         }
939         if (!strcmp(menu_pntr->name(), "verb")) {
940             gchar const *verb_name = menu_pntr->attribute("verb-id");
941             Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name);
943             if (verb != NULL) {
944                 if (menu_pntr->attribute("radio") != NULL) {
945                     GtkWidget *item = sp_ui_menu_append_item_from_verb (GTK_MENU(menu), verb, view, true, group);
946                     group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item));
947                     if (menu_pntr->attribute("default") != NULL) {
948                         gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
949                     }
950                     if (verb->get_code() != SP_VERB_NONE) {
951                         SPAction *action = verb->get_action(view);
952                         g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) update_view_menu, (void *) action);
953                     }
954                 } else {
955                     sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view);
956                     group = NULL;
957                 }
958             } else {
959                 gchar string[120];
960                 g_snprintf(string, 120, _("Verb \"%s\" Unknown"), verb_name);
961                 string[119] = '\0'; /* may not be terminated */
962                 GtkWidget *item = gtk_menu_item_new_with_label(string);
963                 gtk_widget_set_sensitive(item, false);
964                 gtk_widget_show(item);
965                 gtk_menu_append(GTK_MENU(menu), item);
966             }
967             continue;
968         }
969         if (!strcmp(menu_pntr->name(), "separator")
970                 // This was spelt wrong in the original version
971                 // and so this is for backward compatibility.  It can
972                 // probably be dropped after the 0.44 release.
973              || !strcmp(menu_pntr->name(), "seperator")) {
974             GtkWidget *item = gtk_separator_menu_item_new();
975             gtk_widget_show(item);
976             gtk_menu_append(GTK_MENU(menu), item);
977             continue;
978         }
979         if (!strcmp(menu_pntr->name(), "template-list")) {
980             sp_menu_append_new_templates(menu, view);
981             continue;
982         }
983         if (!strcmp(menu_pntr->name(), "recent-file-list")) {
984             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
986             // create recent files menu
987             int max_recent = prefs->getInt("/options/maxrecentdocuments/value");
988             GtkWidget *recent_menu = gtk_recent_chooser_menu_new_for_manager(gtk_recent_manager_get_default());
989             gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(recent_menu), max_recent);
990             // sort most recently used documents first to preserve previous behavior
991             gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(recent_menu), GTK_RECENT_SORT_MRU);
992             g_signal_connect(G_OBJECT(recent_menu), "item-activated", G_CALLBACK(sp_recent_open), (gpointer) NULL);
994             // add filter to only open files added by Inkscape
995             GtkRecentFilter *inkscape_only_filter = gtk_recent_filter_new();
996             gtk_recent_filter_add_application(inkscape_only_filter, g_get_prgname());
997             gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_menu), inkscape_only_filter);
999             gtk_recent_chooser_set_show_tips (GTK_RECENT_CHOOSER(recent_menu), TRUE);
1000             gtk_recent_chooser_set_show_not_found (GTK_RECENT_CHOOSER(recent_menu), FALSE);
1002             GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic(_("Open _Recent"));
1003             gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), recent_menu);
1005             gtk_menu_append(GTK_MENU(menu), GTK_WIDGET(recent_item));
1006             // this will just sit and update the list's item count
1007             static MaxRecentObserver *mro = new MaxRecentObserver(recent_menu);
1008             prefs->addObserver(*mro);
1009             continue;
1010         }
1011         if (!strcmp(menu_pntr->name(), "objects-checkboxes")) {
1012             sp_ui_checkboxes_menus(GTK_MENU(menu), view);
1013             continue;
1014         }
1015     }
1018 /** \brief  Build the main tool bar
1019     \param  view  View to build the bar for
1021     Currently the main tool bar is built as a dynamic XML menu using
1022     \c sp_ui_build_dyn_menus.  This function builds the bar, and then
1023     pass it to get items attached to it.
1024 */
1025 GtkWidget *
1026 sp_ui_main_menubar(Inkscape::UI::View::View *view)
1028     GtkWidget *mbar = gtk_menu_bar_new();
1030 #ifdef GDK_WINDOWING_QUARTZ
1031     ige_mac_menu_set_menu_bar(GTK_MENU_SHELL(mbar));
1032 #endif
1034     sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view);
1036 #ifdef GDK_WINDOWING_QUARTZ
1037     return NULL;
1038 #else
1039     return mbar;
1040 #endif
1043 static void leave_group(GtkMenuItem *, SPDesktop *desktop) {
1044     desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer()));
1047 static void enter_group(GtkMenuItem *mi, SPDesktop *desktop) {
1048     desktop->setCurrentLayer(reinterpret_cast<SPObject *>(g_object_get_data(G_OBJECT(mi), "group")));
1049     sp_desktop_selection(desktop)->clear();
1052 GtkWidget *
1053 sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item)
1055     GtkWidget *m;
1056     SPDesktop *dt;
1058     dt = static_cast<SPDesktop*>(view);
1060     m = gtk_menu_new();
1062     /* Undo and Redo */
1063     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_UNDO), view);
1064     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_REDO), view);
1066     /* Separator */
1067     sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1069     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_CUT), view);
1070     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_COPY), view);
1071     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_PASTE), view);
1073     /* Separator */
1074     sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1076     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DUPLICATE), view);
1077     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DELETE), view);
1079     /* Item menu */
1080     if (item) {
1081         sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1082         sp_object_menu((SPObject *) item, dt, GTK_MENU(m));
1083     }
1085     /* layer menu */
1086     SPGroup *group=NULL;
1087     if (item) {
1088         if (SP_IS_GROUP(item)) {
1089             group = SP_GROUP(item);
1090         } else if ( item != dt->currentRoot() && SP_IS_GROUP(SP_OBJECT_PARENT(item)) ) {
1091             group = SP_GROUP(SP_OBJECT_PARENT(item));
1092         }
1093     }
1095     if (( group && group != dt->currentLayer() ) ||
1096         ( dt->currentLayer() != dt->currentRoot() && SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) ) {
1097         /* Separator */
1098         sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1099     }
1101     if ( group && group != dt->currentLayer() ) {
1102         /* TRANSLATORS: #%s is the id of the group e.g. <g id="#g7">, not a number. */
1103         gchar *label=g_strdup_printf(_("Enter group #%s"), group->getId());
1104         GtkWidget *w = gtk_menu_item_new_with_label(label);
1105         g_free(label);
1106         g_object_set_data(G_OBJECT(w), "group", group);
1107         g_signal_connect(G_OBJECT(w), "activate", GCallback(enter_group), dt);
1108         gtk_widget_show(w);
1109         gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1110     }
1112     if ( dt->currentLayer() != dt->currentRoot() ) {
1113         if ( SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) {
1114             GtkWidget *w = gtk_menu_item_new_with_label(_("Go to parent"));
1115             g_signal_connect(G_OBJECT(w), "activate", GCallback(leave_group), dt);
1116             gtk_widget_show(w);
1117             gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1119         }
1120     }
1122     return m;
1125 /* Drag and Drop */
1126 void
1127 sp_ui_drag_data_received(GtkWidget *widget,
1128                          GdkDragContext *drag_context,
1129                          gint x, gint y,
1130                          GtkSelectionData *data,
1131                          guint info,
1132                          guint /*event_time*/,
1133                          gpointer /*user_data*/)
1135     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1136     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1138     switch (info) {
1139 #if ENABLE_MAGIC_COLORS
1140         case APP_X_INKY_COLOR:
1141         {
1142             int destX = 0;
1143             int destY = 0;
1144             gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1145             Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1147             SPItem *item = desktop->item_at_point( where, true );
1148             if ( item )
1149             {
1150                 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1152                 if ( data->length >= 8 ) {
1153                     cmsHPROFILE srgbProf = cmsCreate_sRGBProfile();
1155                     gchar c[64] = {0};
1156                     // Careful about endian issues.
1157                     guint16* dataVals = (guint16*)data->data;
1158                     sp_svg_write_color( c, sizeof(c),
1159                                         SP_RGBA32_U_COMPOSE(
1160                                             0x0ff & (dataVals[0] >> 8),
1161                                             0x0ff & (dataVals[1] >> 8),
1162                                             0x0ff & (dataVals[2] >> 8),
1163                                             0xff // can't have transparency in the color itself
1164                                             //0x0ff & (data->data[3] >> 8),
1165                                             ));
1166                     SPCSSAttr *css = sp_repr_css_attr_new();
1167                     bool updatePerformed = false;
1169                     if ( data->length > 14 ) {
1170                         int flags = dataVals[4];
1172                         // piggie-backed palette entry info
1173                         int index = dataVals[5];
1174                         Glib::ustring palName;
1175                         for ( int i = 0; i < dataVals[6]; i++ ) {
1176                             palName += (gunichar)dataVals[7+i];
1177                         }
1179                         // Now hook in a magic tag of some sort.
1180                         if ( !palName.empty() && (flags & 1) ) {
1181                             gchar* str = g_strdup_printf("%d|", index);
1182                             palName.insert( 0, str );
1183                             g_free(str);
1184                             str = 0;
1186                             sp_object_setAttribute( SP_OBJECT(item),
1187                                                     fillnotstroke ? "inkscape:x-fill-tag":"inkscape:x-stroke-tag",
1188                                                     palName.c_str(),
1189                                                     false );
1190                             item->updateRepr();
1192                             sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1193                             updatePerformed = true;
1194                         }
1195                     }
1197                     if ( !updatePerformed ) {
1198                         sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1199                     }
1201                     sp_desktop_apply_css_recursive( item, css, true );
1202                     item->updateRepr();
1204                     sp_document_done( doc , SP_VERB_NONE,
1205                                       _("Drop color"));
1207                     if ( srgbProf ) {
1208                         cmsCloseProfile( srgbProf );
1209                     }
1210                 }
1211             }
1212         }
1213         break;
1214 #endif // ENABLE_MAGIC_COLORS
1216         case APP_X_COLOR:
1217         {
1218             int destX = 0;
1219             int destY = 0;
1220             gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1221             Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1222             Geom::Point const button_dt(desktop->w2d(where));
1223             Geom::Point const button_doc(desktop->dt2doc(button_dt));
1225             if ( data->length == 8 ) {
1226                 gchar colorspec[64] = {0};
1227                 // Careful about endian issues.
1228                 guint16* dataVals = (guint16*)data->data;
1229                 sp_svg_write_color( colorspec, sizeof(colorspec),
1230                                     SP_RGBA32_U_COMPOSE(
1231                                         0x0ff & (dataVals[0] >> 8),
1232                                         0x0ff & (dataVals[1] >> 8),
1233                                         0x0ff & (dataVals[2] >> 8),
1234                                         0xff // can't have transparency in the color itself
1235                                         //0x0ff & (data->data[3] >> 8),
1236                                         ));
1238                 SPItem *item = desktop->item_at_point( where, true );
1240                 bool consumed = false;
1241                 if (desktop->event_context && desktop->event_context->get_drag()) {
1242                     consumed = desktop->event_context->get_drag()->dropColor(item, colorspec, button_dt);
1243                     if (consumed) {
1244                         sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1245                         desktop->event_context->get_drag()->updateDraggers();
1246                     }
1247                 }
1249                 //if (!consumed && tools_active(desktop, TOOLS_TEXT)) {
1250                 //    consumed = sp_text_context_drop_color(c, button_doc);
1251                 //    if (consumed) {
1252                 //        sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient stop"));
1253                 //    }
1254                 //}
1256                 if (!consumed && item) {
1257                     bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1258                     if (fillnotstroke &&
1259                         (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1260                         Path *livarot_path = Path_for_item(item, true, true);
1261                         livarot_path->ConvertWithBackData(0.04);
1263                         boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1264                         if (position) {
1265                             Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1266                             Geom::Point delta = nearest - button_doc;
1267                             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1268                             delta = desktop->d2w(delta);
1269                             double stroke_tolerance =
1270                                 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1271                                   desktop->current_zoom() *
1272                                   SP_OBJECT_STYLE (item)->stroke_width.computed *
1273                                   to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1274                                   : 0.0)
1275                                 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1277                             if (Geom::L2 (delta) < stroke_tolerance) {
1278                                 fillnotstroke = false;
1279                             }
1280                         }
1281                         delete livarot_path;
1282                     }
1284                     SPCSSAttr *css = sp_repr_css_attr_new();
1285                     sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec );
1287                     sp_desktop_apply_css_recursive( item, css, true );
1288                     item->updateRepr();
1290                     sp_document_done( doc , SP_VERB_NONE,
1291                                       _("Drop color"));
1292                 }
1293             }
1294         }
1295         break;
1297         case APP_OSWB_COLOR:
1298         {
1299             bool worked = false;
1300             Glib::ustring colorspec;
1301             if ( data->format == 8 ) {
1302                 ege::PaintDef color;
1303                 worked = color.fromMIMEData("application/x-oswb-color",
1304                                             reinterpret_cast<char*>(data->data),
1305                                             data->length,
1306                                             data->format);
1307                 if ( worked ) {
1308                     if ( color.getType() == ege::PaintDef::CLEAR ) {
1309                         colorspec = ""; // TODO check if this is sufficient
1310                     } else if ( color.getType() == ege::PaintDef::NONE ) {
1311                         colorspec = "none";
1312                     } else {
1313                         unsigned int r = color.getR();
1314                         unsigned int g = color.getG();
1315                         unsigned int b = color.getB();
1317                         SPGradient* matches = 0;
1318                         const GSList *gradients = sp_document_get_resource_list(doc, "gradient");
1319                         for (const GSList *item = gradients; item; item = item->next) {
1320                             SPGradient* grad = SP_GRADIENT(item->data);
1321                             if ( color.descr == grad->getId() ) {
1322                                 if ( grad->has_stops ) {
1323                                     matches = grad;
1324                                     break;
1325                                 }
1326                             }
1327                         }
1328                         if (matches) {
1329                             colorspec = "url(#";
1330                             colorspec += matches->getId();
1331                             colorspec += ")";
1332                         } else {
1333                             gchar* tmp = g_strdup_printf("#%02x%02x%02x", r, g, b);
1334                             colorspec = tmp;
1335                             g_free(tmp);
1336                         }
1337                     }
1338                 }
1339             }
1340             if ( worked ) {
1341                 int destX = 0;
1342                 int destY = 0;
1343                 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1344                 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1345                 Geom::Point const button_dt(desktop->w2d(where));
1346                 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1348                 SPItem *item = desktop->item_at_point( where, true );
1350                 bool consumed = false;
1351                 if (desktop->event_context && desktop->event_context->get_drag()) {
1352                     consumed = desktop->event_context->get_drag()->dropColor(item, colorspec.c_str(), button_dt);
1353                     if (consumed) {
1354                         sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1355                         desktop->event_context->get_drag()->updateDraggers();
1356                     }
1357                 }
1359                 if (!consumed && item) {
1360                     bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1361                     if (fillnotstroke &&
1362                         (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1363                         Path *livarot_path = Path_for_item(item, true, true);
1364                         livarot_path->ConvertWithBackData(0.04);
1366                         boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1367                         if (position) {
1368                             Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1369                             Geom::Point delta = nearest - button_doc;
1370                             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1371                             delta = desktop->d2w(delta);
1372                             double stroke_tolerance =
1373                                 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1374                                   desktop->current_zoom() *
1375                                   SP_OBJECT_STYLE (item)->stroke_width.computed *
1376                                   to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1377                                   : 0.0)
1378                                 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1380                             if (Geom::L2 (delta) < stroke_tolerance) {
1381                                 fillnotstroke = false;
1382                             }
1383                         }
1384                         delete livarot_path;
1385                     }
1387                     SPCSSAttr *css = sp_repr_css_attr_new();
1388                     sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec.c_str() );
1390                     sp_desktop_apply_css_recursive( item, css, true );
1391                     item->updateRepr();
1393                     sp_document_done( doc , SP_VERB_NONE,
1394                                       _("Drop color"));
1395                 }
1396             }
1397         }
1398         break;
1400         case SVG_DATA:
1401         case SVG_XML_DATA: {
1402             gchar *svgdata = (gchar *)data->data;
1404             Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI);
1406             if (rnewdoc == NULL) {
1407                 sp_ui_error_dialog(_("Could not parse SVG data"));
1408                 return;
1409             }
1411             Inkscape::XML::Node *repr = rnewdoc->root();
1412             gchar const *style = repr->attribute("style");
1414             Inkscape::XML::Node *newgroup = rnewdoc->createElement("svg:g");
1415             newgroup->setAttribute("style", style);
1417             Inkscape::XML::Document * xml_doc =  sp_document_repr_doc(doc);
1418             for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) {
1419                 Inkscape::XML::Node *newchild = child->duplicate(xml_doc);
1420                 newgroup->appendChild(newchild);
1421             }
1423             Inkscape::GC::release(rnewdoc);
1425             // Add it to the current layer
1427             // Greg's edits to add intelligent positioning of svg drops
1428             SPObject *new_obj = NULL;
1429             new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
1431             Inkscape::Selection *selection = sp_desktop_selection(desktop);
1432             selection->set(SP_ITEM(new_obj));
1434             // move to mouse pointer
1435             {
1436                 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
1437                 Geom::OptRect sel_bbox = selection->bounds();
1438                 if (sel_bbox) {
1439                     Geom::Point m( desktop->point() - sel_bbox->midpoint() );
1440                     sp_selection_move_relative(selection, m, false);
1441                 }
1442             }
1444             Inkscape::GC::release(newgroup);
1445             sp_document_done(doc, SP_VERB_NONE,
1446                              _("Drop SVG"));
1447             break;
1448         }
1450         case URI_LIST: {
1451             gchar *uri = (gchar *)data->data;
1452             sp_ui_import_files(uri);
1453             break;
1454         }
1456         case PNG_DATA:
1457         case JPEG_DATA:
1458         case IMAGE_DATA: {
1459             const char *mime = (info == JPEG_DATA ? "image/jpeg" : "image/png");
1461             Inkscape::Extension::DB::InputList o;
1462             Inkscape::Extension::db.get_input_list(o);
1463             Inkscape::Extension::DB::InputList::const_iterator i = o.begin();
1464             while (i != o.end() && strcmp( (*i)->get_mimetype(), mime ) != 0) {
1465                 ++i;
1466             }
1467             Inkscape::Extension::Extension *ext = *i;
1468             bool save = ext->get_param_bool("link");
1469             ext->set_param_bool("link", false);
1470             ext->set_gui(false);
1472             gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-dnd-import", NULL );
1473             g_file_set_contents(filename, reinterpret_cast<gchar const *>(data->data), data->length, NULL);
1474             file_import(doc, filename, ext);
1475             g_free(filename);
1477             ext->set_param_bool("link", save);
1478             ext->set_gui(true);
1479             sp_document_done( doc , SP_VERB_NONE,
1480                               _("Drop bitmap image"));
1481             break;
1482         }
1483     }
1486 #include "gradient-context.h"
1488 void sp_ui_drag_motion( GtkWidget */*widget*/,
1489                         GdkDragContext */*drag_context*/,
1490                         gint /*x*/, gint /*y*/,
1491                         GtkSelectionData */*data*/,
1492                         guint /*info*/,
1493                         guint /*event_time*/,
1494                         gpointer /*user_data*/)
1496 //     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1497 //     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1500 //     g_message("drag-n-drop motion (%4d, %4d)  at %d", x, y, event_time);
1503 static void sp_ui_drag_leave( GtkWidget */*widget*/,
1504                               GdkDragContext */*drag_context*/,
1505                               guint /*event_time*/,
1506                               gpointer /*user_data*/ )
1508 //     g_message("drag-n-drop leave                at %d", event_time);
1511 static void
1512 sp_ui_import_files(gchar *buffer)
1514     GList *list = gnome_uri_list_extract_filenames(buffer);
1515     if (!list)
1516         return;
1517     g_list_foreach(list, sp_ui_import_one_file_with_check, NULL);
1518     g_list_foreach(list, (GFunc) g_free, NULL);
1519     g_list_free(list);
1522 static void
1523 sp_ui_import_one_file_with_check(gpointer filename, gpointer /*unused*/)
1525     if (filename) {
1526         if (strlen((char const *)filename) > 2)
1527             sp_ui_import_one_file((char const *)filename);
1528     }
1531 static void
1532 sp_ui_import_one_file(char const *filename)
1534     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1535     if (!doc) return;
1537     if (filename == NULL) return;
1539     // Pass off to common implementation
1540     // TODO might need to get the proper type of Inkscape::Extension::Extension
1541     file_import( doc, filename, NULL );
1544 void
1545 sp_ui_error_dialog(gchar const *message)
1547     GtkWidget *dlg;
1548     gchar *safeMsg = Inkscape::IO::sanitizeString(message);
1550     dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
1551                                  GTK_BUTTONS_CLOSE, "%s", safeMsg);
1552     sp_transientize(dlg);
1553     gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE);
1554     gtk_dialog_run(GTK_DIALOG(dlg));
1555     gtk_widget_destroy(dlg);
1556     g_free(safeMsg);
1559 bool
1560 sp_ui_overwrite_file(gchar const *filename)
1562     bool return_value = FALSE;
1564     if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) {
1565         Gtk::Window *window = SP_ACTIVE_DESKTOP->getToplevel();
1566         gchar* baseName = g_path_get_basename( filename );
1567         gchar* dirName = g_path_get_dirname( filename );
1568         GtkWidget* dialog = gtk_message_dialog_new_with_markup( window->gobj(),
1569                                                                 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
1570                                                                 GTK_MESSAGE_QUESTION,
1571                                                                 GTK_BUTTONS_NONE,
1572                                                                 _( "<span weight=\"bold\" size=\"larger\">A file named \"%s\" already exists. Do you want to replace it?</span>\n\n"
1573                                                                    "The file already exists in \"%s\". Replacing it will overwrite its contents." ),
1574                                                                 baseName,
1575                                                                 dirName
1576             );
1577         gtk_dialog_add_buttons( GTK_DIALOG(dialog),
1578                                 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1579                                 _("Replace"), GTK_RESPONSE_YES,
1580                                 NULL );
1581         gtk_dialog_set_default_response( GTK_DIALOG(dialog), GTK_RESPONSE_YES );
1583         if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_YES ) {
1584             return_value = TRUE;
1585         } else {
1586             return_value = FALSE;
1587         }
1588         gtk_widget_destroy(dialog);
1589         g_free( baseName );
1590         g_free( dirName );
1591     } else {
1592         return_value = TRUE;
1593     }
1595     return return_value;
1598 static void
1599 sp_ui_menu_item_set_sensitive(SPAction */*action*/, unsigned int sensitive, void *data)
1601     return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
1604 static void
1605 sp_ui_menu_item_set_name(SPAction */*action*/, Glib::ustring name, void *data)
1607     void *child = GTK_BIN (data)->child;
1608     //child is either
1609     //- a GtkHBox, whose first child is a label displaying name if the menu
1610     //item has an accel key
1611     //- a GtkLabel if the menu has no accel key
1612     if (GTK_IS_LABEL(child)) {
1613         gtk_label_set_markup_with_mnemonic(GTK_LABEL (child), name.c_str());
1614     } else if (GTK_IS_HBOX(child)) {
1615         gtk_label_set_markup_with_mnemonic(
1616         GTK_LABEL (gtk_container_get_children(GTK_CONTAINER (child))->data),
1617         name.c_str());
1618     }//else sp_ui_menu_append_item_from_verb has been modified and can set
1619     //a menu item in yet another way...
1623 /*
1624   Local Variables:
1625   mode:c++
1626   c-file-style:"stroustrup"
1627   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1628   indent-tabs-mode:nil
1629   fill-column:99
1630   End:
1631 */
1632 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :