Code

Moving dnd data generation into the generic ColorDef class.
[inkscape.git] / src / interface.cpp
1 #define __SP_INTERFACE_C__
3 /** @file
4  * @brief Main UI stuff
5  */
6 /* Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   Frank Felfe <innerspace@iname.com>
9  *   bulia byak <buliabyak@users.sf.net>
10  *
11  * Copyright (C) 1999-2005 authors
12  * Copyright (C) 2001-2002 Ximian, Inc.
13  * Copyright (C) 2004 David Turner
14  *
15  * Released under GNU GPL, read the file 'COPYING' for more information
16  */
18 #ifdef HAVE_CONFIG_H
19 # include "config.h"
20 #endif
22 #include <gtk/gtk.h>
23 #include <glib.h>
25 #include "inkscape-private.h"
26 #include "extension/effect.h"
27 #include "widgets/icon.h"
28 #include "preferences.h"
29 #include "path-prefix.h"
30 #include "shortcuts.h"
31 #include "document.h"
32 #include "desktop-handles.h"
33 #include "file.h"
34 #include "interface.h"
35 #include "desktop.h"
36 #include "ui/context-menu.h"
37 #include "selection.h"
38 #include "selection-chemistry.h"
39 #include "svg-view-widget.h"
40 #include "widgets/desktop-widget.h"
41 #include "sp-item-group.h"
42 #include "sp-text.h"
43 #include "sp-flowtext.h"
44 #include "sp-namedview.h"
45 #include "ui/view/view.h"
46 #include "helper/action.h"
47 #include "helper/gnome-utils.h"
48 #include "helper/window.h"
49 #include "io/sys.h"
50 #include "dialogs/dialog-events.h"
51 #include "message-context.h"
53 // Added for color drag-n-drop
54 #if ENABLE_LCMS
55 #include "lcms.h"
56 #endif // ENABLE_LCMS
57 #include "display/sp-canvas.h"
58 #include "color.h"
59 #include "svg/svg-color.h"
60 #include "desktop-style.h"
61 #include "style.h"
62 #include "event-context.h"
63 #include "gradient-drag.h"
65 // Include Mac OS X menu synchronization on native OSX build
66 #ifdef GDK_WINDOWING_QUARTZ
67 #include "ige-mac-menu.h"
68 #endif
70 /* Drag and Drop */
71 typedef enum {
72     URI_LIST,
73     SVG_XML_DATA,
74     SVG_DATA,
75     PNG_DATA,
76     JPEG_DATA,
77     IMAGE_DATA,
78     APP_X_INKY_COLOR,
79     APP_X_COLOR,
80     APP_X_NOCOLOR,
81     APP_X_XCOLOR
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-nocolor",   0, APP_X_NOCOLOR     },
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 c[64] = {0};
1169                 // Careful about endian issues.
1170                 guint16* dataVals = (guint16*)data->data;
1171                 sp_svg_write_color( c, 64,
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, c, 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", c );
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_X_NOCOLOR:
1240         case APP_X_XCOLOR:
1241         {
1242             gchar* c = g_strdup("none"); // temp
1243             int destX = 0;
1244             int destY = 0;
1245             gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1246             Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1247             Geom::Point const button_dt(desktop->w2d(where));
1248             Geom::Point const button_doc(desktop->dt2doc(button_dt));
1250             SPItem *item = desktop->item_at_point( where, true );
1252             bool consumed = false;
1253             if (desktop->event_context && desktop->event_context->get_drag()) {
1254                 consumed = desktop->event_context->get_drag()->dropColor(item, c, button_dt);
1255                 if (consumed) {
1256                     sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1257                     desktop->event_context->get_drag()->updateDraggers();
1258                 }
1259             }
1261             if (!consumed && item) {
1262                 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1263                 if (fillnotstroke &&
1264                     (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1265                     Path *livarot_path = Path_for_item(item, true, true);
1266                     livarot_path->ConvertWithBackData(0.04);
1268                     boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1269                     if (position) {
1270                         Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1271                         Geom::Point delta = nearest - button_doc;
1272                         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1273                         delta = desktop->d2w(delta);
1274                         double stroke_tolerance =
1275                             ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1276                               desktop->current_zoom() *
1277                               SP_OBJECT_STYLE (item)->stroke_width.computed *
1278                               to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1279                               : 0.0)
1280                             + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1282                         if (Geom::L2 (delta) < stroke_tolerance) {
1283                             fillnotstroke = false;
1284                         }
1285                     }
1286                     delete livarot_path;
1287                 }
1289                 SPCSSAttr *css = sp_repr_css_attr_new();
1290                 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1292                 sp_desktop_apply_css_recursive( item, css, true );
1293                 item->updateRepr();
1295                 sp_document_done( doc , SP_VERB_NONE,
1296                                   _("Drop color"));
1297             }
1298             g_free(c);
1299         }
1300         break;
1302         case SVG_DATA:
1303         case SVG_XML_DATA: {
1304             gchar *svgdata = (gchar *)data->data;
1306             Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI);
1308             if (rnewdoc == NULL) {
1309                 sp_ui_error_dialog(_("Could not parse SVG data"));
1310                 return;
1311             }
1313             Inkscape::XML::Node *repr = rnewdoc->root();
1314             gchar const *style = repr->attribute("style");
1316             Inkscape::XML::Node *newgroup = rnewdoc->createElement("svg:g");
1317             newgroup->setAttribute("style", style);
1319             Inkscape::XML::Document * xml_doc =  sp_document_repr_doc(doc);
1320             for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) {
1321                 Inkscape::XML::Node *newchild = child->duplicate(xml_doc);
1322                 newgroup->appendChild(newchild);
1323             }
1325             Inkscape::GC::release(rnewdoc);
1327             // Add it to the current layer
1329             // Greg's edits to add intelligent positioning of svg drops
1330             SPObject *new_obj = NULL;
1331             new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
1333             Inkscape::Selection *selection = sp_desktop_selection(desktop);
1334             selection->set(SP_ITEM(new_obj));
1335             // To move the imported object, we must temporarily set the "transform pattern with
1336             // object" option.
1337             {
1338                 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1339                 bool const saved_pref = prefs->getBool("/options/transform/pattern", true);
1340                 prefs->setBool("/options/transform/pattern", true);
1341                 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
1342                 Geom::OptRect sel_bbox = selection->bounds();
1343                 if (sel_bbox) {
1344                     Geom::Point m( desktop->point() - sel_bbox->midpoint() );
1345                     sp_selection_move_relative(selection, m);
1346                 }
1347                 prefs->setBool("/options/transform/pattern", saved_pref);
1348             }
1350             Inkscape::GC::release(newgroup);
1351             sp_document_done(doc, SP_VERB_NONE,
1352                              _("Drop SVG"));
1353             break;
1354         }
1356         case URI_LIST: {
1357             gchar *uri = (gchar *)data->data;
1358             sp_ui_import_files(uri);
1359             break;
1360         }
1362         case PNG_DATA:
1363         case JPEG_DATA:
1364         case IMAGE_DATA: {
1365             Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
1366             Inkscape::XML::Node *newImage = xml_doc->createElement("svg:image");
1367             gchar *atom_name = gdk_atom_name(data->type);
1368             
1369             // this formula taken from Glib docs
1370             guint needed_size = data->length * 4 / 3 + data->length * 4 / (3 * 72) + 7;
1371             needed_size += 5 + 8 + strlen(atom_name); // 5 bytes for data:, 8 for ;base64,
1372             
1373             gchar *buffer = (gchar *) g_malloc(needed_size), *buf_work = buffer;
1374             buf_work += g_sprintf(buffer, "data:%s;base64,", atom_name);
1375             
1376             gint state = 0, save = 0;
1377             g_base64_encode_step(data->data, data->length, TRUE, buf_work, &state, &save);
1378             g_base64_encode_close(TRUE, buf_work, &state, &save);
1380             newImage->setAttribute("xlink:href", buffer);
1381             g_free(buffer);
1383             GError *error = NULL;
1384             GdkPixbufLoader *loader = gdk_pixbuf_loader_new_with_mime_type( gdk_atom_name(data->type), &error );
1385             if ( loader ) {
1386                 error = NULL;
1387                 if ( gdk_pixbuf_loader_write( loader, data->data, data->length, &error) ) {
1388                     GdkPixbuf *pbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1389                     if ( pbuf ) {
1390                         char tmp[1024];
1391                         int width = gdk_pixbuf_get_width(pbuf);
1392                         int height = gdk_pixbuf_get_height(pbuf);
1393                         snprintf( tmp, sizeof(tmp), "%d", width );
1394                         newImage->setAttribute("width", tmp);
1396                         snprintf( tmp, sizeof(tmp), "%d", height );
1397                         newImage->setAttribute("height", tmp);
1398                     }
1399                 }
1400             }
1401             g_free(atom_name);
1403             // Add it to the current layer
1404             desktop->currentLayer()->appendChildRepr(newImage);
1406             Inkscape::GC::release(newImage);
1407             sp_document_done( doc , SP_VERB_NONE,
1408                               _("Drop bitmap image"));
1409             break;
1410         }
1411     }
1414 #include "gradient-context.h"
1416 void sp_ui_drag_motion( GtkWidget */*widget*/,
1417                         GdkDragContext */*drag_context*/,
1418                         gint /*x*/, gint /*y*/,
1419                         GtkSelectionData */*data*/,
1420                         guint /*info*/,
1421                         guint /*event_time*/,
1422                         gpointer /*user_data*/)
1424 //     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1425 //     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1428 //     g_message("drag-n-drop motion (%4d, %4d)  at %d", x, y, event_time);
1431 static void sp_ui_drag_leave( GtkWidget */*widget*/,
1432                               GdkDragContext */*drag_context*/,
1433                               guint /*event_time*/,
1434                               gpointer /*user_data*/ )
1436 //     g_message("drag-n-drop leave                at %d", event_time);
1439 static void
1440 sp_ui_import_files(gchar *buffer)
1442     GList *list = gnome_uri_list_extract_filenames(buffer);
1443     if (!list)
1444         return;
1445     g_list_foreach(list, sp_ui_import_one_file_with_check, NULL);
1446     g_list_foreach(list, (GFunc) g_free, NULL);
1447     g_list_free(list);
1450 static void
1451 sp_ui_import_one_file_with_check(gpointer filename, gpointer /*unused*/)
1453     if (filename) {
1454         if (strlen((char const *)filename) > 2)
1455             sp_ui_import_one_file((char const *)filename);
1456     }
1459 static void
1460 sp_ui_import_one_file(char const *filename)
1462     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1463     if (!doc) return;
1465     if (filename == NULL) return;
1467     // Pass off to common implementation
1468     // TODO might need to get the proper type of Inkscape::Extension::Extension
1469     file_import( doc, filename, NULL );
1472 void
1473 sp_ui_error_dialog(gchar const *message)
1475     GtkWidget *dlg;
1476     gchar *safeMsg = Inkscape::IO::sanitizeString(message);
1478     dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
1479                                  GTK_BUTTONS_CLOSE, "%s", safeMsg);
1480     sp_transientize(dlg);
1481     gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE);
1482     gtk_dialog_run(GTK_DIALOG(dlg));
1483     gtk_widget_destroy(dlg);
1484     g_free(safeMsg);
1487 bool
1488 sp_ui_overwrite_file(gchar const *filename)
1490     bool return_value = FALSE;
1492     if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) {
1493         Gtk::Window *window = SP_ACTIVE_DESKTOP->getToplevel();
1494         gchar* baseName = g_path_get_basename( filename );
1495         gchar* dirName = g_path_get_dirname( filename );
1496         GtkWidget* dialog = gtk_message_dialog_new_with_markup( window->gobj(),
1497                                                                 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
1498                                                                 GTK_MESSAGE_QUESTION,
1499                                                                 GTK_BUTTONS_NONE,
1500                                                                 _( "<span weight=\"bold\" size=\"larger\">A file named \"%s\" already exists. Do you want to replace it?</span>\n\n"
1501                                                                    "The file already exists in \"%s\". Replacing it will overwrite its contents." ),
1502                                                                 baseName,
1503                                                                 dirName
1504             );
1505         gtk_dialog_add_buttons( GTK_DIALOG(dialog),
1506                                 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1507                                 _("Replace"), GTK_RESPONSE_YES,
1508                                 NULL );
1509         gtk_dialog_set_default_response( GTK_DIALOG(dialog), GTK_RESPONSE_YES );
1511         if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_YES ) {
1512             return_value = TRUE;
1513         } else {
1514             return_value = FALSE;
1515         }
1516         gtk_widget_destroy(dialog);
1517         g_free( baseName );
1518         g_free( dirName );
1519     } else {
1520         return_value = TRUE;
1521     }
1523     return return_value;
1526 static void
1527 sp_ui_menu_item_set_sensitive(SPAction */*action*/, unsigned int sensitive, void *data)
1529     return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
1532 static void
1533 sp_ui_menu_item_set_name(SPAction */*action*/, Glib::ustring name, void *data)
1535     void *child = GTK_BIN (data)->child;
1536     //child is either
1537     //- a GtkHBox, whose first child is a label displaying name if the menu
1538     //item has an accel key
1539     //- a GtkLabel if the menu has no accel key
1540     if (GTK_IS_LABEL(child)) {
1541         gtk_label_set_markup_with_mnemonic(GTK_LABEL (child), name.c_str());
1542     } else if (GTK_IS_HBOX(child)) {
1543         gtk_label_set_markup_with_mnemonic(
1544         GTK_LABEL (gtk_container_get_children(GTK_CONTAINER (child))->data),
1545         name.c_str());
1546     }//else sp_ui_menu_append_item_from_verb has been modified and can set
1547     //a menu item in yet another way...
1551 /*
1552   Local Variables:
1553   mode:c++
1554   c-file-style:"stroustrup"
1555   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1556   indent-tabs-mode:nil
1557   fill-column:99
1558   End:
1559 */
1560 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :