Code

Reworked internals of color and drag-n-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"
64 #include "widgets/eek-color-def.h"
66 // Include Mac OS X menu synchronization on native OSX build
67 #ifdef GDK_WINDOWING_QUARTZ
68 #include "ige-mac-menu.h"
69 #endif
71 /* Drag and Drop */
72 typedef enum {
73     URI_LIST,
74     SVG_XML_DATA,
75     SVG_DATA,
76     PNG_DATA,
77     JPEG_DATA,
78     IMAGE_DATA,
79     APP_X_INKY_COLOR,
80     APP_X_COLOR,
81     APP_OSWB_COLOR,
82 } ui_drop_target_info;
84 static GtkTargetEntry ui_drop_target_entries [] = {
85     {(gchar *)"text/uri-list",                0, URI_LIST        },
86     {(gchar *)"image/svg+xml",                0, SVG_XML_DATA    },
87     {(gchar *)"image/svg",                    0, SVG_DATA        },
88     {(gchar *)"image/png",                    0, PNG_DATA        },
89     {(gchar *)"image/jpeg",                   0, JPEG_DATA       },
90 #if ENABLE_MAGIC_COLORS
91     {(gchar *)"application/x-inkscape-color", 0, APP_X_INKY_COLOR},
92 #endif // ENABLE_MAGIC_COLORS
93     {(gchar *)"application/x-oswb-color",     0, APP_OSWB_COLOR  },
94     {(gchar *)"application/x-color",          0, APP_X_COLOR     }
95 };
97 static GtkTargetEntry *completeDropTargets = 0;
98 static int completeDropTargetsCount = 0;
100 #define ENTRIES_SIZE(n) sizeof(n)/sizeof(n[0])
101 static guint nui_drop_target_entries = ENTRIES_SIZE(ui_drop_target_entries);
102 static void sp_ui_import_files(gchar *buffer);
103 static void sp_ui_import_one_file(char const *filename);
104 static void sp_ui_import_one_file_with_check(gpointer filename, gpointer unused);
105 static void sp_ui_drag_data_received(GtkWidget *widget,
106                                      GdkDragContext *drag_context,
107                                      gint x, gint y,
108                                      GtkSelectionData *data,
109                                      guint info,
110                                      guint event_time,
111                                      gpointer user_data);
112 static void sp_ui_drag_motion( GtkWidget *widget,
113                                GdkDragContext *drag_context,
114                                gint x, gint y,
115                                GtkSelectionData *data,
116                                guint info,
117                                guint event_time,
118                                gpointer user_data );
119 static void sp_ui_drag_leave( GtkWidget *widget,
120                               GdkDragContext *drag_context,
121                               guint event_time,
122                               gpointer user_data );
123 static void sp_ui_menu_item_set_sensitive(SPAction *action,
124                                           unsigned int sensitive,
125                                           void *data);
126 static void sp_ui_menu_item_set_name(SPAction *action,
127                                      Glib::ustring name,
128                                      void *data);
129 static void sp_recent_open(GtkRecentChooser *, gpointer);
131 SPActionEventVector menu_item_event_vector = {
132     {NULL},
133     NULL,
134     NULL, /* set_active */
135     sp_ui_menu_item_set_sensitive, /* set_sensitive */
136     NULL, /* set_shortcut */
137     sp_ui_menu_item_set_name /* set_name */
138 };
140 static const int MIN_ONSCREEN_DISTANCE = 50;
142 void
143 sp_create_window(SPViewWidget *vw, gboolean editable)
145     g_return_if_fail(vw != NULL);
146     g_return_if_fail(SP_IS_VIEW_WIDGET(vw));
148     Gtk::Window *win = Inkscape::UI::window_new("", TRUE);
150     gtk_container_add(GTK_CONTAINER(win->gobj()), GTK_WIDGET(vw));
151     gtk_widget_show(GTK_WIDGET(vw));
153     if (editable) {
154                 g_object_set_data(G_OBJECT(vw), "window", win);
156                 SPDesktopWidget *desktop_widget = reinterpret_cast<SPDesktopWidget*>(vw);
157                 SPDesktop* desktop = desktop_widget->desktop;
159                 desktop_widget->window = win;
161         win->set_data("desktop", desktop);
162         win->set_data("desktopwidget", desktop_widget);
164         win->signal_delete_event().connect(sigc::mem_fun(*(SPDesktop*)vw->view, &SPDesktop::onDeleteUI));
165                 win->signal_window_state_event().connect(sigc::mem_fun(*desktop, &SPDesktop::onWindowStateEvent));
166                 win->signal_focus_in_event().connect(sigc::mem_fun(*desktop_widget, &SPDesktopWidget::onFocusInEvent));
168         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
169         gint prefs_geometry =
170             (2==prefs->getInt("/options/savewindowgeometry/value", 0));
171         if (prefs_geometry) {
172             gint pw = prefs->getInt("/desktop/geometry/width", -1);
173             gint ph = prefs->getInt("/desktop/geometry/height", -1);
174             gint px = prefs->getInt("/desktop/geometry/x", -1);
175             gint py = prefs->getInt("/desktop/geometry/y", -1);
176             gint full = prefs->getBool("/desktop/geometry/fullscreen");
177             gint maxed = prefs->getBool("/desktop/geometry/maximized");
178             if (pw>0 && ph>0) {
179                 gint w = MIN(gdk_screen_width(), pw);
180                 gint h = MIN(gdk_screen_height(), ph);
181                 gint x = MIN(gdk_screen_width() - MIN_ONSCREEN_DISTANCE, px);
182                 gint y = MIN(gdk_screen_height() - MIN_ONSCREEN_DISTANCE, py);
183                 if (w>0 && h>0 && x>0 && y>0) {
184                     x = MIN(gdk_screen_width() - w, x);
185                     y = MIN(gdk_screen_height() - h, y);
186                 }
187                 if (w>0 && h>0) {
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                 if (x>0 && y>0) {
198                     SPDesktop *active_desktop = SP_ACTIVE_DESKTOP;
199                     if (active_desktop == desktop || active_desktop==NULL) {
200                         desktop->setWindowPosition(Geom::Point(x, y));
201                     }
202                 }
203             }
204             if (maxed) {
205                 win->maximize();
206             }
207             if (full) {
208                 win->fullscreen();
209             }
210         }
212     } else {
213         gtk_window_set_policy(GTK_WINDOW(win->gobj()), TRUE, TRUE, TRUE);
214     }
216     if ( completeDropTargets == 0 || completeDropTargetsCount == 0 )
217     {
218         std::vector<gchar*> types;
220         GSList *list = gdk_pixbuf_get_formats();
221         while ( list ) {
222             int i = 0;
223             GdkPixbufFormat *one = (GdkPixbufFormat*)list->data;
224             gchar** typesXX = gdk_pixbuf_format_get_mime_types(one);
225             for ( i = 0; typesXX[i]; i++ ) {
226                 types.push_back(g_strdup(typesXX[i]));
227             }
228             g_strfreev(typesXX);
230             list = g_slist_next(list);
231         }
232         completeDropTargetsCount = nui_drop_target_entries + types.size();
233         completeDropTargets = new GtkTargetEntry[completeDropTargetsCount];
234         for ( int i = 0; i < (int)nui_drop_target_entries; i++ ) {
235             completeDropTargets[i] = ui_drop_target_entries[i];
236         }
237         int pos = nui_drop_target_entries;
239         for (std::vector<gchar*>::iterator it = types.begin() ; it != types.end() ; it++) {
240             completeDropTargets[pos].target = *it;
241             completeDropTargets[pos].flags = 0;
242             completeDropTargets[pos].info = IMAGE_DATA;
243             pos++;
244         }
245     }
247     gtk_drag_dest_set((GtkWidget*)win->gobj(),
248                       GTK_DEST_DEFAULT_ALL,
249                       completeDropTargets,
250                       completeDropTargetsCount,
251                       GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE));
254     g_signal_connect(G_OBJECT(win->gobj()),
255                      "drag_data_received",
256                      G_CALLBACK(sp_ui_drag_data_received),
257                      NULL);
258     g_signal_connect(G_OBJECT(win->gobj()),
259                      "drag_motion",
260                      G_CALLBACK(sp_ui_drag_motion),
261                      NULL);
262     g_signal_connect(G_OBJECT(win->gobj()),
263                      "drag_leave",
264                      G_CALLBACK(sp_ui_drag_leave),
265                      NULL);
266     win->show();
268     // needed because the first ACTIVATE_DESKTOP was sent when there was no window yet
269     inkscape_reactivate_desktop(SP_DESKTOP_WIDGET(vw)->desktop);
272 void
273 sp_ui_new_view()
275     SPDocument *document;
276     SPViewWidget *dtw;
278     document = SP_ACTIVE_DOCUMENT;
279     if (!document) return;
281     dtw = sp_desktop_widget_new(sp_document_namedview(document, NULL));
282     g_return_if_fail(dtw != NULL);
284     sp_create_window(dtw, TRUE);
285     sp_namedview_window_from_document(static_cast<SPDesktop*>(dtw->view));
286     sp_namedview_update_layers_from_document(static_cast<SPDesktop*>(dtw->view));
289 /* TODO: not yet working */
290 /* To be re-enabled (by adding to menu) once it works. */
291 void
292 sp_ui_new_view_preview()
294     SPDocument *document;
295     SPViewWidget *dtw;
297     document = SP_ACTIVE_DOCUMENT;
298     if (!document) return;
300     dtw = (SPViewWidget *) sp_svg_view_widget_new(document);
301     g_return_if_fail(dtw != NULL);
302     sp_svg_view_widget_set_resize(SP_SVG_VIEW_WIDGET(dtw), TRUE, 400.0, 400.0);
304     sp_create_window(dtw, FALSE);
307 /**
308  * \param widget unused
309  */
310 void
311 sp_ui_close_view(GtkWidget */*widget*/)
313     if (SP_ACTIVE_DESKTOP == NULL) {
314         return;
315     }
316     if ((SP_ACTIVE_DESKTOP)->shutdown()) {
317         return;
318     }
319     SP_ACTIVE_DESKTOP->destroyWidget();
323 /**
324  *  sp_ui_close_all
325  *
326  *  This function is called to exit the program, and iterates through all
327  *  open document view windows, attempting to close each in turn.  If the
328  *  view has unsaved information, the user will be prompted to save,
329  *  discard, or cancel.
330  *
331  *  Returns FALSE if the user cancels the close_all operation, TRUE
332  *  otherwise.
333  */
334 unsigned int
335 sp_ui_close_all(void)
337     /* Iterate through all the windows, destroying each in the order they
338        become active */
339     while (SP_ACTIVE_DESKTOP) {
340         if ((SP_ACTIVE_DESKTOP)->shutdown()) {
341             /* The user cancelled the operation, so end doing the close */
342             return FALSE;
343         }
344         SP_ACTIVE_DESKTOP->destroyWidget();
345     }
347     return TRUE;
350 /*
351  * Some day when the right-click menus are ready to start working
352  * smarter with the verbs, we'll need to change this NULL being
353  * sent to sp_action_perform to something useful, or set some kind
354  * of global "right-clicked position" variable for actions to
355  * investigate when they're called.
356  */
357 static void
358 sp_ui_menu_activate(void */*object*/, SPAction *action)
360     sp_action_perform(action, NULL);
363 static void
364 sp_ui_menu_select_action(void */*object*/, SPAction *action)
366     action->view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, action->tip);
369 static void
370 sp_ui_menu_deselect_action(void */*object*/, SPAction *action)
372     action->view->tipsMessageContext()->clear();
375 static void
376 sp_ui_menu_select(gpointer object, gpointer tip)
378     Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view"));
379     view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, (gchar *)tip);
382 static void
383 sp_ui_menu_deselect(gpointer object)
385     Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*>  (g_object_get_data(G_OBJECT(object), "view"));
386     view->tipsMessageContext()->clear();
389 /**
390  * sp_ui_menuitem_add_icon
391  *
392  * Creates and attaches a scaled icon to the given menu item.
393  *
394  */
395 void
396 sp_ui_menuitem_add_icon( GtkWidget *item, gchar *icon_name )
398     GtkWidget *icon;
400     icon = sp_icon_new( Inkscape::ICON_SIZE_MENU, icon_name );
401     gtk_widget_show(icon);
402     gtk_image_menu_item_set_image((GtkImageMenuItem *) item, icon);
403 } // end of sp_ui_menu_add_icon
405 /**
406  * sp_ui_menu_append_item
407  *
408  * Appends a UI item with specific info for Inkscape/Sodipodi.
409  *
410  */
411 static GtkWidget *
412 sp_ui_menu_append_item( GtkMenu *menu, gchar const *stock,
413                         gchar const *label, gchar const *tip, Inkscape::UI::View::View *view, GCallback callback,
414                         gpointer data, gboolean with_mnemonic = TRUE )
416     GtkWidget *item;
418     if (stock) {
419         item = gtk_image_menu_item_new_from_stock(stock, NULL);
420     } else if (label) {
421         item = (with_mnemonic)
422             ? gtk_image_menu_item_new_with_mnemonic(label) :
423             gtk_image_menu_item_new_with_label(label);
424     } else {
425         item = gtk_separator_menu_item_new();
426     }
428     gtk_widget_show(item);
430     if (callback) {
431         g_signal_connect(G_OBJECT(item), "activate", callback, data);
432     }
434     if (tip && view) {
435         g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
436         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) tip );
437         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
438     }
440     gtk_menu_append(GTK_MENU(menu), item);
442     return item;
444 } // end of sp_ui_menu_append_item()
446 /**
447 \brief  a wrapper around gdk_keyval_name producing (when possible) characters, not names
448  */
449 static gchar const *
450 sp_key_name(guint keyval)
452     /* TODO: Compare with the definition of gtk_accel_label_refetch in gtk/gtkaccellabel.c (or
453        simply use GtkAccelLabel as the TODO comment in sp_ui_shortcut_string suggests). */
454     gchar const *n = gdk_keyval_name(gdk_keyval_to_upper(keyval));
456     if      (!strcmp(n, "asciicircum"))  return "^";
457     else if (!strcmp(n, "parenleft"  ))  return "(";
458     else if (!strcmp(n, "parenright" ))  return ")";
459     else if (!strcmp(n, "plus"       ))  return "+";
460     else if (!strcmp(n, "minus"      ))  return "-";
461     else if (!strcmp(n, "asterisk"   ))  return "*";
462     else if (!strcmp(n, "KP_Multiply"))  return "*";
463     else if (!strcmp(n, "Delete"     ))  return "Del";
464     else if (!strcmp(n, "Page_Up"    ))  return "PgUp";
465     else if (!strcmp(n, "Page_Down"  ))  return "PgDn";
466     else if (!strcmp(n, "grave"      ))  return "`";
467     else if (!strcmp(n, "numbersign" ))  return "#";
468     else if (!strcmp(n, "bar"        ))  return "|";
469     else if (!strcmp(n, "slash"      ))  return "/";
470     else if (!strcmp(n, "exclam"     ))  return "!";
471     else if (!strcmp(n, "percent"    ))  return "%";
472     else return n;
476 /**
477  * \param shortcut A GDK keyval OR'd with SP_SHORTCUT_blah_MASK values.
478  * \param c Points to a buffer at least 256 bytes long.
479  */
480 void
481 sp_ui_shortcut_string(unsigned const shortcut, gchar *const c)
483     /* TODO: This function shouldn't exist.  Our callers should use GtkAccelLabel instead of
484      * a generic GtkLabel containing this string, and should call gtk_widget_add_accelerator.
485      * Will probably need to change sp_shortcut_invoke callers.
486      *
487      * The existing gtk_label_new_with_mnemonic call can be replaced with
488      * g_object_new(GTK_TYPE_ACCEL_LABEL, NULL) followed by
489      * gtk_label_set_text_with_mnemonic(lbl, str).
490      */
491     static GtkAccelLabelClass const &accel_lbl_cls
492         = *(GtkAccelLabelClass const *) g_type_class_peek_static(GTK_TYPE_ACCEL_LABEL);
494     struct { unsigned test; char const *name; } const modifier_tbl[] = {
495         { SP_SHORTCUT_SHIFT_MASK,   accel_lbl_cls.mod_name_shift   },
496         { SP_SHORTCUT_CONTROL_MASK, accel_lbl_cls.mod_name_control },
497         { SP_SHORTCUT_ALT_MASK,     accel_lbl_cls.mod_name_alt     }
498     };
500     gchar *p = c;
501     gchar *end = p + 256;
503     for (unsigned i = 0; i < G_N_ELEMENTS(modifier_tbl); ++i) {
504         if ((shortcut & modifier_tbl[i].test)
505             && (p < end))
506         {
507             p += g_snprintf(p, end - p, "%s%s",
508                             modifier_tbl[i].name,
509                             accel_lbl_cls.mod_separator);
510         }
511     }
512     if (p < end) {
513         p += g_snprintf(p, end - p, "%s", sp_key_name(shortcut & 0xffffff));
514     }
515     end[-1] = '\0';  // snprintf doesn't guarantee to nul-terminate the string.
518 void
519 sp_ui_dialog_title_string(Inkscape::Verb *verb, gchar *c)
521     SPAction     *action;
522     unsigned int shortcut;
523     gchar        *s;
524     gchar        key[256];
525     gchar        *atitle;
527     action = verb->get_action(NULL);
528     if (!action)
529         return;
531     atitle = sp_action_get_title(action);
533     s = g_stpcpy(c, atitle);
535     g_free(atitle);
537     shortcut = sp_shortcut_get_primary(verb);
538     if (shortcut) {
539         s = g_stpcpy(s, " (");
540         sp_ui_shortcut_string(shortcut, key);
541         s = g_stpcpy(s, key);
542         s = g_stpcpy(s, ")");
543     }
547 /**
548  * sp_ui_menu_append_item_from_verb
549  *
550  * Appends a custom menu UI from a verb.
551  *
552  */
554 static GtkWidget *
555 sp_ui_menu_append_item_from_verb(GtkMenu *menu, Inkscape::Verb *verb, Inkscape::UI::View::View *view, bool radio = false, GSList *group = NULL)
557     SPAction *action;
558     GtkWidget *item;
560     if (verb->get_code() == SP_VERB_NONE) {
562         item = gtk_separator_menu_item_new();
564     } else {
565         unsigned int shortcut;
567         action = verb->get_action(view);
569         if (!action) return NULL;
571         shortcut = sp_shortcut_get_primary(verb);
572         if (shortcut) {
573             gchar c[256];
574             sp_ui_shortcut_string(shortcut, c);
575             GtkWidget *const hb = gtk_hbox_new(FALSE, 16);
576             GtkWidget *const name_lbl = gtk_label_new("");
577             gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
578             gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
579             gtk_box_pack_start((GtkBox *) hb, name_lbl, TRUE, TRUE, 0);
580             GtkWidget *const accel_lbl = gtk_label_new(c);
581             gtk_misc_set_alignment((GtkMisc *) accel_lbl, 1.0, 0.5);
582             gtk_box_pack_end((GtkBox *) hb, accel_lbl, FALSE, FALSE, 0);
583             gtk_widget_show_all(hb);
584             if (radio) {
585                 item = gtk_radio_menu_item_new (group);
586             } else {
587                 item = gtk_image_menu_item_new();
588             }
589             gtk_container_add((GtkContainer *) item, hb);
590         } else {
591             if (radio) {
592                 item = gtk_radio_menu_item_new (group);
593             } else {
594                 item = gtk_image_menu_item_new ();
595             }
596             GtkWidget *const name_lbl = gtk_label_new("");
597             gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
598             gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
599             gtk_container_add((GtkContainer *) item, name_lbl);
600         }
602         nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
603         if (!action->sensitive) {
604             gtk_widget_set_sensitive(item, FALSE);
605         }
607         if (action->image) {
608             sp_ui_menuitem_add_icon(item, action->image);
609         }
610         gtk_widget_set_events(item, GDK_KEY_PRESS_MASK);
611         g_signal_connect( G_OBJECT(item), "activate",
612                           G_CALLBACK(sp_ui_menu_activate), action );
614         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select_action), action );
615         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect_action), action );
616     }
618     gtk_widget_show(item);
619     gtk_menu_append(GTK_MENU(menu), item);
621     return item;
623 } // end of sp_ui_menu_append_item_from_verb
626 static void
627 checkitem_toggled(GtkCheckMenuItem *menuitem, gpointer user_data)
629     gchar const *pref = (gchar const *) user_data;
630     Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
632     Glib::ustring pref_path;
633     if (reinterpret_cast<SPDesktop*>(view)->is_focusMode()) {
634         pref_path = "/focus/";
635     } else if (reinterpret_cast<SPDesktop*>(view)->is_fullscreen()) {
636         pref_path = "/fullscreen/";
637     } else {
638         pref_path = "/window/";
639     }
640     pref_path += pref;
641     pref_path += "/state";
643     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
644     gboolean checked = gtk_check_menu_item_get_active(menuitem);
645     prefs->setBool(pref_path, checked);
647     reinterpret_cast<SPDesktop*>(view)->layoutWidget();
650 static gboolean
651 checkitem_update(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_data)
653     GtkCheckMenuItem *menuitem=GTK_CHECK_MENU_ITEM(widget);
655     gchar const *pref = (gchar const *) user_data;
656     Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
658     Glib::ustring pref_path;
659     if ((static_cast<SPDesktop*>(view))->is_fullscreen()) {
660         pref_path = "/fullscreen/";
661     } else {
662         pref_path = "/window/";
663     }
664     pref_path += pref;
666     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
667     bool ison = prefs->getBool(pref_path + "/state", true);
669     g_signal_handlers_block_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
670     gtk_check_menu_item_set_active(menuitem, ison);
671     g_signal_handlers_unblock_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
673     return FALSE;
677 void
678 sp_ui_menu_append_check_item_from_verb(GtkMenu *menu, Inkscape::UI::View::View *view, gchar const *label, gchar const *tip, gchar const *pref,
679                                        void (*callback_toggle)(GtkCheckMenuItem *, gpointer user_data),
680                                        gboolean (*callback_update)(GtkWidget *widget, GdkEventExpose *event, gpointer user_data),
681                                        Inkscape::Verb *verb)
683     GtkWidget *item;
685     unsigned int shortcut = 0;
686     SPAction *action = NULL;
688     if (verb) {
689         shortcut = sp_shortcut_get_primary(verb);
690         action = verb->get_action(view);
691     }
693     if (verb && shortcut) {
694         gchar c[256];
695         sp_ui_shortcut_string(shortcut, c);
697         GtkWidget *hb = gtk_hbox_new(FALSE, 16);
699         {
700             GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
701             gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
702             gtk_box_pack_start((GtkBox *) hb, l, TRUE, TRUE, 0);
703         }
705         {
706             GtkWidget *l = gtk_label_new(c);
707             gtk_misc_set_alignment((GtkMisc *) l, 1.0, 0.5);
708             gtk_box_pack_end((GtkBox *) hb, l, FALSE, FALSE, 0);
709         }
711         gtk_widget_show_all(hb);
713         item = gtk_check_menu_item_new();
714         gtk_container_add((GtkContainer *) item, hb);
715     } else {
716         GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
717         gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
718         item = gtk_check_menu_item_new();
719         gtk_container_add((GtkContainer *) item, l);
720     }
721 #if 0
722     nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
723     if (!action->sensitive) {
724         gtk_widget_set_sensitive(item, FALSE);
725     }
726 #endif
727     gtk_widget_show(item);
729     gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
731     g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
733     g_signal_connect( G_OBJECT(item), "toggled", (GCallback) callback_toggle, (void *) pref);
734     g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) callback_update, (void *) pref);
736     g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) (action ? action->tip : tip));
737     g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
740 static void
741 sp_recent_open(GtkRecentChooser *recent_menu, gpointer /*user_data*/)
743     // dealing with the bizarre filename convention in Inkscape for now
744     gchar *uri = gtk_recent_chooser_get_current_uri(GTK_RECENT_CHOOSER(recent_menu));
745     gchar *local_fn = g_filename_from_uri(uri, NULL, NULL);
746     gchar *utf8_fn = g_filename_to_utf8(local_fn, -1, NULL, NULL, NULL);
747     sp_file_open(utf8_fn, NULL);
748     g_free(utf8_fn);
749     g_free(local_fn);
750     g_free(uri);
753 static void
754 sp_file_new_from_template(GtkWidget */*widget*/, gchar const *uri)
756     sp_file_new(uri);
759 void
760 sp_menu_append_new_templates(GtkWidget *menu, Inkscape::UI::View::View *view)
762     std::list<gchar *> sources;
763     sources.push_back( profile_path("templates") ); // first try user's local dir
764     sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir
766     // Use this loop to iterate through a list of possible document locations.
767     while (!sources.empty()) {
768         gchar *dirname = sources.front();
770         if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) {
771             GError *err = 0;
772             GDir *dir = g_dir_open(dirname, 0, &err);
774             if (dir) {
775                 for (gchar const *file = g_dir_read_name(dir); file != NULL; file = g_dir_read_name(dir)) {
776                     if (!g_str_has_suffix(file, ".svg") && !g_str_has_suffix(file, ".svgz"))
777                         continue; // skip non-svg files
779                     gchar *basename = g_path_get_basename(file);
780                     if (g_str_has_suffix(basename, ".svg") && g_str_has_prefix(basename, "default."))
781                         continue; // skip default.*.svg (i.e. default.svg and translations) - it's in the menu already
783                     gchar const *filepath = g_build_filename(dirname, file, NULL);
784                     gchar *dupfile = g_strndup(file, strlen(file) - 4);
785                     gchar *filename =  g_filename_to_utf8(dupfile,  -1, NULL, NULL, NULL);
786                     g_free(dupfile);
787                     GtkWidget *item = gtk_menu_item_new_with_label(filename);
788                     g_free(filename);
790                     gtk_widget_show(item);
791                     // how does "filepath" ever get freed?
792                     g_signal_connect(G_OBJECT(item),
793                                      "activate",
794                                      G_CALLBACK(sp_file_new_from_template),
795                                      (gpointer) filepath);
797                     if (view) {
798                         // set null tip for now; later use a description from the template file
799                         g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
800                         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) NULL );
801                         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
802                     }
804                     gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
805                 }
806                 g_dir_close(dir);
807             }
808         }
810         // toss the dirname
811         g_free(dirname);
812         sources.pop_front();
813     }
816 void
817 sp_ui_checkboxes_menus(GtkMenu *m, Inkscape::UI::View::View *view)
819     //sp_ui_menu_append_check_item_from_verb(m, view, _("_Menu"), _("Show or hide the menu bar"), "menu",
820     //                                       checkitem_toggled, checkitem_update, 0);
821     sp_ui_menu_append_check_item_from_verb(m, view, _("Commands Bar"), _("Show or hide the Commands bar (under the menu)"), "commands",
822                                            checkitem_toggled, checkitem_update, 0);
823     sp_ui_menu_append_check_item_from_verb(m, view, _("Snap controls Bar"), _("Show or hide the snapping controls"), "snaptoolbox",
824                                                                                    checkitem_toggled, checkitem_update, 0);
825     sp_ui_menu_append_check_item_from_verb(m, view, _("Tool Controls Bar"), _("Show or hide the Tool Controls bar"), "toppanel",
826                                            checkitem_toggled, checkitem_update, 0);
827     sp_ui_menu_append_check_item_from_verb(m, view, _("_Toolbox"), _("Show or hide the main toolbox (on the left)"), "toolbox",
828                                            checkitem_toggled, checkitem_update, 0);
829     sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "rulers",
830                                            checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_RULERS));
831     sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "scrollbars",
832                                            checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_SCROLLBARS));
833     sp_ui_menu_append_check_item_from_verb(m, view, _("_Palette"), _("Show or hide the color palette"), "panels",
834                                            checkitem_toggled, checkitem_update, 0);
835     sp_ui_menu_append_check_item_from_verb(m, view, _("_Statusbar"), _("Show or hide the statusbar (at the bottom of the window)"), "statusbar",
836                                            checkitem_toggled, checkitem_update, 0);
839 /** @brief Observer that updates the recent list's max document count */
840 class MaxRecentObserver : public Inkscape::Preferences::Observer {
841 public:
842     MaxRecentObserver(GtkWidget *recent_menu) :
843         Observer("/options/maxrecentdocuments/value"),
844         _rm(recent_menu)
845     {}
846     virtual void notify(Inkscape::Preferences::Entry const &e) {
847         gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(_rm), e.getInt());
848         // hack: the recent menu doesn't repopulate after changing the limit, so we force it
849         g_signal_emit_by_name((gpointer) gtk_recent_manager_get_default(), "changed");
850     }
851 private:
852     GtkWidget *_rm;
853 };
855 /** \brief  This function turns XML into a menu
856     \param  menus  This is the XML that defines the menu
857     \param  menu   Menu to be added to
858     \param  view   The View that this menu is being built for
860     This function is realitively simple as it just goes through the XML
861     and parses the individual elements.  In the case of a submenu, it
862     just calls itself recursively.  Because it is only reasonable to have
863     a couple of submenus, it is unlikely this will go more than two or
864     three times.
866     In the case of an unreconginzed verb, a menu item is made to identify
867     the verb that is missing, and display that.  The menu item is also made
868     insensitive.
869 */
870 void
871 sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI::View::View *view)
873     if (menus == NULL) return;
874     if (menu == NULL)  return;
875     GSList *group = NULL;
877     for (Inkscape::XML::Node *menu_pntr = menus;
878          menu_pntr != NULL;
879          menu_pntr = menu_pntr->next()) {
880         if (!strcmp(menu_pntr->name(), "submenu")) {
881             GtkWidget *mitem = gtk_menu_item_new_with_mnemonic(_(menu_pntr->attribute("name")));
882             GtkWidget *submenu = gtk_menu_new();
883             sp_ui_build_dyn_menus(menu_pntr->firstChild(), submenu, view);
884             gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), GTK_WIDGET(submenu));
885             gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
886             continue;
887         }
888         if (!strcmp(menu_pntr->name(), "verb")) {
889             gchar const *verb_name = menu_pntr->attribute("verb-id");
890             Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name);
892             if (verb != NULL) {
893                 if (menu_pntr->attribute("radio") != NULL) {
894                     GtkWidget *item = sp_ui_menu_append_item_from_verb (GTK_MENU(menu), verb, view, true, group);
895                     group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item));
896                     if (menu_pntr->attribute("default") != NULL) {
897                         gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
898                     }
899                 } else {
900                     sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view);
901                     group = NULL;
902                 }
903             } else {
904                 gchar string[120];
905                 g_snprintf(string, 120, _("Verb \"%s\" Unknown"), verb_name);
906                 string[119] = '\0'; /* may not be terminated */
907                 GtkWidget *item = gtk_menu_item_new_with_label(string);
908                 gtk_widget_set_sensitive(item, false);
909                 gtk_widget_show(item);
910                 gtk_menu_append(GTK_MENU(menu), item);
911             }
912             continue;
913         }
914         if (!strcmp(menu_pntr->name(), "separator")
915                 // This was spelt wrong in the original version
916                 // and so this is for backward compatibility.  It can
917                 // probably be dropped after the 0.44 release.
918              || !strcmp(menu_pntr->name(), "seperator")) {
919             GtkWidget *item = gtk_separator_menu_item_new();
920             gtk_widget_show(item);
921             gtk_menu_append(GTK_MENU(menu), item);
922             continue;
923         }
924         if (!strcmp(menu_pntr->name(), "template-list")) {
925             sp_menu_append_new_templates(menu, view);
926             continue;
927         }
928         if (!strcmp(menu_pntr->name(), "recent-file-list")) {
929             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
931             // create recent files menu
932             int max_recent = prefs->getInt("/options/maxrecentdocuments/value");
933             GtkWidget *recent_menu = gtk_recent_chooser_menu_new_for_manager(gtk_recent_manager_get_default());
934             gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(recent_menu), max_recent);
935             // sort most recently used documents first to preserve previous behavior
936             gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(recent_menu), GTK_RECENT_SORT_MRU);
937             g_signal_connect(G_OBJECT(recent_menu), "item-activated", G_CALLBACK(sp_recent_open), (gpointer) NULL);
939             // add filter to only open files added by Inkscape
940             GtkRecentFilter *inkscape_only_filter = gtk_recent_filter_new();
941             gtk_recent_filter_add_application(inkscape_only_filter, g_get_prgname());
942             gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_menu), inkscape_only_filter);
944             GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic(_("Open _Recent"));
945             gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), recent_menu);
947             gtk_menu_append(GTK_MENU(menu), GTK_WIDGET(recent_item));
948             // this will just sit and update the list's item count
949             static MaxRecentObserver *mro = new MaxRecentObserver(recent_menu);
950             prefs->addObserver(*mro);
951             continue;
952         }
953         if (!strcmp(menu_pntr->name(), "objects-checkboxes")) {
954             sp_ui_checkboxes_menus(GTK_MENU(menu), view);
955             continue;
956         }
957     }
960 /** \brief  Build the main tool bar
961     \param  view  View to build the bar for
963     Currently the main tool bar is built as a dynamic XML menu using
964     \c sp_ui_build_dyn_menus.  This function builds the bar, and then
965     pass it to get items attached to it.
966 */
967 GtkWidget *
968 sp_ui_main_menubar(Inkscape::UI::View::View *view)
970     GtkWidget *mbar = gtk_menu_bar_new();
972 #ifdef GDK_WINDOWING_QUARTZ
973         ige_mac_menu_set_menu_bar(GTK_MENU_SHELL(mbar));
974 #endif
976     sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view);
978 #ifdef GDK_WINDOWING_QUARTZ
979         return NULL;
980 #else
981     return mbar;
982 #endif
985 static void leave_group(GtkMenuItem *, SPDesktop *desktop) {
986     desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer()));
989 static void enter_group(GtkMenuItem *mi, SPDesktop *desktop) {
990     desktop->setCurrentLayer(reinterpret_cast<SPObject *>(g_object_get_data(G_OBJECT(mi), "group")));
991     sp_desktop_selection(desktop)->clear();
994 GtkWidget *
995 sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item)
997     GtkWidget *m;
998     SPDesktop *dt;
1000     dt = static_cast<SPDesktop*>(view);
1002     m = gtk_menu_new();
1004     /* Undo and Redo */
1005     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_UNDO), view);
1006     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_REDO), view);
1008     /* Separator */
1009     sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1011     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_CUT), view);
1012     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_COPY), view);
1013     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_PASTE), 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_DUPLICATE), view);
1019     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DELETE), view);
1021     /* Item menu */
1022     if (item) {
1023         sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1024         sp_object_menu((SPObject *) item, dt, GTK_MENU(m));
1025     }
1027     /* layer menu */
1028     SPGroup *group=NULL;
1029     if (item) {
1030         if (SP_IS_GROUP(item)) {
1031             group = SP_GROUP(item);
1032         } else if ( item != dt->currentRoot() && SP_IS_GROUP(SP_OBJECT_PARENT(item)) ) {
1033             group = SP_GROUP(SP_OBJECT_PARENT(item));
1034         }
1035     }
1037     if (( group && group != dt->currentLayer() ) ||
1038         ( dt->currentLayer() != dt->currentRoot() && SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) ) {
1039         /* Separator */
1040         sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1041     }
1043     if ( group && group != dt->currentLayer() ) {
1044         /* TRANSLATORS: #%s is the id of the group e.g. <g id="#g7">, not a number. */
1045         gchar *label=g_strdup_printf(_("Enter group #%s"), SP_OBJECT_ID(group));
1046         GtkWidget *w = gtk_menu_item_new_with_label(label);
1047         g_free(label);
1048         g_object_set_data(G_OBJECT(w), "group", group);
1049         g_signal_connect(G_OBJECT(w), "activate", GCallback(enter_group), dt);
1050         gtk_widget_show(w);
1051         gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1052     }
1054     if ( dt->currentLayer() != dt->currentRoot() ) {
1055         if ( SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) {
1056             GtkWidget *w = gtk_menu_item_new_with_label(_("Go to parent"));
1057             g_signal_connect(G_OBJECT(w), "activate", GCallback(leave_group), dt);
1058             gtk_widget_show(w);
1059             gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1061         }
1062     }
1064     return m;
1067 /* Drag and Drop */
1068 void
1069 sp_ui_drag_data_received(GtkWidget *widget,
1070                          GdkDragContext *drag_context,
1071                          gint x, gint y,
1072                          GtkSelectionData *data,
1073                          guint info,
1074                          guint /*event_time*/,
1075                          gpointer /*user_data*/)
1077     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1078     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1080     switch (info) {
1081 #if ENABLE_MAGIC_COLORS
1082         case APP_X_INKY_COLOR:
1083         {
1084             int destX = 0;
1085             int destY = 0;
1086             gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1087             Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1089             SPItem *item = desktop->item_at_point( where, true );
1090             if ( item )
1091             {
1092                 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1094                 if ( data->length >= 8 ) {
1095                     cmsHPROFILE srgbProf = cmsCreate_sRGBProfile();
1097                     gchar c[64] = {0};
1098                     // Careful about endian issues.
1099                     guint16* dataVals = (guint16*)data->data;
1100                     sp_svg_write_color( c, sizeof(c),
1101                                         SP_RGBA32_U_COMPOSE(
1102                                             0x0ff & (dataVals[0] >> 8),
1103                                             0x0ff & (dataVals[1] >> 8),
1104                                             0x0ff & (dataVals[2] >> 8),
1105                                             0xff // can't have transparency in the color itself
1106                                             //0x0ff & (data->data[3] >> 8),
1107                                             ));
1108                     SPCSSAttr *css = sp_repr_css_attr_new();
1109                     bool updatePerformed = false;
1111                     if ( data->length > 14 ) {
1112                         int flags = dataVals[4];
1114                         // piggie-backed palette entry info
1115                         int index = dataVals[5];
1116                         Glib::ustring palName;
1117                         for ( int i = 0; i < dataVals[6]; i++ ) {
1118                             palName += (gunichar)dataVals[7+i];
1119                         }
1121                         // Now hook in a magic tag of some sort.
1122                         if ( !palName.empty() && (flags & 1) ) {
1123                             gchar* str = g_strdup_printf("%d|", index);
1124                             palName.insert( 0, str );
1125                             g_free(str);
1126                             str = 0;
1128                             sp_object_setAttribute( SP_OBJECT(item),
1129                                                     fillnotstroke ? "inkscape:x-fill-tag":"inkscape:x-stroke-tag",
1130                                                     palName.c_str(),
1131                                                     false );
1132                             item->updateRepr();
1134                             sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1135                             updatePerformed = true;
1136                         }
1137                     }
1139                     if ( !updatePerformed ) {
1140                         sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1141                     }
1143                     sp_desktop_apply_css_recursive( item, css, true );
1144                     item->updateRepr();
1146                     sp_document_done( doc , SP_VERB_NONE,
1147                                       _("Drop color"));
1149                     if ( srgbProf ) {
1150                         cmsCloseProfile( srgbProf );
1151                     }
1152                 }
1153             }
1154         }
1155         break;
1156 #endif // ENABLE_MAGIC_COLORS
1158         case APP_X_COLOR:
1159         {
1160             int destX = 0;
1161             int destY = 0;
1162             gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1163             Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1164             Geom::Point const button_dt(desktop->w2d(where));
1165             Geom::Point const button_doc(desktop->dt2doc(button_dt));
1167             if ( data->length == 8 ) {
1168                 gchar colorspec[64] = {0};
1169                 // Careful about endian issues.
1170                 guint16* dataVals = (guint16*)data->data;
1171                 sp_svg_write_color( colorspec, sizeof(colorspec),
1172                                     SP_RGBA32_U_COMPOSE(
1173                                         0x0ff & (dataVals[0] >> 8),
1174                                         0x0ff & (dataVals[1] >> 8),
1175                                         0x0ff & (dataVals[2] >> 8),
1176                                         0xff // can't have transparency in the color itself
1177                                         //0x0ff & (data->data[3] >> 8),
1178                                         ));
1180                 SPItem *item = desktop->item_at_point( where, true );
1182                 bool consumed = false;
1183                 if (desktop->event_context && desktop->event_context->get_drag()) {
1184                     consumed = desktop->event_context->get_drag()->dropColor(item, colorspec, button_dt);
1185                     if (consumed) {
1186                         sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1187                         desktop->event_context->get_drag()->updateDraggers();
1188                     }
1189                 }
1191                 //if (!consumed && tools_active(desktop, TOOLS_TEXT)) {
1192                 //    consumed = sp_text_context_drop_color(c, button_doc);
1193                 //    if (consumed) {
1194                 //        sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient stop"));
1195                 //    }
1196                 //}
1198                 if (!consumed && item) {
1199                     bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1200                     if (fillnotstroke &&
1201                         (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1202                         Path *livarot_path = Path_for_item(item, true, true);
1203                         livarot_path->ConvertWithBackData(0.04);
1205                         boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1206                         if (position) {
1207                             Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1208                             Geom::Point delta = nearest - button_doc;
1209                             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1210                             delta = desktop->d2w(delta);
1211                             double stroke_tolerance =
1212                                 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1213                                   desktop->current_zoom() *
1214                                   SP_OBJECT_STYLE (item)->stroke_width.computed *
1215                                   to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1216                                   : 0.0)
1217                                 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1219                             if (Geom::L2 (delta) < stroke_tolerance) {
1220                                 fillnotstroke = false;
1221                             }
1222                         }
1223                         delete livarot_path;
1224                     }
1226                     SPCSSAttr *css = sp_repr_css_attr_new();
1227                     sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec );
1229                     sp_desktop_apply_css_recursive( item, css, true );
1230                     item->updateRepr();
1232                     sp_document_done( doc , SP_VERB_NONE,
1233                                       _("Drop color"));
1234                 }
1235             }
1236         }
1237         break;
1239         case APP_OSWB_COLOR:
1240         {
1241             bool worked = false;
1242             Glib::ustring colorspec;
1243             if ( data->format == 8 ) {
1244                 eek::ColorDef color;
1245                 worked = color.fromMIMEData("application/x-oswb-color",
1246                                             reinterpret_cast<char*>(data->data),
1247                                             data->length,
1248                                             data->format);
1249                 if ( worked ) {
1250                     if ( color.getType() == eek::ColorDef::CLEAR ) {
1251                         colorspec = ""; // TODO check if this is sufficient
1252                     } else if ( color.getType() == eek::ColorDef::NONE ) {
1253                         colorspec = "none";
1254                     } else {                        
1255                         gchar* tmp = g_strdup_printf("#%02x%02x%02x", color.getR(), color.getG(), color.getB());
1256                         colorspec = tmp;
1257                         g_free(tmp);
1258                     }
1259                 }
1260             }
1261             if ( worked ) {
1262                 int destX = 0;
1263                 int destY = 0;
1264                 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1265                 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1266                 Geom::Point const button_dt(desktop->w2d(where));
1267                 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1269                 SPItem *item = desktop->item_at_point( where, true );
1271                 bool consumed = false;
1272                 if (desktop->event_context && desktop->event_context->get_drag()) {
1273                     consumed = desktop->event_context->get_drag()->dropColor(item, colorspec.c_str(), button_dt);
1274                     if (consumed) {
1275                         sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1276                         desktop->event_context->get_drag()->updateDraggers();
1277                     }
1278                 }
1280                 if (!consumed && item) {
1281                     bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1282                     if (fillnotstroke &&
1283                         (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1284                         Path *livarot_path = Path_for_item(item, true, true);
1285                         livarot_path->ConvertWithBackData(0.04);
1287                         boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1288                         if (position) {
1289                             Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1290                             Geom::Point delta = nearest - button_doc;
1291                             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1292                             delta = desktop->d2w(delta);
1293                             double stroke_tolerance =
1294                                 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1295                                   desktop->current_zoom() *
1296                                   SP_OBJECT_STYLE (item)->stroke_width.computed *
1297                                   to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1298                                   : 0.0)
1299                                 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1301                             if (Geom::L2 (delta) < stroke_tolerance) {
1302                                 fillnotstroke = false;
1303                             }
1304                         }
1305                         delete livarot_path;
1306                     }
1308                     SPCSSAttr *css = sp_repr_css_attr_new();
1309                     sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec.c_str() );
1311                     sp_desktop_apply_css_recursive( item, css, true );
1312                     item->updateRepr();
1314                     sp_document_done( doc , SP_VERB_NONE,
1315                                       _("Drop color"));
1316                 }
1317             }
1318         }
1319         break;
1321         case SVG_DATA:
1322         case SVG_XML_DATA: {
1323             gchar *svgdata = (gchar *)data->data;
1325             Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI);
1327             if (rnewdoc == NULL) {
1328                 sp_ui_error_dialog(_("Could not parse SVG data"));
1329                 return;
1330             }
1332             Inkscape::XML::Node *repr = rnewdoc->root();
1333             gchar const *style = repr->attribute("style");
1335             Inkscape::XML::Node *newgroup = rnewdoc->createElement("svg:g");
1336             newgroup->setAttribute("style", style);
1338             Inkscape::XML::Document * xml_doc =  sp_document_repr_doc(doc);
1339             for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) {
1340                 Inkscape::XML::Node *newchild = child->duplicate(xml_doc);
1341                 newgroup->appendChild(newchild);
1342             }
1344             Inkscape::GC::release(rnewdoc);
1346             // Add it to the current layer
1348             // Greg's edits to add intelligent positioning of svg drops
1349             SPObject *new_obj = NULL;
1350             new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
1352             Inkscape::Selection *selection = sp_desktop_selection(desktop);
1353             selection->set(SP_ITEM(new_obj));
1354             // To move the imported object, we must temporarily set the "transform pattern with
1355             // object" option.
1356             {
1357                 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1358                 bool const saved_pref = prefs->getBool("/options/transform/pattern", true);
1359                 prefs->setBool("/options/transform/pattern", true);
1360                 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
1361                 Geom::OptRect sel_bbox = selection->bounds();
1362                 if (sel_bbox) {
1363                     Geom::Point m( desktop->point() - sel_bbox->midpoint() );
1364                     sp_selection_move_relative(selection, m);
1365                 }
1366                 prefs->setBool("/options/transform/pattern", saved_pref);
1367             }
1369             Inkscape::GC::release(newgroup);
1370             sp_document_done(doc, SP_VERB_NONE,
1371                              _("Drop SVG"));
1372             break;
1373         }
1375         case URI_LIST: {
1376             gchar *uri = (gchar *)data->data;
1377             sp_ui_import_files(uri);
1378             break;
1379         }
1381         case PNG_DATA:
1382         case JPEG_DATA:
1383         case IMAGE_DATA: {
1384             Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
1385             Inkscape::XML::Node *newImage = xml_doc->createElement("svg:image");
1386             gchar *atom_name = gdk_atom_name(data->type);
1387             
1388             // this formula taken from Glib docs
1389             guint needed_size = data->length * 4 / 3 + data->length * 4 / (3 * 72) + 7;
1390             needed_size += 5 + 8 + strlen(atom_name); // 5 bytes for data:, 8 for ;base64,
1391             
1392             gchar *buffer = (gchar *) g_malloc(needed_size), *buf_work = buffer;
1393             buf_work += g_sprintf(buffer, "data:%s;base64,", atom_name);
1394             
1395             gint state = 0, save = 0;
1396             g_base64_encode_step(data->data, data->length, TRUE, buf_work, &state, &save);
1397             g_base64_encode_close(TRUE, buf_work, &state, &save);
1399             newImage->setAttribute("xlink:href", buffer);
1400             g_free(buffer);
1402             GError *error = NULL;
1403             GdkPixbufLoader *loader = gdk_pixbuf_loader_new_with_mime_type( gdk_atom_name(data->type), &error );
1404             if ( loader ) {
1405                 error = NULL;
1406                 if ( gdk_pixbuf_loader_write( loader, data->data, data->length, &error) ) {
1407                     GdkPixbuf *pbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1408                     if ( pbuf ) {
1409                         char tmp[1024];
1410                         int width = gdk_pixbuf_get_width(pbuf);
1411                         int height = gdk_pixbuf_get_height(pbuf);
1412                         snprintf( tmp, sizeof(tmp), "%d", width );
1413                         newImage->setAttribute("width", tmp);
1415                         snprintf( tmp, sizeof(tmp), "%d", height );
1416                         newImage->setAttribute("height", tmp);
1417                     }
1418                 }
1419             }
1420             g_free(atom_name);
1422             // Add it to the current layer
1423             desktop->currentLayer()->appendChildRepr(newImage);
1425             Inkscape::GC::release(newImage);
1426             sp_document_done( doc , SP_VERB_NONE,
1427                               _("Drop bitmap image"));
1428             break;
1429         }
1430     }
1433 #include "gradient-context.h"
1435 void sp_ui_drag_motion( GtkWidget */*widget*/,
1436                         GdkDragContext */*drag_context*/,
1437                         gint /*x*/, gint /*y*/,
1438                         GtkSelectionData */*data*/,
1439                         guint /*info*/,
1440                         guint /*event_time*/,
1441                         gpointer /*user_data*/)
1443 //     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1444 //     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1447 //     g_message("drag-n-drop motion (%4d, %4d)  at %d", x, y, event_time);
1450 static void sp_ui_drag_leave( GtkWidget */*widget*/,
1451                               GdkDragContext */*drag_context*/,
1452                               guint /*event_time*/,
1453                               gpointer /*user_data*/ )
1455 //     g_message("drag-n-drop leave                at %d", event_time);
1458 static void
1459 sp_ui_import_files(gchar *buffer)
1461     GList *list = gnome_uri_list_extract_filenames(buffer);
1462     if (!list)
1463         return;
1464     g_list_foreach(list, sp_ui_import_one_file_with_check, NULL);
1465     g_list_foreach(list, (GFunc) g_free, NULL);
1466     g_list_free(list);
1469 static void
1470 sp_ui_import_one_file_with_check(gpointer filename, gpointer /*unused*/)
1472     if (filename) {
1473         if (strlen((char const *)filename) > 2)
1474             sp_ui_import_one_file((char const *)filename);
1475     }
1478 static void
1479 sp_ui_import_one_file(char const *filename)
1481     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1482     if (!doc) return;
1484     if (filename == NULL) return;
1486     // Pass off to common implementation
1487     // TODO might need to get the proper type of Inkscape::Extension::Extension
1488     file_import( doc, filename, NULL );
1491 void
1492 sp_ui_error_dialog(gchar const *message)
1494     GtkWidget *dlg;
1495     gchar *safeMsg = Inkscape::IO::sanitizeString(message);
1497     dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
1498                                  GTK_BUTTONS_CLOSE, "%s", safeMsg);
1499     sp_transientize(dlg);
1500     gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE);
1501     gtk_dialog_run(GTK_DIALOG(dlg));
1502     gtk_widget_destroy(dlg);
1503     g_free(safeMsg);
1506 bool
1507 sp_ui_overwrite_file(gchar const *filename)
1509     bool return_value = FALSE;
1511     if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) {
1512         Gtk::Window *window = SP_ACTIVE_DESKTOP->getToplevel();
1513         gchar* baseName = g_path_get_basename( filename );
1514         gchar* dirName = g_path_get_dirname( filename );
1515         GtkWidget* dialog = gtk_message_dialog_new_with_markup( window->gobj(),
1516                                                                 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
1517                                                                 GTK_MESSAGE_QUESTION,
1518                                                                 GTK_BUTTONS_NONE,
1519                                                                 _( "<span weight=\"bold\" size=\"larger\">A file named \"%s\" already exists. Do you want to replace it?</span>\n\n"
1520                                                                    "The file already exists in \"%s\". Replacing it will overwrite its contents." ),
1521                                                                 baseName,
1522                                                                 dirName
1523             );
1524         gtk_dialog_add_buttons( GTK_DIALOG(dialog),
1525                                 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1526                                 _("Replace"), GTK_RESPONSE_YES,
1527                                 NULL );
1528         gtk_dialog_set_default_response( GTK_DIALOG(dialog), GTK_RESPONSE_YES );
1530         if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_YES ) {
1531             return_value = TRUE;
1532         } else {
1533             return_value = FALSE;
1534         }
1535         gtk_widget_destroy(dialog);
1536         g_free( baseName );
1537         g_free( dirName );
1538     } else {
1539         return_value = TRUE;
1540     }
1542     return return_value;
1545 static void
1546 sp_ui_menu_item_set_sensitive(SPAction */*action*/, unsigned int sensitive, void *data)
1548     return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
1551 static void
1552 sp_ui_menu_item_set_name(SPAction */*action*/, Glib::ustring name, void *data)
1554     void *child = GTK_BIN (data)->child;
1555     //child is either
1556     //- a GtkHBox, whose first child is a label displaying name if the menu
1557     //item has an accel key
1558     //- a GtkLabel if the menu has no accel key
1559     if (GTK_IS_LABEL(child)) {
1560         gtk_label_set_markup_with_mnemonic(GTK_LABEL (child), name.c_str());
1561     } else if (GTK_IS_HBOX(child)) {
1562         gtk_label_set_markup_with_mnemonic(
1563         GTK_LABEL (gtk_container_get_children(GTK_CONTAINER (child))->data),
1564         name.c_str());
1565     }//else sp_ui_menu_append_item_from_verb has been modified and can set
1566     //a menu item in yet another way...
1570 /*
1571   Local Variables:
1572   mode:c++
1573   c-file-style:"stroustrup"
1574   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1575   indent-tabs-mode:nil
1576   fill-column:99
1577   End:
1578 */
1579 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :