Code

Use g_base64_encode_step when importing images via drag and drop.
[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-flowtext.h"
44 #include "sp-namedview.h"
45 #include "ui/view/view.h"
46 #include "helper/action.h"
47 #include "helper/gnome-utils.h"
48 #include "helper/window.h"
49 #include "io/sys.h"
50 #include "dialogs/dialog-events.h"
51 #include "message-context.h"
53 // Added for color drag-n-drop
54 #if ENABLE_LCMS
55 #include "lcms.h"
56 #endif // ENABLE_LCMS
57 #include "display/sp-canvas.h"
58 #include "color.h"
59 #include "svg/svg-color.h"
60 #include "desktop-style.h"
61 #include "style.h"
62 #include "event-context.h"
63 #include "gradient-drag.h"
65 // Include Mac OS X menu synchronization on native OSX build
66 #ifdef GDK_WINDOWING_QUARTZ
67 #include "ige-mac-menu.h"
68 #endif
70 /* Drag and Drop */
71 typedef enum {
72     URI_LIST,
73     SVG_XML_DATA,
74     SVG_DATA,
75     PNG_DATA,
76     JPEG_DATA,
77     IMAGE_DATA,
78     APP_X_INKY_COLOR,
79     APP_X_COLOR
80 } ui_drop_target_info;
82 static GtkTargetEntry ui_drop_target_entries [] = {
83     {(gchar *)"text/uri-list",                0, URI_LIST        },
84     {(gchar *)"image/svg+xml",                0, SVG_XML_DATA    },
85     {(gchar *)"image/svg",                    0, SVG_DATA        },
86     {(gchar *)"image/png",                    0, PNG_DATA        },
87     {(gchar *)"image/jpeg",                   0, JPEG_DATA       },
88 #if ENABLE_MAGIC_COLORS
89     {(gchar *)"application/x-inkscape-color", 0, APP_X_INKY_COLOR},
90 #endif // ENABLE_MAGIC_COLORS
91     {(gchar *)"application/x-color",          0, APP_X_COLOR     }
92 };
94 static GtkTargetEntry *completeDropTargets = 0;
95 static int completeDropTargetsCount = 0;
97 #define ENTRIES_SIZE(n) sizeof(n)/sizeof(n[0])
98 static guint nui_drop_target_entries = ENTRIES_SIZE(ui_drop_target_entries);
99 static void sp_ui_import_files(gchar *buffer);
100 static void sp_ui_import_one_file(char const *filename);
101 static void sp_ui_import_one_file_with_check(gpointer filename, gpointer unused);
102 static void sp_ui_drag_data_received(GtkWidget *widget,
103                                      GdkDragContext *drag_context,
104                                      gint x, gint y,
105                                      GtkSelectionData *data,
106                                      guint info,
107                                      guint event_time,
108                                      gpointer user_data);
109 static void sp_ui_drag_motion( GtkWidget *widget,
110                                GdkDragContext *drag_context,
111                                gint x, gint y,
112                                GtkSelectionData *data,
113                                guint info,
114                                guint event_time,
115                                gpointer user_data );
116 static void sp_ui_drag_leave( GtkWidget *widget,
117                               GdkDragContext *drag_context,
118                               guint event_time,
119                               gpointer user_data );
120 static void sp_ui_menu_item_set_sensitive(SPAction *action,
121                                           unsigned int sensitive,
122                                           void *data);
123 static void sp_ui_menu_item_set_name(SPAction *action,
124                                      Glib::ustring name,
125                                      void *data);
126 static void sp_recent_open(GtkRecentChooser *, gpointer);
128 SPActionEventVector menu_item_event_vector = {
129     {NULL},
130     NULL,
131     NULL, /* set_active */
132     sp_ui_menu_item_set_sensitive, /* set_sensitive */
133     NULL, /* set_shortcut */
134     sp_ui_menu_item_set_name /* set_name */
135 };
137 static const int MIN_ONSCREEN_DISTANCE = 50;
139 void
140 sp_create_window(SPViewWidget *vw, gboolean editable)
142     g_return_if_fail(vw != NULL);
143     g_return_if_fail(SP_IS_VIEW_WIDGET(vw));
145     Gtk::Window *win = Inkscape::UI::window_new("", TRUE);
147     gtk_container_add(GTK_CONTAINER(win->gobj()), GTK_WIDGET(vw));
148     gtk_widget_show(GTK_WIDGET(vw));
150     if (editable) {
151                 g_object_set_data(G_OBJECT(vw), "window", win);
153                 SPDesktopWidget *desktop_widget = reinterpret_cast<SPDesktopWidget*>(vw);
154                 SPDesktop* desktop = desktop_widget->desktop;
156                 desktop_widget->window = win;
158         win->set_data("desktop", desktop);
159         win->set_data("desktopwidget", desktop_widget);
161         win->signal_delete_event().connect(sigc::mem_fun(*(SPDesktop*)vw->view, &SPDesktop::onDeleteUI));
162                 win->signal_window_state_event().connect(sigc::mem_fun(*desktop, &SPDesktop::onWindowStateEvent));
163                 win->signal_focus_in_event().connect(sigc::mem_fun(*desktop_widget, &SPDesktopWidget::onFocusInEvent));
165         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
166         gint prefs_geometry =
167             (2==prefs->getInt("/options/savewindowgeometry/value", 0));
168         if (prefs_geometry) {
169             gint pw = prefs->getInt("/desktop/geometry/width", -1);
170             gint ph = prefs->getInt("/desktop/geometry/height", -1);
171             gint px = prefs->getInt("/desktop/geometry/x", -1);
172             gint py = prefs->getInt("/desktop/geometry/y", -1);
173             gint full = prefs->getBool("/desktop/geometry/fullscreen");
174             gint maxed = prefs->getBool("/desktop/geometry/maximized");
175             if (pw>0 && ph>0) {
176                 gint w = MIN(gdk_screen_width(), pw);
177                 gint h = MIN(gdk_screen_height(), ph);
178                 gint x = MIN(gdk_screen_width() - MIN_ONSCREEN_DISTANCE, px);
179                 gint y = MIN(gdk_screen_height() - MIN_ONSCREEN_DISTANCE, py);
180                 if (w>0 && h>0 && x>0 && y>0) {
181                     x = MIN(gdk_screen_width() - w, x);
182                     y = MIN(gdk_screen_height() - h, y);
183                 }
184                 if (w>0 && h>0) {
185                     desktop->setWindowSize(w, h);
186                 }
188                 // Only restore xy for the first window so subsequent windows don't overlap exactly
189                 // with first.  (Maybe rule should be only restore xy if it's different from xy of
190                 // other desktops?)
192                 // Empirically it seems that active_desktop==this desktop only the first time a
193                 // desktop is created.
194                 if (x>0 && y>0) {
195                     SPDesktop *active_desktop = SP_ACTIVE_DESKTOP;
196                     if (active_desktop == desktop || active_desktop==NULL) {
197                         desktop->setWindowPosition(Geom::Point(x, y));
198                     }
199                 }
200             }
201             if (maxed) {
202                 win->maximize();
203             }
204             if (full) {
205                 win->fullscreen();
206             }
207         }
209     } else {
210         gtk_window_set_policy(GTK_WINDOW(win->gobj()), TRUE, TRUE, TRUE);
211     }
213     if ( completeDropTargets == 0 || completeDropTargetsCount == 0 )
214     {
215         std::vector<gchar*> types;
217         GSList *list = gdk_pixbuf_get_formats();
218         while ( list ) {
219             int i = 0;
220             GdkPixbufFormat *one = (GdkPixbufFormat*)list->data;
221             gchar** typesXX = gdk_pixbuf_format_get_mime_types(one);
222             for ( i = 0; typesXX[i]; i++ ) {
223                 types.push_back(g_strdup(typesXX[i]));
224             }
225             g_strfreev(typesXX);
227             list = g_slist_next(list);
228         }
229         completeDropTargetsCount = nui_drop_target_entries + types.size();
230         completeDropTargets = new GtkTargetEntry[completeDropTargetsCount];
231         for ( int i = 0; i < (int)nui_drop_target_entries; i++ ) {
232             completeDropTargets[i] = ui_drop_target_entries[i];
233         }
234         int pos = nui_drop_target_entries;
236         for (std::vector<gchar*>::iterator it = types.begin() ; it != types.end() ; it++) {
237             completeDropTargets[pos].target = *it;
238             completeDropTargets[pos].flags = 0;
239             completeDropTargets[pos].info = IMAGE_DATA;
240             pos++;
241         }
242     }
244     gtk_drag_dest_set((GtkWidget*)win->gobj(),
245                       GTK_DEST_DEFAULT_ALL,
246                       completeDropTargets,
247                       completeDropTargetsCount,
248                       GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE));
251     g_signal_connect(G_OBJECT(win->gobj()),
252                      "drag_data_received",
253                      G_CALLBACK(sp_ui_drag_data_received),
254                      NULL);
255     g_signal_connect(G_OBJECT(win->gobj()),
256                      "drag_motion",
257                      G_CALLBACK(sp_ui_drag_motion),
258                      NULL);
259     g_signal_connect(G_OBJECT(win->gobj()),
260                      "drag_leave",
261                      G_CALLBACK(sp_ui_drag_leave),
262                      NULL);
263     win->show();
265     // needed because the first ACTIVATE_DESKTOP was sent when there was no window yet
266     inkscape_reactivate_desktop(SP_DESKTOP_WIDGET(vw)->desktop);
269 void
270 sp_ui_new_view()
272     SPDocument *document;
273     SPViewWidget *dtw;
275     document = SP_ACTIVE_DOCUMENT;
276     if (!document) return;
278     dtw = sp_desktop_widget_new(sp_document_namedview(document, NULL));
279     g_return_if_fail(dtw != NULL);
281     sp_create_window(dtw, TRUE);
282     sp_namedview_window_from_document(static_cast<SPDesktop*>(dtw->view));
283     sp_namedview_update_layers_from_document(static_cast<SPDesktop*>(dtw->view));
286 /* TODO: not yet working */
287 /* To be re-enabled (by adding to menu) once it works. */
288 void
289 sp_ui_new_view_preview()
291     SPDocument *document;
292     SPViewWidget *dtw;
294     document = SP_ACTIVE_DOCUMENT;
295     if (!document) return;
297     dtw = (SPViewWidget *) sp_svg_view_widget_new(document);
298     g_return_if_fail(dtw != NULL);
299     sp_svg_view_widget_set_resize(SP_SVG_VIEW_WIDGET(dtw), TRUE, 400.0, 400.0);
301     sp_create_window(dtw, FALSE);
304 /**
305  * \param widget unused
306  */
307 void
308 sp_ui_close_view(GtkWidget */*widget*/)
310     if (SP_ACTIVE_DESKTOP == NULL) {
311         return;
312     }
313     if ((SP_ACTIVE_DESKTOP)->shutdown()) {
314         return;
315     }
316     SP_ACTIVE_DESKTOP->destroyWidget();
320 /**
321  *  sp_ui_close_all
322  *
323  *  This function is called to exit the program, and iterates through all
324  *  open document view windows, attempting to close each in turn.  If the
325  *  view has unsaved information, the user will be prompted to save,
326  *  discard, or cancel.
327  *
328  *  Returns FALSE if the user cancels the close_all operation, TRUE
329  *  otherwise.
330  */
331 unsigned int
332 sp_ui_close_all(void)
334     /* Iterate through all the windows, destroying each in the order they
335        become active */
336     while (SP_ACTIVE_DESKTOP) {
337         if ((SP_ACTIVE_DESKTOP)->shutdown()) {
338             /* The user cancelled the operation, so end doing the close */
339             return FALSE;
340         }
341         SP_ACTIVE_DESKTOP->destroyWidget();
342     }
344     return TRUE;
347 /*
348  * Some day when the right-click menus are ready to start working
349  * smarter with the verbs, we'll need to change this NULL being
350  * sent to sp_action_perform to something useful, or set some kind
351  * of global "right-clicked position" variable for actions to
352  * investigate when they're called.
353  */
354 static void
355 sp_ui_menu_activate(void */*object*/, SPAction *action)
357     sp_action_perform(action, NULL);
360 static void
361 sp_ui_menu_select_action(void */*object*/, SPAction *action)
363     action->view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, action->tip);
366 static void
367 sp_ui_menu_deselect_action(void */*object*/, SPAction *action)
369     action->view->tipsMessageContext()->clear();
372 static void
373 sp_ui_menu_select(gpointer object, gpointer tip)
375     Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view"));
376     view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, (gchar *)tip);
379 static void
380 sp_ui_menu_deselect(gpointer object)
382     Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*>  (g_object_get_data(G_OBJECT(object), "view"));
383     view->tipsMessageContext()->clear();
386 /**
387  * sp_ui_menuitem_add_icon
388  *
389  * Creates and attaches a scaled icon to the given menu item.
390  *
391  */
392 void
393 sp_ui_menuitem_add_icon( GtkWidget *item, gchar *icon_name )
395     GtkWidget *icon;
397     icon = sp_icon_new( Inkscape::ICON_SIZE_MENU, icon_name );
398     gtk_widget_show(icon);
399     gtk_image_menu_item_set_image((GtkImageMenuItem *) item, icon);
400 } // end of sp_ui_menu_add_icon
402 /**
403  * sp_ui_menu_append_item
404  *
405  * Appends a UI item with specific info for Inkscape/Sodipodi.
406  *
407  */
408 static GtkWidget *
409 sp_ui_menu_append_item( GtkMenu *menu, gchar const *stock,
410                         gchar const *label, gchar const *tip, Inkscape::UI::View::View *view, GCallback callback,
411                         gpointer data, gboolean with_mnemonic = TRUE )
413     GtkWidget *item;
415     if (stock) {
416         item = gtk_image_menu_item_new_from_stock(stock, NULL);
417     } else if (label) {
418         item = (with_mnemonic)
419             ? gtk_image_menu_item_new_with_mnemonic(label) :
420             gtk_image_menu_item_new_with_label(label);
421     } else {
422         item = gtk_separator_menu_item_new();
423     }
425     gtk_widget_show(item);
427     if (callback) {
428         g_signal_connect(G_OBJECT(item), "activate", callback, data);
429     }
431     if (tip && view) {
432         g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
433         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) tip );
434         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
435     }
437     gtk_menu_append(GTK_MENU(menu), item);
439     return item;
441 } // end of sp_ui_menu_append_item()
443 /**
444 \brief  a wrapper around gdk_keyval_name producing (when possible) characters, not names
445  */
446 static gchar const *
447 sp_key_name(guint keyval)
449     /* TODO: Compare with the definition of gtk_accel_label_refetch in gtk/gtkaccellabel.c (or
450        simply use GtkAccelLabel as the TODO comment in sp_ui_shortcut_string suggests). */
451     gchar const *n = gdk_keyval_name(gdk_keyval_to_upper(keyval));
453     if      (!strcmp(n, "asciicircum"))  return "^";
454     else if (!strcmp(n, "parenleft"  ))  return "(";
455     else if (!strcmp(n, "parenright" ))  return ")";
456     else if (!strcmp(n, "plus"       ))  return "+";
457     else if (!strcmp(n, "minus"      ))  return "-";
458     else if (!strcmp(n, "asterisk"   ))  return "*";
459     else if (!strcmp(n, "KP_Multiply"))  return "*";
460     else if (!strcmp(n, "Delete"     ))  return "Del";
461     else if (!strcmp(n, "Page_Up"    ))  return "PgUp";
462     else if (!strcmp(n, "Page_Down"  ))  return "PgDn";
463     else if (!strcmp(n, "grave"      ))  return "`";
464     else if (!strcmp(n, "numbersign" ))  return "#";
465     else if (!strcmp(n, "bar"        ))  return "|";
466     else if (!strcmp(n, "slash"      ))  return "/";
467     else if (!strcmp(n, "exclam"     ))  return "!";
468     else if (!strcmp(n, "percent"    ))  return "%";
469     else return n;
473 /**
474  * \param shortcut A GDK keyval OR'd with SP_SHORTCUT_blah_MASK values.
475  * \param c Points to a buffer at least 256 bytes long.
476  */
477 void
478 sp_ui_shortcut_string(unsigned const shortcut, gchar *const c)
480     /* TODO: This function shouldn't exist.  Our callers should use GtkAccelLabel instead of
481      * a generic GtkLabel containing this string, and should call gtk_widget_add_accelerator.
482      * Will probably need to change sp_shortcut_invoke callers.
483      *
484      * The existing gtk_label_new_with_mnemonic call can be replaced with
485      * g_object_new(GTK_TYPE_ACCEL_LABEL, NULL) followed by
486      * gtk_label_set_text_with_mnemonic(lbl, str).
487      */
488     static GtkAccelLabelClass const &accel_lbl_cls
489         = *(GtkAccelLabelClass const *) g_type_class_peek_static(GTK_TYPE_ACCEL_LABEL);
491     struct { unsigned test; char const *name; } const modifier_tbl[] = {
492         { SP_SHORTCUT_SHIFT_MASK,   accel_lbl_cls.mod_name_shift   },
493         { SP_SHORTCUT_CONTROL_MASK, accel_lbl_cls.mod_name_control },
494         { SP_SHORTCUT_ALT_MASK,     accel_lbl_cls.mod_name_alt     }
495     };
497     gchar *p = c;
498     gchar *end = p + 256;
500     for (unsigned i = 0; i < G_N_ELEMENTS(modifier_tbl); ++i) {
501         if ((shortcut & modifier_tbl[i].test)
502             && (p < end))
503         {
504             p += g_snprintf(p, end - p, "%s%s",
505                             modifier_tbl[i].name,
506                             accel_lbl_cls.mod_separator);
507         }
508     }
509     if (p < end) {
510         p += g_snprintf(p, end - p, "%s", sp_key_name(shortcut & 0xffffff));
511     }
512     end[-1] = '\0';  // snprintf doesn't guarantee to nul-terminate the string.
515 void
516 sp_ui_dialog_title_string(Inkscape::Verb *verb, gchar *c)
518     SPAction     *action;
519     unsigned int shortcut;
520     gchar        *s;
521     gchar        key[256];
522     gchar        *atitle;
524     action = verb->get_action(NULL);
525     if (!action)
526         return;
528     atitle = sp_action_get_title(action);
530     s = g_stpcpy(c, atitle);
532     g_free(atitle);
534     shortcut = sp_shortcut_get_primary(verb);
535     if (shortcut) {
536         s = g_stpcpy(s, " (");
537         sp_ui_shortcut_string(shortcut, key);
538         s = g_stpcpy(s, key);
539         s = g_stpcpy(s, ")");
540     }
544 /**
545  * sp_ui_menu_append_item_from_verb
546  *
547  * Appends a custom menu UI from a verb.
548  *
549  */
551 static GtkWidget *
552 sp_ui_menu_append_item_from_verb(GtkMenu *menu, Inkscape::Verb *verb, Inkscape::UI::View::View *view, bool radio = false, GSList *group = NULL)
554     SPAction *action;
555     GtkWidget *item;
557     if (verb->get_code() == SP_VERB_NONE) {
559         item = gtk_separator_menu_item_new();
561     } else {
562         unsigned int shortcut;
564         action = verb->get_action(view);
566         if (!action) return NULL;
568         shortcut = sp_shortcut_get_primary(verb);
569         if (shortcut) {
570             gchar c[256];
571             sp_ui_shortcut_string(shortcut, c);
572             GtkWidget *const hb = gtk_hbox_new(FALSE, 16);
573             GtkWidget *const name_lbl = gtk_label_new("");
574             gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
575             gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
576             gtk_box_pack_start((GtkBox *) hb, name_lbl, TRUE, TRUE, 0);
577             GtkWidget *const accel_lbl = gtk_label_new(c);
578             gtk_misc_set_alignment((GtkMisc *) accel_lbl, 1.0, 0.5);
579             gtk_box_pack_end((GtkBox *) hb, accel_lbl, FALSE, FALSE, 0);
580             gtk_widget_show_all(hb);
581             if (radio) {
582                 item = gtk_radio_menu_item_new (group);
583             } else {
584                 item = gtk_image_menu_item_new();
585             }
586             gtk_container_add((GtkContainer *) item, hb);
587         } else {
588             if (radio) {
589                 item = gtk_radio_menu_item_new (group);
590             } else {
591                 item = gtk_image_menu_item_new ();
592             }
593             GtkWidget *const name_lbl = gtk_label_new("");
594             gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
595             gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
596             gtk_container_add((GtkContainer *) item, name_lbl);
597         }
599         nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
600         if (!action->sensitive) {
601             gtk_widget_set_sensitive(item, FALSE);
602         }
604         if (action->image) {
605             sp_ui_menuitem_add_icon(item, action->image);
606         }
607         gtk_widget_set_events(item, GDK_KEY_PRESS_MASK);
608         g_signal_connect( G_OBJECT(item), "activate",
609                           G_CALLBACK(sp_ui_menu_activate), action );
611         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select_action), action );
612         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect_action), action );
613     }
615     gtk_widget_show(item);
616     gtk_menu_append(GTK_MENU(menu), item);
618     return item;
620 } // end of sp_ui_menu_append_item_from_verb
623 static void
624 checkitem_toggled(GtkCheckMenuItem *menuitem, gpointer user_data)
626     gchar const *pref = (gchar const *) user_data;
627     Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
629     Glib::ustring pref_path;
630     if (reinterpret_cast<SPDesktop*>(view)->is_focusMode()) {
631         pref_path = "/focus/";
632     } else if (reinterpret_cast<SPDesktop*>(view)->is_fullscreen()) {
633         pref_path = "/fullscreen/";
634     } else {
635         pref_path = "/window/";
636     }
637     pref_path += pref;
638     pref_path += "/state";
640     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
641     gboolean checked = gtk_check_menu_item_get_active(menuitem);
642     prefs->setBool(pref_path, checked);
644     reinterpret_cast<SPDesktop*>(view)->layoutWidget();
647 static gboolean
648 checkitem_update(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_data)
650     GtkCheckMenuItem *menuitem=GTK_CHECK_MENU_ITEM(widget);
652     gchar const *pref = (gchar const *) user_data;
653     Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
655     Glib::ustring pref_path;
656     if ((static_cast<SPDesktop*>(view))->is_fullscreen()) {
657         pref_path = "/fullscreen/";
658     } else {
659         pref_path = "/window/";
660     }
661     pref_path += pref;
663     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
664     bool ison = prefs->getBool(pref_path + "/state", true);
666     g_signal_handlers_block_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
667     gtk_check_menu_item_set_active(menuitem, ison);
668     g_signal_handlers_unblock_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
670     return FALSE;
674 void
675 sp_ui_menu_append_check_item_from_verb(GtkMenu *menu, Inkscape::UI::View::View *view, gchar const *label, gchar const *tip, gchar const *pref,
676                                        void (*callback_toggle)(GtkCheckMenuItem *, gpointer user_data),
677                                        gboolean (*callback_update)(GtkWidget *widget, GdkEventExpose *event, gpointer user_data),
678                                        Inkscape::Verb *verb)
680     GtkWidget *item;
682     unsigned int shortcut = 0;
683     SPAction *action = NULL;
685     if (verb) {
686         shortcut = sp_shortcut_get_primary(verb);
687         action = verb->get_action(view);
688     }
690     if (verb && shortcut) {
691         gchar c[256];
692         sp_ui_shortcut_string(shortcut, c);
694         GtkWidget *hb = gtk_hbox_new(FALSE, 16);
696         {
697             GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
698             gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
699             gtk_box_pack_start((GtkBox *) hb, l, TRUE, TRUE, 0);
700         }
702         {
703             GtkWidget *l = gtk_label_new(c);
704             gtk_misc_set_alignment((GtkMisc *) l, 1.0, 0.5);
705             gtk_box_pack_end((GtkBox *) hb, l, FALSE, FALSE, 0);
706         }
708         gtk_widget_show_all(hb);
710         item = gtk_check_menu_item_new();
711         gtk_container_add((GtkContainer *) item, hb);
712     } else {
713         GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
714         gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
715         item = gtk_check_menu_item_new();
716         gtk_container_add((GtkContainer *) item, l);
717     }
718 #if 0
719     nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
720     if (!action->sensitive) {
721         gtk_widget_set_sensitive(item, FALSE);
722     }
723 #endif
724     gtk_widget_show(item);
726     gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
728     g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
730     g_signal_connect( G_OBJECT(item), "toggled", (GCallback) callback_toggle, (void *) pref);
731     g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) callback_update, (void *) pref);
733     g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) (action ? action->tip : tip));
734     g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
737 static void
738 sp_recent_open(GtkRecentChooser *recent_menu, gpointer /*user_data*/)
740     // dealing with the bizarre filename convention in Inkscape for now
741     gchar *uri = gtk_recent_chooser_get_current_uri(GTK_RECENT_CHOOSER(recent_menu));
742     gchar *local_fn = g_filename_from_uri(uri, NULL, NULL);
743     gchar *utf8_fn = g_filename_to_utf8(local_fn, -1, NULL, NULL, NULL);
744     sp_file_open(utf8_fn, NULL);
745     g_free(utf8_fn);
746     g_free(local_fn);
747     g_free(uri);
750 static void
751 sp_file_new_from_template(GtkWidget */*widget*/, gchar const *uri)
753     sp_file_new(uri);
756 void
757 sp_menu_append_new_templates(GtkWidget *menu, Inkscape::UI::View::View *view)
759     std::list<gchar *> sources;
760     sources.push_back( profile_path("templates") ); // first try user's local dir
761     sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir
763     // Use this loop to iterate through a list of possible document locations.
764     while (!sources.empty()) {
765         gchar *dirname = sources.front();
767         if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) {
768             GError *err = 0;
769             GDir *dir = g_dir_open(dirname, 0, &err);
771             if (dir) {
772                 for (gchar const *file = g_dir_read_name(dir); file != NULL; file = g_dir_read_name(dir)) {
773                     if (!g_str_has_suffix(file, ".svg") && !g_str_has_suffix(file, ".svgz"))
774                         continue; // skip non-svg files
776                     gchar *basename = g_path_get_basename(file);
777                     if (g_str_has_suffix(basename, ".svg") && g_str_has_prefix(basename, "default."))
778                         continue; // skip default.*.svg (i.e. default.svg and translations) - it's in the menu already
780                     gchar const *filepath = g_build_filename(dirname, file, NULL);
781                     gchar *dupfile = g_strndup(file, strlen(file) - 4);
782                     gchar *filename =  g_filename_to_utf8(dupfile,  -1, NULL, NULL, NULL);
783                     g_free(dupfile);
784                     GtkWidget *item = gtk_menu_item_new_with_label(filename);
785                     g_free(filename);
787                     gtk_widget_show(item);
788                     // how does "filepath" ever get freed?
789                     g_signal_connect(G_OBJECT(item),
790                                      "activate",
791                                      G_CALLBACK(sp_file_new_from_template),
792                                      (gpointer) filepath);
794                     if (view) {
795                         // set null tip for now; later use a description from the template file
796                         g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
797                         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) NULL );
798                         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
799                     }
801                     gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
802                 }
803                 g_dir_close(dir);
804             }
805         }
807         // toss the dirname
808         g_free(dirname);
809         sources.pop_front();
810     }
813 void
814 sp_ui_checkboxes_menus(GtkMenu *m, Inkscape::UI::View::View *view)
816     //sp_ui_menu_append_check_item_from_verb(m, view, _("_Menu"), _("Show or hide the menu bar"), "menu",
817     //                                       checkitem_toggled, checkitem_update, 0);
818     sp_ui_menu_append_check_item_from_verb(m, view, _("Commands Bar"), _("Show or hide the Commands bar (under the menu)"), "commands",
819                                            checkitem_toggled, checkitem_update, 0);
820     sp_ui_menu_append_check_item_from_verb(m, view, _("Snap controls Bar"), _("Show or hide the snapping controls"), "snaptoolbox",
821                                                                                    checkitem_toggled, checkitem_update, 0);
822     sp_ui_menu_append_check_item_from_verb(m, view, _("Tool Controls Bar"), _("Show or hide the Tool Controls bar"), "toppanel",
823                                            checkitem_toggled, checkitem_update, 0);
824     sp_ui_menu_append_check_item_from_verb(m, view, _("_Toolbox"), _("Show or hide the main toolbox (on the left)"), "toolbox",
825                                            checkitem_toggled, checkitem_update, 0);
826     sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "rulers",
827                                            checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_RULERS));
828     sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "scrollbars",
829                                            checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_SCROLLBARS));
830     sp_ui_menu_append_check_item_from_verb(m, view, _("_Palette"), _("Show or hide the color palette"), "panels",
831                                            checkitem_toggled, checkitem_update, 0);
832     sp_ui_menu_append_check_item_from_verb(m, view, _("_Statusbar"), _("Show or hide the statusbar (at the bottom of the window)"), "statusbar",
833                                            checkitem_toggled, checkitem_update, 0);
836 /** @brief Observer that updates the recent list's max document count */
837 class MaxRecentObserver : public Inkscape::Preferences::Observer {
838 public:
839     MaxRecentObserver(GtkWidget *recent_menu) :
840         Observer("/options/maxrecentdocuments/value"),
841         _rm(recent_menu)
842     {}
843     virtual void notify(Inkscape::Preferences::Entry const &e) {
844         gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(_rm), e.getInt());
845         // hack: the recent menu doesn't repopulate after changing the limit, so we force it
846         g_signal_emit_by_name((gpointer) gtk_recent_manager_get_default(), "changed");
847     }
848 private:
849     GtkWidget *_rm;
850 };
852 /** \brief  This function turns XML into a menu
853     \param  menus  This is the XML that defines the menu
854     \param  menu   Menu to be added to
855     \param  view   The View that this menu is being built for
857     This function is realitively simple as it just goes through the XML
858     and parses the individual elements.  In the case of a submenu, it
859     just calls itself recursively.  Because it is only reasonable to have
860     a couple of submenus, it is unlikely this will go more than two or
861     three times.
863     In the case of an unreconginzed verb, a menu item is made to identify
864     the verb that is missing, and display that.  The menu item is also made
865     insensitive.
866 */
867 void
868 sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI::View::View *view)
870     if (menus == NULL) return;
871     if (menu == NULL)  return;
872     GSList *group = NULL;
874     for (Inkscape::XML::Node *menu_pntr = menus;
875          menu_pntr != NULL;
876          menu_pntr = menu_pntr->next()) {
877         if (!strcmp(menu_pntr->name(), "submenu")) {
878             GtkWidget *mitem = gtk_menu_item_new_with_mnemonic(_(menu_pntr->attribute("name")));
879             GtkWidget *submenu = gtk_menu_new();
880             sp_ui_build_dyn_menus(menu_pntr->firstChild(), submenu, view);
881             gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), GTK_WIDGET(submenu));
882             gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
883             continue;
884         }
885         if (!strcmp(menu_pntr->name(), "verb")) {
886             gchar const *verb_name = menu_pntr->attribute("verb-id");
887             Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name);
889             if (verb != NULL) {
890                 if (menu_pntr->attribute("radio") != NULL) {
891                     GtkWidget *item = sp_ui_menu_append_item_from_verb (GTK_MENU(menu), verb, view, true, group);
892                     group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item));
893                     if (menu_pntr->attribute("default") != NULL) {
894                         gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
895                     }
896                 } else {
897                     sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view);
898                     group = NULL;
899                 }
900             } else {
901                 gchar string[120];
902                 g_snprintf(string, 120, _("Verb \"%s\" Unknown"), verb_name);
903                 string[119] = '\0'; /* may not be terminated */
904                 GtkWidget *item = gtk_menu_item_new_with_label(string);
905                 gtk_widget_set_sensitive(item, false);
906                 gtk_widget_show(item);
907                 gtk_menu_append(GTK_MENU(menu), item);
908             }
909             continue;
910         }
911         if (!strcmp(menu_pntr->name(), "separator")
912                 // This was spelt wrong in the original version
913                 // and so this is for backward compatibility.  It can
914                 // probably be dropped after the 0.44 release.
915              || !strcmp(menu_pntr->name(), "seperator")) {
916             GtkWidget *item = gtk_separator_menu_item_new();
917             gtk_widget_show(item);
918             gtk_menu_append(GTK_MENU(menu), item);
919             continue;
920         }
921         if (!strcmp(menu_pntr->name(), "template-list")) {
922             sp_menu_append_new_templates(menu, view);
923             continue;
924         }
925         if (!strcmp(menu_pntr->name(), "recent-file-list")) {
926             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
928             // create recent files menu
929             int max_recent = prefs->getInt("/options/maxrecentdocuments/value");
930             GtkWidget *recent_menu = gtk_recent_chooser_menu_new_for_manager(gtk_recent_manager_get_default());
931             gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(recent_menu), max_recent);
932             // sort most recently used documents first to preserve previous behavior
933             gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(recent_menu), GTK_RECENT_SORT_MRU);
934             g_signal_connect(G_OBJECT(recent_menu), "item-activated", G_CALLBACK(sp_recent_open), (gpointer) NULL);
936             // add filter to only open files added by Inkscape
937             GtkRecentFilter *inkscape_only_filter = gtk_recent_filter_new();
938             gtk_recent_filter_add_application(inkscape_only_filter, g_get_prgname());
939             gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_menu), inkscape_only_filter);
941             GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic(_("Open _Recent"));
942             gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), recent_menu);
944             gtk_menu_append(GTK_MENU(menu), GTK_WIDGET(recent_item));
945             // this will just sit and update the list's item count
946             static MaxRecentObserver *mro = new MaxRecentObserver(recent_menu);
947             prefs->addObserver(*mro);
948             continue;
949         }
950         if (!strcmp(menu_pntr->name(), "objects-checkboxes")) {
951             sp_ui_checkboxes_menus(GTK_MENU(menu), view);
952             continue;
953         }
954     }
957 /** \brief  Build the main tool bar
958     \param  view  View to build the bar for
960     Currently the main tool bar is built as a dynamic XML menu using
961     \c sp_ui_build_dyn_menus.  This function builds the bar, and then
962     pass it to get items attached to it.
963 */
964 GtkWidget *
965 sp_ui_main_menubar(Inkscape::UI::View::View *view)
967     GtkWidget *mbar = gtk_menu_bar_new();
969 #ifdef GDK_WINDOWING_QUARTZ
970         ige_mac_menu_set_menu_bar(GTK_MENU_SHELL(mbar));
971 #endif
973     sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view);
975 #ifdef GDK_WINDOWING_QUARTZ
976         return NULL;
977 #else
978     return mbar;
979 #endif
982 static void leave_group(GtkMenuItem *, SPDesktop *desktop) {
983     desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer()));
986 static void enter_group(GtkMenuItem *mi, SPDesktop *desktop) {
987     desktop->setCurrentLayer(reinterpret_cast<SPObject *>(g_object_get_data(G_OBJECT(mi), "group")));
988     sp_desktop_selection(desktop)->clear();
991 GtkWidget *
992 sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item)
994     GtkWidget *m;
995     SPDesktop *dt;
997     dt = static_cast<SPDesktop*>(view);
999     m = gtk_menu_new();
1001     /* Undo and Redo */
1002     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_UNDO), view);
1003     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_REDO), view);
1005     /* Separator */
1006     sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1008     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_CUT), view);
1009     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_COPY), view);
1010     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_PASTE), view);
1012     /* Separator */
1013     sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1015     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DUPLICATE), view);
1016     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DELETE), view);
1018     /* Item menu */
1019     if (item) {
1020         sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1021         sp_object_menu((SPObject *) item, dt, GTK_MENU(m));
1022     }
1024     /* layer menu */
1025     SPGroup *group=NULL;
1026     if (item) {
1027         if (SP_IS_GROUP(item)) {
1028             group = SP_GROUP(item);
1029         } else if ( item != dt->currentRoot() && SP_IS_GROUP(SP_OBJECT_PARENT(item)) ) {
1030             group = SP_GROUP(SP_OBJECT_PARENT(item));
1031         }
1032     }
1034     if (( group && group != dt->currentLayer() ) ||
1035         ( dt->currentLayer() != dt->currentRoot() && SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) ) {
1036         /* Separator */
1037         sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1038     }
1040     if ( group && group != dt->currentLayer() ) {
1041         /* TRANSLATORS: #%s is the id of the group e.g. <g id="#g7">, not a number. */
1042         gchar *label=g_strdup_printf(_("Enter group #%s"), SP_OBJECT_ID(group));
1043         GtkWidget *w = gtk_menu_item_new_with_label(label);
1044         g_free(label);
1045         g_object_set_data(G_OBJECT(w), "group", group);
1046         g_signal_connect(G_OBJECT(w), "activate", GCallback(enter_group), dt);
1047         gtk_widget_show(w);
1048         gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1049     }
1051     if ( dt->currentLayer() != dt->currentRoot() ) {
1052         if ( SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) {
1053             GtkWidget *w = gtk_menu_item_new_with_label(_("Go to parent"));
1054             g_signal_connect(G_OBJECT(w), "activate", GCallback(leave_group), dt);
1055             gtk_widget_show(w);
1056             gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1058         }
1059     }
1061     return m;
1064 /* Drag and Drop */
1065 void
1066 sp_ui_drag_data_received(GtkWidget *widget,
1067                          GdkDragContext *drag_context,
1068                          gint x, gint y,
1069                          GtkSelectionData *data,
1070                          guint info,
1071                          guint /*event_time*/,
1072                          gpointer /*user_data*/)
1074     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1075     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1077     switch (info) {
1078 #if ENABLE_MAGIC_COLORS
1079         case APP_X_INKY_COLOR:
1080         {
1081             int destX = 0;
1082             int destY = 0;
1083             gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1084             Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1086             SPItem *item = desktop->item_at_point( where, true );
1087             if ( item )
1088             {
1089                 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1091                 if ( data->length >= 8 ) {
1092                     cmsHPROFILE srgbProf = cmsCreate_sRGBProfile();
1094                     gchar c[64] = {0};
1095                     // Careful about endian issues.
1096                     guint16* dataVals = (guint16*)data->data;
1097                     sp_svg_write_color( c, sizeof(c),
1098                                         SP_RGBA32_U_COMPOSE(
1099                                             0x0ff & (dataVals[0] >> 8),
1100                                             0x0ff & (dataVals[1] >> 8),
1101                                             0x0ff & (dataVals[2] >> 8),
1102                                             0xff // can't have transparency in the color itself
1103                                             //0x0ff & (data->data[3] >> 8),
1104                                             ));
1105                     SPCSSAttr *css = sp_repr_css_attr_new();
1106                     bool updatePerformed = false;
1108                     if ( data->length > 14 ) {
1109                         int flags = dataVals[4];
1111                         // piggie-backed palette entry info
1112                         int index = dataVals[5];
1113                         Glib::ustring palName;
1114                         for ( int i = 0; i < dataVals[6]; i++ ) {
1115                             palName += (gunichar)dataVals[7+i];
1116                         }
1118                         // Now hook in a magic tag of some sort.
1119                         if ( !palName.empty() && (flags & 1) ) {
1120                             gchar* str = g_strdup_printf("%d|", index);
1121                             palName.insert( 0, str );
1122                             g_free(str);
1123                             str = 0;
1125                             sp_object_setAttribute( SP_OBJECT(item),
1126                                                     fillnotstroke ? "inkscape:x-fill-tag":"inkscape:x-stroke-tag",
1127                                                     palName.c_str(),
1128                                                     false );
1129                             item->updateRepr();
1131                             sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1132                             updatePerformed = true;
1133                         }
1134                     }
1136                     if ( !updatePerformed ) {
1137                         sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1138                     }
1140                     sp_desktop_apply_css_recursive( item, css, true );
1141                     item->updateRepr();
1143                     sp_document_done( doc , SP_VERB_NONE,
1144                                       _("Drop color"));
1146                     if ( srgbProf ) {
1147                         cmsCloseProfile( srgbProf );
1148                     }
1149                 }
1150             }
1151         }
1152         break;
1153 #endif // ENABLE_MAGIC_COLORS
1155         case APP_X_COLOR:
1156         {
1157             int destX = 0;
1158             int destY = 0;
1159             gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1160             Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1161             Geom::Point const button_dt(desktop->w2d(where));
1162             Geom::Point const button_doc(desktop->dt2doc(button_dt));
1164             if ( data->length == 8 ) {
1165                 gchar c[64] = {0};
1166                 // Careful about endian issues.
1167                 guint16* dataVals = (guint16*)data->data;
1168                 sp_svg_write_color( c, 64,
1169                                     SP_RGBA32_U_COMPOSE(
1170                                         0x0ff & (dataVals[0] >> 8),
1171                                         0x0ff & (dataVals[1] >> 8),
1172                                         0x0ff & (dataVals[2] >> 8),
1173                                         0xff // can't have transparency in the color itself
1174                                         //0x0ff & (data->data[3] >> 8),
1175                                         ));
1177                 SPItem *item = desktop->item_at_point( where, true );
1179                 bool consumed = false;
1180                 if (desktop->event_context && desktop->event_context->get_drag()) {
1181                     consumed = desktop->event_context->get_drag()->dropColor(item, c, button_dt);
1182                     if (consumed) {
1183                         sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1184                         desktop->event_context->get_drag()->updateDraggers();
1185                     }
1186                 }
1188                 //if (!consumed && tools_active(desktop, TOOLS_TEXT)) {
1189                 //    consumed = sp_text_context_drop_color(c, button_doc);
1190                 //    if (consumed) {
1191                 //        sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient stop"));
1192                 //    }
1193                 //}
1195                 if (!consumed && item) {
1196                     bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1197                     if (fillnotstroke &&
1198                         (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1199                         Path *livarot_path = Path_for_item(item, true, true);
1200                         livarot_path->ConvertWithBackData(0.04);
1202                         boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1203                         if (position) {
1204                             Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1205                             Geom::Point delta = nearest - button_doc;
1206                             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1207                             delta = desktop->d2w(delta);
1208                             double stroke_tolerance =
1209                                 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1210                                   desktop->current_zoom() *
1211                                   SP_OBJECT_STYLE (item)->stroke_width.computed *
1212                                   to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1213                                   : 0.0)
1214                                 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1216                             if (Geom::L2 (delta) < stroke_tolerance) {
1217                                 fillnotstroke = false;
1218                             }
1219                         }
1220                         delete livarot_path;
1221                     }
1223                     SPCSSAttr *css = sp_repr_css_attr_new();
1224                     sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1226                     sp_desktop_apply_css_recursive( item, css, true );
1227                     item->updateRepr();
1229                     sp_document_done( doc , SP_VERB_NONE,
1230                                       _("Drop color"));
1231                 }
1232             }
1233         }
1234         break;
1236         case SVG_DATA:
1237         case SVG_XML_DATA: {
1238             gchar *svgdata = (gchar *)data->data;
1240             Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI);
1242             if (rnewdoc == NULL) {
1243                 sp_ui_error_dialog(_("Could not parse SVG data"));
1244                 return;
1245             }
1247             Inkscape::XML::Node *repr = rnewdoc->root();
1248             gchar const *style = repr->attribute("style");
1250             Inkscape::XML::Node *newgroup = rnewdoc->createElement("svg:g");
1251             newgroup->setAttribute("style", style);
1253             Inkscape::XML::Document * xml_doc =  sp_document_repr_doc(doc);
1254             for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) {
1255                 Inkscape::XML::Node *newchild = child->duplicate(xml_doc);
1256                 newgroup->appendChild(newchild);
1257             }
1259             Inkscape::GC::release(rnewdoc);
1261             // Add it to the current layer
1263             // Greg's edits to add intelligent positioning of svg drops
1264             SPObject *new_obj = NULL;
1265             new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
1267             Inkscape::Selection *selection = sp_desktop_selection(desktop);
1268             selection->set(SP_ITEM(new_obj));
1269             // To move the imported object, we must temporarily set the "transform pattern with
1270             // object" option.
1271             {
1272                 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1273                 bool const saved_pref = prefs->getBool("/options/transform/pattern", true);
1274                 prefs->setBool("/options/transform/pattern", true);
1275                 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
1276                 Geom::OptRect sel_bbox = selection->bounds();
1277                 if (sel_bbox) {
1278                     Geom::Point m( desktop->point() - sel_bbox->midpoint() );
1279                     sp_selection_move_relative(selection, m);
1280                 }
1281                 prefs->setBool("/options/transform/pattern", saved_pref);
1282             }
1284             Inkscape::GC::release(newgroup);
1285             sp_document_done(doc, SP_VERB_NONE,
1286                              _("Drop SVG"));
1287             break;
1288         }
1290         case URI_LIST: {
1291             gchar *uri = (gchar *)data->data;
1292             sp_ui_import_files(uri);
1293             break;
1294         }
1296         case PNG_DATA:
1297         case JPEG_DATA:
1298         case IMAGE_DATA: {
1299             Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
1300             Inkscape::XML::Node *newImage = xml_doc->createElement("svg:image");
1301             gchar *atom_name = gdk_atom_name(data->type);
1302             
1303             // this formula taken from Glib docs
1304             guint needed_size = data->length * 4 / 3 + data->length * 4 / (3 * 72) + 7;
1305             needed_size += 5 + 8 + strlen(atom_name); // 5 bytes for data:, 8 for ;base64,
1306             
1307             gchar *buffer = (gchar *) g_malloc(needed_size), *buf_work = buffer;
1308             buf_work += g_sprintf(buffer, "data:%s;base64,", atom_name);
1309             
1310             gint state = 0, save = 0;
1311             g_base64_encode_step(data->data, data->length, TRUE, buf_work, &state, &save);
1312             g_base64_encode_close(TRUE, buf_work, &state, &save);
1314             newImage->setAttribute("xlink:href", buffer);
1315             g_free(buffer);
1317             GError *error = NULL;
1318             GdkPixbufLoader *loader = gdk_pixbuf_loader_new_with_mime_type( gdk_atom_name(data->type), &error );
1319             if ( loader ) {
1320                 error = NULL;
1321                 if ( gdk_pixbuf_loader_write( loader, data->data, data->length, &error) ) {
1322                     GdkPixbuf *pbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1323                     if ( pbuf ) {
1324                         char tmp[1024];
1325                         int width = gdk_pixbuf_get_width(pbuf);
1326                         int height = gdk_pixbuf_get_height(pbuf);
1327                         snprintf( tmp, sizeof(tmp), "%d", width );
1328                         newImage->setAttribute("width", tmp);
1330                         snprintf( tmp, sizeof(tmp), "%d", height );
1331                         newImage->setAttribute("height", tmp);
1332                     }
1333                 }
1334             }
1335             g_free(atom_name);
1337             // Add it to the current layer
1338             desktop->currentLayer()->appendChildRepr(newImage);
1340             Inkscape::GC::release(newImage);
1341             sp_document_done( doc , SP_VERB_NONE,
1342                               _("Drop bitmap image"));
1343             break;
1344         }
1345     }
1348 #include "gradient-context.h"
1350 void sp_ui_drag_motion( GtkWidget */*widget*/,
1351                         GdkDragContext */*drag_context*/,
1352                         gint /*x*/, gint /*y*/,
1353                         GtkSelectionData */*data*/,
1354                         guint /*info*/,
1355                         guint /*event_time*/,
1356                         gpointer /*user_data*/)
1358 //     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1359 //     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1362 //     g_message("drag-n-drop motion (%4d, %4d)  at %d", x, y, event_time);
1365 static void sp_ui_drag_leave( GtkWidget */*widget*/,
1366                               GdkDragContext */*drag_context*/,
1367                               guint /*event_time*/,
1368                               gpointer /*user_data*/ )
1370 //     g_message("drag-n-drop leave                at %d", event_time);
1373 static void
1374 sp_ui_import_files(gchar *buffer)
1376     GList *list = gnome_uri_list_extract_filenames(buffer);
1377     if (!list)
1378         return;
1379     g_list_foreach(list, sp_ui_import_one_file_with_check, NULL);
1380     g_list_foreach(list, (GFunc) g_free, NULL);
1381     g_list_free(list);
1384 static void
1385 sp_ui_import_one_file_with_check(gpointer filename, gpointer /*unused*/)
1387     if (filename) {
1388         if (strlen((char const *)filename) > 2)
1389             sp_ui_import_one_file((char const *)filename);
1390     }
1393 static void
1394 sp_ui_import_one_file(char const *filename)
1396     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1397     if (!doc) return;
1399     if (filename == NULL) return;
1401     // Pass off to common implementation
1402     // TODO might need to get the proper type of Inkscape::Extension::Extension
1403     file_import( doc, filename, NULL );
1406 void
1407 sp_ui_error_dialog(gchar const *message)
1409     GtkWidget *dlg;
1410     gchar *safeMsg = Inkscape::IO::sanitizeString(message);
1412     dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
1413                                  GTK_BUTTONS_CLOSE, "%s", safeMsg);
1414     sp_transientize(dlg);
1415     gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE);
1416     gtk_dialog_run(GTK_DIALOG(dlg));
1417     gtk_widget_destroy(dlg);
1418     g_free(safeMsg);
1421 bool
1422 sp_ui_overwrite_file(gchar const *filename)
1424     bool return_value = FALSE;
1426     if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) {
1427         Gtk::Window *window = SP_ACTIVE_DESKTOP->getToplevel();
1428         gchar* baseName = g_path_get_basename( filename );
1429         gchar* dirName = g_path_get_dirname( filename );
1430         GtkWidget* dialog = gtk_message_dialog_new_with_markup( window->gobj(),
1431                                                                 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
1432                                                                 GTK_MESSAGE_QUESTION,
1433                                                                 GTK_BUTTONS_NONE,
1434                                                                 _( "<span weight=\"bold\" size=\"larger\">A file named \"%s\" already exists. Do you want to replace it?</span>\n\n"
1435                                                                    "The file already exists in \"%s\". Replacing it will overwrite its contents." ),
1436                                                                 baseName,
1437                                                                 dirName
1438             );
1439         gtk_dialog_add_buttons( GTK_DIALOG(dialog),
1440                                 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1441                                 _("Replace"), GTK_RESPONSE_YES,
1442                                 NULL );
1443         gtk_dialog_set_default_response( GTK_DIALOG(dialog), GTK_RESPONSE_YES );
1445         if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_YES ) {
1446             return_value = TRUE;
1447         } else {
1448             return_value = FALSE;
1449         }
1450         gtk_widget_destroy(dialog);
1451         g_free( baseName );
1452         g_free( dirName );
1453     } else {
1454         return_value = TRUE;
1455     }
1457     return return_value;
1460 static void
1461 sp_ui_menu_item_set_sensitive(SPAction */*action*/, unsigned int sensitive, void *data)
1463     return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
1466 static void
1467 sp_ui_menu_item_set_name(SPAction */*action*/, Glib::ustring name, void *data)
1469     void *child = GTK_BIN (data)->child;
1470     //child is either
1471     //- a GtkHBox, whose first child is a label displaying name if the menu
1472     //item has an accel key
1473     //- a GtkLabel if the menu has no accel key
1474     if (GTK_IS_LABEL(child)) {
1475         gtk_label_set_markup_with_mnemonic(GTK_LABEL (child), name.c_str());
1476     } else if (GTK_IS_HBOX(child)) {
1477         gtk_label_set_markup_with_mnemonic(
1478         GTK_LABEL (gtk_container_get_children(GTK_CONTAINER (child))->data),
1479         name.c_str());
1480     }//else sp_ui_menu_append_item_from_verb has been modified and can set
1481     //a menu item in yet another way...
1485 /*
1486   Local Variables:
1487   mode:c++
1488   c-file-style:"stroustrup"
1489   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1490   indent-tabs-mode:nil
1491   fill-column:99
1492   End:
1493 */
1494 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :