Code

d628d964d3bc10bfc5e2e62546101d91de8fe7e4
[inkscape.git] / src / interface.cpp
1 #define __SP_INTERFACE_C__
3 /** @file
4  * @brief Main UI stuff
5  */
6 /* Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   Frank Felfe <innerspace@iname.com>
9  *   bulia byak <buliabyak@users.sf.net>
10  *
11  * Copyright (C) 1999-2005 authors
12  * Copyright (C) 2001-2002 Ximian, Inc.
13  * Copyright (C) 2004 David Turner
14  *
15  * Released under GNU GPL, read the file 'COPYING' for more information
16  */
18 #ifdef HAVE_CONFIG_H
19 # include "config.h"
20 #endif
22 #include <gtk/gtk.h>
23 #include <glib.h>
25 #include "inkscape-private.h"
26 #include "extension/effect.h"
27 #include "widgets/icon.h"
28 #include "preferences.h"
29 #include "path-prefix.h"
30 #include "shortcuts.h"
31 #include "document.h"
32 #include "desktop-handles.h"
33 #include "file.h"
34 #include "interface.h"
35 #include "desktop.h"
36 #include "ui/context-menu.h"
37 #include "selection.h"
38 #include "selection-chemistry.h"
39 #include "svg-view-widget.h"
40 #include "widgets/desktop-widget.h"
41 #include "sp-item-group.h"
42 #include "sp-text.h"
43 #include "sp-gradient-fns.h"
44 #include "sp-gradient.h"
45 #include "sp-flowtext.h"
46 #include "sp-namedview.h"
47 #include "ui/view/view.h"
48 #include "helper/action.h"
49 #include "helper/gnome-utils.h"
50 #include "helper/window.h"
51 #include "io/sys.h"
52 #include "dialogs/dialog-events.h"
53 #include "message-context.h"
55 // Added for color drag-n-drop
56 #if ENABLE_LCMS
57 #include "lcms.h"
58 #endif // ENABLE_LCMS
59 #include "display/sp-canvas.h"
60 #include "color.h"
61 #include "svg/svg-color.h"
62 #include "desktop-style.h"
63 #include "style.h"
64 #include "event-context.h"
65 #include "gradient-drag.h"
66 #include "widgets/ege-paint-def.h"
68 // Include Mac OS X menu synchronization on native OSX build
69 #ifdef GDK_WINDOWING_QUARTZ
70 #include "ige-mac-menu.h"
71 #endif
73 /* Drag and Drop */
74 typedef enum {
75     URI_LIST,
76     SVG_XML_DATA,
77     SVG_DATA,
78     PNG_DATA,
79     JPEG_DATA,
80     IMAGE_DATA,
81     APP_X_INKY_COLOR,
82     APP_X_COLOR,
83     APP_OSWB_COLOR,
84 } ui_drop_target_info;
86 static GtkTargetEntry ui_drop_target_entries [] = {
87     {(gchar *)"text/uri-list",                0, URI_LIST        },
88     {(gchar *)"image/svg+xml",                0, SVG_XML_DATA    },
89     {(gchar *)"image/svg",                    0, SVG_DATA        },
90     {(gchar *)"image/png",                    0, PNG_DATA        },
91     {(gchar *)"image/jpeg",                   0, JPEG_DATA       },
92 #if ENABLE_MAGIC_COLORS
93     {(gchar *)"application/x-inkscape-color", 0, APP_X_INKY_COLOR},
94 #endif // ENABLE_MAGIC_COLORS
95     {(gchar *)"application/x-oswb-color",     0, APP_OSWB_COLOR  },
96     {(gchar *)"application/x-color",          0, APP_X_COLOR     }
97 };
99 static GtkTargetEntry *completeDropTargets = 0;
100 static int completeDropTargetsCount = 0;
102 #define ENTRIES_SIZE(n) sizeof(n)/sizeof(n[0])
103 static guint nui_drop_target_entries = ENTRIES_SIZE(ui_drop_target_entries);
104 static void sp_ui_import_files(gchar *buffer);
105 static void sp_ui_import_one_file(char const *filename);
106 static void sp_ui_import_one_file_with_check(gpointer filename, gpointer unused);
107 static void sp_ui_drag_data_received(GtkWidget *widget,
108                                      GdkDragContext *drag_context,
109                                      gint x, gint y,
110                                      GtkSelectionData *data,
111                                      guint info,
112                                      guint event_time,
113                                      gpointer user_data);
114 static void sp_ui_drag_motion( GtkWidget *widget,
115                                GdkDragContext *drag_context,
116                                gint x, gint y,
117                                GtkSelectionData *data,
118                                guint info,
119                                guint event_time,
120                                gpointer user_data );
121 static void sp_ui_drag_leave( GtkWidget *widget,
122                               GdkDragContext *drag_context,
123                               guint event_time,
124                               gpointer user_data );
125 static void sp_ui_menu_item_set_sensitive(SPAction *action,
126                                           unsigned int sensitive,
127                                           void *data);
128 static void sp_ui_menu_item_set_name(SPAction *action,
129                                      Glib::ustring name,
130                                      void *data);
131 static void sp_recent_open(GtkRecentChooser *, gpointer);
133 SPActionEventVector menu_item_event_vector = {
134     {NULL},
135     NULL,
136     NULL, /* set_active */
137     sp_ui_menu_item_set_sensitive, /* set_sensitive */
138     NULL, /* set_shortcut */
139     sp_ui_menu_item_set_name /* set_name */
140 };
142 static const int MIN_ONSCREEN_DISTANCE = 50;
144 void
145 sp_create_window(SPViewWidget *vw, gboolean editable)
147     g_return_if_fail(vw != NULL);
148     g_return_if_fail(SP_IS_VIEW_WIDGET(vw));
150     Gtk::Window *win = Inkscape::UI::window_new("", TRUE);
152     gtk_container_add(GTK_CONTAINER(win->gobj()), GTK_WIDGET(vw));
153     gtk_widget_show(GTK_WIDGET(vw));
155     if (editable) {
156         g_object_set_data(G_OBJECT(vw), "window", win);
158         SPDesktopWidget *desktop_widget = reinterpret_cast<SPDesktopWidget*>(vw);
159         SPDesktop* desktop = desktop_widget->desktop;
161         desktop_widget->window = win;
163         win->set_data("desktop", desktop);
164         win->set_data("desktopwidget", desktop_widget);
166         win->signal_delete_event().connect(sigc::mem_fun(*(SPDesktop*)vw->view, &SPDesktop::onDeleteUI));
167         win->signal_window_state_event().connect(sigc::mem_fun(*desktop, &SPDesktop::onWindowStateEvent));
168         win->signal_focus_in_event().connect(sigc::mem_fun(*desktop_widget, &SPDesktopWidget::onFocusInEvent));
170         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
171         gint prefs_geometry =
172             (2==prefs->getInt("/options/savewindowgeometry/value", 0));
173         if (prefs_geometry) {
174             gint pw = prefs->getInt("/desktop/geometry/width", -1);
175             gint ph = prefs->getInt("/desktop/geometry/height", -1);
176             gint px = prefs->getInt("/desktop/geometry/x", -1);
177             gint py = prefs->getInt("/desktop/geometry/y", -1);
178             gint full = prefs->getBool("/desktop/geometry/fullscreen");
179             gint maxed = prefs->getBool("/desktop/geometry/maximized");
180             if (pw>0 && ph>0) {
181                 gint w = MIN(gdk_screen_width(), pw);
182                 gint h = MIN(gdk_screen_height(), ph);
183                 gint x = MIN(gdk_screen_width() - MIN_ONSCREEN_DISTANCE, px);
184                 gint y = MIN(gdk_screen_height() - MIN_ONSCREEN_DISTANCE, py);
185                 if (w>0 && h>0) {
186                     x = MIN(gdk_screen_width() - w, x);
187                     y = MIN(gdk_screen_height() - h, y);
188                     desktop->setWindowSize(w, h);
189                 }
191                 // Only restore xy for the first window so subsequent windows don't overlap exactly
192                 // with first.  (Maybe rule should be only restore xy if it's different from xy of
193                 // other desktops?)
195                 // Empirically it seems that active_desktop==this desktop only the first time a
196                 // desktop is created.
197                 SPDesktop *active_desktop = SP_ACTIVE_DESKTOP;
198                 if (active_desktop == desktop || active_desktop==NULL) {
199                     desktop->setWindowPosition(Geom::Point(x, y));
200                 }
201             }
202             if (maxed) {
203                 win->maximize();
204             }
205             if (full) {
206                 win->fullscreen();
207             }
208         }
210     } else {
211         gtk_window_set_policy(GTK_WINDOW(win->gobj()), TRUE, TRUE, TRUE);
212     }
214     if ( completeDropTargets == 0 || completeDropTargetsCount == 0 )
215     {
216         std::vector<gchar*> types;
218         GSList *list = gdk_pixbuf_get_formats();
219         while ( list ) {
220             int i = 0;
221             GdkPixbufFormat *one = (GdkPixbufFormat*)list->data;
222             gchar** typesXX = gdk_pixbuf_format_get_mime_types(one);
223             for ( i = 0; typesXX[i]; i++ ) {
224                 types.push_back(g_strdup(typesXX[i]));
225             }
226             g_strfreev(typesXX);
228             list = g_slist_next(list);
229         }
230         completeDropTargetsCount = nui_drop_target_entries + types.size();
231         completeDropTargets = new GtkTargetEntry[completeDropTargetsCount];
232         for ( int i = 0; i < (int)nui_drop_target_entries; i++ ) {
233             completeDropTargets[i] = ui_drop_target_entries[i];
234         }
235         int pos = nui_drop_target_entries;
237         for (std::vector<gchar*>::iterator it = types.begin() ; it != types.end() ; it++) {
238             completeDropTargets[pos].target = *it;
239             completeDropTargets[pos].flags = 0;
240             completeDropTargets[pos].info = IMAGE_DATA;
241             pos++;
242         }
243     }
245     gtk_drag_dest_set((GtkWidget*)win->gobj(),
246                       GTK_DEST_DEFAULT_ALL,
247                       completeDropTargets,
248                       completeDropTargetsCount,
249                       GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE));
252     g_signal_connect(G_OBJECT(win->gobj()),
253                      "drag_data_received",
254                      G_CALLBACK(sp_ui_drag_data_received),
255                      NULL);
256     g_signal_connect(G_OBJECT(win->gobj()),
257                      "drag_motion",
258                      G_CALLBACK(sp_ui_drag_motion),
259                      NULL);
260     g_signal_connect(G_OBJECT(win->gobj()),
261                      "drag_leave",
262                      G_CALLBACK(sp_ui_drag_leave),
263                      NULL);
264     win->show();
266     // needed because the first ACTIVATE_DESKTOP was sent when there was no window yet
267     inkscape_reactivate_desktop(SP_DESKTOP_WIDGET(vw)->desktop);
270 void
271 sp_ui_new_view()
273     SPDocument *document;
274     SPViewWidget *dtw;
276     document = SP_ACTIVE_DOCUMENT;
277     if (!document) return;
279     dtw = sp_desktop_widget_new(sp_document_namedview(document, NULL));
280     g_return_if_fail(dtw != NULL);
282     sp_create_window(dtw, TRUE);
283     sp_namedview_window_from_document(static_cast<SPDesktop*>(dtw->view));
284     sp_namedview_update_layers_from_document(static_cast<SPDesktop*>(dtw->view));
287 /* TODO: not yet working */
288 /* To be re-enabled (by adding to menu) once it works. */
289 void
290 sp_ui_new_view_preview()
292     SPDocument *document;
293     SPViewWidget *dtw;
295     document = SP_ACTIVE_DOCUMENT;
296     if (!document) return;
298     dtw = (SPViewWidget *) sp_svg_view_widget_new(document);
299     g_return_if_fail(dtw != NULL);
300     sp_svg_view_widget_set_resize(SP_SVG_VIEW_WIDGET(dtw), TRUE, 400.0, 400.0);
302     sp_create_window(dtw, FALSE);
305 /**
306  * \param widget unused
307  */
308 void
309 sp_ui_close_view(GtkWidget */*widget*/)
311     if (SP_ACTIVE_DESKTOP == NULL) {
312         return;
313     }
314     if ((SP_ACTIVE_DESKTOP)->shutdown()) {
315         return;
316     }
317     SP_ACTIVE_DESKTOP->destroyWidget();
321 /**
322  *  sp_ui_close_all
323  *
324  *  This function is called to exit the program, and iterates through all
325  *  open document view windows, attempting to close each in turn.  If the
326  *  view has unsaved information, the user will be prompted to save,
327  *  discard, or cancel.
328  *
329  *  Returns FALSE if the user cancels the close_all operation, TRUE
330  *  otherwise.
331  */
332 unsigned int
333 sp_ui_close_all(void)
335     /* Iterate through all the windows, destroying each in the order they
336        become active */
337     while (SP_ACTIVE_DESKTOP) {
338         if ((SP_ACTIVE_DESKTOP)->shutdown()) {
339             /* The user cancelled the operation, so end doing the close */
340             return FALSE;
341         }
342         SP_ACTIVE_DESKTOP->destroyWidget();
343     }
345     return TRUE;
348 /*
349  * Some day when the right-click menus are ready to start working
350  * smarter with the verbs, we'll need to change this NULL being
351  * sent to sp_action_perform to something useful, or set some kind
352  * of global "right-clicked position" variable for actions to
353  * investigate when they're called.
354  */
355 static void
356 sp_ui_menu_activate(void */*object*/, SPAction *action)
358     sp_action_perform(action, NULL);
361 static void
362 sp_ui_menu_select_action(void */*object*/, SPAction *action)
364     action->view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, action->tip);
367 static void
368 sp_ui_menu_deselect_action(void */*object*/, SPAction *action)
370     action->view->tipsMessageContext()->clear();
373 static void
374 sp_ui_menu_select(gpointer object, gpointer tip)
376     Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view"));
377     view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, (gchar *)tip);
380 static void
381 sp_ui_menu_deselect(gpointer object)
383     Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*>  (g_object_get_data(G_OBJECT(object), "view"));
384     view->tipsMessageContext()->clear();
387 /**
388  * sp_ui_menuitem_add_icon
389  *
390  * Creates and attaches a scaled icon to the given menu item.
391  *
392  */
393 void
394 sp_ui_menuitem_add_icon( GtkWidget *item, gchar *icon_name )
396     GtkWidget *icon;
398     icon = sp_icon_new( Inkscape::ICON_SIZE_MENU, icon_name );
399     gtk_widget_show(icon);
400     gtk_image_menu_item_set_image((GtkImageMenuItem *) item, icon);
401 } // end of sp_ui_menu_add_icon
403 /**
404  * sp_ui_menu_append_item
405  *
406  * Appends a UI item with specific info for Inkscape/Sodipodi.
407  *
408  */
409 static GtkWidget *
410 sp_ui_menu_append_item( GtkMenu *menu, gchar const *stock,
411                         gchar const *label, gchar const *tip, Inkscape::UI::View::View *view, GCallback callback,
412                         gpointer data, gboolean with_mnemonic = TRUE )
414     GtkWidget *item;
416     if (stock) {
417         item = gtk_image_menu_item_new_from_stock(stock, NULL);
418     } else if (label) {
419         item = (with_mnemonic)
420             ? gtk_image_menu_item_new_with_mnemonic(label) :
421             gtk_image_menu_item_new_with_label(label);
422     } else {
423         item = gtk_separator_menu_item_new();
424     }
426     gtk_widget_show(item);
428     if (callback) {
429         g_signal_connect(G_OBJECT(item), "activate", callback, data);
430     }
432     if (tip && view) {
433         g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
434         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) tip );
435         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
436     }
438     gtk_menu_append(GTK_MENU(menu), item);
440     return item;
442 } // end of sp_ui_menu_append_item()
444 /**
445 \brief  a wrapper around gdk_keyval_name producing (when possible) characters, not names
446  */
447 static gchar const *
448 sp_key_name(guint keyval)
450     /* TODO: Compare with the definition of gtk_accel_label_refetch in gtk/gtkaccellabel.c (or
451        simply use GtkAccelLabel as the TODO comment in sp_ui_shortcut_string suggests). */
452     gchar const *n = gdk_keyval_name(gdk_keyval_to_upper(keyval));
454     if      (!strcmp(n, "asciicircum"))  return "^";
455     else if (!strcmp(n, "parenleft"  ))  return "(";
456     else if (!strcmp(n, "parenright" ))  return ")";
457     else if (!strcmp(n, "plus"       ))  return "+";
458     else if (!strcmp(n, "minus"      ))  return "-";
459     else if (!strcmp(n, "asterisk"   ))  return "*";
460     else if (!strcmp(n, "KP_Multiply"))  return "*";
461     else if (!strcmp(n, "Delete"     ))  return "Del";
462     else if (!strcmp(n, "Page_Up"    ))  return "PgUp";
463     else if (!strcmp(n, "Page_Down"  ))  return "PgDn";
464     else if (!strcmp(n, "grave"      ))  return "`";
465     else if (!strcmp(n, "numbersign" ))  return "#";
466     else if (!strcmp(n, "bar"        ))  return "|";
467     else if (!strcmp(n, "slash"      ))  return "/";
468     else if (!strcmp(n, "exclam"     ))  return "!";
469     else if (!strcmp(n, "percent"    ))  return "%";
470     else return n;
474 /**
475  * \param shortcut A GDK keyval OR'd with SP_SHORTCUT_blah_MASK values.
476  * \param c Points to a buffer at least 256 bytes long.
477  */
478 void
479 sp_ui_shortcut_string(unsigned const shortcut, gchar *const c)
481     /* TODO: This function shouldn't exist.  Our callers should use GtkAccelLabel instead of
482      * a generic GtkLabel containing this string, and should call gtk_widget_add_accelerator.
483      * Will probably need to change sp_shortcut_invoke callers.
484      *
485      * The existing gtk_label_new_with_mnemonic call can be replaced with
486      * g_object_new(GTK_TYPE_ACCEL_LABEL, NULL) followed by
487      * gtk_label_set_text_with_mnemonic(lbl, str).
488      */
489     static GtkAccelLabelClass const &accel_lbl_cls
490         = *(GtkAccelLabelClass const *) g_type_class_peek_static(GTK_TYPE_ACCEL_LABEL);
492     struct { unsigned test; char const *name; } const modifier_tbl[] = {
493         { SP_SHORTCUT_SHIFT_MASK,   accel_lbl_cls.mod_name_shift   },
494         { SP_SHORTCUT_CONTROL_MASK, accel_lbl_cls.mod_name_control },
495         { SP_SHORTCUT_ALT_MASK,     accel_lbl_cls.mod_name_alt     }
496     };
498     gchar *p = c;
499     gchar *end = p + 256;
501     for (unsigned i = 0; i < G_N_ELEMENTS(modifier_tbl); ++i) {
502         if ((shortcut & modifier_tbl[i].test)
503             && (p < end))
504         {
505             p += g_snprintf(p, end - p, "%s%s",
506                             modifier_tbl[i].name,
507                             accel_lbl_cls.mod_separator);
508         }
509     }
510     if (p < end) {
511         p += g_snprintf(p, end - p, "%s", sp_key_name(shortcut & 0xffffff));
512     }
513     end[-1] = '\0';  // snprintf doesn't guarantee to nul-terminate the string.
516 void
517 sp_ui_dialog_title_string(Inkscape::Verb *verb, gchar *c)
519     SPAction     *action;
520     unsigned int shortcut;
521     gchar        *s;
522     gchar        key[256];
523     gchar        *atitle;
525     action = verb->get_action(NULL);
526     if (!action)
527         return;
529     atitle = sp_action_get_title(action);
531     s = g_stpcpy(c, atitle);
533     g_free(atitle);
535     shortcut = sp_shortcut_get_primary(verb);
536     if (shortcut) {
537         s = g_stpcpy(s, " (");
538         sp_ui_shortcut_string(shortcut, key);
539         s = g_stpcpy(s, key);
540         s = g_stpcpy(s, ")");
541     }
545 /**
546  * sp_ui_menu_append_item_from_verb
547  *
548  * Appends a custom menu UI from a verb.
549  *
550  */
552 static GtkWidget *
553 sp_ui_menu_append_item_from_verb(GtkMenu *menu, Inkscape::Verb *verb, Inkscape::UI::View::View *view, bool radio = false, GSList *group = NULL)
555     SPAction *action;
556     GtkWidget *item;
558     if (verb->get_code() == SP_VERB_NONE) {
560         item = gtk_separator_menu_item_new();
562     } else {
563         unsigned int shortcut;
565         action = verb->get_action(view);
567         if (!action) return NULL;
569         shortcut = sp_shortcut_get_primary(verb);
570         if (shortcut) {
571             gchar c[256];
572             sp_ui_shortcut_string(shortcut, c);
573             GtkWidget *const hb = gtk_hbox_new(FALSE, 16);
574             GtkWidget *const name_lbl = gtk_label_new("");
575             gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
576             gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
577             gtk_box_pack_start((GtkBox *) hb, name_lbl, TRUE, TRUE, 0);
578             GtkWidget *const accel_lbl = gtk_label_new(c);
579             gtk_misc_set_alignment((GtkMisc *) accel_lbl, 1.0, 0.5);
580             gtk_box_pack_end((GtkBox *) hb, accel_lbl, FALSE, FALSE, 0);
581             gtk_widget_show_all(hb);
582             if (radio) {
583                 item = gtk_radio_menu_item_new (group);
584             } else {
585                 item = gtk_image_menu_item_new();
586             }
587             gtk_container_add((GtkContainer *) item, hb);
588         } else {
589             if (radio) {
590                 item = gtk_radio_menu_item_new (group);
591             } else {
592                 item = gtk_image_menu_item_new ();
593             }
594             GtkWidget *const name_lbl = gtk_label_new("");
595             gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
596             gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
597             gtk_container_add((GtkContainer *) item, name_lbl);
598         }
600         nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
601         if (!action->sensitive) {
602             gtk_widget_set_sensitive(item, FALSE);
603         }
605         if (action->image) {
606             sp_ui_menuitem_add_icon(item, action->image);
607         }
608         gtk_widget_set_events(item, GDK_KEY_PRESS_MASK);
609         g_signal_connect( G_OBJECT(item), "activate",
610                           G_CALLBACK(sp_ui_menu_activate), action );
612         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select_action), action );
613         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect_action), action );
614     }
616     gtk_widget_show(item);
617     gtk_menu_append(GTK_MENU(menu), item);
619     return item;
621 } // end of sp_ui_menu_append_item_from_verb
624 static void
625 checkitem_toggled(GtkCheckMenuItem *menuitem, gpointer user_data)
627     gchar const *pref = (gchar const *) user_data;
628     Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
630     Glib::ustring pref_path;
631     if (reinterpret_cast<SPDesktop*>(view)->is_focusMode()) {
632         pref_path = "/focus/";
633     } else if (reinterpret_cast<SPDesktop*>(view)->is_fullscreen()) {
634         pref_path = "/fullscreen/";
635     } else {
636         pref_path = "/window/";
637     }
638     pref_path += pref;
639     pref_path += "/state";
641     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
642     gboolean checked = gtk_check_menu_item_get_active(menuitem);
643     prefs->setBool(pref_path, checked);
645     reinterpret_cast<SPDesktop*>(view)->layoutWidget();
648 static gboolean
649 checkitem_update(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_data)
651     GtkCheckMenuItem *menuitem=GTK_CHECK_MENU_ITEM(widget);
653     gchar const *pref = (gchar const *) user_data;
654     Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
656     Glib::ustring pref_path;
657     if ((static_cast<SPDesktop*>(view))->is_fullscreen()) {
658         pref_path = "/fullscreen/";
659     } else {
660         pref_path = "/window/";
661     }
662     pref_path += pref;
664     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
665     bool ison = prefs->getBool(pref_path + "/state", true);
667     g_signal_handlers_block_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
668     gtk_check_menu_item_set_active(menuitem, ison);
669     g_signal_handlers_unblock_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
671     return FALSE;
675 void
676 sp_ui_menu_append_check_item_from_verb(GtkMenu *menu, Inkscape::UI::View::View *view, gchar const *label, gchar const *tip, gchar const *pref,
677                                        void (*callback_toggle)(GtkCheckMenuItem *, gpointer user_data),
678                                        gboolean (*callback_update)(GtkWidget *widget, GdkEventExpose *event, gpointer user_data),
679                                        Inkscape::Verb *verb)
681     GtkWidget *item;
683     unsigned int shortcut = 0;
684     SPAction *action = NULL;
686     if (verb) {
687         shortcut = sp_shortcut_get_primary(verb);
688         action = verb->get_action(view);
689     }
691     if (verb && shortcut) {
692         gchar c[256];
693         sp_ui_shortcut_string(shortcut, c);
695         GtkWidget *hb = gtk_hbox_new(FALSE, 16);
697         {
698             GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
699             gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
700             gtk_box_pack_start((GtkBox *) hb, l, TRUE, TRUE, 0);
701         }
703         {
704             GtkWidget *l = gtk_label_new(c);
705             gtk_misc_set_alignment((GtkMisc *) l, 1.0, 0.5);
706             gtk_box_pack_end((GtkBox *) hb, l, FALSE, FALSE, 0);
707         }
709         gtk_widget_show_all(hb);
711         item = gtk_check_menu_item_new();
712         gtk_container_add((GtkContainer *) item, hb);
713     } else {
714         GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
715         gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
716         item = gtk_check_menu_item_new();
717         gtk_container_add((GtkContainer *) item, l);
718     }
719 #if 0
720     nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
721     if (!action->sensitive) {
722         gtk_widget_set_sensitive(item, FALSE);
723     }
724 #endif
725     gtk_widget_show(item);
727     gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
729     g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
731     g_signal_connect( G_OBJECT(item), "toggled", (GCallback) callback_toggle, (void *) pref);
732     g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) callback_update, (void *) pref);
734     g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) (action ? action->tip : tip));
735     g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
738 static void
739 sp_recent_open(GtkRecentChooser *recent_menu, gpointer /*user_data*/)
741     // dealing with the bizarre filename convention in Inkscape for now
742     gchar *uri = gtk_recent_chooser_get_current_uri(GTK_RECENT_CHOOSER(recent_menu));
743     gchar *local_fn = g_filename_from_uri(uri, NULL, NULL);
744     gchar *utf8_fn = g_filename_to_utf8(local_fn, -1, NULL, NULL, NULL);
745     sp_file_open(utf8_fn, NULL);
746     g_free(utf8_fn);
747     g_free(local_fn);
748     g_free(uri);
751 static void
752 sp_file_new_from_template(GtkWidget */*widget*/, gchar const *uri)
754     sp_file_new(uri);
757 void
758 sp_menu_append_new_templates(GtkWidget *menu, Inkscape::UI::View::View *view)
760     std::list<gchar *> sources;
761     sources.push_back( profile_path("templates") ); // first try user's local dir
762     sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir
764     // Use this loop to iterate through a list of possible document locations.
765     while (!sources.empty()) {
766         gchar *dirname = sources.front();
768         if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) {
769             GError *err = 0;
770             GDir *dir = g_dir_open(dirname, 0, &err);
772             if (dir) {
773                 for (gchar const *file = g_dir_read_name(dir); file != NULL; file = g_dir_read_name(dir)) {
774                     if (!g_str_has_suffix(file, ".svg") && !g_str_has_suffix(file, ".svgz"))
775                         continue; // skip non-svg files
777                     gchar *basename = g_path_get_basename(file);
778                     if (g_str_has_suffix(basename, ".svg") && g_str_has_prefix(basename, "default."))
779                         continue; // skip default.*.svg (i.e. default.svg and translations) - it's in the menu already
781                     gchar const *filepath = g_build_filename(dirname, file, NULL);
782                     gchar *dupfile = g_strndup(file, strlen(file) - 4);
783                     gchar *filename =  g_filename_to_utf8(dupfile,  -1, NULL, NULL, NULL);
784                     g_free(dupfile);
785                     GtkWidget *item = gtk_menu_item_new_with_label(filename);
786                     g_free(filename);
788                     gtk_widget_show(item);
789                     // how does "filepath" ever get freed?
790                     g_signal_connect(G_OBJECT(item),
791                                      "activate",
792                                      G_CALLBACK(sp_file_new_from_template),
793                                      (gpointer) filepath);
795                     if (view) {
796                         // set null tip for now; later use a description from the template file
797                         g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
798                         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) NULL );
799                         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
800                     }
802                     gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
803                 }
804                 g_dir_close(dir);
805             }
806         }
808         // toss the dirname
809         g_free(dirname);
810         sources.pop_front();
811     }
814 void
815 sp_ui_checkboxes_menus(GtkMenu *m, Inkscape::UI::View::View *view)
817     //sp_ui_menu_append_check_item_from_verb(m, view, _("_Menu"), _("Show or hide the menu bar"), "menu",
818     //                                       checkitem_toggled, checkitem_update, 0);
819     sp_ui_menu_append_check_item_from_verb(m, view, _("Commands Bar"), _("Show or hide the Commands bar (under the menu)"), "commands",
820                                            checkitem_toggled, checkitem_update, 0);
821     sp_ui_menu_append_check_item_from_verb(m, view, _("Snap Controls Bar"), _("Show or hide the snapping controls"), "snaptoolbox",
822                                            checkitem_toggled, checkitem_update, 0);
823     sp_ui_menu_append_check_item_from_verb(m, view, _("Tool Controls Bar"), _("Show or hide the Tool Controls bar"), "toppanel",
824                                            checkitem_toggled, checkitem_update, 0);
825     sp_ui_menu_append_check_item_from_verb(m, view, _("_Toolbox"), _("Show or hide the main toolbox (on the left)"), "toolbox",
826                                            checkitem_toggled, checkitem_update, 0);
827     sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "rulers",
828                                            checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_RULERS));
829     sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "scrollbars",
830                                            checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_SCROLLBARS));
831     sp_ui_menu_append_check_item_from_verb(m, view, _("_Palette"), _("Show or hide the color palette"), "panels",
832                                            checkitem_toggled, checkitem_update, 0);
833     sp_ui_menu_append_check_item_from_verb(m, view, _("_Statusbar"), _("Show or hide the statusbar (at the bottom of the window)"), "statusbar",
834                                            checkitem_toggled, checkitem_update, 0);
837 /** @brief Observer that updates the recent list's max document count */
838 class MaxRecentObserver : public Inkscape::Preferences::Observer {
839 public:
840     MaxRecentObserver(GtkWidget *recent_menu) :
841         Observer("/options/maxrecentdocuments/value"),
842         _rm(recent_menu)
843     {}
844     virtual void notify(Inkscape::Preferences::Entry const &e) {
845         gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(_rm), e.getInt());
846         // hack: the recent menu doesn't repopulate after changing the limit, so we force it
847         g_signal_emit_by_name((gpointer) gtk_recent_manager_get_default(), "changed");
848     }
849 private:
850     GtkWidget *_rm;
851 };
853 /** \brief  This function turns XML into a menu
854     \param  menus  This is the XML that defines the menu
855     \param  menu   Menu to be added to
856     \param  view   The View that this menu is being built for
858     This function is realitively simple as it just goes through the XML
859     and parses the individual elements.  In the case of a submenu, it
860     just calls itself recursively.  Because it is only reasonable to have
861     a couple of submenus, it is unlikely this will go more than two or
862     three times.
864     In the case of an unreconginzed verb, a menu item is made to identify
865     the verb that is missing, and display that.  The menu item is also made
866     insensitive.
867 */
868 void
869 sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI::View::View *view)
871     if (menus == NULL) return;
872     if (menu == NULL)  return;
873     GSList *group = NULL;
875     for (Inkscape::XML::Node *menu_pntr = menus;
876          menu_pntr != NULL;
877          menu_pntr = menu_pntr->next()) {
878         if (!strcmp(menu_pntr->name(), "submenu")) {
879             GtkWidget *mitem = gtk_menu_item_new_with_mnemonic(_(menu_pntr->attribute("name")));
880             GtkWidget *submenu = gtk_menu_new();
881             sp_ui_build_dyn_menus(menu_pntr->firstChild(), submenu, view);
882             gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), GTK_WIDGET(submenu));
883             gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
884             continue;
885         }
886         if (!strcmp(menu_pntr->name(), "verb")) {
887             gchar const *verb_name = menu_pntr->attribute("verb-id");
888             Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name);
890             if (verb != NULL) {
891                 if (menu_pntr->attribute("radio") != NULL) {
892                     GtkWidget *item = sp_ui_menu_append_item_from_verb (GTK_MENU(menu), verb, view, true, group);
893                     group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item));
894                     if (menu_pntr->attribute("default") != NULL) {
895                         gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
896                     }
897                 } else {
898                     sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view);
899                     group = NULL;
900                 }
901             } else {
902                 gchar string[120];
903                 g_snprintf(string, 120, _("Verb \"%s\" Unknown"), verb_name);
904                 string[119] = '\0'; /* may not be terminated */
905                 GtkWidget *item = gtk_menu_item_new_with_label(string);
906                 gtk_widget_set_sensitive(item, false);
907                 gtk_widget_show(item);
908                 gtk_menu_append(GTK_MENU(menu), item);
909             }
910             continue;
911         }
912         if (!strcmp(menu_pntr->name(), "separator")
913                 // This was spelt wrong in the original version
914                 // and so this is for backward compatibility.  It can
915                 // probably be dropped after the 0.44 release.
916              || !strcmp(menu_pntr->name(), "seperator")) {
917             GtkWidget *item = gtk_separator_menu_item_new();
918             gtk_widget_show(item);
919             gtk_menu_append(GTK_MENU(menu), item);
920             continue;
921         }
922         if (!strcmp(menu_pntr->name(), "template-list")) {
923             sp_menu_append_new_templates(menu, view);
924             continue;
925         }
926         if (!strcmp(menu_pntr->name(), "recent-file-list")) {
927             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
929             // create recent files menu
930             int max_recent = prefs->getInt("/options/maxrecentdocuments/value");
931             GtkWidget *recent_menu = gtk_recent_chooser_menu_new_for_manager(gtk_recent_manager_get_default());
932             gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(recent_menu), max_recent);
933             // sort most recently used documents first to preserve previous behavior
934             gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(recent_menu), GTK_RECENT_SORT_MRU);
935             g_signal_connect(G_OBJECT(recent_menu), "item-activated", G_CALLBACK(sp_recent_open), (gpointer) NULL);
937             // add filter to only open files added by Inkscape
938             GtkRecentFilter *inkscape_only_filter = gtk_recent_filter_new();
939             gtk_recent_filter_add_application(inkscape_only_filter, g_get_prgname());
940             gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_menu), inkscape_only_filter);
942             GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic(_("Open _Recent"));
943             gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), recent_menu);
945             gtk_menu_append(GTK_MENU(menu), GTK_WIDGET(recent_item));
946             // this will just sit and update the list's item count
947             static MaxRecentObserver *mro = new MaxRecentObserver(recent_menu);
948             prefs->addObserver(*mro);
949             continue;
950         }
951         if (!strcmp(menu_pntr->name(), "objects-checkboxes")) {
952             sp_ui_checkboxes_menus(GTK_MENU(menu), view);
953             continue;
954         }
955     }
958 /** \brief  Build the main tool bar
959     \param  view  View to build the bar for
961     Currently the main tool bar is built as a dynamic XML menu using
962     \c sp_ui_build_dyn_menus.  This function builds the bar, and then
963     pass it to get items attached to it.
964 */
965 GtkWidget *
966 sp_ui_main_menubar(Inkscape::UI::View::View *view)
968     GtkWidget *mbar = gtk_menu_bar_new();
970 #ifdef GDK_WINDOWING_QUARTZ
971     ige_mac_menu_set_menu_bar(GTK_MENU_SHELL(mbar));
972 #endif
974     sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view);
976 #ifdef GDK_WINDOWING_QUARTZ
977     return NULL;
978 #else
979     return mbar;
980 #endif
983 static void leave_group(GtkMenuItem *, SPDesktop *desktop) {
984     desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer()));
987 static void enter_group(GtkMenuItem *mi, SPDesktop *desktop) {
988     desktop->setCurrentLayer(reinterpret_cast<SPObject *>(g_object_get_data(G_OBJECT(mi), "group")));
989     sp_desktop_selection(desktop)->clear();
992 GtkWidget *
993 sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item)
995     GtkWidget *m;
996     SPDesktop *dt;
998     dt = static_cast<SPDesktop*>(view);
1000     m = gtk_menu_new();
1002     /* Undo and Redo */
1003     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_UNDO), view);
1004     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_REDO), view);
1006     /* Separator */
1007     sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1009     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_CUT), view);
1010     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_COPY), view);
1011     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_PASTE), view);
1013     /* Separator */
1014     sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1016     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DUPLICATE), view);
1017     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DELETE), view);
1019     /* Item menu */
1020     if (item) {
1021         sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1022         sp_object_menu((SPObject *) item, dt, GTK_MENU(m));
1023     }
1025     /* layer menu */
1026     SPGroup *group=NULL;
1027     if (item) {
1028         if (SP_IS_GROUP(item)) {
1029             group = SP_GROUP(item);
1030         } else if ( item != dt->currentRoot() && SP_IS_GROUP(SP_OBJECT_PARENT(item)) ) {
1031             group = SP_GROUP(SP_OBJECT_PARENT(item));
1032         }
1033     }
1035     if (( group && group != dt->currentLayer() ) ||
1036         ( dt->currentLayer() != dt->currentRoot() && SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) ) {
1037         /* Separator */
1038         sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1039     }
1041     if ( group && group != dt->currentLayer() ) {
1042         /* TRANSLATORS: #%s is the id of the group e.g. <g id="#g7">, not a number. */
1043         gchar *label=g_strdup_printf(_("Enter group #%s"), SP_OBJECT_ID(group));
1044         GtkWidget *w = gtk_menu_item_new_with_label(label);
1045         g_free(label);
1046         g_object_set_data(G_OBJECT(w), "group", group);
1047         g_signal_connect(G_OBJECT(w), "activate", GCallback(enter_group), dt);
1048         gtk_widget_show(w);
1049         gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1050     }
1052     if ( dt->currentLayer() != dt->currentRoot() ) {
1053         if ( SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) {
1054             GtkWidget *w = gtk_menu_item_new_with_label(_("Go to parent"));
1055             g_signal_connect(G_OBJECT(w), "activate", GCallback(leave_group), dt);
1056             gtk_widget_show(w);
1057             gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1059         }
1060     }
1062     return m;
1065 /* Drag and Drop */
1066 void
1067 sp_ui_drag_data_received(GtkWidget *widget,
1068                          GdkDragContext *drag_context,
1069                          gint x, gint y,
1070                          GtkSelectionData *data,
1071                          guint info,
1072                          guint /*event_time*/,
1073                          gpointer /*user_data*/)
1075     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1076     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1078     switch (info) {
1079 #if ENABLE_MAGIC_COLORS
1080         case APP_X_INKY_COLOR:
1081         {
1082             int destX = 0;
1083             int destY = 0;
1084             gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1085             Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1087             SPItem *item = desktop->item_at_point( where, true );
1088             if ( item )
1089             {
1090                 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1092                 if ( data->length >= 8 ) {
1093                     cmsHPROFILE srgbProf = cmsCreate_sRGBProfile();
1095                     gchar c[64] = {0};
1096                     // Careful about endian issues.
1097                     guint16* dataVals = (guint16*)data->data;
1098                     sp_svg_write_color( c, sizeof(c),
1099                                         SP_RGBA32_U_COMPOSE(
1100                                             0x0ff & (dataVals[0] >> 8),
1101                                             0x0ff & (dataVals[1] >> 8),
1102                                             0x0ff & (dataVals[2] >> 8),
1103                                             0xff // can't have transparency in the color itself
1104                                             //0x0ff & (data->data[3] >> 8),
1105                                             ));
1106                     SPCSSAttr *css = sp_repr_css_attr_new();
1107                     bool updatePerformed = false;
1109                     if ( data->length > 14 ) {
1110                         int flags = dataVals[4];
1112                         // piggie-backed palette entry info
1113                         int index = dataVals[5];
1114                         Glib::ustring palName;
1115                         for ( int i = 0; i < dataVals[6]; i++ ) {
1116                             palName += (gunichar)dataVals[7+i];
1117                         }
1119                         // Now hook in a magic tag of some sort.
1120                         if ( !palName.empty() && (flags & 1) ) {
1121                             gchar* str = g_strdup_printf("%d|", index);
1122                             palName.insert( 0, str );
1123                             g_free(str);
1124                             str = 0;
1126                             sp_object_setAttribute( SP_OBJECT(item),
1127                                                     fillnotstroke ? "inkscape:x-fill-tag":"inkscape:x-stroke-tag",
1128                                                     palName.c_str(),
1129                                                     false );
1130                             item->updateRepr();
1132                             sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1133                             updatePerformed = true;
1134                         }
1135                     }
1137                     if ( !updatePerformed ) {
1138                         sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1139                     }
1141                     sp_desktop_apply_css_recursive( item, css, true );
1142                     item->updateRepr();
1144                     sp_document_done( doc , SP_VERB_NONE,
1145                                       _("Drop color"));
1147                     if ( srgbProf ) {
1148                         cmsCloseProfile( srgbProf );
1149                     }
1150                 }
1151             }
1152         }
1153         break;
1154 #endif // ENABLE_MAGIC_COLORS
1156         case APP_X_COLOR:
1157         {
1158             int destX = 0;
1159             int destY = 0;
1160             gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1161             Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1162             Geom::Point const button_dt(desktop->w2d(where));
1163             Geom::Point const button_doc(desktop->dt2doc(button_dt));
1165             if ( data->length == 8 ) {
1166                 gchar colorspec[64] = {0};
1167                 // Careful about endian issues.
1168                 guint16* dataVals = (guint16*)data->data;
1169                 sp_svg_write_color( colorspec, sizeof(colorspec),
1170                                     SP_RGBA32_U_COMPOSE(
1171                                         0x0ff & (dataVals[0] >> 8),
1172                                         0x0ff & (dataVals[1] >> 8),
1173                                         0x0ff & (dataVals[2] >> 8),
1174                                         0xff // can't have transparency in the color itself
1175                                         //0x0ff & (data->data[3] >> 8),
1176                                         ));
1178                 SPItem *item = desktop->item_at_point( where, true );
1180                 bool consumed = false;
1181                 if (desktop->event_context && desktop->event_context->get_drag()) {
1182                     consumed = desktop->event_context->get_drag()->dropColor(item, colorspec, button_dt);
1183                     if (consumed) {
1184                         sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1185                         desktop->event_context->get_drag()->updateDraggers();
1186                     }
1187                 }
1189                 //if (!consumed && tools_active(desktop, TOOLS_TEXT)) {
1190                 //    consumed = sp_text_context_drop_color(c, button_doc);
1191                 //    if (consumed) {
1192                 //        sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient stop"));
1193                 //    }
1194                 //}
1196                 if (!consumed && item) {
1197                     bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1198                     if (fillnotstroke &&
1199                         (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1200                         Path *livarot_path = Path_for_item(item, true, true);
1201                         livarot_path->ConvertWithBackData(0.04);
1203                         boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1204                         if (position) {
1205                             Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1206                             Geom::Point delta = nearest - button_doc;
1207                             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1208                             delta = desktop->d2w(delta);
1209                             double stroke_tolerance =
1210                                 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1211                                   desktop->current_zoom() *
1212                                   SP_OBJECT_STYLE (item)->stroke_width.computed *
1213                                   to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1214                                   : 0.0)
1215                                 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1217                             if (Geom::L2 (delta) < stroke_tolerance) {
1218                                 fillnotstroke = false;
1219                             }
1220                         }
1221                         delete livarot_path;
1222                     }
1224                     SPCSSAttr *css = sp_repr_css_attr_new();
1225                     sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec );
1227                     sp_desktop_apply_css_recursive( item, css, true );
1228                     item->updateRepr();
1230                     sp_document_done( doc , SP_VERB_NONE,
1231                                       _("Drop color"));
1232                 }
1233             }
1234         }
1235         break;
1237         case APP_OSWB_COLOR:
1238         {
1239             bool worked = false;
1240             Glib::ustring colorspec;
1241             if ( data->format == 8 ) {
1242                 ege::PaintDef color;
1243                 worked = color.fromMIMEData("application/x-oswb-color",
1244                                             reinterpret_cast<char*>(data->data),
1245                                             data->length,
1246                                             data->format);
1247                 if ( worked ) {
1248                     if ( color.getType() == ege::PaintDef::CLEAR ) {
1249                         colorspec = ""; // TODO check if this is sufficient
1250                     } else if ( color.getType() == ege::PaintDef::NONE ) {
1251                         colorspec = "none";
1252                     } else {
1253                         unsigned int r = color.getR();
1254                         unsigned int g = color.getG();
1255                         unsigned int b = color.getB();
1257                         SPGradient* matches = 0;
1258                         const GSList *gradients = sp_document_get_resource_list(doc, "gradient");
1259                         for (const GSList *item = gradients; item; item = item->next) {
1260                             SPGradient* grad = SP_GRADIENT(item->data);
1261                             if ( color.descr == grad->id ) {
1262                                 if ( grad->has_stops ) {
1263                                     matches = grad;
1264                                     break;
1265                                 }
1266                             }
1267                         }
1268                         if (matches) {
1269                             colorspec = "url(#";
1270                             colorspec += matches->id;
1271                             colorspec += ")";
1272                         } else {
1273                             gchar* tmp = g_strdup_printf("#%02x%02x%02x", r, g, b);
1274                             colorspec = tmp;
1275                             g_free(tmp);
1276                         }
1277                     }
1278                 }
1279             }
1280             if ( worked ) {
1281                 int destX = 0;
1282                 int destY = 0;
1283                 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1284                 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1285                 Geom::Point const button_dt(desktop->w2d(where));
1286                 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1288                 SPItem *item = desktop->item_at_point( where, true );
1290                 bool consumed = false;
1291                 if (desktop->event_context && desktop->event_context->get_drag()) {
1292                     consumed = desktop->event_context->get_drag()->dropColor(item, colorspec.c_str(), button_dt);
1293                     if (consumed) {
1294                         sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1295                         desktop->event_context->get_drag()->updateDraggers();
1296                     }
1297                 }
1299                 if (!consumed && item) {
1300                     bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1301                     if (fillnotstroke &&
1302                         (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1303                         Path *livarot_path = Path_for_item(item, true, true);
1304                         livarot_path->ConvertWithBackData(0.04);
1306                         boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1307                         if (position) {
1308                             Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1309                             Geom::Point delta = nearest - button_doc;
1310                             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1311                             delta = desktop->d2w(delta);
1312                             double stroke_tolerance =
1313                                 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1314                                   desktop->current_zoom() *
1315                                   SP_OBJECT_STYLE (item)->stroke_width.computed *
1316                                   to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1317                                   : 0.0)
1318                                 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1320                             if (Geom::L2 (delta) < stroke_tolerance) {
1321                                 fillnotstroke = false;
1322                             }
1323                         }
1324                         delete livarot_path;
1325                     }
1327                     SPCSSAttr *css = sp_repr_css_attr_new();
1328                     sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec.c_str() );
1330                     sp_desktop_apply_css_recursive( item, css, true );
1331                     item->updateRepr();
1333                     sp_document_done( doc , SP_VERB_NONE,
1334                                       _("Drop color"));
1335                 }
1336             }
1337         }
1338         break;
1340         case SVG_DATA:
1341         case SVG_XML_DATA: {
1342             gchar *svgdata = (gchar *)data->data;
1344             Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI);
1346             if (rnewdoc == NULL) {
1347                 sp_ui_error_dialog(_("Could not parse SVG data"));
1348                 return;
1349             }
1351             Inkscape::XML::Node *repr = rnewdoc->root();
1352             gchar const *style = repr->attribute("style");
1354             Inkscape::XML::Node *newgroup = rnewdoc->createElement("svg:g");
1355             newgroup->setAttribute("style", style);
1357             Inkscape::XML::Document * xml_doc =  sp_document_repr_doc(doc);
1358             for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) {
1359                 Inkscape::XML::Node *newchild = child->duplicate(xml_doc);
1360                 newgroup->appendChild(newchild);
1361             }
1363             Inkscape::GC::release(rnewdoc);
1365             // Add it to the current layer
1367             // Greg's edits to add intelligent positioning of svg drops
1368             SPObject *new_obj = NULL;
1369             new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
1371             Inkscape::Selection *selection = sp_desktop_selection(desktop);
1372             selection->set(SP_ITEM(new_obj));
1374             // move to mouse pointer
1375             {
1376                 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
1377                 Geom::OptRect sel_bbox = selection->bounds();
1378                 if (sel_bbox) {
1379                     Geom::Point m( desktop->point() - sel_bbox->midpoint() );
1380                     sp_selection_move_relative(selection, m, false);
1381                 }
1382             }
1384             Inkscape::GC::release(newgroup);
1385             sp_document_done(doc, SP_VERB_NONE,
1386                              _("Drop SVG"));
1387             break;
1388         }
1390         case URI_LIST: {
1391             gchar *uri = (gchar *)data->data;
1392             sp_ui_import_files(uri);
1393             break;
1394         }
1396         case PNG_DATA:
1397         case JPEG_DATA:
1398         case IMAGE_DATA: {
1399             Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
1400             Inkscape::XML::Node *newImage = xml_doc->createElement("svg:image");
1401             gchar *atom_name = gdk_atom_name(data->type);
1403             // this formula taken from Glib docs
1404             guint needed_size = data->length * 4 / 3 + data->length * 4 / (3 * 72) + 7;
1405             needed_size += 5 + 8 + strlen(atom_name); // 5 bytes for data:, 8 for ;base64,
1407             gchar *buffer = (gchar *) g_malloc(needed_size), *buf_work = buffer;
1408             buf_work += g_sprintf(buffer, "data:%s;base64,", atom_name);
1410             gint state = 0, save = 0;
1411             g_base64_encode_step(data->data, data->length, TRUE, buf_work, &state, &save);
1412             g_base64_encode_close(TRUE, buf_work, &state, &save);
1414             newImage->setAttribute("xlink:href", buffer);
1415             g_free(buffer);
1417             GError *error = NULL;
1418             GdkPixbufLoader *loader = gdk_pixbuf_loader_new_with_mime_type( gdk_atom_name(data->type), &error );
1419             if ( loader ) {
1420                 error = NULL;
1421                 if ( gdk_pixbuf_loader_write( loader, data->data, data->length, &error) ) {
1422                     GdkPixbuf *pbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1423                     if ( pbuf ) {
1424                         char tmp[1024];
1425                         int width = gdk_pixbuf_get_width(pbuf);
1426                         int height = gdk_pixbuf_get_height(pbuf);
1427                         snprintf( tmp, sizeof(tmp), "%d", width );
1428                         newImage->setAttribute("width", tmp);
1430                         snprintf( tmp, sizeof(tmp), "%d", height );
1431                         newImage->setAttribute("height", tmp);
1432                     }
1433                 }
1434             }
1435             g_free(atom_name);
1437             // Add it to the current layer
1438             desktop->currentLayer()->appendChildRepr(newImage);
1440             Inkscape::GC::release(newImage);
1441             sp_document_done( doc , SP_VERB_NONE,
1442                               _("Drop bitmap image"));
1443             break;
1444         }
1445     }
1448 #include "gradient-context.h"
1450 void sp_ui_drag_motion( GtkWidget */*widget*/,
1451                         GdkDragContext */*drag_context*/,
1452                         gint /*x*/, gint /*y*/,
1453                         GtkSelectionData */*data*/,
1454                         guint /*info*/,
1455                         guint /*event_time*/,
1456                         gpointer /*user_data*/)
1458 //     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1459 //     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1462 //     g_message("drag-n-drop motion (%4d, %4d)  at %d", x, y, event_time);
1465 static void sp_ui_drag_leave( GtkWidget */*widget*/,
1466                               GdkDragContext */*drag_context*/,
1467                               guint /*event_time*/,
1468                               gpointer /*user_data*/ )
1470 //     g_message("drag-n-drop leave                at %d", event_time);
1473 static void
1474 sp_ui_import_files(gchar *buffer)
1476     GList *list = gnome_uri_list_extract_filenames(buffer);
1477     if (!list)
1478         return;
1479     g_list_foreach(list, sp_ui_import_one_file_with_check, NULL);
1480     g_list_foreach(list, (GFunc) g_free, NULL);
1481     g_list_free(list);
1484 static void
1485 sp_ui_import_one_file_with_check(gpointer filename, gpointer /*unused*/)
1487     if (filename) {
1488         if (strlen((char const *)filename) > 2)
1489             sp_ui_import_one_file((char const *)filename);
1490     }
1493 static void
1494 sp_ui_import_one_file(char const *filename)
1496     SPDocument *doc = SP_ACTIVE_DOCUMENT;
1497     if (!doc) return;
1499     if (filename == NULL) return;
1501     // Pass off to common implementation
1502     // TODO might need to get the proper type of Inkscape::Extension::Extension
1503     file_import( doc, filename, NULL );
1506 void
1507 sp_ui_error_dialog(gchar const *message)
1509     GtkWidget *dlg;
1510     gchar *safeMsg = Inkscape::IO::sanitizeString(message);
1512     dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
1513                                  GTK_BUTTONS_CLOSE, "%s", safeMsg);
1514     sp_transientize(dlg);
1515     gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE);
1516     gtk_dialog_run(GTK_DIALOG(dlg));
1517     gtk_widget_destroy(dlg);
1518     g_free(safeMsg);
1521 bool
1522 sp_ui_overwrite_file(gchar const *filename)
1524     bool return_value = FALSE;
1526     if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) {
1527         Gtk::Window *window = SP_ACTIVE_DESKTOP->getToplevel();
1528         gchar* baseName = g_path_get_basename( filename );
1529         gchar* dirName = g_path_get_dirname( filename );
1530         GtkWidget* dialog = gtk_message_dialog_new_with_markup( window->gobj(),
1531                                                                 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
1532                                                                 GTK_MESSAGE_QUESTION,
1533                                                                 GTK_BUTTONS_NONE,
1534                                                                 _( "<span weight=\"bold\" size=\"larger\">A file named \"%s\" already exists. Do you want to replace it?</span>\n\n"
1535                                                                    "The file already exists in \"%s\". Replacing it will overwrite its contents." ),
1536                                                                 baseName,
1537                                                                 dirName
1538             );
1539         gtk_dialog_add_buttons( GTK_DIALOG(dialog),
1540                                 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1541                                 _("Replace"), GTK_RESPONSE_YES,
1542                                 NULL );
1543         gtk_dialog_set_default_response( GTK_DIALOG(dialog), GTK_RESPONSE_YES );
1545         if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_YES ) {
1546             return_value = TRUE;
1547         } else {
1548             return_value = FALSE;
1549         }
1550         gtk_widget_destroy(dialog);
1551         g_free( baseName );
1552         g_free( dirName );
1553     } else {
1554         return_value = TRUE;
1555     }
1557     return return_value;
1560 static void
1561 sp_ui_menu_item_set_sensitive(SPAction */*action*/, unsigned int sensitive, void *data)
1563     return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
1566 static void
1567 sp_ui_menu_item_set_name(SPAction */*action*/, Glib::ustring name, void *data)
1569     void *child = GTK_BIN (data)->child;
1570     //child is either
1571     //- a GtkHBox, whose first child is a label displaying name if the menu
1572     //item has an accel key
1573     //- a GtkLabel if the menu has no accel key
1574     if (GTK_IS_LABEL(child)) {
1575         gtk_label_set_markup_with_mnemonic(GTK_LABEL (child), name.c_str());
1576     } else if (GTK_IS_HBOX(child)) {
1577         gtk_label_set_markup_with_mnemonic(
1578         GTK_LABEL (gtk_container_get_children(GTK_CONTAINER (child))->data),
1579         name.c_str());
1580     }//else sp_ui_menu_append_item_from_verb has been modified and can set
1581     //a menu item in yet another way...
1585 /*
1586   Local Variables:
1587   mode:c++
1588   c-file-style:"stroustrup"
1589   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1590   indent-tabs-mode:nil
1591   fill-column:99
1592   End:
1593 */
1594 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :