Code

recent files: show tooltips with uri, hide local files which are missing or unaccessible
[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/effect.h"
27 #include "widgets/icon.h"
28 #include "preferences.h"
29 #include "path-prefix.h"
30 #include "shortcuts.h"
31 #include "document.h"
32 #include "desktop-handles.h"
33 #include "file.h"
34 #include "interface.h"
35 #include "desktop.h"
36 #include "ui/context-menu.h"
37 #include "selection.h"
38 #include "selection-chemistry.h"
39 #include "svg-view-widget.h"
40 #include "widgets/desktop-widget.h"
41 #include "sp-item-group.h"
42 #include "sp-text.h"
43 #include "sp-gradient-fns.h"
44 #include "sp-gradient.h"
45 #include "sp-flowtext.h"
46 #include "sp-namedview.h"
47 #include "ui/view/view.h"
48 #include "helper/action.h"
49 #include "helper/gnome-utils.h"
50 #include "helper/window.h"
51 #include "io/sys.h"
52 #include "dialogs/dialog-events.h"
53 #include "message-context.h"
55 // Added for color drag-n-drop
56 #if ENABLE_LCMS
57 #include "lcms.h"
58 #endif // ENABLE_LCMS
59 #include "display/sp-canvas.h"
60 #include "color.h"
61 #include "svg/svg-color.h"
62 #include "desktop-style.h"
63 #include "style.h"
64 #include "event-context.h"
65 #include "gradient-drag.h"
66 #include "widgets/ege-paint-def.h"
68 // Include Mac OS X menu synchronization on native OSX build
69 #ifdef GDK_WINDOWING_QUARTZ
70 #include "ige-mac-menu.h"
71 #endif
73 /* Drag and Drop */
74 typedef enum {
75     URI_LIST,
76     SVG_XML_DATA,
77     SVG_DATA,
78     PNG_DATA,
79     JPEG_DATA,
80     IMAGE_DATA,
81     APP_X_INKY_COLOR,
82     APP_X_COLOR,
83     APP_OSWB_COLOR,
84 } ui_drop_target_info;
86 static GtkTargetEntry ui_drop_target_entries [] = {
87     {(gchar *)"text/uri-list",                0, URI_LIST        },
88     {(gchar *)"image/svg+xml",                0, SVG_XML_DATA    },
89     {(gchar *)"image/svg",                    0, SVG_DATA        },
90     {(gchar *)"image/png",                    0, PNG_DATA        },
91     {(gchar *)"image/jpeg",                   0, JPEG_DATA       },
92 #if ENABLE_MAGIC_COLORS
93     {(gchar *)"application/x-inkscape-color", 0, APP_X_INKY_COLOR},
94 #endif // ENABLE_MAGIC_COLORS
95     {(gchar *)"application/x-oswb-color",     0, APP_OSWB_COLOR  },
96     {(gchar *)"application/x-color",          0, APP_X_COLOR     }
97 };
99 static GtkTargetEntry *completeDropTargets = 0;
100 static int completeDropTargetsCount = 0;
101 static bool temporarily_block_actions = false;
103 #define ENTRIES_SIZE(n) sizeof(n)/sizeof(n[0])
104 static guint nui_drop_target_entries = ENTRIES_SIZE(ui_drop_target_entries);
105 static void sp_ui_import_files(gchar *buffer);
106 static void sp_ui_import_one_file(char const *filename);
107 static void sp_ui_import_one_file_with_check(gpointer filename, gpointer unused);
108 static void sp_ui_drag_data_received(GtkWidget *widget,
109                                      GdkDragContext *drag_context,
110                                      gint x, gint y,
111                                      GtkSelectionData *data,
112                                      guint info,
113                                      guint event_time,
114                                      gpointer user_data);
115 static void sp_ui_drag_motion( GtkWidget *widget,
116                                GdkDragContext *drag_context,
117                                gint x, gint y,
118                                GtkSelectionData *data,
119                                guint info,
120                                guint event_time,
121                                gpointer user_data );
122 static void sp_ui_drag_leave( GtkWidget *widget,
123                               GdkDragContext *drag_context,
124                               guint event_time,
125                               gpointer user_data );
126 static void sp_ui_menu_item_set_sensitive(SPAction *action,
127                                           unsigned int sensitive,
128                                           void *data);
129 static void sp_ui_menu_item_set_name(SPAction *action,
130                                      Glib::ustring name,
131                                      void *data);
132 static void sp_recent_open(GtkRecentChooser *, gpointer);
134 SPActionEventVector menu_item_event_vector = {
135     {NULL},
136     NULL,
137     NULL, /* set_active */
138     sp_ui_menu_item_set_sensitive, /* set_sensitive */
139     NULL, /* set_shortcut */
140     sp_ui_menu_item_set_name /* set_name */
141 };
143 static const int MIN_ONSCREEN_DISTANCE = 50;
145 void
146 sp_create_window(SPViewWidget *vw, gboolean editable)
148     g_return_if_fail(vw != NULL);
149     g_return_if_fail(SP_IS_VIEW_WIDGET(vw));
151     Gtk::Window *win = Inkscape::UI::window_new("", TRUE);
153     gtk_container_add(GTK_CONTAINER(win->gobj()), GTK_WIDGET(vw));
154     gtk_widget_show(GTK_WIDGET(vw));
156     if (editable) {
157         g_object_set_data(G_OBJECT(vw), "window", win);
159         SPDesktopWidget *desktop_widget = reinterpret_cast<SPDesktopWidget*>(vw);
160         SPDesktop* desktop = desktop_widget->desktop;
162         desktop_widget->window = win;
164         win->set_data("desktop", desktop);
165         win->set_data("desktopwidget", desktop_widget);
167         win->signal_delete_event().connect(sigc::mem_fun(*(SPDesktop*)vw->view, &SPDesktop::onDeleteUI));
168         win->signal_window_state_event().connect(sigc::mem_fun(*desktop, &SPDesktop::onWindowStateEvent));
169         win->signal_focus_in_event().connect(sigc::mem_fun(*desktop_widget, &SPDesktopWidget::onFocusInEvent));
171         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
172         gint prefs_geometry =
173             (2==prefs->getInt("/options/savewindowgeometry/value", 0));
174         if (prefs_geometry) {
175             gint pw = prefs->getInt("/desktop/geometry/width", -1);
176             gint ph = prefs->getInt("/desktop/geometry/height", -1);
177             gint px = prefs->getInt("/desktop/geometry/x", -1);
178             gint py = prefs->getInt("/desktop/geometry/y", -1);
179             gint full = prefs->getBool("/desktop/geometry/fullscreen");
180             gint maxed = prefs->getBool("/desktop/geometry/maximized");
181             if (pw>0 && ph>0) {
182                 gint w = MIN(gdk_screen_width(), pw);
183                 gint h = MIN(gdk_screen_height(), ph);
184                 gint x = MIN(gdk_screen_width() - MIN_ONSCREEN_DISTANCE, px);
185                 gint y = MIN(gdk_screen_height() - MIN_ONSCREEN_DISTANCE, py);
186                 if (w>0 && h>0) {
187                     x = MIN(gdk_screen_width() - w, x);
188                     y = MIN(gdk_screen_height() - h, y);
189                     desktop->setWindowSize(w, h);
190                 }
192                 // Only restore xy for the first window so subsequent windows don't overlap exactly
193                 // with first.  (Maybe rule should be only restore xy if it's different from xy of
194                 // other desktops?)
196                 // Empirically it seems that active_desktop==this desktop only the first time a
197                 // desktop is created.
198                 SPDesktop *active_desktop = SP_ACTIVE_DESKTOP;
199                 if (active_desktop == desktop || active_desktop==NULL) {
200                     desktop->setWindowPosition(Geom::Point(x, y));
201                 }
202             }
203             if (maxed) {
204                 win->maximize();
205             }
206             if (full) {
207                 win->fullscreen();
208             }
209         }
211     } else {
212         gtk_window_set_policy(GTK_WINDOW(win->gobj()), TRUE, TRUE, TRUE);
213     }
215     if ( completeDropTargets == 0 || completeDropTargetsCount == 0 )
216     {
217         std::vector<gchar*> types;
219         GSList *list = gdk_pixbuf_get_formats();
220         while ( list ) {
221             int i = 0;
222             GdkPixbufFormat *one = (GdkPixbufFormat*)list->data;
223             gchar** typesXX = gdk_pixbuf_format_get_mime_types(one);
224             for ( i = 0; typesXX[i]; i++ ) {
225                 types.push_back(g_strdup(typesXX[i]));
226             }
227             g_strfreev(typesXX);
229             list = g_slist_next(list);
230         }
231         completeDropTargetsCount = nui_drop_target_entries + types.size();
232         completeDropTargets = new GtkTargetEntry[completeDropTargetsCount];
233         for ( int i = 0; i < (int)nui_drop_target_entries; i++ ) {
234             completeDropTargets[i] = ui_drop_target_entries[i];
235         }
236         int pos = nui_drop_target_entries;
238         for (std::vector<gchar*>::iterator it = types.begin() ; it != types.end() ; it++) {
239             completeDropTargets[pos].target = *it;
240             completeDropTargets[pos].flags = 0;
241             completeDropTargets[pos].info = IMAGE_DATA;
242             pos++;
243         }
244     }
246     gtk_drag_dest_set((GtkWidget*)win->gobj(),
247                       GTK_DEST_DEFAULT_ALL,
248                       completeDropTargets,
249                       completeDropTargetsCount,
250                       GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE));
253     g_signal_connect(G_OBJECT(win->gobj()),
254                      "drag_data_received",
255                      G_CALLBACK(sp_ui_drag_data_received),
256                      NULL);
257     g_signal_connect(G_OBJECT(win->gobj()),
258                      "drag_motion",
259                      G_CALLBACK(sp_ui_drag_motion),
260                      NULL);
261     g_signal_connect(G_OBJECT(win->gobj()),
262                      "drag_leave",
263                      G_CALLBACK(sp_ui_drag_leave),
264                      NULL);
265     win->show();
267     // needed because the first ACTIVATE_DESKTOP was sent when there was no window yet
268     inkscape_reactivate_desktop(SP_DESKTOP_WIDGET(vw)->desktop);
271 void
272 sp_ui_new_view()
274     SPDocument *document;
275     SPViewWidget *dtw;
277     document = SP_ACTIVE_DOCUMENT;
278     if (!document) return;
280     dtw = sp_desktop_widget_new(sp_document_namedview(document, NULL));
281     g_return_if_fail(dtw != NULL);
283     sp_create_window(dtw, TRUE);
284     sp_namedview_window_from_document(static_cast<SPDesktop*>(dtw->view));
285     sp_namedview_update_layers_from_document(static_cast<SPDesktop*>(dtw->view));
288 /* TODO: not yet working */
289 /* To be re-enabled (by adding to menu) once it works. */
290 void
291 sp_ui_new_view_preview()
293     SPDocument *document;
294     SPViewWidget *dtw;
296     document = SP_ACTIVE_DOCUMENT;
297     if (!document) return;
299     dtw = (SPViewWidget *) sp_svg_view_widget_new(document);
300     g_return_if_fail(dtw != NULL);
301     sp_svg_view_widget_set_resize(SP_SVG_VIEW_WIDGET(dtw), TRUE, 400.0, 400.0);
303     sp_create_window(dtw, FALSE);
306 /**
307  * \param widget unused
308  */
309 void
310 sp_ui_close_view(GtkWidget */*widget*/)
312         SPDesktop *dt = SP_ACTIVE_DESKTOP;
314         if (dt == NULL) {
315         return;
316     }
318     if (dt->shutdown()) {
319         return; // Shutdown operation has been canceled, so do nothing
320     }
322     // Shutdown can proceed; use the stored reference to the desktop here instead of the current SP_ACTIVE_DESKTOP,
323     // because the user might have changed the focus in the meantime (see bug #381357 on Launchpad)
324     dt->destroyWidget();
328 /**
329  *  sp_ui_close_all
330  *
331  *  This function is called to exit the program, and iterates through all
332  *  open document view windows, attempting to close each in turn.  If the
333  *  view has unsaved information, the user will be prompted to save,
334  *  discard, or cancel.
335  *
336  *  Returns FALSE if the user cancels the close_all operation, TRUE
337  *  otherwise.
338  */
339 unsigned int
340 sp_ui_close_all(void)
342     /* Iterate through all the windows, destroying each in the order they
343        become active */
344     while (SP_ACTIVE_DESKTOP) {
345         SPDesktop *dt = SP_ACTIVE_DESKTOP;
346         if (dt->shutdown()) {
347             /* The user canceled the operation, so end doing the close */
348             return FALSE;
349         }
350         // Shutdown can proceed; use the stored reference to the desktop here instead of the current SP_ACTIVE_DESKTOP,
351         // because the user might have changed the focus in the meantime (see bug #381357 on Launchpad)
352         dt->destroyWidget();
353     }
355     return TRUE;
358 /*
359  * Some day when the right-click menus are ready to start working
360  * smarter with the verbs, we'll need to change this NULL being
361  * sent to sp_action_perform to something useful, or set some kind
362  * of global "right-clicked position" variable for actions to
363  * investigate when they're called.
364  */
365 static void
366 sp_ui_menu_activate(void */*object*/, SPAction *action)
368     if (!temporarily_block_actions) {
369         sp_action_perform(action, NULL);
370     }
373 static void
374 sp_ui_menu_select_action(void */*object*/, SPAction *action)
376     action->view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, action->tip);
379 static void
380 sp_ui_menu_deselect_action(void */*object*/, SPAction *action)
382     action->view->tipsMessageContext()->clear();
385 static void
386 sp_ui_menu_select(gpointer object, gpointer tip)
388     Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view"));
389     view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, (gchar *)tip);
392 static void
393 sp_ui_menu_deselect(gpointer object)
395     Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*>  (g_object_get_data(G_OBJECT(object), "view"));
396     view->tipsMessageContext()->clear();
399 /**
400  * sp_ui_menuitem_add_icon
401  *
402  * Creates and attaches a scaled icon to the given menu item.
403  *
404  */
405 void
406 sp_ui_menuitem_add_icon( GtkWidget *item, gchar *icon_name )
408     GtkWidget *icon;
410     icon = sp_icon_new( Inkscape::ICON_SIZE_MENU, icon_name );
411     gtk_widget_show(icon);
412     gtk_image_menu_item_set_image((GtkImageMenuItem *) item, icon);
413 } // end of sp_ui_menu_add_icon
415 /**
416  * sp_ui_menu_append_item
417  *
418  * Appends a UI item with specific info for Inkscape/Sodipodi.
419  *
420  */
421 static GtkWidget *
422 sp_ui_menu_append_item( GtkMenu *menu, gchar const *stock,
423                         gchar const *label, gchar const *tip, Inkscape::UI::View::View *view, GCallback callback,
424                         gpointer data, gboolean with_mnemonic = TRUE )
426     GtkWidget *item;
428     if (stock) {
429         item = gtk_image_menu_item_new_from_stock(stock, NULL);
430     } else if (label) {
431         item = (with_mnemonic)
432             ? gtk_image_menu_item_new_with_mnemonic(label) :
433             gtk_image_menu_item_new_with_label(label);
434     } else {
435         item = gtk_separator_menu_item_new();
436     }
438     gtk_widget_show(item);
440     if (callback) {
441         g_signal_connect(G_OBJECT(item), "activate", callback, data);
442     }
444     if (tip && view) {
445         g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
446         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) tip );
447         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
448     }
450     gtk_menu_append(GTK_MENU(menu), item);
452     return item;
454 } // end of sp_ui_menu_append_item()
456 /**
457 \brief  a wrapper around gdk_keyval_name producing (when possible) characters, not names
458  */
459 static gchar const *
460 sp_key_name(guint keyval)
462     /* TODO: Compare with the definition of gtk_accel_label_refetch in gtk/gtkaccellabel.c (or
463        simply use GtkAccelLabel as the TODO comment in sp_ui_shortcut_string suggests). */
464     gchar const *n = gdk_keyval_name(gdk_keyval_to_upper(keyval));
466     if      (!strcmp(n, "asciicircum"))  return "^";
467     else if (!strcmp(n, "parenleft"  ))  return "(";
468     else if (!strcmp(n, "parenright" ))  return ")";
469     else if (!strcmp(n, "plus"       ))  return "+";
470     else if (!strcmp(n, "minus"      ))  return "-";
471     else if (!strcmp(n, "asterisk"   ))  return "*";
472     else if (!strcmp(n, "KP_Multiply"))  return "*";
473     else if (!strcmp(n, "Delete"     ))  return "Del";
474     else if (!strcmp(n, "Page_Up"    ))  return "PgUp";
475     else if (!strcmp(n, "Page_Down"  ))  return "PgDn";
476     else if (!strcmp(n, "grave"      ))  return "`";
477     else if (!strcmp(n, "numbersign" ))  return "#";
478     else if (!strcmp(n, "bar"        ))  return "|";
479     else if (!strcmp(n, "slash"      ))  return "/";
480     else if (!strcmp(n, "exclam"     ))  return "!";
481     else if (!strcmp(n, "percent"    ))  return "%";
482     else return n;
486 /**
487  * \param shortcut A GDK keyval OR'd with SP_SHORTCUT_blah_MASK values.
488  * \param c Points to a buffer at least 256 bytes long.
489  */
490 void
491 sp_ui_shortcut_string(unsigned const shortcut, gchar *const c)
493     /* TODO: This function shouldn't exist.  Our callers should use GtkAccelLabel instead of
494      * a generic GtkLabel containing this string, and should call gtk_widget_add_accelerator.
495      * Will probably need to change sp_shortcut_invoke callers.
496      *
497      * The existing gtk_label_new_with_mnemonic call can be replaced with
498      * g_object_new(GTK_TYPE_ACCEL_LABEL, NULL) followed by
499      * gtk_label_set_text_with_mnemonic(lbl, str).
500      */
501     static GtkAccelLabelClass const &accel_lbl_cls
502         = *(GtkAccelLabelClass const *) g_type_class_peek_static(GTK_TYPE_ACCEL_LABEL);
504     struct { unsigned test; char const *name; } const modifier_tbl[] = {
505         { SP_SHORTCUT_SHIFT_MASK,   accel_lbl_cls.mod_name_shift   },
506         { SP_SHORTCUT_CONTROL_MASK, accel_lbl_cls.mod_name_control },
507         { SP_SHORTCUT_ALT_MASK,     accel_lbl_cls.mod_name_alt     }
508     };
510     gchar *p = c;
511     gchar *end = p + 256;
513     for (unsigned i = 0; i < G_N_ELEMENTS(modifier_tbl); ++i) {
514         if ((shortcut & modifier_tbl[i].test)
515             && (p < end))
516         {
517             p += g_snprintf(p, end - p, "%s%s",
518                             modifier_tbl[i].name,
519                             accel_lbl_cls.mod_separator);
520         }
521     }
522     if (p < end) {
523         p += g_snprintf(p, end - p, "%s", sp_key_name(shortcut & 0xffffff));
524     }
525     end[-1] = '\0';  // snprintf doesn't guarantee to nul-terminate the string.
528 void
529 sp_ui_dialog_title_string(Inkscape::Verb *verb, gchar *c)
531     SPAction     *action;
532     unsigned int shortcut;
533     gchar        *s;
534     gchar        key[256];
535     gchar        *atitle;
537     action = verb->get_action(NULL);
538     if (!action)
539         return;
541     atitle = sp_action_get_title(action);
543     s = g_stpcpy(c, atitle);
545     g_free(atitle);
547     shortcut = sp_shortcut_get_primary(verb);
548     if (shortcut) {
549         s = g_stpcpy(s, " (");
550         sp_ui_shortcut_string(shortcut, key);
551         s = g_stpcpy(s, key);
552         s = g_stpcpy(s, ")");
553     }
557 /**
558  * sp_ui_menu_append_item_from_verb
559  *
560  * Appends a custom menu UI from a verb.
561  *
562  */
564 static GtkWidget *
565 sp_ui_menu_append_item_from_verb(GtkMenu *menu, Inkscape::Verb *verb, Inkscape::UI::View::View *view, bool radio = false, GSList *group = NULL)
567     SPAction *action;
568     GtkWidget *item;
570     if (verb->get_code() == SP_VERB_NONE) {
572         item = gtk_separator_menu_item_new();
574     } else {
575         unsigned int shortcut;
577         action = verb->get_action(view);
579         if (!action) return NULL;
581         shortcut = sp_shortcut_get_primary(verb);
582         if (shortcut) {
583             gchar c[256];
584             sp_ui_shortcut_string(shortcut, c);
585             GtkWidget *const hb = gtk_hbox_new(FALSE, 16);
586             GtkWidget *const name_lbl = gtk_label_new("");
587             gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
588             gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
589             gtk_box_pack_start((GtkBox *) hb, name_lbl, TRUE, TRUE, 0);
590             GtkWidget *const accel_lbl = gtk_label_new(c);
591             gtk_misc_set_alignment((GtkMisc *) accel_lbl, 1.0, 0.5);
592             gtk_box_pack_end((GtkBox *) hb, accel_lbl, FALSE, FALSE, 0);
593             gtk_widget_show_all(hb);
594             if (radio) {
595                 item = gtk_radio_menu_item_new (group);
596             } else {
597                 item = gtk_image_menu_item_new();
598             }
599             gtk_container_add((GtkContainer *) item, hb);
600         } else {
601             if (radio) {
602                 item = gtk_radio_menu_item_new (group);
603             } else {
604                 item = gtk_image_menu_item_new ();
605             }
606             GtkWidget *const name_lbl = gtk_label_new("");
607             gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
608             gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
609             gtk_container_add((GtkContainer *) item, name_lbl);
610         }
612         nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
613         if (!action->sensitive) {
614             gtk_widget_set_sensitive(item, FALSE);
615         }
617         if (action->image) {
618             sp_ui_menuitem_add_icon(item, action->image);
619         }
620         gtk_widget_set_events(item, GDK_KEY_PRESS_MASK);
621         g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
622         g_signal_connect( G_OBJECT(item), "activate", G_CALLBACK(sp_ui_menu_activate), action );
623         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select_action), action );
624         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect_action), action );
625     }
627     gtk_widget_show(item);
628     gtk_menu_append(GTK_MENU(menu), item);
630     return item;
632 } // end of sp_ui_menu_append_item_from_verb
635 static void
636 checkitem_toggled(GtkCheckMenuItem *menuitem, gpointer user_data)
638     gchar const *pref = (gchar const *) user_data;
639     Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
641     Glib::ustring pref_path;
642     if (reinterpret_cast<SPDesktop*>(view)->is_focusMode()) {
643         pref_path = "/focus/";
644     } else if (reinterpret_cast<SPDesktop*>(view)->is_fullscreen()) {
645         pref_path = "/fullscreen/";
646     } else {
647         pref_path = "/window/";
648     }
649     pref_path += pref;
650     pref_path += "/state";
652     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
653     gboolean checked = gtk_check_menu_item_get_active(menuitem);
654     prefs->setBool(pref_path, checked);
656     reinterpret_cast<SPDesktop*>(view)->layoutWidget();
659 static gboolean
660 checkitem_update(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_data)
662     GtkCheckMenuItem *menuitem=GTK_CHECK_MENU_ITEM(widget);
664     gchar const *pref = (gchar const *) user_data;
665     Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
667     Glib::ustring pref_path;
668     if ((static_cast<SPDesktop*>(view))->is_fullscreen()) {
669         pref_path = "/fullscreen/";
670     } else {
671         pref_path = "/window/";
672     }
673     pref_path += pref;
675     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
676     bool ison = prefs->getBool(pref_path + "/state", true);
678     g_signal_handlers_block_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
679     gtk_check_menu_item_set_active(menuitem, ison);
680     g_signal_handlers_unblock_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
682     return FALSE;
685 /**
686  *  \brief Callback function to update the status of the radio buttons in the View -> Display mode menu (Normal, No Filters, Outline)
687  */
689 static gboolean
690 update_view_menu(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_data)
692         SPAction *action = (SPAction *) user_data;
693         g_assert(action->id != NULL);
695         Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(widget), "view");
696     SPDesktop *dt = static_cast<SPDesktop*>(view);
697         Inkscape::RenderMode mode = dt->getMode();
699         bool new_state = false;
700         if (!strcmp(action->id, "ViewModeNormal")) {
701         new_state = mode == Inkscape::RENDERMODE_NORMAL;
702         } else if (!strcmp(action->id, "ViewModeNoFilters")) {
703         new_state = mode == Inkscape::RENDERMODE_NO_FILTERS;
704     } else if (!strcmp(action->id, "ViewModeOutline")) {
705         new_state = mode == Inkscape::RENDERMODE_OUTLINE;
706     } else {
707         g_warning("update_view_menu does not handle this verb");
708     }
710         if (new_state) { //only one of the radio buttons has to be activated; the others will automatically be deactivated
711                 if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) {
712                         // When the GtkMenuItem version of the "activate" signal has been emitted by a GtkRadioMenuItem, there is a second
713                         // emission as the most recently active item is toggled to inactive. This is dealt with before the original signal is handled.
714                         // This emission however should not invoke any actions, hence we block it here:
715                         temporarily_block_actions = true;
716                         gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (widget), TRUE);
717                         temporarily_block_actions = false;
718                 }
719         }
721         return FALSE;
724 void
725 sp_ui_menu_append_check_item_from_verb(GtkMenu *menu, Inkscape::UI::View::View *view, gchar const *label, gchar const *tip, gchar const *pref,
726                                        void (*callback_toggle)(GtkCheckMenuItem *, gpointer user_data),
727                                        gboolean (*callback_update)(GtkWidget *widget, GdkEventExpose *event, gpointer user_data),
728                                        Inkscape::Verb *verb)
730     GtkWidget *item;
732     unsigned int shortcut = 0;
733     SPAction *action = NULL;
735     if (verb) {
736         shortcut = sp_shortcut_get_primary(verb);
737         action = verb->get_action(view);
738     }
740     if (verb && shortcut) {
741         gchar c[256];
742         sp_ui_shortcut_string(shortcut, c);
744         GtkWidget *hb = gtk_hbox_new(FALSE, 16);
746         {
747             GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
748             gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
749             gtk_box_pack_start((GtkBox *) hb, l, TRUE, TRUE, 0);
750         }
752         {
753             GtkWidget *l = gtk_label_new(c);
754             gtk_misc_set_alignment((GtkMisc *) l, 1.0, 0.5);
755             gtk_box_pack_end((GtkBox *) hb, l, FALSE, FALSE, 0);
756         }
758         gtk_widget_show_all(hb);
760         item = gtk_check_menu_item_new();
761         gtk_container_add((GtkContainer *) item, hb);
762     } else {
763         GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
764         gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
765         item = gtk_check_menu_item_new();
766         gtk_container_add((GtkContainer *) item, l);
767     }
768 #if 0
769     nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
770     if (!action->sensitive) {
771         gtk_widget_set_sensitive(item, FALSE);
772     }
773 #endif
774     gtk_widget_show(item);
776     gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
778     g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
780     g_signal_connect( G_OBJECT(item), "toggled", (GCallback) callback_toggle, (void *) pref);
781     g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) callback_update, (void *) pref);
783     g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) (action ? action->tip : tip));
784     g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
787 static void
788 sp_recent_open(GtkRecentChooser *recent_menu, gpointer /*user_data*/)
790     // dealing with the bizarre filename convention in Inkscape for now
791     gchar *uri = gtk_recent_chooser_get_current_uri(GTK_RECENT_CHOOSER(recent_menu));
792     gchar *local_fn = g_filename_from_uri(uri, NULL, NULL);
793     gchar *utf8_fn = g_filename_to_utf8(local_fn, -1, NULL, NULL, NULL);
794     sp_file_open(utf8_fn, NULL);
795     g_free(utf8_fn);
796     g_free(local_fn);
797     g_free(uri);
800 static void
801 sp_file_new_from_template(GtkWidget */*widget*/, gchar const *uri)
803     sp_file_new(uri);
806 void
807 sp_menu_append_new_templates(GtkWidget *menu, Inkscape::UI::View::View *view)
809     std::list<gchar *> sources;
810     sources.push_back( profile_path("templates") ); // first try user's local dir
811     sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir
813     // Use this loop to iterate through a list of possible document locations.
814     while (!sources.empty()) {
815         gchar *dirname = sources.front();
817         if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) {
818             GError *err = 0;
819             GDir *dir = g_dir_open(dirname, 0, &err);
821             if (dir) {
822                 for (gchar const *file = g_dir_read_name(dir); file != NULL; file = g_dir_read_name(dir)) {
823                     if (!g_str_has_suffix(file, ".svg") && !g_str_has_suffix(file, ".svgz"))
824                         continue; // skip non-svg files
826                     gchar *basename = g_path_get_basename(file);
827                     if (g_str_has_suffix(basename, ".svg") && g_str_has_prefix(basename, "default."))
828                         continue; // skip default.*.svg (i.e. default.svg and translations) - it's in the menu already
830                     gchar const *filepath = g_build_filename(dirname, file, NULL);
831                     gchar *dupfile = g_strndup(file, strlen(file) - 4);
832                     gchar *filename =  g_filename_to_utf8(dupfile,  -1, NULL, NULL, NULL);
833                     g_free(dupfile);
834                     GtkWidget *item = gtk_menu_item_new_with_label(filename);
835                     g_free(filename);
837                     gtk_widget_show(item);
838                     // how does "filepath" ever get freed?
839                     g_signal_connect(G_OBJECT(item),
840                                      "activate",
841                                      G_CALLBACK(sp_file_new_from_template),
842                                      (gpointer) filepath);
844                     if (view) {
845                         // set null tip for now; later use a description from the template file
846                         g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
847                         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) NULL );
848                         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
849                     }
851                     gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
852                 }
853                 g_dir_close(dir);
854             }
855         }
857         // toss the dirname
858         g_free(dirname);
859         sources.pop_front();
860     }
863 void
864 sp_ui_checkboxes_menus(GtkMenu *m, Inkscape::UI::View::View *view)
866     //sp_ui_menu_append_check_item_from_verb(m, view, _("_Menu"), _("Show or hide the menu bar"), "menu",
867     //                                       checkitem_toggled, checkitem_update, 0);
868     sp_ui_menu_append_check_item_from_verb(m, view, _("Commands Bar"), _("Show or hide the Commands bar (under the menu)"), "commands",
869                                            checkitem_toggled, checkitem_update, 0);
870     sp_ui_menu_append_check_item_from_verb(m, view, _("Snap Controls Bar"), _("Show or hide the snapping controls"), "snaptoolbox",
871                                            checkitem_toggled, checkitem_update, 0);
872     sp_ui_menu_append_check_item_from_verb(m, view, _("Tool Controls Bar"), _("Show or hide the Tool Controls bar"), "toppanel",
873                                            checkitem_toggled, checkitem_update, 0);
874     sp_ui_menu_append_check_item_from_verb(m, view, _("_Toolbox"), _("Show or hide the main toolbox (on the left)"), "toolbox",
875                                            checkitem_toggled, checkitem_update, 0);
876     sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "rulers",
877                                            checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_RULERS));
878     sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "scrollbars",
879                                            checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_SCROLLBARS));
880     sp_ui_menu_append_check_item_from_verb(m, view, _("_Palette"), _("Show or hide the color palette"), "panels",
881                                            checkitem_toggled, checkitem_update, 0);
882     sp_ui_menu_append_check_item_from_verb(m, view, _("_Statusbar"), _("Show or hide the statusbar (at the bottom of the window)"), "statusbar",
883                                            checkitem_toggled, checkitem_update, 0);
886 /** @brief Observer that updates the recent list's max document count */
887 class MaxRecentObserver : public Inkscape::Preferences::Observer {
888 public:
889     MaxRecentObserver(GtkWidget *recent_menu) :
890         Observer("/options/maxrecentdocuments/value"),
891         _rm(recent_menu)
892     {}
893     virtual void notify(Inkscape::Preferences::Entry const &e) {
894         gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(_rm), e.getInt());
895         // hack: the recent menu doesn't repopulate after changing the limit, so we force it
896         g_signal_emit_by_name((gpointer) gtk_recent_manager_get_default(), "changed");
897     }
898 private:
899     GtkWidget *_rm;
900 };
902 /** \brief  This function turns XML into a menu
903     \param  menus  This is the XML that defines the menu
904     \param  menu   Menu to be added to
905     \param  view   The View that this menu is being built for
907     This function is realitively simple as it just goes through the XML
908     and parses the individual elements.  In the case of a submenu, it
909     just calls itself recursively.  Because it is only reasonable to have
910     a couple of submenus, it is unlikely this will go more than two or
911     three times.
913     In the case of an unrecognized verb, a menu item is made to identify
914     the verb that is missing, and display that.  The menu item is also made
915     insensitive.
916 */
917 void
918 sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI::View::View *view)
920     if (menus == NULL) return;
921     if (menu == NULL)  return;
922     GSList *group = NULL;
924     for (Inkscape::XML::Node *menu_pntr = menus;
925          menu_pntr != NULL;
926          menu_pntr = menu_pntr->next()) {
927         if (!strcmp(menu_pntr->name(), "submenu")) {
928             GtkWidget *mitem = gtk_menu_item_new_with_mnemonic(_(menu_pntr->attribute("name")));
929             GtkWidget *submenu = gtk_menu_new();
930             sp_ui_build_dyn_menus(menu_pntr->firstChild(), submenu, view);
931             gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), GTK_WIDGET(submenu));
932             gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
933             continue;
934         }
935         if (!strcmp(menu_pntr->name(), "verb")) {
936             gchar const *verb_name = menu_pntr->attribute("verb-id");
937             Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name);
939             if (verb != NULL) {
940                 if (menu_pntr->attribute("radio") != NULL) {
941                     GtkWidget *item = sp_ui_menu_append_item_from_verb (GTK_MENU(menu), verb, view, true, group);
942                     group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item));
943                     if (menu_pntr->attribute("default") != NULL) {
944                         gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
945                     }
946                     if (verb->get_code() != SP_VERB_NONE) {
947                         SPAction *action = verb->get_action(view);
948                         g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) update_view_menu, (void *) action);
949                     }
950                 } else {
951                     sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view);
952                     group = NULL;
953                 }
954             } else {
955                 gchar string[120];
956                 g_snprintf(string, 120, _("Verb \"%s\" Unknown"), verb_name);
957                 string[119] = '\0'; /* may not be terminated */
958                 GtkWidget *item = gtk_menu_item_new_with_label(string);
959                 gtk_widget_set_sensitive(item, false);
960                 gtk_widget_show(item);
961                 gtk_menu_append(GTK_MENU(menu), item);
962             }
963             continue;
964         }
965         if (!strcmp(menu_pntr->name(), "separator")
966                 // This was spelt wrong in the original version
967                 // and so this is for backward compatibility.  It can
968                 // probably be dropped after the 0.44 release.
969              || !strcmp(menu_pntr->name(), "seperator")) {
970             GtkWidget *item = gtk_separator_menu_item_new();
971             gtk_widget_show(item);
972             gtk_menu_append(GTK_MENU(menu), item);
973             continue;
974         }
975         if (!strcmp(menu_pntr->name(), "template-list")) {
976             sp_menu_append_new_templates(menu, view);
977             continue;
978         }
979         if (!strcmp(menu_pntr->name(), "recent-file-list")) {
980             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
982             // create recent files menu
983             int max_recent = prefs->getInt("/options/maxrecentdocuments/value");
984             GtkWidget *recent_menu = gtk_recent_chooser_menu_new_for_manager(gtk_recent_manager_get_default());
985             gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(recent_menu), max_recent);
986             // sort most recently used documents first to preserve previous behavior
987             gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(recent_menu), GTK_RECENT_SORT_MRU);
988             g_signal_connect(G_OBJECT(recent_menu), "item-activated", G_CALLBACK(sp_recent_open), (gpointer) NULL);
990             // add filter to only open files added by Inkscape
991             GtkRecentFilter *inkscape_only_filter = gtk_recent_filter_new();
992             gtk_recent_filter_add_application(inkscape_only_filter, g_get_prgname());
993             gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_menu), inkscape_only_filter);
995             gtk_recent_chooser_set_show_tips (GTK_RECENT_CHOOSER(recent_menu), TRUE);
996             gtk_recent_chooser_set_show_not_found (GTK_RECENT_CHOOSER(recent_menu), FALSE);
998             GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic(_("Open _Recent"));
999             gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), recent_menu);
1001             gtk_menu_append(GTK_MENU(menu), GTK_WIDGET(recent_item));
1002             // this will just sit and update the list's item count
1003             static MaxRecentObserver *mro = new MaxRecentObserver(recent_menu);
1004             prefs->addObserver(*mro);
1005             continue;
1006         }
1007         if (!strcmp(menu_pntr->name(), "objects-checkboxes")) {
1008             sp_ui_checkboxes_menus(GTK_MENU(menu), view);
1009             continue;
1010         }
1011     }
1014 /** \brief  Build the main tool bar
1015     \param  view  View to build the bar for
1017     Currently the main tool bar is built as a dynamic XML menu using
1018     \c sp_ui_build_dyn_menus.  This function builds the bar, and then
1019     pass it to get items attached to it.
1020 */
1021 GtkWidget *
1022 sp_ui_main_menubar(Inkscape::UI::View::View *view)
1024     GtkWidget *mbar = gtk_menu_bar_new();
1026 #ifdef GDK_WINDOWING_QUARTZ
1027     ige_mac_menu_set_menu_bar(GTK_MENU_SHELL(mbar));
1028 #endif
1030     sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view);
1032 #ifdef GDK_WINDOWING_QUARTZ
1033     return NULL;
1034 #else
1035     return mbar;
1036 #endif
1039 static void leave_group(GtkMenuItem *, SPDesktop *desktop) {
1040     desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer()));
1043 static void enter_group(GtkMenuItem *mi, SPDesktop *desktop) {
1044     desktop->setCurrentLayer(reinterpret_cast<SPObject *>(g_object_get_data(G_OBJECT(mi), "group")));
1045     sp_desktop_selection(desktop)->clear();
1048 GtkWidget *
1049 sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item)
1051     GtkWidget *m;
1052     SPDesktop *dt;
1054     dt = static_cast<SPDesktop*>(view);
1056     m = gtk_menu_new();
1058     /* Undo and Redo */
1059     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_UNDO), view);
1060     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_REDO), view);
1062     /* Separator */
1063     sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1065     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_CUT), view);
1066     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_COPY), view);
1067     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_PASTE), view);
1069     /* Separator */
1070     sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1072     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DUPLICATE), view);
1073     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DELETE), view);
1075     /* Item menu */
1076     if (item) {
1077         sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1078         sp_object_menu((SPObject *) item, dt, GTK_MENU(m));
1079     }
1081     /* layer menu */
1082     SPGroup *group=NULL;
1083     if (item) {
1084         if (SP_IS_GROUP(item)) {
1085             group = SP_GROUP(item);
1086         } else if ( item != dt->currentRoot() && SP_IS_GROUP(SP_OBJECT_PARENT(item)) ) {
1087             group = SP_GROUP(SP_OBJECT_PARENT(item));
1088         }
1089     }
1091     if (( group && group != dt->currentLayer() ) ||
1092         ( dt->currentLayer() != dt->currentRoot() && SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) ) {
1093         /* Separator */
1094         sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1095     }
1097     if ( group && group != dt->currentLayer() ) {
1098         /* TRANSLATORS: #%s is the id of the group e.g. <g id="#g7">, not a number. */
1099         gchar *label=g_strdup_printf(_("Enter group #%s"), SP_OBJECT_ID(group));
1100         GtkWidget *w = gtk_menu_item_new_with_label(label);
1101         g_free(label);
1102         g_object_set_data(G_OBJECT(w), "group", group);
1103         g_signal_connect(G_OBJECT(w), "activate", GCallback(enter_group), dt);
1104         gtk_widget_show(w);
1105         gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1106     }
1108     if ( dt->currentLayer() != dt->currentRoot() ) {
1109         if ( SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) {
1110             GtkWidget *w = gtk_menu_item_new_with_label(_("Go to parent"));
1111             g_signal_connect(G_OBJECT(w), "activate", GCallback(leave_group), dt);
1112             gtk_widget_show(w);
1113             gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1115         }
1116     }
1118     return m;
1121 /* Drag and Drop */
1122 void
1123 sp_ui_drag_data_received(GtkWidget *widget,
1124                          GdkDragContext *drag_context,
1125                          gint x, gint y,
1126                          GtkSelectionData *data,
1127                          guint info,
1128                          guint /*event_time*/,
1129                          gpointer /*user_data*/)
1131     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1132     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1134     switch (info) {
1135 #if ENABLE_MAGIC_COLORS
1136         case APP_X_INKY_COLOR:
1137         {
1138             int destX = 0;
1139             int destY = 0;
1140             gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1141             Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1143             SPItem *item = desktop->item_at_point( where, true );
1144             if ( item )
1145             {
1146                 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1148                 if ( data->length >= 8 ) {
1149                     cmsHPROFILE srgbProf = cmsCreate_sRGBProfile();
1151                     gchar c[64] = {0};
1152                     // Careful about endian issues.
1153                     guint16* dataVals = (guint16*)data->data;
1154                     sp_svg_write_color( c, sizeof(c),
1155                                         SP_RGBA32_U_COMPOSE(
1156                                             0x0ff & (dataVals[0] >> 8),
1157                                             0x0ff & (dataVals[1] >> 8),
1158                                             0x0ff & (dataVals[2] >> 8),
1159                                             0xff // can't have transparency in the color itself
1160                                             //0x0ff & (data->data[3] >> 8),
1161                                             ));
1162                     SPCSSAttr *css = sp_repr_css_attr_new();
1163                     bool updatePerformed = false;
1165                     if ( data->length > 14 ) {
1166                         int flags = dataVals[4];
1168                         // piggie-backed palette entry info
1169                         int index = dataVals[5];
1170                         Glib::ustring palName;
1171                         for ( int i = 0; i < dataVals[6]; i++ ) {
1172                             palName += (gunichar)dataVals[7+i];
1173                         }
1175                         // Now hook in a magic tag of some sort.
1176                         if ( !palName.empty() && (flags & 1) ) {
1177                             gchar* str = g_strdup_printf("%d|", index);
1178                             palName.insert( 0, str );
1179                             g_free(str);
1180                             str = 0;
1182                             sp_object_setAttribute( SP_OBJECT(item),
1183                                                     fillnotstroke ? "inkscape:x-fill-tag":"inkscape:x-stroke-tag",
1184                                                     palName.c_str(),
1185                                                     false );
1186                             item->updateRepr();
1188                             sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1189                             updatePerformed = true;
1190                         }
1191                     }
1193                     if ( !updatePerformed ) {
1194                         sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1195                     }
1197                     sp_desktop_apply_css_recursive( item, css, true );
1198                     item->updateRepr();
1200                     sp_document_done( doc , SP_VERB_NONE,
1201                                       _("Drop color"));
1203                     if ( srgbProf ) {
1204                         cmsCloseProfile( srgbProf );
1205                     }
1206                 }
1207             }
1208         }
1209         break;
1210 #endif // ENABLE_MAGIC_COLORS
1212         case APP_X_COLOR:
1213         {
1214             int destX = 0;
1215             int destY = 0;
1216             gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1217             Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1218             Geom::Point const button_dt(desktop->w2d(where));
1219             Geom::Point const button_doc(desktop->dt2doc(button_dt));
1221             if ( data->length == 8 ) {
1222                 gchar colorspec[64] = {0};
1223                 // Careful about endian issues.
1224                 guint16* dataVals = (guint16*)data->data;
1225                 sp_svg_write_color( colorspec, sizeof(colorspec),
1226                                     SP_RGBA32_U_COMPOSE(
1227                                         0x0ff & (dataVals[0] >> 8),
1228                                         0x0ff & (dataVals[1] >> 8),
1229                                         0x0ff & (dataVals[2] >> 8),
1230                                         0xff // can't have transparency in the color itself
1231                                         //0x0ff & (data->data[3] >> 8),
1232                                         ));
1234                 SPItem *item = desktop->item_at_point( where, true );
1236                 bool consumed = false;
1237                 if (desktop->event_context && desktop->event_context->get_drag()) {
1238                     consumed = desktop->event_context->get_drag()->dropColor(item, colorspec, button_dt);
1239                     if (consumed) {
1240                         sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1241                         desktop->event_context->get_drag()->updateDraggers();
1242                     }
1243                 }
1245                 //if (!consumed && tools_active(desktop, TOOLS_TEXT)) {
1246                 //    consumed = sp_text_context_drop_color(c, button_doc);
1247                 //    if (consumed) {
1248                 //        sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient stop"));
1249                 //    }
1250                 //}
1252                 if (!consumed && item) {
1253                     bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1254                     if (fillnotstroke &&
1255                         (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1256                         Path *livarot_path = Path_for_item(item, true, true);
1257                         livarot_path->ConvertWithBackData(0.04);
1259                         boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1260                         if (position) {
1261                             Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1262                             Geom::Point delta = nearest - button_doc;
1263                             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1264                             delta = desktop->d2w(delta);
1265                             double stroke_tolerance =
1266                                 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1267                                   desktop->current_zoom() *
1268                                   SP_OBJECT_STYLE (item)->stroke_width.computed *
1269                                   to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1270                                   : 0.0)
1271                                 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1273                             if (Geom::L2 (delta) < stroke_tolerance) {
1274                                 fillnotstroke = false;
1275                             }
1276                         }
1277                         delete livarot_path;
1278                     }
1280                     SPCSSAttr *css = sp_repr_css_attr_new();
1281                     sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec );
1283                     sp_desktop_apply_css_recursive( item, css, true );
1284                     item->updateRepr();
1286                     sp_document_done( doc , SP_VERB_NONE,
1287                                       _("Drop color"));
1288                 }
1289             }
1290         }
1291         break;
1293         case APP_OSWB_COLOR:
1294         {
1295             bool worked = false;
1296             Glib::ustring colorspec;
1297             if ( data->format == 8 ) {
1298                 ege::PaintDef color;
1299                 worked = color.fromMIMEData("application/x-oswb-color",
1300                                             reinterpret_cast<char*>(data->data),
1301                                             data->length,
1302                                             data->format);
1303                 if ( worked ) {
1304                     if ( color.getType() == ege::PaintDef::CLEAR ) {
1305                         colorspec = ""; // TODO check if this is sufficient
1306                     } else if ( color.getType() == ege::PaintDef::NONE ) {
1307                         colorspec = "none";
1308                     } else {
1309                         unsigned int r = color.getR();
1310                         unsigned int g = color.getG();
1311                         unsigned int b = color.getB();
1313                         SPGradient* matches = 0;
1314                         const GSList *gradients = sp_document_get_resource_list(doc, "gradient");
1315                         for (const GSList *item = gradients; item; item = item->next) {
1316                             SPGradient* grad = SP_GRADIENT(item->data);
1317                             if ( color.descr == grad->id ) {
1318                                 if ( grad->has_stops ) {
1319                                     matches = grad;
1320                                     break;
1321                                 }
1322                             }
1323                         }
1324                         if (matches) {
1325                             colorspec = "url(#";
1326                             colorspec += matches->id;
1327                             colorspec += ")";
1328                         } else {
1329                             gchar* tmp = g_strdup_printf("#%02x%02x%02x", r, g, b);
1330                             colorspec = tmp;
1331                             g_free(tmp);
1332                         }
1333                     }
1334                 }
1335             }
1336             if ( worked ) {
1337                 int destX = 0;
1338                 int destY = 0;
1339                 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1340                 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1341                 Geom::Point const button_dt(desktop->w2d(where));
1342                 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1344                 SPItem *item = desktop->item_at_point( where, true );
1346                 bool consumed = false;
1347                 if (desktop->event_context && desktop->event_context->get_drag()) {
1348                     consumed = desktop->event_context->get_drag()->dropColor(item, colorspec.c_str(), button_dt);
1349                     if (consumed) {
1350                         sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1351                         desktop->event_context->get_drag()->updateDraggers();
1352                     }
1353                 }
1355                 if (!consumed && item) {
1356                     bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1357                     if (fillnotstroke &&
1358                         (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1359                         Path *livarot_path = Path_for_item(item, true, true);
1360                         livarot_path->ConvertWithBackData(0.04);
1362                         boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1363                         if (position) {
1364                             Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1365                             Geom::Point delta = nearest - button_doc;
1366                             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1367                             delta = desktop->d2w(delta);
1368                             double stroke_tolerance =
1369                                 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1370                                   desktop->current_zoom() *
1371                                   SP_OBJECT_STYLE (item)->stroke_width.computed *
1372                                   to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1373                                   : 0.0)
1374                                 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1376                             if (Geom::L2 (delta) < stroke_tolerance) {
1377                                 fillnotstroke = false;
1378                             }
1379                         }
1380                         delete livarot_path;
1381                     }
1383                     SPCSSAttr *css = sp_repr_css_attr_new();
1384                     sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec.c_str() );
1386                     sp_desktop_apply_css_recursive( item, css, true );
1387                     item->updateRepr();
1389                     sp_document_done( doc , SP_VERB_NONE,
1390                                       _("Drop color"));
1391                 }
1392             }
1393         }
1394         break;
1396         case SVG_DATA:
1397         case SVG_XML_DATA: {
1398             gchar *svgdata = (gchar *)data->data;
1400             Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI);
1402             if (rnewdoc == NULL) {
1403                 sp_ui_error_dialog(_("Could not parse SVG data"));
1404                 return;
1405             }
1407             Inkscape::XML::Node *repr = rnewdoc->root();
1408             gchar const *style = repr->attribute("style");
1410             Inkscape::XML::Node *newgroup = rnewdoc->createElement("svg:g");
1411             newgroup->setAttribute("style", style);
1413             Inkscape::XML::Document * xml_doc =  sp_document_repr_doc(doc);
1414             for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) {
1415                 Inkscape::XML::Node *newchild = child->duplicate(xml_doc);
1416                 newgroup->appendChild(newchild);
1417             }
1419             Inkscape::GC::release(rnewdoc);
1421             // Add it to the current layer
1423             // Greg's edits to add intelligent positioning of svg drops
1424             SPObject *new_obj = NULL;
1425             new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
1427             Inkscape::Selection *selection = sp_desktop_selection(desktop);
1428             selection->set(SP_ITEM(new_obj));
1430             // move to mouse pointer
1431             {
1432                 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
1433                 Geom::OptRect sel_bbox = selection->bounds();
1434                 if (sel_bbox) {
1435                     Geom::Point m( desktop->point() - sel_bbox->midpoint() );
1436                     sp_selection_move_relative(selection, m, false);
1437                 }
1438             }
1440             Inkscape::GC::release(newgroup);
1441             sp_document_done(doc, SP_VERB_NONE,
1442                              _("Drop SVG"));
1443             break;
1444         }
1446         case URI_LIST: {
1447             gchar *uri = (gchar *)data->data;
1448             sp_ui_import_files(uri);
1449             break;
1450         }
1452         case PNG_DATA:
1453         case JPEG_DATA:
1454         case IMAGE_DATA: {
1455             Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
1456             Inkscape::XML::Node *newImage = xml_doc->createElement("svg:image");
1457             gchar *atom_name = gdk_atom_name(data->type);
1459             // this formula taken from Glib docs
1460             guint needed_size = data->length * 4 / 3 + data->length * 4 / (3 * 72) + 7;
1461             needed_size += 5 + 8 + strlen(atom_name); // 5 bytes for data:, 8 for ;base64,
1463             gchar *buffer = (gchar *) g_malloc(needed_size), *buf_work = buffer;
1464             buf_work += g_sprintf(buffer, "data:%s;base64,", atom_name);
1466             gint state = 0, save = 0;
1467             g_base64_encode_step(data->data, data->length, TRUE, buf_work, &state, &save);
1468             g_base64_encode_close(TRUE, buf_work, &state, &save);
1470             newImage->setAttribute("xlink:href", buffer);
1471             g_free(buffer);
1473             GError *error = NULL;
1474             GdkPixbufLoader *loader = gdk_pixbuf_loader_new_with_mime_type( gdk_atom_name(data->type), &error );
1475             if ( loader ) {
1476                 error = NULL;
1477                 if ( gdk_pixbuf_loader_write( loader, data->data, data->length, &error) ) {
1478                     GdkPixbuf *pbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1479                     if ( pbuf ) {
1480                         char tmp[1024];
1481                         int width = gdk_pixbuf_get_width(pbuf);
1482                         int height = gdk_pixbuf_get_height(pbuf);
1483                         snprintf( tmp, sizeof(tmp), "%d", width );
1484                         newImage->setAttribute("width", tmp);
1486                         snprintf( tmp, sizeof(tmp), "%d", height );
1487                         newImage->setAttribute("height", tmp);
1488                     }
1489                 }
1490             }
1491             g_free(atom_name);
1493             // Add it to the current layer
1494             desktop->currentLayer()->appendChildRepr(newImage);
1496             Inkscape::GC::release(newImage);
1497             sp_document_done( doc , SP_VERB_NONE,
1498                               _("Drop bitmap image"));
1499             break;
1500         }
1501     }
1504 #include "gradient-context.h"
1506 void sp_ui_drag_motion( GtkWidget */*widget*/,
1507                         GdkDragContext */*drag_context*/,
1508                         gint /*x*/, gint /*y*/,
1509                         GtkSelectionData */*data*/,
1510                         guint /*info*/,
1511                         guint /*event_time*/,
1512                         gpointer /*user_data*/)
1514 //     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1515 //     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1518 //     g_message("drag-n-drop motion (%4d, %4d)  at %d", x, y, event_time);
1521 static void sp_ui_drag_leave( GtkWidget */*widget*/,
1522                               GdkDragContext */*drag_context*/,
1523                               guint /*event_time*/,
1524                               gpointer /*user_data*/ )
1526 //     g_message("drag-n-drop leave                at %d", event_time);
1529 static void
1530 sp_ui_import_files(gchar *buffer)
1532     GList *list = gnome_uri_list_extract_filenames(buffer);
1533     if (!list)
1534         return;
1535     g_list_foreach(list, sp_ui_import_one_file_with_check, NULL);
1536     g_list_foreach(list, (GFunc) g_free, NULL);
1537     g_list_free(list);
1540 static void
1541 sp_ui_import_one_file_with_check(gpointer filename, gpointer /*unused*/)
1543     if (filename) {
1544         if (strlen((char const *)filename) > 2)
1545             sp_ui_import_one_file((char const *)filename);
1546     }
1549 static void
1550 sp_ui_import_one_file(char const *filename)
1552     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1553     if (!doc) return;
1555     if (filename == NULL) return;
1557     // Pass off to common implementation
1558     // TODO might need to get the proper type of Inkscape::Extension::Extension
1559     file_import( doc, filename, NULL );
1562 void
1563 sp_ui_error_dialog(gchar const *message)
1565     GtkWidget *dlg;
1566     gchar *safeMsg = Inkscape::IO::sanitizeString(message);
1568     dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
1569                                  GTK_BUTTONS_CLOSE, "%s", safeMsg);
1570     sp_transientize(dlg);
1571     gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE);
1572     gtk_dialog_run(GTK_DIALOG(dlg));
1573     gtk_widget_destroy(dlg);
1574     g_free(safeMsg);
1577 bool
1578 sp_ui_overwrite_file(gchar const *filename)
1580     bool return_value = FALSE;
1582     if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) {
1583         Gtk::Window *window = SP_ACTIVE_DESKTOP->getToplevel();
1584         gchar* baseName = g_path_get_basename( filename );
1585         gchar* dirName = g_path_get_dirname( filename );
1586         GtkWidget* dialog = gtk_message_dialog_new_with_markup( window->gobj(),
1587                                                                 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
1588                                                                 GTK_MESSAGE_QUESTION,
1589                                                                 GTK_BUTTONS_NONE,
1590                                                                 _( "<span weight=\"bold\" size=\"larger\">A file named \"%s\" already exists. Do you want to replace it?</span>\n\n"
1591                                                                    "The file already exists in \"%s\". Replacing it will overwrite its contents." ),
1592                                                                 baseName,
1593                                                                 dirName
1594             );
1595         gtk_dialog_add_buttons( GTK_DIALOG(dialog),
1596                                 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1597                                 _("Replace"), GTK_RESPONSE_YES,
1598                                 NULL );
1599         gtk_dialog_set_default_response( GTK_DIALOG(dialog), GTK_RESPONSE_YES );
1601         if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_YES ) {
1602             return_value = TRUE;
1603         } else {
1604             return_value = FALSE;
1605         }
1606         gtk_widget_destroy(dialog);
1607         g_free( baseName );
1608         g_free( dirName );
1609     } else {
1610         return_value = TRUE;
1611     }
1613     return return_value;
1616 static void
1617 sp_ui_menu_item_set_sensitive(SPAction */*action*/, unsigned int sensitive, void *data)
1619     return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
1622 static void
1623 sp_ui_menu_item_set_name(SPAction */*action*/, Glib::ustring name, void *data)
1625     void *child = GTK_BIN (data)->child;
1626     //child is either
1627     //- a GtkHBox, whose first child is a label displaying name if the menu
1628     //item has an accel key
1629     //- a GtkLabel if the menu has no accel key
1630     if (GTK_IS_LABEL(child)) {
1631         gtk_label_set_markup_with_mnemonic(GTK_LABEL (child), name.c_str());
1632     } else if (GTK_IS_HBOX(child)) {
1633         gtk_label_set_markup_with_mnemonic(
1634         GTK_LABEL (gtk_container_get_children(GTK_CONTAINER (child))->data),
1635         name.c_str());
1636     }//else sp_ui_menu_append_item_from_verb has been modified and can set
1637     //a menu item in yet another way...
1641 /*
1642   Local Variables:
1643   mode:c++
1644   c-file-style:"stroustrup"
1645   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1646   indent-tabs-mode:nil
1647   fill-column:99
1648   End:
1649 */
1650 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :