Code

When closing a window and while asking whether to save, Inkscape should not be mislea...
[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;
102 #define ENTRIES_SIZE(n) sizeof(n)/sizeof(n[0])
103 static guint nui_drop_target_entries = ENTRIES_SIZE(ui_drop_target_entries);
104 static void sp_ui_import_files(gchar *buffer);
105 static void sp_ui_import_one_file(char const *filename);
106 static void sp_ui_import_one_file_with_check(gpointer filename, gpointer unused);
107 static void sp_ui_drag_data_received(GtkWidget *widget,
108                                      GdkDragContext *drag_context,
109                                      gint x, gint y,
110                                      GtkSelectionData *data,
111                                      guint info,
112                                      guint event_time,
113                                      gpointer user_data);
114 static void sp_ui_drag_motion( GtkWidget *widget,
115                                GdkDragContext *drag_context,
116                                gint x, gint y,
117                                GtkSelectionData *data,
118                                guint info,
119                                guint event_time,
120                                gpointer user_data );
121 static void sp_ui_drag_leave( GtkWidget *widget,
122                               GdkDragContext *drag_context,
123                               guint event_time,
124                               gpointer user_data );
125 static void sp_ui_menu_item_set_sensitive(SPAction *action,
126                                           unsigned int sensitive,
127                                           void *data);
128 static void sp_ui_menu_item_set_name(SPAction *action,
129                                      Glib::ustring name,
130                                      void *data);
131 static void sp_recent_open(GtkRecentChooser *, gpointer);
133 SPActionEventVector menu_item_event_vector = {
134     {NULL},
135     NULL,
136     NULL, /* set_active */
137     sp_ui_menu_item_set_sensitive, /* set_sensitive */
138     NULL, /* set_shortcut */
139     sp_ui_menu_item_set_name /* set_name */
140 };
142 static const int MIN_ONSCREEN_DISTANCE = 50;
144 void
145 sp_create_window(SPViewWidget *vw, gboolean editable)
147     g_return_if_fail(vw != NULL);
148     g_return_if_fail(SP_IS_VIEW_WIDGET(vw));
150     Gtk::Window *win = Inkscape::UI::window_new("", TRUE);
152     gtk_container_add(GTK_CONTAINER(win->gobj()), GTK_WIDGET(vw));
153     gtk_widget_show(GTK_WIDGET(vw));
155     if (editable) {
156         g_object_set_data(G_OBJECT(vw), "window", win);
158         SPDesktopWidget *desktop_widget = reinterpret_cast<SPDesktopWidget*>(vw);
159         SPDesktop* desktop = desktop_widget->desktop;
161         desktop_widget->window = win;
163         win->set_data("desktop", desktop);
164         win->set_data("desktopwidget", desktop_widget);
166         win->signal_delete_event().connect(sigc::mem_fun(*(SPDesktop*)vw->view, &SPDesktop::onDeleteUI));
167         win->signal_window_state_event().connect(sigc::mem_fun(*desktop, &SPDesktop::onWindowStateEvent));
168         win->signal_focus_in_event().connect(sigc::mem_fun(*desktop_widget, &SPDesktopWidget::onFocusInEvent));
170         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
171         gint prefs_geometry =
172             (2==prefs->getInt("/options/savewindowgeometry/value", 0));
173         if (prefs_geometry) {
174             gint pw = prefs->getInt("/desktop/geometry/width", -1);
175             gint ph = prefs->getInt("/desktop/geometry/height", -1);
176             gint px = prefs->getInt("/desktop/geometry/x", -1);
177             gint py = prefs->getInt("/desktop/geometry/y", -1);
178             gint full = prefs->getBool("/desktop/geometry/fullscreen");
179             gint maxed = prefs->getBool("/desktop/geometry/maximized");
180             if (pw>0 && ph>0) {
181                 gint w = MIN(gdk_screen_width(), pw);
182                 gint h = MIN(gdk_screen_height(), ph);
183                 gint x = MIN(gdk_screen_width() - MIN_ONSCREEN_DISTANCE, px);
184                 gint y = MIN(gdk_screen_height() - MIN_ONSCREEN_DISTANCE, py);
185                 if (w>0 && h>0) {
186                     x = MIN(gdk_screen_width() - w, x);
187                     y = MIN(gdk_screen_height() - h, y);
188                     desktop->setWindowSize(w, h);
189                 }
191                 // Only restore xy for the first window so subsequent windows don't overlap exactly
192                 // with first.  (Maybe rule should be only restore xy if it's different from xy of
193                 // other desktops?)
195                 // Empirically it seems that active_desktop==this desktop only the first time a
196                 // desktop is created.
197                 SPDesktop *active_desktop = SP_ACTIVE_DESKTOP;
198                 if (active_desktop == desktop || active_desktop==NULL) {
199                     desktop->setWindowPosition(Geom::Point(x, y));
200                 }
201             }
202             if (maxed) {
203                 win->maximize();
204             }
205             if (full) {
206                 win->fullscreen();
207             }
208         }
210     } else {
211         gtk_window_set_policy(GTK_WINDOW(win->gobj()), TRUE, TRUE, TRUE);
212     }
214     if ( completeDropTargets == 0 || completeDropTargetsCount == 0 )
215     {
216         std::vector<gchar*> types;
218         GSList *list = gdk_pixbuf_get_formats();
219         while ( list ) {
220             int i = 0;
221             GdkPixbufFormat *one = (GdkPixbufFormat*)list->data;
222             gchar** typesXX = gdk_pixbuf_format_get_mime_types(one);
223             for ( i = 0; typesXX[i]; i++ ) {
224                 types.push_back(g_strdup(typesXX[i]));
225             }
226             g_strfreev(typesXX);
228             list = g_slist_next(list);
229         }
230         completeDropTargetsCount = nui_drop_target_entries + types.size();
231         completeDropTargets = new GtkTargetEntry[completeDropTargetsCount];
232         for ( int i = 0; i < (int)nui_drop_target_entries; i++ ) {
233             completeDropTargets[i] = ui_drop_target_entries[i];
234         }
235         int pos = nui_drop_target_entries;
237         for (std::vector<gchar*>::iterator it = types.begin() ; it != types.end() ; it++) {
238             completeDropTargets[pos].target = *it;
239             completeDropTargets[pos].flags = 0;
240             completeDropTargets[pos].info = IMAGE_DATA;
241             pos++;
242         }
243     }
245     gtk_drag_dest_set((GtkWidget*)win->gobj(),
246                       GTK_DEST_DEFAULT_ALL,
247                       completeDropTargets,
248                       completeDropTargetsCount,
249                       GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE));
252     g_signal_connect(G_OBJECT(win->gobj()),
253                      "drag_data_received",
254                      G_CALLBACK(sp_ui_drag_data_received),
255                      NULL);
256     g_signal_connect(G_OBJECT(win->gobj()),
257                      "drag_motion",
258                      G_CALLBACK(sp_ui_drag_motion),
259                      NULL);
260     g_signal_connect(G_OBJECT(win->gobj()),
261                      "drag_leave",
262                      G_CALLBACK(sp_ui_drag_leave),
263                      NULL);
264     win->show();
266     // needed because the first ACTIVATE_DESKTOP was sent when there was no window yet
267     inkscape_reactivate_desktop(SP_DESKTOP_WIDGET(vw)->desktop);
270 void
271 sp_ui_new_view()
273     SPDocument *document;
274     SPViewWidget *dtw;
276     document = SP_ACTIVE_DOCUMENT;
277     if (!document) return;
279     dtw = sp_desktop_widget_new(sp_document_namedview(document, NULL));
280     g_return_if_fail(dtw != NULL);
282     sp_create_window(dtw, TRUE);
283     sp_namedview_window_from_document(static_cast<SPDesktop*>(dtw->view));
284     sp_namedview_update_layers_from_document(static_cast<SPDesktop*>(dtw->view));
287 /* TODO: not yet working */
288 /* To be re-enabled (by adding to menu) once it works. */
289 void
290 sp_ui_new_view_preview()
292     SPDocument *document;
293     SPViewWidget *dtw;
295     document = SP_ACTIVE_DOCUMENT;
296     if (!document) return;
298     dtw = (SPViewWidget *) sp_svg_view_widget_new(document);
299     g_return_if_fail(dtw != NULL);
300     sp_svg_view_widget_set_resize(SP_SVG_VIEW_WIDGET(dtw), TRUE, 400.0, 400.0);
302     sp_create_window(dtw, FALSE);
305 /**
306  * \param widget unused
307  */
308 void
309 sp_ui_close_view(GtkWidget */*widget*/)
311         SPDesktop *dt = SP_ACTIVE_DESKTOP;
313         if (dt == NULL) {
314         return;
315     }
317     if (dt->shutdown()) {
318         return; // Shutdown operation has been canceled, so do nothing
319     }
321     // Shutdown can proceed; use the stored reference to the desktop here instead of the current SP_ACTIVE_DESKTOP,
322     // because the user might have changed the focus in the meantime (see bug #381357 on Launchpad)
323     dt->destroyWidget();
327 /**
328  *  sp_ui_close_all
329  *
330  *  This function is called to exit the program, and iterates through all
331  *  open document view windows, attempting to close each in turn.  If the
332  *  view has unsaved information, the user will be prompted to save,
333  *  discard, or cancel.
334  *
335  *  Returns FALSE if the user cancels the close_all operation, TRUE
336  *  otherwise.
337  */
338 unsigned int
339 sp_ui_close_all(void)
341     /* Iterate through all the windows, destroying each in the order they
342        become active */
343     while (SP_ACTIVE_DESKTOP) {
344         SPDesktop *dt = SP_ACTIVE_DESKTOP;
345         if (dt->shutdown()) {
346             /* The user canceled the operation, so end doing the close */
347             return FALSE;
348         }
349         // Shutdown can proceed; use the stored reference to the desktop here instead of the current SP_ACTIVE_DESKTOP,
350         // because the user might have changed the focus in the meantime (see bug #381357 on Launchpad)
351         dt->destroyWidget();
352     }
354     return TRUE;
357 /*
358  * Some day when the right-click menus are ready to start working
359  * smarter with the verbs, we'll need to change this NULL being
360  * sent to sp_action_perform to something useful, or set some kind
361  * of global "right-clicked position" variable for actions to
362  * investigate when they're called.
363  */
364 static void
365 sp_ui_menu_activate(void */*object*/, SPAction *action)
367     sp_action_perform(action, NULL);
370 static void
371 sp_ui_menu_select_action(void */*object*/, SPAction *action)
373     action->view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, action->tip);
376 static void
377 sp_ui_menu_deselect_action(void */*object*/, SPAction *action)
379     action->view->tipsMessageContext()->clear();
382 static void
383 sp_ui_menu_select(gpointer object, gpointer tip)
385     Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view"));
386     view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, (gchar *)tip);
389 static void
390 sp_ui_menu_deselect(gpointer object)
392     Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*>  (g_object_get_data(G_OBJECT(object), "view"));
393     view->tipsMessageContext()->clear();
396 /**
397  * sp_ui_menuitem_add_icon
398  *
399  * Creates and attaches a scaled icon to the given menu item.
400  *
401  */
402 void
403 sp_ui_menuitem_add_icon( GtkWidget *item, gchar *icon_name )
405     GtkWidget *icon;
407     icon = sp_icon_new( Inkscape::ICON_SIZE_MENU, icon_name );
408     gtk_widget_show(icon);
409     gtk_image_menu_item_set_image((GtkImageMenuItem *) item, icon);
410 } // end of sp_ui_menu_add_icon
412 /**
413  * sp_ui_menu_append_item
414  *
415  * Appends a UI item with specific info for Inkscape/Sodipodi.
416  *
417  */
418 static GtkWidget *
419 sp_ui_menu_append_item( GtkMenu *menu, gchar const *stock,
420                         gchar const *label, gchar const *tip, Inkscape::UI::View::View *view, GCallback callback,
421                         gpointer data, gboolean with_mnemonic = TRUE )
423     GtkWidget *item;
425     if (stock) {
426         item = gtk_image_menu_item_new_from_stock(stock, NULL);
427     } else if (label) {
428         item = (with_mnemonic)
429             ? gtk_image_menu_item_new_with_mnemonic(label) :
430             gtk_image_menu_item_new_with_label(label);
431     } else {
432         item = gtk_separator_menu_item_new();
433     }
435     gtk_widget_show(item);
437     if (callback) {
438         g_signal_connect(G_OBJECT(item), "activate", callback, data);
439     }
441     if (tip && view) {
442         g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
443         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) tip );
444         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
445     }
447     gtk_menu_append(GTK_MENU(menu), item);
449     return item;
451 } // end of sp_ui_menu_append_item()
453 /**
454 \brief  a wrapper around gdk_keyval_name producing (when possible) characters, not names
455  */
456 static gchar const *
457 sp_key_name(guint keyval)
459     /* TODO: Compare with the definition of gtk_accel_label_refetch in gtk/gtkaccellabel.c (or
460        simply use GtkAccelLabel as the TODO comment in sp_ui_shortcut_string suggests). */
461     gchar const *n = gdk_keyval_name(gdk_keyval_to_upper(keyval));
463     if      (!strcmp(n, "asciicircum"))  return "^";
464     else if (!strcmp(n, "parenleft"  ))  return "(";
465     else if (!strcmp(n, "parenright" ))  return ")";
466     else if (!strcmp(n, "plus"       ))  return "+";
467     else if (!strcmp(n, "minus"      ))  return "-";
468     else if (!strcmp(n, "asterisk"   ))  return "*";
469     else if (!strcmp(n, "KP_Multiply"))  return "*";
470     else if (!strcmp(n, "Delete"     ))  return "Del";
471     else if (!strcmp(n, "Page_Up"    ))  return "PgUp";
472     else if (!strcmp(n, "Page_Down"  ))  return "PgDn";
473     else if (!strcmp(n, "grave"      ))  return "`";
474     else if (!strcmp(n, "numbersign" ))  return "#";
475     else if (!strcmp(n, "bar"        ))  return "|";
476     else if (!strcmp(n, "slash"      ))  return "/";
477     else if (!strcmp(n, "exclam"     ))  return "!";
478     else if (!strcmp(n, "percent"    ))  return "%";
479     else return n;
483 /**
484  * \param shortcut A GDK keyval OR'd with SP_SHORTCUT_blah_MASK values.
485  * \param c Points to a buffer at least 256 bytes long.
486  */
487 void
488 sp_ui_shortcut_string(unsigned const shortcut, gchar *const c)
490     /* TODO: This function shouldn't exist.  Our callers should use GtkAccelLabel instead of
491      * a generic GtkLabel containing this string, and should call gtk_widget_add_accelerator.
492      * Will probably need to change sp_shortcut_invoke callers.
493      *
494      * The existing gtk_label_new_with_mnemonic call can be replaced with
495      * g_object_new(GTK_TYPE_ACCEL_LABEL, NULL) followed by
496      * gtk_label_set_text_with_mnemonic(lbl, str).
497      */
498     static GtkAccelLabelClass const &accel_lbl_cls
499         = *(GtkAccelLabelClass const *) g_type_class_peek_static(GTK_TYPE_ACCEL_LABEL);
501     struct { unsigned test; char const *name; } const modifier_tbl[] = {
502         { SP_SHORTCUT_SHIFT_MASK,   accel_lbl_cls.mod_name_shift   },
503         { SP_SHORTCUT_CONTROL_MASK, accel_lbl_cls.mod_name_control },
504         { SP_SHORTCUT_ALT_MASK,     accel_lbl_cls.mod_name_alt     }
505     };
507     gchar *p = c;
508     gchar *end = p + 256;
510     for (unsigned i = 0; i < G_N_ELEMENTS(modifier_tbl); ++i) {
511         if ((shortcut & modifier_tbl[i].test)
512             && (p < end))
513         {
514             p += g_snprintf(p, end - p, "%s%s",
515                             modifier_tbl[i].name,
516                             accel_lbl_cls.mod_separator);
517         }
518     }
519     if (p < end) {
520         p += g_snprintf(p, end - p, "%s", sp_key_name(shortcut & 0xffffff));
521     }
522     end[-1] = '\0';  // snprintf doesn't guarantee to nul-terminate the string.
525 void
526 sp_ui_dialog_title_string(Inkscape::Verb *verb, gchar *c)
528     SPAction     *action;
529     unsigned int shortcut;
530     gchar        *s;
531     gchar        key[256];
532     gchar        *atitle;
534     action = verb->get_action(NULL);
535     if (!action)
536         return;
538     atitle = sp_action_get_title(action);
540     s = g_stpcpy(c, atitle);
542     g_free(atitle);
544     shortcut = sp_shortcut_get_primary(verb);
545     if (shortcut) {
546         s = g_stpcpy(s, " (");
547         sp_ui_shortcut_string(shortcut, key);
548         s = g_stpcpy(s, key);
549         s = g_stpcpy(s, ")");
550     }
554 /**
555  * sp_ui_menu_append_item_from_verb
556  *
557  * Appends a custom menu UI from a verb.
558  *
559  */
561 static GtkWidget *
562 sp_ui_menu_append_item_from_verb(GtkMenu *menu, Inkscape::Verb *verb, Inkscape::UI::View::View *view, bool radio = false, GSList *group = NULL)
564     SPAction *action;
565     GtkWidget *item;
567     if (verb->get_code() == SP_VERB_NONE) {
569         item = gtk_separator_menu_item_new();
571     } else {
572         unsigned int shortcut;
574         action = verb->get_action(view);
576         if (!action) return NULL;
578         shortcut = sp_shortcut_get_primary(verb);
579         if (shortcut) {
580             gchar c[256];
581             sp_ui_shortcut_string(shortcut, c);
582             GtkWidget *const hb = gtk_hbox_new(FALSE, 16);
583             GtkWidget *const name_lbl = gtk_label_new("");
584             gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
585             gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
586             gtk_box_pack_start((GtkBox *) hb, name_lbl, TRUE, TRUE, 0);
587             GtkWidget *const accel_lbl = gtk_label_new(c);
588             gtk_misc_set_alignment((GtkMisc *) accel_lbl, 1.0, 0.5);
589             gtk_box_pack_end((GtkBox *) hb, accel_lbl, FALSE, FALSE, 0);
590             gtk_widget_show_all(hb);
591             if (radio) {
592                 item = gtk_radio_menu_item_new (group);
593             } else {
594                 item = gtk_image_menu_item_new();
595             }
596             gtk_container_add((GtkContainer *) item, hb);
597         } else {
598             if (radio) {
599                 item = gtk_radio_menu_item_new (group);
600             } else {
601                 item = gtk_image_menu_item_new ();
602             }
603             GtkWidget *const name_lbl = gtk_label_new("");
604             gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
605             gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
606             gtk_container_add((GtkContainer *) item, name_lbl);
607         }
609         nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
610         if (!action->sensitive) {
611             gtk_widget_set_sensitive(item, FALSE);
612         }
614         if (action->image) {
615             sp_ui_menuitem_add_icon(item, action->image);
616         }
617         gtk_widget_set_events(item, GDK_KEY_PRESS_MASK);
618         g_signal_connect( G_OBJECT(item), "activate",
619                           G_CALLBACK(sp_ui_menu_activate), action );
621         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select_action), action );
622         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect_action), action );
623     }
625     gtk_widget_show(item);
626     gtk_menu_append(GTK_MENU(menu), item);
628     return item;
630 } // end of sp_ui_menu_append_item_from_verb
633 static void
634 checkitem_toggled(GtkCheckMenuItem *menuitem, gpointer user_data)
636     gchar const *pref = (gchar const *) user_data;
637     Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
639     Glib::ustring pref_path;
640     if (reinterpret_cast<SPDesktop*>(view)->is_focusMode()) {
641         pref_path = "/focus/";
642     } else if (reinterpret_cast<SPDesktop*>(view)->is_fullscreen()) {
643         pref_path = "/fullscreen/";
644     } else {
645         pref_path = "/window/";
646     }
647     pref_path += pref;
648     pref_path += "/state";
650     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
651     gboolean checked = gtk_check_menu_item_get_active(menuitem);
652     prefs->setBool(pref_path, checked);
654     reinterpret_cast<SPDesktop*>(view)->layoutWidget();
657 static gboolean
658 checkitem_update(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_data)
660     GtkCheckMenuItem *menuitem=GTK_CHECK_MENU_ITEM(widget);
662     gchar const *pref = (gchar const *) user_data;
663     Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
665     Glib::ustring pref_path;
666     if ((static_cast<SPDesktop*>(view))->is_fullscreen()) {
667         pref_path = "/fullscreen/";
668     } else {
669         pref_path = "/window/";
670     }
671     pref_path += pref;
673     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
674     bool ison = prefs->getBool(pref_path + "/state", true);
676     g_signal_handlers_block_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
677     gtk_check_menu_item_set_active(menuitem, ison);
678     g_signal_handlers_unblock_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
680     return FALSE;
684 void
685 sp_ui_menu_append_check_item_from_verb(GtkMenu *menu, Inkscape::UI::View::View *view, gchar const *label, gchar const *tip, gchar const *pref,
686                                        void (*callback_toggle)(GtkCheckMenuItem *, gpointer user_data),
687                                        gboolean (*callback_update)(GtkWidget *widget, GdkEventExpose *event, gpointer user_data),
688                                        Inkscape::Verb *verb)
690     GtkWidget *item;
692     unsigned int shortcut = 0;
693     SPAction *action = NULL;
695     if (verb) {
696         shortcut = sp_shortcut_get_primary(verb);
697         action = verb->get_action(view);
698     }
700     if (verb && shortcut) {
701         gchar c[256];
702         sp_ui_shortcut_string(shortcut, c);
704         GtkWidget *hb = gtk_hbox_new(FALSE, 16);
706         {
707             GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
708             gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
709             gtk_box_pack_start((GtkBox *) hb, l, TRUE, TRUE, 0);
710         }
712         {
713             GtkWidget *l = gtk_label_new(c);
714             gtk_misc_set_alignment((GtkMisc *) l, 1.0, 0.5);
715             gtk_box_pack_end((GtkBox *) hb, l, FALSE, FALSE, 0);
716         }
718         gtk_widget_show_all(hb);
720         item = gtk_check_menu_item_new();
721         gtk_container_add((GtkContainer *) item, hb);
722     } else {
723         GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
724         gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
725         item = gtk_check_menu_item_new();
726         gtk_container_add((GtkContainer *) item, l);
727     }
728 #if 0
729     nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
730     if (!action->sensitive) {
731         gtk_widget_set_sensitive(item, FALSE);
732     }
733 #endif
734     gtk_widget_show(item);
736     gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
738     g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
740     g_signal_connect( G_OBJECT(item), "toggled", (GCallback) callback_toggle, (void *) pref);
741     g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) callback_update, (void *) pref);
743     g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) (action ? action->tip : tip));
744     g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
747 static void
748 sp_recent_open(GtkRecentChooser *recent_menu, gpointer /*user_data*/)
750     // dealing with the bizarre filename convention in Inkscape for now
751     gchar *uri = gtk_recent_chooser_get_current_uri(GTK_RECENT_CHOOSER(recent_menu));
752     gchar *local_fn = g_filename_from_uri(uri, NULL, NULL);
753     gchar *utf8_fn = g_filename_to_utf8(local_fn, -1, NULL, NULL, NULL);
754     sp_file_open(utf8_fn, NULL);
755     g_free(utf8_fn);
756     g_free(local_fn);
757     g_free(uri);
760 static void
761 sp_file_new_from_template(GtkWidget */*widget*/, gchar const *uri)
763     sp_file_new(uri);
766 void
767 sp_menu_append_new_templates(GtkWidget *menu, Inkscape::UI::View::View *view)
769     std::list<gchar *> sources;
770     sources.push_back( profile_path("templates") ); // first try user's local dir
771     sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir
773     // Use this loop to iterate through a list of possible document locations.
774     while (!sources.empty()) {
775         gchar *dirname = sources.front();
777         if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) {
778             GError *err = 0;
779             GDir *dir = g_dir_open(dirname, 0, &err);
781             if (dir) {
782                 for (gchar const *file = g_dir_read_name(dir); file != NULL; file = g_dir_read_name(dir)) {
783                     if (!g_str_has_suffix(file, ".svg") && !g_str_has_suffix(file, ".svgz"))
784                         continue; // skip non-svg files
786                     gchar *basename = g_path_get_basename(file);
787                     if (g_str_has_suffix(basename, ".svg") && g_str_has_prefix(basename, "default."))
788                         continue; // skip default.*.svg (i.e. default.svg and translations) - it's in the menu already
790                     gchar const *filepath = g_build_filename(dirname, file, NULL);
791                     gchar *dupfile = g_strndup(file, strlen(file) - 4);
792                     gchar *filename =  g_filename_to_utf8(dupfile,  -1, NULL, NULL, NULL);
793                     g_free(dupfile);
794                     GtkWidget *item = gtk_menu_item_new_with_label(filename);
795                     g_free(filename);
797                     gtk_widget_show(item);
798                     // how does "filepath" ever get freed?
799                     g_signal_connect(G_OBJECT(item),
800                                      "activate",
801                                      G_CALLBACK(sp_file_new_from_template),
802                                      (gpointer) filepath);
804                     if (view) {
805                         // set null tip for now; later use a description from the template file
806                         g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
807                         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) NULL );
808                         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
809                     }
811                     gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
812                 }
813                 g_dir_close(dir);
814             }
815         }
817         // toss the dirname
818         g_free(dirname);
819         sources.pop_front();
820     }
823 void
824 sp_ui_checkboxes_menus(GtkMenu *m, Inkscape::UI::View::View *view)
826     //sp_ui_menu_append_check_item_from_verb(m, view, _("_Menu"), _("Show or hide the menu bar"), "menu",
827     //                                       checkitem_toggled, checkitem_update, 0);
828     sp_ui_menu_append_check_item_from_verb(m, view, _("Commands Bar"), _("Show or hide the Commands bar (under the menu)"), "commands",
829                                            checkitem_toggled, checkitem_update, 0);
830     sp_ui_menu_append_check_item_from_verb(m, view, _("Snap Controls Bar"), _("Show or hide the snapping controls"), "snaptoolbox",
831                                            checkitem_toggled, checkitem_update, 0);
832     sp_ui_menu_append_check_item_from_verb(m, view, _("Tool Controls Bar"), _("Show or hide the Tool Controls bar"), "toppanel",
833                                            checkitem_toggled, checkitem_update, 0);
834     sp_ui_menu_append_check_item_from_verb(m, view, _("_Toolbox"), _("Show or hide the main toolbox (on the left)"), "toolbox",
835                                            checkitem_toggled, checkitem_update, 0);
836     sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "rulers",
837                                            checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_RULERS));
838     sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "scrollbars",
839                                            checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_SCROLLBARS));
840     sp_ui_menu_append_check_item_from_verb(m, view, _("_Palette"), _("Show or hide the color palette"), "panels",
841                                            checkitem_toggled, checkitem_update, 0);
842     sp_ui_menu_append_check_item_from_verb(m, view, _("_Statusbar"), _("Show or hide the statusbar (at the bottom of the window)"), "statusbar",
843                                            checkitem_toggled, checkitem_update, 0);
846 /** @brief Observer that updates the recent list's max document count */
847 class MaxRecentObserver : public Inkscape::Preferences::Observer {
848 public:
849     MaxRecentObserver(GtkWidget *recent_menu) :
850         Observer("/options/maxrecentdocuments/value"),
851         _rm(recent_menu)
852     {}
853     virtual void notify(Inkscape::Preferences::Entry const &e) {
854         gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(_rm), e.getInt());
855         // hack: the recent menu doesn't repopulate after changing the limit, so we force it
856         g_signal_emit_by_name((gpointer) gtk_recent_manager_get_default(), "changed");
857     }
858 private:
859     GtkWidget *_rm;
860 };
862 /** \brief  This function turns XML into a menu
863     \param  menus  This is the XML that defines the menu
864     \param  menu   Menu to be added to
865     \param  view   The View that this menu is being built for
867     This function is realitively simple as it just goes through the XML
868     and parses the individual elements.  In the case of a submenu, it
869     just calls itself recursively.  Because it is only reasonable to have
870     a couple of submenus, it is unlikely this will go more than two or
871     three times.
873     In the case of an unreconginzed verb, a menu item is made to identify
874     the verb that is missing, and display that.  The menu item is also made
875     insensitive.
876 */
877 void
878 sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI::View::View *view)
880     if (menus == NULL) return;
881     if (menu == NULL)  return;
882     GSList *group = NULL;
884     for (Inkscape::XML::Node *menu_pntr = menus;
885          menu_pntr != NULL;
886          menu_pntr = menu_pntr->next()) {
887         if (!strcmp(menu_pntr->name(), "submenu")) {
888             GtkWidget *mitem = gtk_menu_item_new_with_mnemonic(_(menu_pntr->attribute("name")));
889             GtkWidget *submenu = gtk_menu_new();
890             sp_ui_build_dyn_menus(menu_pntr->firstChild(), submenu, view);
891             gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), GTK_WIDGET(submenu));
892             gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
893             continue;
894         }
895         if (!strcmp(menu_pntr->name(), "verb")) {
896             gchar const *verb_name = menu_pntr->attribute("verb-id");
897             Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name);
899             if (verb != NULL) {
900                 if (menu_pntr->attribute("radio") != NULL) {
901                     GtkWidget *item = sp_ui_menu_append_item_from_verb (GTK_MENU(menu), verb, view, true, group);
902                     group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item));
903                     if (menu_pntr->attribute("default") != NULL) {
904                         gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
905                     }
906                 } else {
907                     sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view);
908                     group = NULL;
909                 }
910             } else {
911                 gchar string[120];
912                 g_snprintf(string, 120, _("Verb \"%s\" Unknown"), verb_name);
913                 string[119] = '\0'; /* may not be terminated */
914                 GtkWidget *item = gtk_menu_item_new_with_label(string);
915                 gtk_widget_set_sensitive(item, false);
916                 gtk_widget_show(item);
917                 gtk_menu_append(GTK_MENU(menu), item);
918             }
919             continue;
920         }
921         if (!strcmp(menu_pntr->name(), "separator")
922                 // This was spelt wrong in the original version
923                 // and so this is for backward compatibility.  It can
924                 // probably be dropped after the 0.44 release.
925              || !strcmp(menu_pntr->name(), "seperator")) {
926             GtkWidget *item = gtk_separator_menu_item_new();
927             gtk_widget_show(item);
928             gtk_menu_append(GTK_MENU(menu), item);
929             continue;
930         }
931         if (!strcmp(menu_pntr->name(), "template-list")) {
932             sp_menu_append_new_templates(menu, view);
933             continue;
934         }
935         if (!strcmp(menu_pntr->name(), "recent-file-list")) {
936             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
938             // create recent files menu
939             int max_recent = prefs->getInt("/options/maxrecentdocuments/value");
940             GtkWidget *recent_menu = gtk_recent_chooser_menu_new_for_manager(gtk_recent_manager_get_default());
941             gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(recent_menu), max_recent);
942             // sort most recently used documents first to preserve previous behavior
943             gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(recent_menu), GTK_RECENT_SORT_MRU);
944             g_signal_connect(G_OBJECT(recent_menu), "item-activated", G_CALLBACK(sp_recent_open), (gpointer) NULL);
946             // add filter to only open files added by Inkscape
947             GtkRecentFilter *inkscape_only_filter = gtk_recent_filter_new();
948             gtk_recent_filter_add_application(inkscape_only_filter, g_get_prgname());
949             gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_menu), inkscape_only_filter);
951             GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic(_("Open _Recent"));
952             gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), recent_menu);
954             gtk_menu_append(GTK_MENU(menu), GTK_WIDGET(recent_item));
955             // this will just sit and update the list's item count
956             static MaxRecentObserver *mro = new MaxRecentObserver(recent_menu);
957             prefs->addObserver(*mro);
958             continue;
959         }
960         if (!strcmp(menu_pntr->name(), "objects-checkboxes")) {
961             sp_ui_checkboxes_menus(GTK_MENU(menu), view);
962             continue;
963         }
964     }
967 /** \brief  Build the main tool bar
968     \param  view  View to build the bar for
970     Currently the main tool bar is built as a dynamic XML menu using
971     \c sp_ui_build_dyn_menus.  This function builds the bar, and then
972     pass it to get items attached to it.
973 */
974 GtkWidget *
975 sp_ui_main_menubar(Inkscape::UI::View::View *view)
977     GtkWidget *mbar = gtk_menu_bar_new();
979 #ifdef GDK_WINDOWING_QUARTZ
980     ige_mac_menu_set_menu_bar(GTK_MENU_SHELL(mbar));
981 #endif
983     sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view);
985 #ifdef GDK_WINDOWING_QUARTZ
986     return NULL;
987 #else
988     return mbar;
989 #endif
992 static void leave_group(GtkMenuItem *, SPDesktop *desktop) {
993     desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer()));
996 static void enter_group(GtkMenuItem *mi, SPDesktop *desktop) {
997     desktop->setCurrentLayer(reinterpret_cast<SPObject *>(g_object_get_data(G_OBJECT(mi), "group")));
998     sp_desktop_selection(desktop)->clear();
1001 GtkWidget *
1002 sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item)
1004     GtkWidget *m;
1005     SPDesktop *dt;
1007     dt = static_cast<SPDesktop*>(view);
1009     m = gtk_menu_new();
1011     /* Undo and Redo */
1012     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_UNDO), view);
1013     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_REDO), view);
1015     /* Separator */
1016     sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1018     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_CUT), view);
1019     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_COPY), view);
1020     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_PASTE), view);
1022     /* Separator */
1023     sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1025     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DUPLICATE), view);
1026     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DELETE), view);
1028     /* Item menu */
1029     if (item) {
1030         sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1031         sp_object_menu((SPObject *) item, dt, GTK_MENU(m));
1032     }
1034     /* layer menu */
1035     SPGroup *group=NULL;
1036     if (item) {
1037         if (SP_IS_GROUP(item)) {
1038             group = SP_GROUP(item);
1039         } else if ( item != dt->currentRoot() && SP_IS_GROUP(SP_OBJECT_PARENT(item)) ) {
1040             group = SP_GROUP(SP_OBJECT_PARENT(item));
1041         }
1042     }
1044     if (( group && group != dt->currentLayer() ) ||
1045         ( dt->currentLayer() != dt->currentRoot() && SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) ) {
1046         /* Separator */
1047         sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1048     }
1050     if ( group && group != dt->currentLayer() ) {
1051         /* TRANSLATORS: #%s is the id of the group e.g. <g id="#g7">, not a number. */
1052         gchar *label=g_strdup_printf(_("Enter group #%s"), SP_OBJECT_ID(group));
1053         GtkWidget *w = gtk_menu_item_new_with_label(label);
1054         g_free(label);
1055         g_object_set_data(G_OBJECT(w), "group", group);
1056         g_signal_connect(G_OBJECT(w), "activate", GCallback(enter_group), dt);
1057         gtk_widget_show(w);
1058         gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1059     }
1061     if ( dt->currentLayer() != dt->currentRoot() ) {
1062         if ( SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) {
1063             GtkWidget *w = gtk_menu_item_new_with_label(_("Go to parent"));
1064             g_signal_connect(G_OBJECT(w), "activate", GCallback(leave_group), dt);
1065             gtk_widget_show(w);
1066             gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1068         }
1069     }
1071     return m;
1074 /* Drag and Drop */
1075 void
1076 sp_ui_drag_data_received(GtkWidget *widget,
1077                          GdkDragContext *drag_context,
1078                          gint x, gint y,
1079                          GtkSelectionData *data,
1080                          guint info,
1081                          guint /*event_time*/,
1082                          gpointer /*user_data*/)
1084     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1085     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1087     switch (info) {
1088 #if ENABLE_MAGIC_COLORS
1089         case APP_X_INKY_COLOR:
1090         {
1091             int destX = 0;
1092             int destY = 0;
1093             gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1094             Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1096             SPItem *item = desktop->item_at_point( where, true );
1097             if ( item )
1098             {
1099                 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1101                 if ( data->length >= 8 ) {
1102                     cmsHPROFILE srgbProf = cmsCreate_sRGBProfile();
1104                     gchar c[64] = {0};
1105                     // Careful about endian issues.
1106                     guint16* dataVals = (guint16*)data->data;
1107                     sp_svg_write_color( c, sizeof(c),
1108                                         SP_RGBA32_U_COMPOSE(
1109                                             0x0ff & (dataVals[0] >> 8),
1110                                             0x0ff & (dataVals[1] >> 8),
1111                                             0x0ff & (dataVals[2] >> 8),
1112                                             0xff // can't have transparency in the color itself
1113                                             //0x0ff & (data->data[3] >> 8),
1114                                             ));
1115                     SPCSSAttr *css = sp_repr_css_attr_new();
1116                     bool updatePerformed = false;
1118                     if ( data->length > 14 ) {
1119                         int flags = dataVals[4];
1121                         // piggie-backed palette entry info
1122                         int index = dataVals[5];
1123                         Glib::ustring palName;
1124                         for ( int i = 0; i < dataVals[6]; i++ ) {
1125                             palName += (gunichar)dataVals[7+i];
1126                         }
1128                         // Now hook in a magic tag of some sort.
1129                         if ( !palName.empty() && (flags & 1) ) {
1130                             gchar* str = g_strdup_printf("%d|", index);
1131                             palName.insert( 0, str );
1132                             g_free(str);
1133                             str = 0;
1135                             sp_object_setAttribute( SP_OBJECT(item),
1136                                                     fillnotstroke ? "inkscape:x-fill-tag":"inkscape:x-stroke-tag",
1137                                                     palName.c_str(),
1138                                                     false );
1139                             item->updateRepr();
1141                             sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1142                             updatePerformed = true;
1143                         }
1144                     }
1146                     if ( !updatePerformed ) {
1147                         sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1148                     }
1150                     sp_desktop_apply_css_recursive( item, css, true );
1151                     item->updateRepr();
1153                     sp_document_done( doc , SP_VERB_NONE,
1154                                       _("Drop color"));
1156                     if ( srgbProf ) {
1157                         cmsCloseProfile( srgbProf );
1158                     }
1159                 }
1160             }
1161         }
1162         break;
1163 #endif // ENABLE_MAGIC_COLORS
1165         case APP_X_COLOR:
1166         {
1167             int destX = 0;
1168             int destY = 0;
1169             gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1170             Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1171             Geom::Point const button_dt(desktop->w2d(where));
1172             Geom::Point const button_doc(desktop->dt2doc(button_dt));
1174             if ( data->length == 8 ) {
1175                 gchar colorspec[64] = {0};
1176                 // Careful about endian issues.
1177                 guint16* dataVals = (guint16*)data->data;
1178                 sp_svg_write_color( colorspec, sizeof(colorspec),
1179                                     SP_RGBA32_U_COMPOSE(
1180                                         0x0ff & (dataVals[0] >> 8),
1181                                         0x0ff & (dataVals[1] >> 8),
1182                                         0x0ff & (dataVals[2] >> 8),
1183                                         0xff // can't have transparency in the color itself
1184                                         //0x0ff & (data->data[3] >> 8),
1185                                         ));
1187                 SPItem *item = desktop->item_at_point( where, true );
1189                 bool consumed = false;
1190                 if (desktop->event_context && desktop->event_context->get_drag()) {
1191                     consumed = desktop->event_context->get_drag()->dropColor(item, colorspec, button_dt);
1192                     if (consumed) {
1193                         sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1194                         desktop->event_context->get_drag()->updateDraggers();
1195                     }
1196                 }
1198                 //if (!consumed && tools_active(desktop, TOOLS_TEXT)) {
1199                 //    consumed = sp_text_context_drop_color(c, button_doc);
1200                 //    if (consumed) {
1201                 //        sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient stop"));
1202                 //    }
1203                 //}
1205                 if (!consumed && item) {
1206                     bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1207                     if (fillnotstroke &&
1208                         (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1209                         Path *livarot_path = Path_for_item(item, true, true);
1210                         livarot_path->ConvertWithBackData(0.04);
1212                         boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1213                         if (position) {
1214                             Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1215                             Geom::Point delta = nearest - button_doc;
1216                             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1217                             delta = desktop->d2w(delta);
1218                             double stroke_tolerance =
1219                                 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1220                                   desktop->current_zoom() *
1221                                   SP_OBJECT_STYLE (item)->stroke_width.computed *
1222                                   to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1223                                   : 0.0)
1224                                 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1226                             if (Geom::L2 (delta) < stroke_tolerance) {
1227                                 fillnotstroke = false;
1228                             }
1229                         }
1230                         delete livarot_path;
1231                     }
1233                     SPCSSAttr *css = sp_repr_css_attr_new();
1234                     sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec );
1236                     sp_desktop_apply_css_recursive( item, css, true );
1237                     item->updateRepr();
1239                     sp_document_done( doc , SP_VERB_NONE,
1240                                       _("Drop color"));
1241                 }
1242             }
1243         }
1244         break;
1246         case APP_OSWB_COLOR:
1247         {
1248             bool worked = false;
1249             Glib::ustring colorspec;
1250             if ( data->format == 8 ) {
1251                 ege::PaintDef color;
1252                 worked = color.fromMIMEData("application/x-oswb-color",
1253                                             reinterpret_cast<char*>(data->data),
1254                                             data->length,
1255                                             data->format);
1256                 if ( worked ) {
1257                     if ( color.getType() == ege::PaintDef::CLEAR ) {
1258                         colorspec = ""; // TODO check if this is sufficient
1259                     } else if ( color.getType() == ege::PaintDef::NONE ) {
1260                         colorspec = "none";
1261                     } else {
1262                         unsigned int r = color.getR();
1263                         unsigned int g = color.getG();
1264                         unsigned int b = color.getB();
1266                         SPGradient* matches = 0;
1267                         const GSList *gradients = sp_document_get_resource_list(doc, "gradient");
1268                         for (const GSList *item = gradients; item; item = item->next) {
1269                             SPGradient* grad = SP_GRADIENT(item->data);
1270                             if ( color.descr == grad->id ) {
1271                                 if ( grad->has_stops ) {
1272                                     matches = grad;
1273                                     break;
1274                                 }
1275                             }
1276                         }
1277                         if (matches) {
1278                             colorspec = "url(#";
1279                             colorspec += matches->id;
1280                             colorspec += ")";
1281                         } else {
1282                             gchar* tmp = g_strdup_printf("#%02x%02x%02x", r, g, b);
1283                             colorspec = tmp;
1284                             g_free(tmp);
1285                         }
1286                     }
1287                 }
1288             }
1289             if ( worked ) {
1290                 int destX = 0;
1291                 int destY = 0;
1292                 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1293                 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1294                 Geom::Point const button_dt(desktop->w2d(where));
1295                 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1297                 SPItem *item = desktop->item_at_point( where, true );
1299                 bool consumed = false;
1300                 if (desktop->event_context && desktop->event_context->get_drag()) {
1301                     consumed = desktop->event_context->get_drag()->dropColor(item, colorspec.c_str(), button_dt);
1302                     if (consumed) {
1303                         sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1304                         desktop->event_context->get_drag()->updateDraggers();
1305                     }
1306                 }
1308                 if (!consumed && item) {
1309                     bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1310                     if (fillnotstroke &&
1311                         (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1312                         Path *livarot_path = Path_for_item(item, true, true);
1313                         livarot_path->ConvertWithBackData(0.04);
1315                         boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1316                         if (position) {
1317                             Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1318                             Geom::Point delta = nearest - button_doc;
1319                             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1320                             delta = desktop->d2w(delta);
1321                             double stroke_tolerance =
1322                                 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1323                                   desktop->current_zoom() *
1324                                   SP_OBJECT_STYLE (item)->stroke_width.computed *
1325                                   to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1326                                   : 0.0)
1327                                 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1329                             if (Geom::L2 (delta) < stroke_tolerance) {
1330                                 fillnotstroke = false;
1331                             }
1332                         }
1333                         delete livarot_path;
1334                     }
1336                     SPCSSAttr *css = sp_repr_css_attr_new();
1337                     sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec.c_str() );
1339                     sp_desktop_apply_css_recursive( item, css, true );
1340                     item->updateRepr();
1342                     sp_document_done( doc , SP_VERB_NONE,
1343                                       _("Drop color"));
1344                 }
1345             }
1346         }
1347         break;
1349         case SVG_DATA:
1350         case SVG_XML_DATA: {
1351             gchar *svgdata = (gchar *)data->data;
1353             Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI);
1355             if (rnewdoc == NULL) {
1356                 sp_ui_error_dialog(_("Could not parse SVG data"));
1357                 return;
1358             }
1360             Inkscape::XML::Node *repr = rnewdoc->root();
1361             gchar const *style = repr->attribute("style");
1363             Inkscape::XML::Node *newgroup = rnewdoc->createElement("svg:g");
1364             newgroup->setAttribute("style", style);
1366             Inkscape::XML::Document * xml_doc =  sp_document_repr_doc(doc);
1367             for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) {
1368                 Inkscape::XML::Node *newchild = child->duplicate(xml_doc);
1369                 newgroup->appendChild(newchild);
1370             }
1372             Inkscape::GC::release(rnewdoc);
1374             // Add it to the current layer
1376             // Greg's edits to add intelligent positioning of svg drops
1377             SPObject *new_obj = NULL;
1378             new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
1380             Inkscape::Selection *selection = sp_desktop_selection(desktop);
1381             selection->set(SP_ITEM(new_obj));
1383             // move to mouse pointer
1384             {
1385                 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
1386                 Geom::OptRect sel_bbox = selection->bounds();
1387                 if (sel_bbox) {
1388                     Geom::Point m( desktop->point() - sel_bbox->midpoint() );
1389                     sp_selection_move_relative(selection, m, false);
1390                 }
1391             }
1393             Inkscape::GC::release(newgroup);
1394             sp_document_done(doc, SP_VERB_NONE,
1395                              _("Drop SVG"));
1396             break;
1397         }
1399         case URI_LIST: {
1400             gchar *uri = (gchar *)data->data;
1401             sp_ui_import_files(uri);
1402             break;
1403         }
1405         case PNG_DATA:
1406         case JPEG_DATA:
1407         case IMAGE_DATA: {
1408             Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
1409             Inkscape::XML::Node *newImage = xml_doc->createElement("svg:image");
1410             gchar *atom_name = gdk_atom_name(data->type);
1412             // this formula taken from Glib docs
1413             guint needed_size = data->length * 4 / 3 + data->length * 4 / (3 * 72) + 7;
1414             needed_size += 5 + 8 + strlen(atom_name); // 5 bytes for data:, 8 for ;base64,
1416             gchar *buffer = (gchar *) g_malloc(needed_size), *buf_work = buffer;
1417             buf_work += g_sprintf(buffer, "data:%s;base64,", atom_name);
1419             gint state = 0, save = 0;
1420             g_base64_encode_step(data->data, data->length, TRUE, buf_work, &state, &save);
1421             g_base64_encode_close(TRUE, buf_work, &state, &save);
1423             newImage->setAttribute("xlink:href", buffer);
1424             g_free(buffer);
1426             GError *error = NULL;
1427             GdkPixbufLoader *loader = gdk_pixbuf_loader_new_with_mime_type( gdk_atom_name(data->type), &error );
1428             if ( loader ) {
1429                 error = NULL;
1430                 if ( gdk_pixbuf_loader_write( loader, data->data, data->length, &error) ) {
1431                     GdkPixbuf *pbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1432                     if ( pbuf ) {
1433                         char tmp[1024];
1434                         int width = gdk_pixbuf_get_width(pbuf);
1435                         int height = gdk_pixbuf_get_height(pbuf);
1436                         snprintf( tmp, sizeof(tmp), "%d", width );
1437                         newImage->setAttribute("width", tmp);
1439                         snprintf( tmp, sizeof(tmp), "%d", height );
1440                         newImage->setAttribute("height", tmp);
1441                     }
1442                 }
1443             }
1444             g_free(atom_name);
1446             // Add it to the current layer
1447             desktop->currentLayer()->appendChildRepr(newImage);
1449             Inkscape::GC::release(newImage);
1450             sp_document_done( doc , SP_VERB_NONE,
1451                               _("Drop bitmap image"));
1452             break;
1453         }
1454     }
1457 #include "gradient-context.h"
1459 void sp_ui_drag_motion( GtkWidget */*widget*/,
1460                         GdkDragContext */*drag_context*/,
1461                         gint /*x*/, gint /*y*/,
1462                         GtkSelectionData */*data*/,
1463                         guint /*info*/,
1464                         guint /*event_time*/,
1465                         gpointer /*user_data*/)
1467 //     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1468 //     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1471 //     g_message("drag-n-drop motion (%4d, %4d)  at %d", x, y, event_time);
1474 static void sp_ui_drag_leave( GtkWidget */*widget*/,
1475                               GdkDragContext */*drag_context*/,
1476                               guint /*event_time*/,
1477                               gpointer /*user_data*/ )
1479 //     g_message("drag-n-drop leave                at %d", event_time);
1482 static void
1483 sp_ui_import_files(gchar *buffer)
1485     GList *list = gnome_uri_list_extract_filenames(buffer);
1486     if (!list)
1487         return;
1488     g_list_foreach(list, sp_ui_import_one_file_with_check, NULL);
1489     g_list_foreach(list, (GFunc) g_free, NULL);
1490     g_list_free(list);
1493 static void
1494 sp_ui_import_one_file_with_check(gpointer filename, gpointer /*unused*/)
1496     if (filename) {
1497         if (strlen((char const *)filename) > 2)
1498             sp_ui_import_one_file((char const *)filename);
1499     }
1502 static void
1503 sp_ui_import_one_file(char const *filename)
1505     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1506     if (!doc) return;
1508     if (filename == NULL) return;
1510     // Pass off to common implementation
1511     // TODO might need to get the proper type of Inkscape::Extension::Extension
1512     file_import( doc, filename, NULL );
1515 void
1516 sp_ui_error_dialog(gchar const *message)
1518     GtkWidget *dlg;
1519     gchar *safeMsg = Inkscape::IO::sanitizeString(message);
1521     dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
1522                                  GTK_BUTTONS_CLOSE, "%s", safeMsg);
1523     sp_transientize(dlg);
1524     gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE);
1525     gtk_dialog_run(GTK_DIALOG(dlg));
1526     gtk_widget_destroy(dlg);
1527     g_free(safeMsg);
1530 bool
1531 sp_ui_overwrite_file(gchar const *filename)
1533     bool return_value = FALSE;
1535     if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) {
1536         Gtk::Window *window = SP_ACTIVE_DESKTOP->getToplevel();
1537         gchar* baseName = g_path_get_basename( filename );
1538         gchar* dirName = g_path_get_dirname( filename );
1539         GtkWidget* dialog = gtk_message_dialog_new_with_markup( window->gobj(),
1540                                                                 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
1541                                                                 GTK_MESSAGE_QUESTION,
1542                                                                 GTK_BUTTONS_NONE,
1543                                                                 _( "<span weight=\"bold\" size=\"larger\">A file named \"%s\" already exists. Do you want to replace it?</span>\n\n"
1544                                                                    "The file already exists in \"%s\". Replacing it will overwrite its contents." ),
1545                                                                 baseName,
1546                                                                 dirName
1547             );
1548         gtk_dialog_add_buttons( GTK_DIALOG(dialog),
1549                                 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1550                                 _("Replace"), GTK_RESPONSE_YES,
1551                                 NULL );
1552         gtk_dialog_set_default_response( GTK_DIALOG(dialog), GTK_RESPONSE_YES );
1554         if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_YES ) {
1555             return_value = TRUE;
1556         } else {
1557             return_value = FALSE;
1558         }
1559         gtk_widget_destroy(dialog);
1560         g_free( baseName );
1561         g_free( dirName );
1562     } else {
1563         return_value = TRUE;
1564     }
1566     return return_value;
1569 static void
1570 sp_ui_menu_item_set_sensitive(SPAction */*action*/, unsigned int sensitive, void *data)
1572     return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
1575 static void
1576 sp_ui_menu_item_set_name(SPAction */*action*/, Glib::ustring name, void *data)
1578     void *child = GTK_BIN (data)->child;
1579     //child is either
1580     //- a GtkHBox, whose first child is a label displaying name if the menu
1581     //item has an accel key
1582     //- a GtkLabel if the menu has no accel key
1583     if (GTK_IS_LABEL(child)) {
1584         gtk_label_set_markup_with_mnemonic(GTK_LABEL (child), name.c_str());
1585     } else if (GTK_IS_HBOX(child)) {
1586         gtk_label_set_markup_with_mnemonic(
1587         GTK_LABEL (gtk_container_get_children(GTK_CONTAINER (child))->data),
1588         name.c_str());
1589     }//else sp_ui_menu_append_item_from_verb has been modified and can set
1590     //a menu item in yet another way...
1594 /*
1595   Local Variables:
1596   mode:c++
1597   c-file-style:"stroustrup"
1598   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1599   indent-tabs-mode:nil
1600   fill-column:99
1601   End:
1602 */
1603 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :