Code

SPDocument->Document
[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;
101 static bool temporarily_block_actions = false;
103 #define ENTRIES_SIZE(n) sizeof(n)/sizeof(n[0])
104 static guint nui_drop_target_entries = ENTRIES_SIZE(ui_drop_target_entries);
105 static void sp_ui_import_files(gchar *buffer);
106 static void sp_ui_import_one_file(char const *filename);
107 static void sp_ui_import_one_file_with_check(gpointer filename, gpointer unused);
108 static void sp_ui_drag_data_received(GtkWidget *widget,
109                                      GdkDragContext *drag_context,
110                                      gint x, gint y,
111                                      GtkSelectionData *data,
112                                      guint info,
113                                      guint event_time,
114                                      gpointer user_data);
115 static void sp_ui_drag_motion( GtkWidget *widget,
116                                GdkDragContext *drag_context,
117                                gint x, gint y,
118                                GtkSelectionData *data,
119                                guint info,
120                                guint event_time,
121                                gpointer user_data );
122 static void sp_ui_drag_leave( GtkWidget *widget,
123                               GdkDragContext *drag_context,
124                               guint event_time,
125                               gpointer user_data );
126 static void sp_ui_menu_item_set_sensitive(SPAction *action,
127                                           unsigned int sensitive,
128                                           void *data);
129 static void sp_ui_menu_item_set_name(SPAction *action,
130                                      Glib::ustring name,
131                                      void *data);
132 static void sp_recent_open(GtkRecentChooser *, gpointer);
134 SPActionEventVector menu_item_event_vector = {
135     {NULL},
136     NULL,
137     NULL, /* set_active */
138     sp_ui_menu_item_set_sensitive, /* set_sensitive */
139     NULL, /* set_shortcut */
140     sp_ui_menu_item_set_name /* set_name */
141 };
143 static const int MIN_ONSCREEN_DISTANCE = 50;
145 void
146 sp_create_window(SPViewWidget *vw, gboolean editable)
148     g_return_if_fail(vw != NULL);
149     g_return_if_fail(SP_IS_VIEW_WIDGET(vw));
151     Gtk::Window *win = Inkscape::UI::window_new("", TRUE);
153     gtk_container_add(GTK_CONTAINER(win->gobj()), GTK_WIDGET(vw));
154     gtk_widget_show(GTK_WIDGET(vw));
156     if (editable) {
157         g_object_set_data(G_OBJECT(vw), "window", win);
159         SPDesktopWidget *desktop_widget = reinterpret_cast<SPDesktopWidget*>(vw);
160         SPDesktop* desktop = desktop_widget->desktop;
162         desktop_widget->window = win;
164         win->set_data("desktop", desktop);
165         win->set_data("desktopwidget", desktop_widget);
167         win->signal_delete_event().connect(sigc::mem_fun(*(SPDesktop*)vw->view, &SPDesktop::onDeleteUI));
168         win->signal_window_state_event().connect(sigc::mem_fun(*desktop, &SPDesktop::onWindowStateEvent));
169         win->signal_focus_in_event().connect(sigc::mem_fun(*desktop_widget, &SPDesktopWidget::onFocusInEvent));
171         Inkscape::Preferences *prefs = Inkscape::Preferences::get();
172         gint prefs_geometry =
173             (2==prefs->getInt("/options/savewindowgeometry/value", 0));
174         if (prefs_geometry) {
175             gint pw = prefs->getInt("/desktop/geometry/width", -1);
176             gint ph = prefs->getInt("/desktop/geometry/height", -1);
177             gint px = prefs->getInt("/desktop/geometry/x", -1);
178             gint py = prefs->getInt("/desktop/geometry/y", -1);
179             gint full = prefs->getBool("/desktop/geometry/fullscreen");
180             gint maxed = prefs->getBool("/desktop/geometry/maximized");
181             if (pw>0 && ph>0) {
182                 gint w = MIN(gdk_screen_width(), pw);
183                 gint h = MIN(gdk_screen_height(), ph);
184                 gint x = MIN(gdk_screen_width() - MIN_ONSCREEN_DISTANCE, px);
185                 gint y = MIN(gdk_screen_height() - MIN_ONSCREEN_DISTANCE, py);
186                 if (w>0 && h>0) {
187                     x = MIN(gdk_screen_width() - w, x);
188                     y = MIN(gdk_screen_height() - h, y);
189                     desktop->setWindowSize(w, h);
190                 }
192                 // Only restore xy for the first window so subsequent windows don't overlap exactly
193                 // with first.  (Maybe rule should be only restore xy if it's different from xy of
194                 // other desktops?)
196                 // Empirically it seems that active_desktop==this desktop only the first time a
197                 // desktop is created.
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             if (maxed) {
204                 win->maximize();
205             }
206             if (full) {
207                 win->fullscreen();
208             }
209         }
211     } else {
212         gtk_window_set_policy(GTK_WINDOW(win->gobj()), TRUE, TRUE, TRUE);
213     }
215     if ( completeDropTargets == 0 || completeDropTargetsCount == 0 )
216     {
217         std::vector<gchar*> types;
219         GSList *list = gdk_pixbuf_get_formats();
220         while ( list ) {
221             int i = 0;
222             GdkPixbufFormat *one = (GdkPixbufFormat*)list->data;
223             gchar** typesXX = gdk_pixbuf_format_get_mime_types(one);
224             for ( i = 0; typesXX[i]; i++ ) {
225                 types.push_back(g_strdup(typesXX[i]));
226             }
227             g_strfreev(typesXX);
229             list = g_slist_next(list);
230         }
231         completeDropTargetsCount = nui_drop_target_entries + types.size();
232         completeDropTargets = new GtkTargetEntry[completeDropTargetsCount];
233         for ( int i = 0; i < (int)nui_drop_target_entries; i++ ) {
234             completeDropTargets[i] = ui_drop_target_entries[i];
235         }
236         int pos = nui_drop_target_entries;
238         for (std::vector<gchar*>::iterator it = types.begin() ; it != types.end() ; it++) {
239             completeDropTargets[pos].target = *it;
240             completeDropTargets[pos].flags = 0;
241             completeDropTargets[pos].info = IMAGE_DATA;
242             pos++;
243         }
244     }
246     gtk_drag_dest_set((GtkWidget*)win->gobj(),
247                       GTK_DEST_DEFAULT_ALL,
248                       completeDropTargets,
249                       completeDropTargetsCount,
250                       GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE));
253     g_signal_connect(G_OBJECT(win->gobj()),
254                      "drag_data_received",
255                      G_CALLBACK(sp_ui_drag_data_received),
256                      NULL);
257     g_signal_connect(G_OBJECT(win->gobj()),
258                      "drag_motion",
259                      G_CALLBACK(sp_ui_drag_motion),
260                      NULL);
261     g_signal_connect(G_OBJECT(win->gobj()),
262                      "drag_leave",
263                      G_CALLBACK(sp_ui_drag_leave),
264                      NULL);
265     win->show();
267     // needed because the first ACTIVATE_DESKTOP was sent when there was no window yet
268     inkscape_reactivate_desktop(SP_DESKTOP_WIDGET(vw)->desktop);
271 void
272 sp_ui_new_view()
274     Document *document;
275     SPViewWidget *dtw;
277     document = SP_ACTIVE_DOCUMENT;
278     if (!document) return;
280     dtw = sp_desktop_widget_new(sp_document_namedview(document, NULL));
281     g_return_if_fail(dtw != NULL);
283     sp_create_window(dtw, TRUE);
284     sp_namedview_window_from_document(static_cast<SPDesktop*>(dtw->view));
285     sp_namedview_update_layers_from_document(static_cast<SPDesktop*>(dtw->view));
288 /* TODO: not yet working */
289 /* To be re-enabled (by adding to menu) once it works. */
290 void
291 sp_ui_new_view_preview()
293     Document *document;
294     SPViewWidget *dtw;
296     document = SP_ACTIVE_DOCUMENT;
297     if (!document) return;
299     dtw = (SPViewWidget *) sp_svg_view_widget_new(document);
300     g_return_if_fail(dtw != NULL);
301     sp_svg_view_widget_set_resize(SP_SVG_VIEW_WIDGET(dtw), TRUE, 400.0, 400.0);
303     sp_create_window(dtw, FALSE);
306 /**
307  * \param widget unused
308  */
309 void
310 sp_ui_close_view(GtkWidget */*widget*/)
312         SPDesktop *dt = SP_ACTIVE_DESKTOP;
314         if (dt == NULL) {
315         return;
316     }
318     if (dt->shutdown()) {
319         return; // Shutdown operation has been canceled, so do nothing
320     }
322     // Shutdown can proceed; use the stored reference to the desktop here instead of the current SP_ACTIVE_DESKTOP,
323     // because the user might have changed the focus in the meantime (see bug #381357 on Launchpad)
324     dt->destroyWidget();
328 /**
329  *  sp_ui_close_all
330  *
331  *  This function is called to exit the program, and iterates through all
332  *  open document view windows, attempting to close each in turn.  If the
333  *  view has unsaved information, the user will be prompted to save,
334  *  discard, or cancel.
335  *
336  *  Returns FALSE if the user cancels the close_all operation, TRUE
337  *  otherwise.
338  */
339 unsigned int
340 sp_ui_close_all(void)
342     /* Iterate through all the windows, destroying each in the order they
343        become active */
344     while (SP_ACTIVE_DESKTOP) {
345         SPDesktop *dt = SP_ACTIVE_DESKTOP;
346         if (dt->shutdown()) {
347             /* The user canceled the operation, so end doing the close */
348             return FALSE;
349         }
350         // Shutdown can proceed; use the stored reference to the desktop here instead of the current SP_ACTIVE_DESKTOP,
351         // because the user might have changed the focus in the meantime (see bug #381357 on Launchpad)
352         dt->destroyWidget();
353     }
355     return TRUE;
358 /*
359  * Some day when the right-click menus are ready to start working
360  * smarter with the verbs, we'll need to change this NULL being
361  * sent to sp_action_perform to something useful, or set some kind
362  * of global "right-clicked position" variable for actions to
363  * investigate when they're called.
364  */
365 static void
366 sp_ui_menu_activate(void */*object*/, SPAction *action)
368     if (!temporarily_block_actions) {
369         sp_action_perform(action, NULL);
370     }
373 static void
374 sp_ui_menu_select_action(void */*object*/, SPAction *action)
376     action->view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, action->tip);
379 static void
380 sp_ui_menu_deselect_action(void */*object*/, SPAction *action)
382     action->view->tipsMessageContext()->clear();
385 static void
386 sp_ui_menu_select(gpointer object, gpointer tip)
388     Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view"));
389     view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, (gchar *)tip);
392 static void
393 sp_ui_menu_deselect(gpointer object)
395     Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*>  (g_object_get_data(G_OBJECT(object), "view"));
396     view->tipsMessageContext()->clear();
399 /**
400  * sp_ui_menuitem_add_icon
401  *
402  * Creates and attaches a scaled icon to the given menu item.
403  *
404  */
405 void
406 sp_ui_menuitem_add_icon( GtkWidget *item, gchar *icon_name )
408     GtkWidget *icon;
410     icon = sp_icon_new( Inkscape::ICON_SIZE_MENU, icon_name );
411     gtk_widget_show(icon);
412     gtk_image_menu_item_set_image((GtkImageMenuItem *) item, icon);
413 } // end of sp_ui_menu_add_icon
415 /**
416  * sp_ui_menu_append_item
417  *
418  * Appends a UI item with specific info for Inkscape/Sodipodi.
419  *
420  */
421 static GtkWidget *
422 sp_ui_menu_append_item( GtkMenu *menu, gchar const *stock,
423                         gchar const *label, gchar const *tip, Inkscape::UI::View::View *view, GCallback callback,
424                         gpointer data, gboolean with_mnemonic = TRUE )
426     GtkWidget *item;
428     if (stock) {
429         item = gtk_image_menu_item_new_from_stock(stock, NULL);
430     } else if (label) {
431         item = (with_mnemonic)
432             ? gtk_image_menu_item_new_with_mnemonic(label) :
433             gtk_image_menu_item_new_with_label(label);
434     } else {
435         item = gtk_separator_menu_item_new();
436     }
438     gtk_widget_show(item);
440     if (callback) {
441         g_signal_connect(G_OBJECT(item), "activate", callback, data);
442     }
444     if (tip && view) {
445         g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
446         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) tip );
447         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
448     }
450     gtk_menu_append(GTK_MENU(menu), item);
452     return item;
454 } // end of sp_ui_menu_append_item()
456 /**
457 \brief  a wrapper around gdk_keyval_name producing (when possible) characters, not names
458  */
459 static gchar const *
460 sp_key_name(guint keyval)
462     /* TODO: Compare with the definition of gtk_accel_label_refetch in gtk/gtkaccellabel.c (or
463        simply use GtkAccelLabel as the TODO comment in sp_ui_shortcut_string suggests). */
464     gchar const *n = gdk_keyval_name(gdk_keyval_to_upper(keyval));
466     if      (!strcmp(n, "asciicircum"))  return "^";
467     else if (!strcmp(n, "parenleft"  ))  return "(";
468     else if (!strcmp(n, "parenright" ))  return ")";
469     else if (!strcmp(n, "plus"       ))  return "+";
470     else if (!strcmp(n, "minus"      ))  return "-";
471     else if (!strcmp(n, "asterisk"   ))  return "*";
472     else if (!strcmp(n, "KP_Multiply"))  return "*";
473     else if (!strcmp(n, "Delete"     ))  return "Del";
474     else if (!strcmp(n, "Page_Up"    ))  return "PgUp";
475     else if (!strcmp(n, "Page_Down"  ))  return "PgDn";
476     else if (!strcmp(n, "grave"      ))  return "`";
477     else if (!strcmp(n, "numbersign" ))  return "#";
478     else if (!strcmp(n, "bar"        ))  return "|";
479     else if (!strcmp(n, "slash"      ))  return "/";
480     else if (!strcmp(n, "exclam"     ))  return "!";
481     else if (!strcmp(n, "percent"    ))  return "%";
482     else return n;
486 /**
487  * \param shortcut A GDK keyval OR'd with SP_SHORTCUT_blah_MASK values.
488  * \param c Points to a buffer at least 256 bytes long.
489  */
490 void
491 sp_ui_shortcut_string(unsigned const shortcut, gchar *const c)
493     /* TODO: This function shouldn't exist.  Our callers should use GtkAccelLabel instead of
494      * a generic GtkLabel containing this string, and should call gtk_widget_add_accelerator.
495      * Will probably need to change sp_shortcut_invoke callers.
496      *
497      * The existing gtk_label_new_with_mnemonic call can be replaced with
498      * g_object_new(GTK_TYPE_ACCEL_LABEL, NULL) followed by
499      * gtk_label_set_text_with_mnemonic(lbl, str).
500      */
501     static GtkAccelLabelClass const &accel_lbl_cls
502         = *(GtkAccelLabelClass const *) g_type_class_peek_static(GTK_TYPE_ACCEL_LABEL);
504     struct { unsigned test; char const *name; } const modifier_tbl[] = {
505         { SP_SHORTCUT_SHIFT_MASK,   accel_lbl_cls.mod_name_shift   },
506         { SP_SHORTCUT_CONTROL_MASK, accel_lbl_cls.mod_name_control },
507         { SP_SHORTCUT_ALT_MASK,     accel_lbl_cls.mod_name_alt     }
508     };
510     gchar *p = c;
511     gchar *end = p + 256;
513     for (unsigned i = 0; i < G_N_ELEMENTS(modifier_tbl); ++i) {
514         if ((shortcut & modifier_tbl[i].test)
515             && (p < end))
516         {
517             p += g_snprintf(p, end - p, "%s%s",
518                             modifier_tbl[i].name,
519                             accel_lbl_cls.mod_separator);
520         }
521     }
522     if (p < end) {
523         p += g_snprintf(p, end - p, "%s", sp_key_name(shortcut & 0xffffff));
524     }
525     end[-1] = '\0';  // snprintf doesn't guarantee to nul-terminate the string.
528 void
529 sp_ui_dialog_title_string(Inkscape::Verb *verb, gchar *c)
531     SPAction     *action;
532     unsigned int shortcut;
533     gchar        *s;
534     gchar        key[256];
535     gchar        *atitle;
537     action = verb->get_action(NULL);
538     if (!action)
539         return;
541     atitle = sp_action_get_title(action);
543     s = g_stpcpy(c, atitle);
545     g_free(atitle);
547     shortcut = sp_shortcut_get_primary(verb);
548     if (shortcut) {
549         s = g_stpcpy(s, " (");
550         sp_ui_shortcut_string(shortcut, key);
551         s = g_stpcpy(s, key);
552         s = g_stpcpy(s, ")");
553     }
557 /**
558  * sp_ui_menu_append_item_from_verb
559  *
560  * Appends a custom menu UI from a verb.
561  *
562  */
564 static GtkWidget *
565 sp_ui_menu_append_item_from_verb(GtkMenu *menu, Inkscape::Verb *verb, Inkscape::UI::View::View *view, bool radio = false, GSList *group = NULL)
567     SPAction *action;
568     GtkWidget *item;
570     if (verb->get_code() == SP_VERB_NONE) {
572         item = gtk_separator_menu_item_new();
574     } else {
575         unsigned int shortcut;
577         action = verb->get_action(view);
579         if (!action) return NULL;
581         shortcut = sp_shortcut_get_primary(verb);
582         if (shortcut) {
583             gchar c[256];
584             sp_ui_shortcut_string(shortcut, c);
585             GtkWidget *const hb = gtk_hbox_new(FALSE, 16);
586             GtkWidget *const name_lbl = gtk_label_new("");
587             gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
588             gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
589             gtk_box_pack_start((GtkBox *) hb, name_lbl, TRUE, TRUE, 0);
590             GtkWidget *const accel_lbl = gtk_label_new(c);
591             gtk_misc_set_alignment((GtkMisc *) accel_lbl, 1.0, 0.5);
592             gtk_box_pack_end((GtkBox *) hb, accel_lbl, FALSE, FALSE, 0);
593             gtk_widget_show_all(hb);
594             if (radio) {
595                 item = gtk_radio_menu_item_new (group);
596             } else {
597                 item = gtk_image_menu_item_new();
598             }
599             gtk_container_add((GtkContainer *) item, hb);
600         } else {
601             if (radio) {
602                 item = gtk_radio_menu_item_new (group);
603             } else {
604                 item = gtk_image_menu_item_new ();
605             }
606             GtkWidget *const name_lbl = gtk_label_new("");
607             gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
608             gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
609             gtk_container_add((GtkContainer *) item, name_lbl);
610         }
612         nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
613         if (!action->sensitive) {
614             gtk_widget_set_sensitive(item, FALSE);
615         }
617         if (action->image) {
618             sp_ui_menuitem_add_icon(item, action->image);
619         }
620         gtk_widget_set_events(item, GDK_KEY_PRESS_MASK);
621         g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
622         g_signal_connect( G_OBJECT(item), "activate", G_CALLBACK(sp_ui_menu_activate), action );
623         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select_action), action );
624         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect_action), action );
625     }
627     gtk_widget_show(item);
628     gtk_menu_append(GTK_MENU(menu), item);
630     return item;
632 } // end of sp_ui_menu_append_item_from_verb
635 static void
636 checkitem_toggled(GtkCheckMenuItem *menuitem, gpointer user_data)
638     gchar const *pref = (gchar const *) user_data;
639     Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
641     Glib::ustring pref_path;
642     if (reinterpret_cast<SPDesktop*>(view)->is_focusMode()) {
643         pref_path = "/focus/";
644     } else if (reinterpret_cast<SPDesktop*>(view)->is_fullscreen()) {
645         pref_path = "/fullscreen/";
646     } else {
647         pref_path = "/window/";
648     }
649     pref_path += pref;
650     pref_path += "/state";
652     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
653     gboolean checked = gtk_check_menu_item_get_active(menuitem);
654     prefs->setBool(pref_path, checked);
656     reinterpret_cast<SPDesktop*>(view)->layoutWidget();
659 static gboolean
660 checkitem_update(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_data)
662     GtkCheckMenuItem *menuitem=GTK_CHECK_MENU_ITEM(widget);
664     gchar const *pref = (gchar const *) user_data;
665     Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
667     Glib::ustring pref_path;
668     if ((static_cast<SPDesktop*>(view))->is_fullscreen()) {
669         pref_path = "/fullscreen/";
670     } else {
671         pref_path = "/window/";
672     }
673     pref_path += pref;
675     Inkscape::Preferences *prefs = Inkscape::Preferences::get();
676     bool ison = prefs->getBool(pref_path + "/state", true);
678     g_signal_handlers_block_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
679     gtk_check_menu_item_set_active(menuitem, ison);
680     g_signal_handlers_unblock_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
682     return FALSE;
685 /**
686  *  \brief Callback function to update the status of the radio buttons in the View -> Display mode menu (Normal, No Filters, Outline)
687  */
689 static gboolean
690 update_view_menu(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_data)
692         SPAction *action = (SPAction *) user_data;
693         g_assert(action->id != NULL);
695         Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(widget), "view");
696     SPDesktop *dt = static_cast<SPDesktop*>(view);
697         Inkscape::RenderMode mode = dt->getMode();
699         bool new_state = false;
700         if (!strcmp(action->id, "ViewModeNormal")) {
701         new_state = mode == Inkscape::RENDERMODE_NORMAL;
702         } else if (!strcmp(action->id, "ViewModeNoFilters")) {
703         new_state = mode == Inkscape::RENDERMODE_NO_FILTERS;
704     } else if (!strcmp(action->id, "ViewModeOutline")) {
705         new_state = mode == Inkscape::RENDERMODE_OUTLINE;
706     } else {
707         g_warning("update_view_menu does not handle this verb");
708     }
710         if (new_state) { //only one of the radio buttons has to be activated; the others will automatically be deactivated
711                 if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) {
712                         // When the GtkMenuItem version of the "activate" signal has been emitted by a GtkRadioMenuItem, there is a second
713                         // emission as the most recently active item is toggled to inactive. This is dealt with before the original signal is handled.
714                         // This emission however should not invoke any actions, hence we block it here:
715                         temporarily_block_actions = true;
716                         gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (widget), TRUE);
717                         temporarily_block_actions = false;
718                 }
719         }
721         return FALSE;
724 void
725 sp_ui_menu_append_check_item_from_verb(GtkMenu *menu, Inkscape::UI::View::View *view, gchar const *label, gchar const *tip, gchar const *pref,
726                                        void (*callback_toggle)(GtkCheckMenuItem *, gpointer user_data),
727                                        gboolean (*callback_update)(GtkWidget *widget, GdkEventExpose *event, gpointer user_data),
728                                        Inkscape::Verb *verb)
730     GtkWidget *item;
732     unsigned int shortcut = 0;
733     SPAction *action = NULL;
735     if (verb) {
736         shortcut = sp_shortcut_get_primary(verb);
737         action = verb->get_action(view);
738     }
740     if (verb && shortcut) {
741         gchar c[256];
742         sp_ui_shortcut_string(shortcut, c);
744         GtkWidget *hb = gtk_hbox_new(FALSE, 16);
746         {
747             GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
748             gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
749             gtk_box_pack_start((GtkBox *) hb, l, TRUE, TRUE, 0);
750         }
752         {
753             GtkWidget *l = gtk_label_new(c);
754             gtk_misc_set_alignment((GtkMisc *) l, 1.0, 0.5);
755             gtk_box_pack_end((GtkBox *) hb, l, FALSE, FALSE, 0);
756         }
758         gtk_widget_show_all(hb);
760         item = gtk_check_menu_item_new();
761         gtk_container_add((GtkContainer *) item, hb);
762     } else {
763         GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
764         gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
765         item = gtk_check_menu_item_new();
766         gtk_container_add((GtkContainer *) item, l);
767     }
768 #if 0
769     nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
770     if (!action->sensitive) {
771         gtk_widget_set_sensitive(item, FALSE);
772     }
773 #endif
774     gtk_widget_show(item);
776     gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
778     g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
780     g_signal_connect( G_OBJECT(item), "toggled", (GCallback) callback_toggle, (void *) pref);
781     g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) callback_update, (void *) pref);
783     g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) (action ? action->tip : tip));
784     g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
787 static void
788 sp_recent_open(GtkRecentChooser *recent_menu, gpointer /*user_data*/)
790     // dealing with the bizarre filename convention in Inkscape for now
791     gchar *uri = gtk_recent_chooser_get_current_uri(GTK_RECENT_CHOOSER(recent_menu));
792     gchar *local_fn = g_filename_from_uri(uri, NULL, NULL);
793     gchar *utf8_fn = g_filename_to_utf8(local_fn, -1, NULL, NULL, NULL);
794     sp_file_open(utf8_fn, NULL);
795     g_free(utf8_fn);
796     g_free(local_fn);
797     g_free(uri);
800 static void
801 sp_file_new_from_template(GtkWidget */*widget*/, gchar const *uri)
803     sp_file_new(uri);
806 void
807 sp_menu_append_new_templates(GtkWidget *menu, Inkscape::UI::View::View *view)
809     std::list<gchar *> sources;
810     sources.push_back( profile_path("templates") ); // first try user's local dir
811     sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir
813     // Use this loop to iterate through a list of possible document locations.
814     while (!sources.empty()) {
815         gchar *dirname = sources.front();
817         if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) {
818             GError *err = 0;
819             GDir *dir = g_dir_open(dirname, 0, &err);
821             if (dir) {
822                 for (gchar const *file = g_dir_read_name(dir); file != NULL; file = g_dir_read_name(dir)) {
823                     if (!g_str_has_suffix(file, ".svg") && !g_str_has_suffix(file, ".svgz"))
824                         continue; // skip non-svg files
826                     gchar *basename = g_path_get_basename(file);
827                     if (g_str_has_suffix(basename, ".svg") && g_str_has_prefix(basename, "default."))
828                         continue; // skip default.*.svg (i.e. default.svg and translations) - it's in the menu already
830                     gchar const *filepath = g_build_filename(dirname, file, NULL);
831                     gchar *dupfile = g_strndup(file, strlen(file) - 4);
832                     gchar *filename =  g_filename_to_utf8(dupfile,  -1, NULL, NULL, NULL);
833                     g_free(dupfile);
834                     GtkWidget *item = gtk_menu_item_new_with_label(filename);
835                     g_free(filename);
837                     gtk_widget_show(item);
838                     // how does "filepath" ever get freed?
839                     g_signal_connect(G_OBJECT(item),
840                                      "activate",
841                                      G_CALLBACK(sp_file_new_from_template),
842                                      (gpointer) filepath);
844                     if (view) {
845                         // set null tip for now; later use a description from the template file
846                         g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
847                         g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) NULL );
848                         g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
849                     }
851                     gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
852                 }
853                 g_dir_close(dir);
854             }
855         }
857         // toss the dirname
858         g_free(dirname);
859         sources.pop_front();
860     }
863 void
864 sp_ui_checkboxes_menus(GtkMenu *m, Inkscape::UI::View::View *view)
866     //sp_ui_menu_append_check_item_from_verb(m, view, _("_Menu"), _("Show or hide the menu bar"), "menu",
867     //                                       checkitem_toggled, checkitem_update, 0);
868     sp_ui_menu_append_check_item_from_verb(m, view, _("Commands Bar"), _("Show or hide the Commands bar (under the menu)"), "commands",
869                                            checkitem_toggled, checkitem_update, 0);
870     sp_ui_menu_append_check_item_from_verb(m, view, _("Snap Controls Bar"), _("Show or hide the snapping controls"), "snaptoolbox",
871                                            checkitem_toggled, checkitem_update, 0);
872     sp_ui_menu_append_check_item_from_verb(m, view, _("Tool Controls Bar"), _("Show or hide the Tool Controls bar"), "toppanel",
873                                            checkitem_toggled, checkitem_update, 0);
874     sp_ui_menu_append_check_item_from_verb(m, view, _("_Toolbox"), _("Show or hide the main toolbox (on the left)"), "toolbox",
875                                            checkitem_toggled, checkitem_update, 0);
876     sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "rulers",
877                                            checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_RULERS));
878     sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "scrollbars",
879                                            checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_SCROLLBARS));
880     sp_ui_menu_append_check_item_from_verb(m, view, _("_Palette"), _("Show or hide the color palette"), "panels",
881                                            checkitem_toggled, checkitem_update, 0);
882     sp_ui_menu_append_check_item_from_verb(m, view, _("_Statusbar"), _("Show or hide the statusbar (at the bottom of the window)"), "statusbar",
883                                            checkitem_toggled, checkitem_update, 0);
886 /** @brief Observer that updates the recent list's max document count */
887 class MaxRecentObserver : public Inkscape::Preferences::Observer {
888 public:
889     MaxRecentObserver(GtkWidget *recent_menu) :
890         Observer("/options/maxrecentdocuments/value"),
891         _rm(recent_menu)
892     {}
893     virtual void notify(Inkscape::Preferences::Entry const &e) {
894         gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(_rm), e.getInt());
895         // hack: the recent menu doesn't repopulate after changing the limit, so we force it
896         g_signal_emit_by_name((gpointer) gtk_recent_manager_get_default(), "changed");
897     }
898 private:
899     GtkWidget *_rm;
900 };
902 /** \brief  This function turns XML into a menu
903     \param  menus  This is the XML that defines the menu
904     \param  menu   Menu to be added to
905     \param  view   The View that this menu is being built for
907     This function is realitively simple as it just goes through the XML
908     and parses the individual elements.  In the case of a submenu, it
909     just calls itself recursively.  Because it is only reasonable to have
910     a couple of submenus, it is unlikely this will go more than two or
911     three times.
913     In the case of an unrecognized verb, a menu item is made to identify
914     the verb that is missing, and display that.  The menu item is also made
915     insensitive.
916 */
917 void
918 sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI::View::View *view)
920     if (menus == NULL) return;
921     if (menu == NULL)  return;
922     GSList *group = NULL;
924     for (Inkscape::XML::Node *menu_pntr = menus;
925          menu_pntr != NULL;
926          menu_pntr = menu_pntr->next()) {
927         if (!strcmp(menu_pntr->name(), "submenu")) {
928             GtkWidget *mitem = gtk_menu_item_new_with_mnemonic(_(menu_pntr->attribute("name")));
929             GtkWidget *submenu = gtk_menu_new();
930             sp_ui_build_dyn_menus(menu_pntr->firstChild(), submenu, view);
931             gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), GTK_WIDGET(submenu));
932             gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
933             continue;
934         }
935         if (!strcmp(menu_pntr->name(), "verb")) {
936             gchar const *verb_name = menu_pntr->attribute("verb-id");
937             Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name);
939             if (verb != NULL) {
940                 if (menu_pntr->attribute("radio") != NULL) {
941                     GtkWidget *item = sp_ui_menu_append_item_from_verb (GTK_MENU(menu), verb, view, true, group);
942                     group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item));
943                     if (menu_pntr->attribute("default") != NULL) {
944                         gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
945                     }
946                     if (verb->get_code() != SP_VERB_NONE) {
947                         SPAction *action = verb->get_action(view);
948                         g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) update_view_menu, (void *) action);
949                     }
950                 } else {
951                     sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view);
952                     group = NULL;
953                 }
954             } else {
955                 gchar string[120];
956                 g_snprintf(string, 120, _("Verb \"%s\" Unknown"), verb_name);
957                 string[119] = '\0'; /* may not be terminated */
958                 GtkWidget *item = gtk_menu_item_new_with_label(string);
959                 gtk_widget_set_sensitive(item, false);
960                 gtk_widget_show(item);
961                 gtk_menu_append(GTK_MENU(menu), item);
962             }
963             continue;
964         }
965         if (!strcmp(menu_pntr->name(), "separator")
966                 // This was spelt wrong in the original version
967                 // and so this is for backward compatibility.  It can
968                 // probably be dropped after the 0.44 release.
969              || !strcmp(menu_pntr->name(), "seperator")) {
970             GtkWidget *item = gtk_separator_menu_item_new();
971             gtk_widget_show(item);
972             gtk_menu_append(GTK_MENU(menu), item);
973             continue;
974         }
975         if (!strcmp(menu_pntr->name(), "template-list")) {
976             sp_menu_append_new_templates(menu, view);
977             continue;
978         }
979         if (!strcmp(menu_pntr->name(), "recent-file-list")) {
980             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
982             // create recent files menu
983             int max_recent = prefs->getInt("/options/maxrecentdocuments/value");
984             GtkWidget *recent_menu = gtk_recent_chooser_menu_new_for_manager(gtk_recent_manager_get_default());
985             gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(recent_menu), max_recent);
986             // sort most recently used documents first to preserve previous behavior
987             gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(recent_menu), GTK_RECENT_SORT_MRU);
988             g_signal_connect(G_OBJECT(recent_menu), "item-activated", G_CALLBACK(sp_recent_open), (gpointer) NULL);
990             // add filter to only open files added by Inkscape
991             GtkRecentFilter *inkscape_only_filter = gtk_recent_filter_new();
992             gtk_recent_filter_add_application(inkscape_only_filter, g_get_prgname());
993             gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_menu), inkscape_only_filter);
995             GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic(_("Open _Recent"));
996             gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), recent_menu);
998             gtk_menu_append(GTK_MENU(menu), GTK_WIDGET(recent_item));
999             // this will just sit and update the list's item count
1000             static MaxRecentObserver *mro = new MaxRecentObserver(recent_menu);
1001             prefs->addObserver(*mro);
1002             continue;
1003         }
1004         if (!strcmp(menu_pntr->name(), "objects-checkboxes")) {
1005             sp_ui_checkboxes_menus(GTK_MENU(menu), view);
1006             continue;
1007         }
1008     }
1011 /** \brief  Build the main tool bar
1012     \param  view  View to build the bar for
1014     Currently the main tool bar is built as a dynamic XML menu using
1015     \c sp_ui_build_dyn_menus.  This function builds the bar, and then
1016     pass it to get items attached to it.
1017 */
1018 GtkWidget *
1019 sp_ui_main_menubar(Inkscape::UI::View::View *view)
1021     GtkWidget *mbar = gtk_menu_bar_new();
1023 #ifdef GDK_WINDOWING_QUARTZ
1024     ige_mac_menu_set_menu_bar(GTK_MENU_SHELL(mbar));
1025 #endif
1027     sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view);
1029 #ifdef GDK_WINDOWING_QUARTZ
1030     return NULL;
1031 #else
1032     return mbar;
1033 #endif
1036 static void leave_group(GtkMenuItem *, SPDesktop *desktop) {
1037     desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer()));
1040 static void enter_group(GtkMenuItem *mi, SPDesktop *desktop) {
1041     desktop->setCurrentLayer(reinterpret_cast<SPObject *>(g_object_get_data(G_OBJECT(mi), "group")));
1042     sp_desktop_selection(desktop)->clear();
1045 GtkWidget *
1046 sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item)
1048     GtkWidget *m;
1049     SPDesktop *dt;
1051     dt = static_cast<SPDesktop*>(view);
1053     m = gtk_menu_new();
1055     /* Undo and Redo */
1056     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_UNDO), view);
1057     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_REDO), view);
1059     /* Separator */
1060     sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1062     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_CUT), view);
1063     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_COPY), view);
1064     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_PASTE), view);
1066     /* Separator */
1067     sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1069     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DUPLICATE), view);
1070     sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DELETE), view);
1072     /* Item menu */
1073     if (item) {
1074         sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1075         sp_object_menu((SPObject *) item, dt, GTK_MENU(m));
1076     }
1078     /* layer menu */
1079     SPGroup *group=NULL;
1080     if (item) {
1081         if (SP_IS_GROUP(item)) {
1082             group = SP_GROUP(item);
1083         } else if ( item != dt->currentRoot() && SP_IS_GROUP(SP_OBJECT_PARENT(item)) ) {
1084             group = SP_GROUP(SP_OBJECT_PARENT(item));
1085         }
1086     }
1088     if (( group && group != dt->currentLayer() ) ||
1089         ( dt->currentLayer() != dt->currentRoot() && SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) ) {
1090         /* Separator */
1091         sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1092     }
1094     if ( group && group != dt->currentLayer() ) {
1095         /* TRANSLATORS: #%s is the id of the group e.g. <g id="#g7">, not a number. */
1096         gchar *label=g_strdup_printf(_("Enter group #%s"), SP_OBJECT_ID(group));
1097         GtkWidget *w = gtk_menu_item_new_with_label(label);
1098         g_free(label);
1099         g_object_set_data(G_OBJECT(w), "group", group);
1100         g_signal_connect(G_OBJECT(w), "activate", GCallback(enter_group), dt);
1101         gtk_widget_show(w);
1102         gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1103     }
1105     if ( dt->currentLayer() != dt->currentRoot() ) {
1106         if ( SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) {
1107             GtkWidget *w = gtk_menu_item_new_with_label(_("Go to parent"));
1108             g_signal_connect(G_OBJECT(w), "activate", GCallback(leave_group), dt);
1109             gtk_widget_show(w);
1110             gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1112         }
1113     }
1115     return m;
1118 /* Drag and Drop */
1119 void
1120 sp_ui_drag_data_received(GtkWidget *widget,
1121                          GdkDragContext *drag_context,
1122                          gint x, gint y,
1123                          GtkSelectionData *data,
1124                          guint info,
1125                          guint /*event_time*/,
1126                          gpointer /*user_data*/)
1128     Document *doc = SP_ACTIVE_DOCUMENT;
1129     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1131     switch (info) {
1132 #if ENABLE_MAGIC_COLORS
1133         case APP_X_INKY_COLOR:
1134         {
1135             int destX = 0;
1136             int destY = 0;
1137             gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1138             Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1140             SPItem *item = desktop->item_at_point( where, true );
1141             if ( item )
1142             {
1143                 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1145                 if ( data->length >= 8 ) {
1146                     cmsHPROFILE srgbProf = cmsCreate_sRGBProfile();
1148                     gchar c[64] = {0};
1149                     // Careful about endian issues.
1150                     guint16* dataVals = (guint16*)data->data;
1151                     sp_svg_write_color( c, sizeof(c),
1152                                         SP_RGBA32_U_COMPOSE(
1153                                             0x0ff & (dataVals[0] >> 8),
1154                                             0x0ff & (dataVals[1] >> 8),
1155                                             0x0ff & (dataVals[2] >> 8),
1156                                             0xff // can't have transparency in the color itself
1157                                             //0x0ff & (data->data[3] >> 8),
1158                                             ));
1159                     SPCSSAttr *css = sp_repr_css_attr_new();
1160                     bool updatePerformed = false;
1162                     if ( data->length > 14 ) {
1163                         int flags = dataVals[4];
1165                         // piggie-backed palette entry info
1166                         int index = dataVals[5];
1167                         Glib::ustring palName;
1168                         for ( int i = 0; i < dataVals[6]; i++ ) {
1169                             palName += (gunichar)dataVals[7+i];
1170                         }
1172                         // Now hook in a magic tag of some sort.
1173                         if ( !palName.empty() && (flags & 1) ) {
1174                             gchar* str = g_strdup_printf("%d|", index);
1175                             palName.insert( 0, str );
1176                             g_free(str);
1177                             str = 0;
1179                             sp_object_setAttribute( SP_OBJECT(item),
1180                                                     fillnotstroke ? "inkscape:x-fill-tag":"inkscape:x-stroke-tag",
1181                                                     palName.c_str(),
1182                                                     false );
1183                             item->updateRepr();
1185                             sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1186                             updatePerformed = true;
1187                         }
1188                     }
1190                     if ( !updatePerformed ) {
1191                         sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1192                     }
1194                     sp_desktop_apply_css_recursive( item, css, true );
1195                     item->updateRepr();
1197                     sp_document_done( doc , SP_VERB_NONE,
1198                                       _("Drop color"));
1200                     if ( srgbProf ) {
1201                         cmsCloseProfile( srgbProf );
1202                     }
1203                 }
1204             }
1205         }
1206         break;
1207 #endif // ENABLE_MAGIC_COLORS
1209         case APP_X_COLOR:
1210         {
1211             int destX = 0;
1212             int destY = 0;
1213             gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1214             Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1215             Geom::Point const button_dt(desktop->w2d(where));
1216             Geom::Point const button_doc(desktop->dt2doc(button_dt));
1218             if ( data->length == 8 ) {
1219                 gchar colorspec[64] = {0};
1220                 // Careful about endian issues.
1221                 guint16* dataVals = (guint16*)data->data;
1222                 sp_svg_write_color( colorspec, sizeof(colorspec),
1223                                     SP_RGBA32_U_COMPOSE(
1224                                         0x0ff & (dataVals[0] >> 8),
1225                                         0x0ff & (dataVals[1] >> 8),
1226                                         0x0ff & (dataVals[2] >> 8),
1227                                         0xff // can't have transparency in the color itself
1228                                         //0x0ff & (data->data[3] >> 8),
1229                                         ));
1231                 SPItem *item = desktop->item_at_point( where, true );
1233                 bool consumed = false;
1234                 if (desktop->event_context && desktop->event_context->get_drag()) {
1235                     consumed = desktop->event_context->get_drag()->dropColor(item, colorspec, button_dt);
1236                     if (consumed) {
1237                         sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1238                         desktop->event_context->get_drag()->updateDraggers();
1239                     }
1240                 }
1242                 //if (!consumed && tools_active(desktop, TOOLS_TEXT)) {
1243                 //    consumed = sp_text_context_drop_color(c, button_doc);
1244                 //    if (consumed) {
1245                 //        sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient stop"));
1246                 //    }
1247                 //}
1249                 if (!consumed && item) {
1250                     bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1251                     if (fillnotstroke &&
1252                         (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1253                         Path *livarot_path = Path_for_item(item, true, true);
1254                         livarot_path->ConvertWithBackData(0.04);
1256                         boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1257                         if (position) {
1258                             Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1259                             Geom::Point delta = nearest - button_doc;
1260                             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1261                             delta = desktop->d2w(delta);
1262                             double stroke_tolerance =
1263                                 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1264                                   desktop->current_zoom() *
1265                                   SP_OBJECT_STYLE (item)->stroke_width.computed *
1266                                   to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1267                                   : 0.0)
1268                                 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1270                             if (Geom::L2 (delta) < stroke_tolerance) {
1271                                 fillnotstroke = false;
1272                             }
1273                         }
1274                         delete livarot_path;
1275                     }
1277                     SPCSSAttr *css = sp_repr_css_attr_new();
1278                     sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec );
1280                     sp_desktop_apply_css_recursive( item, css, true );
1281                     item->updateRepr();
1283                     sp_document_done( doc , SP_VERB_NONE,
1284                                       _("Drop color"));
1285                 }
1286             }
1287         }
1288         break;
1290         case APP_OSWB_COLOR:
1291         {
1292             bool worked = false;
1293             Glib::ustring colorspec;
1294             if ( data->format == 8 ) {
1295                 ege::PaintDef color;
1296                 worked = color.fromMIMEData("application/x-oswb-color",
1297                                             reinterpret_cast<char*>(data->data),
1298                                             data->length,
1299                                             data->format);
1300                 if ( worked ) {
1301                     if ( color.getType() == ege::PaintDef::CLEAR ) {
1302                         colorspec = ""; // TODO check if this is sufficient
1303                     } else if ( color.getType() == ege::PaintDef::NONE ) {
1304                         colorspec = "none";
1305                     } else {
1306                         unsigned int r = color.getR();
1307                         unsigned int g = color.getG();
1308                         unsigned int b = color.getB();
1310                         SPGradient* matches = 0;
1311                         const GSList *gradients = sp_document_get_resource_list(doc, "gradient");
1312                         for (const GSList *item = gradients; item; item = item->next) {
1313                             SPGradient* grad = SP_GRADIENT(item->data);
1314                             if ( color.descr == grad->id ) {
1315                                 if ( grad->has_stops ) {
1316                                     matches = grad;
1317                                     break;
1318                                 }
1319                             }
1320                         }
1321                         if (matches) {
1322                             colorspec = "url(#";
1323                             colorspec += matches->id;
1324                             colorspec += ")";
1325                         } else {
1326                             gchar* tmp = g_strdup_printf("#%02x%02x%02x", r, g, b);
1327                             colorspec = tmp;
1328                             g_free(tmp);
1329                         }
1330                     }
1331                 }
1332             }
1333             if ( worked ) {
1334                 int destX = 0;
1335                 int destY = 0;
1336                 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1337                 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1338                 Geom::Point const button_dt(desktop->w2d(where));
1339                 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1341                 SPItem *item = desktop->item_at_point( where, true );
1343                 bool consumed = false;
1344                 if (desktop->event_context && desktop->event_context->get_drag()) {
1345                     consumed = desktop->event_context->get_drag()->dropColor(item, colorspec.c_str(), button_dt);
1346                     if (consumed) {
1347                         sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1348                         desktop->event_context->get_drag()->updateDraggers();
1349                     }
1350                 }
1352                 if (!consumed && item) {
1353                     bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1354                     if (fillnotstroke &&
1355                         (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1356                         Path *livarot_path = Path_for_item(item, true, true);
1357                         livarot_path->ConvertWithBackData(0.04);
1359                         boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1360                         if (position) {
1361                             Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1362                             Geom::Point delta = nearest - button_doc;
1363                             Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1364                             delta = desktop->d2w(delta);
1365                             double stroke_tolerance =
1366                                 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1367                                   desktop->current_zoom() *
1368                                   SP_OBJECT_STYLE (item)->stroke_width.computed *
1369                                   to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1370                                   : 0.0)
1371                                 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1373                             if (Geom::L2 (delta) < stroke_tolerance) {
1374                                 fillnotstroke = false;
1375                             }
1376                         }
1377                         delete livarot_path;
1378                     }
1380                     SPCSSAttr *css = sp_repr_css_attr_new();
1381                     sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec.c_str() );
1383                     sp_desktop_apply_css_recursive( item, css, true );
1384                     item->updateRepr();
1386                     sp_document_done( doc , SP_VERB_NONE,
1387                                       _("Drop color"));
1388                 }
1389             }
1390         }
1391         break;
1393         case SVG_DATA:
1394         case SVG_XML_DATA: {
1395             gchar *svgdata = (gchar *)data->data;
1397             Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI);
1399             if (rnewdoc == NULL) {
1400                 sp_ui_error_dialog(_("Could not parse SVG data"));
1401                 return;
1402             }
1404             Inkscape::XML::Node *repr = rnewdoc->root();
1405             gchar const *style = repr->attribute("style");
1407             Inkscape::XML::Node *newgroup = rnewdoc->createElement("svg:g");
1408             newgroup->setAttribute("style", style);
1410             Inkscape::XML::Document * xml_doc =  sp_document_repr_doc(doc);
1411             for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) {
1412                 Inkscape::XML::Node *newchild = child->duplicate(xml_doc);
1413                 newgroup->appendChild(newchild);
1414             }
1416             Inkscape::GC::release(rnewdoc);
1418             // Add it to the current layer
1420             // Greg's edits to add intelligent positioning of svg drops
1421             SPObject *new_obj = NULL;
1422             new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
1424             Inkscape::Selection *selection = sp_desktop_selection(desktop);
1425             selection->set(SP_ITEM(new_obj));
1427             // move to mouse pointer
1428             {
1429                 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
1430                 Geom::OptRect sel_bbox = selection->bounds();
1431                 if (sel_bbox) {
1432                     Geom::Point m( desktop->point() - sel_bbox->midpoint() );
1433                     sp_selection_move_relative(selection, m, false);
1434                 }
1435             }
1437             Inkscape::GC::release(newgroup);
1438             sp_document_done(doc, SP_VERB_NONE,
1439                              _("Drop SVG"));
1440             break;
1441         }
1443         case URI_LIST: {
1444             gchar *uri = (gchar *)data->data;
1445             sp_ui_import_files(uri);
1446             break;
1447         }
1449         case PNG_DATA:
1450         case JPEG_DATA:
1451         case IMAGE_DATA: {
1452             Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
1453             Inkscape::XML::Node *newImage = xml_doc->createElement("svg:image");
1454             gchar *atom_name = gdk_atom_name(data->type);
1456             // this formula taken from Glib docs
1457             guint needed_size = data->length * 4 / 3 + data->length * 4 / (3 * 72) + 7;
1458             needed_size += 5 + 8 + strlen(atom_name); // 5 bytes for data:, 8 for ;base64,
1460             gchar *buffer = (gchar *) g_malloc(needed_size), *buf_work = buffer;
1461             buf_work += g_sprintf(buffer, "data:%s;base64,", atom_name);
1463             gint state = 0, save = 0;
1464             g_base64_encode_step(data->data, data->length, TRUE, buf_work, &state, &save);
1465             g_base64_encode_close(TRUE, buf_work, &state, &save);
1467             newImage->setAttribute("xlink:href", buffer);
1468             g_free(buffer);
1470             GError *error = NULL;
1471             GdkPixbufLoader *loader = gdk_pixbuf_loader_new_with_mime_type( gdk_atom_name(data->type), &error );
1472             if ( loader ) {
1473                 error = NULL;
1474                 if ( gdk_pixbuf_loader_write( loader, data->data, data->length, &error) ) {
1475                     GdkPixbuf *pbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1476                     if ( pbuf ) {
1477                         char tmp[1024];
1478                         int width = gdk_pixbuf_get_width(pbuf);
1479                         int height = gdk_pixbuf_get_height(pbuf);
1480                         snprintf( tmp, sizeof(tmp), "%d", width );
1481                         newImage->setAttribute("width", tmp);
1483                         snprintf( tmp, sizeof(tmp), "%d", height );
1484                         newImage->setAttribute("height", tmp);
1485                     }
1486                 }
1487             }
1488             g_free(atom_name);
1490             // Add it to the current layer
1491             desktop->currentLayer()->appendChildRepr(newImage);
1493             Inkscape::GC::release(newImage);
1494             sp_document_done( doc , SP_VERB_NONE,
1495                               _("Drop bitmap image"));
1496             break;
1497         }
1498     }
1501 #include "gradient-context.h"
1503 void sp_ui_drag_motion( GtkWidget */*widget*/,
1504                         GdkDragContext */*drag_context*/,
1505                         gint /*x*/, gint /*y*/,
1506                         GtkSelectionData */*data*/,
1507                         guint /*info*/,
1508                         guint /*event_time*/,
1509                         gpointer /*user_data*/)
1511 //     Document *doc = SP_ACTIVE_DOCUMENT;
1512 //     SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1515 //     g_message("drag-n-drop motion (%4d, %4d)  at %d", x, y, event_time);
1518 static void sp_ui_drag_leave( GtkWidget */*widget*/,
1519                               GdkDragContext */*drag_context*/,
1520                               guint /*event_time*/,
1521                               gpointer /*user_data*/ )
1523 //     g_message("drag-n-drop leave                at %d", event_time);
1526 static void
1527 sp_ui_import_files(gchar *buffer)
1529     GList *list = gnome_uri_list_extract_filenames(buffer);
1530     if (!list)
1531         return;
1532     g_list_foreach(list, sp_ui_import_one_file_with_check, NULL);
1533     g_list_foreach(list, (GFunc) g_free, NULL);
1534     g_list_free(list);
1537 static void
1538 sp_ui_import_one_file_with_check(gpointer filename, gpointer /*unused*/)
1540     if (filename) {
1541         if (strlen((char const *)filename) > 2)
1542             sp_ui_import_one_file((char const *)filename);
1543     }
1546 static void
1547 sp_ui_import_one_file(char const *filename)
1549     Document *doc = SP_ACTIVE_DOCUMENT;
1550     if (!doc) return;
1552     if (filename == NULL) return;
1554     // Pass off to common implementation
1555     // TODO might need to get the proper type of Inkscape::Extension::Extension
1556     file_import( doc, filename, NULL );
1559 void
1560 sp_ui_error_dialog(gchar const *message)
1562     GtkWidget *dlg;
1563     gchar *safeMsg = Inkscape::IO::sanitizeString(message);
1565     dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
1566                                  GTK_BUTTONS_CLOSE, "%s", safeMsg);
1567     sp_transientize(dlg);
1568     gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE);
1569     gtk_dialog_run(GTK_DIALOG(dlg));
1570     gtk_widget_destroy(dlg);
1571     g_free(safeMsg);
1574 bool
1575 sp_ui_overwrite_file(gchar const *filename)
1577     bool return_value = FALSE;
1579     if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) {
1580         Gtk::Window *window = SP_ACTIVE_DESKTOP->getToplevel();
1581         gchar* baseName = g_path_get_basename( filename );
1582         gchar* dirName = g_path_get_dirname( filename );
1583         GtkWidget* dialog = gtk_message_dialog_new_with_markup( window->gobj(),
1584                                                                 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
1585                                                                 GTK_MESSAGE_QUESTION,
1586                                                                 GTK_BUTTONS_NONE,
1587                                                                 _( "<span weight=\"bold\" size=\"larger\">A file named \"%s\" already exists. Do you want to replace it?</span>\n\n"
1588                                                                    "The file already exists in \"%s\". Replacing it will overwrite its contents." ),
1589                                                                 baseName,
1590                                                                 dirName
1591             );
1592         gtk_dialog_add_buttons( GTK_DIALOG(dialog),
1593                                 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1594                                 _("Replace"), GTK_RESPONSE_YES,
1595                                 NULL );
1596         gtk_dialog_set_default_response( GTK_DIALOG(dialog), GTK_RESPONSE_YES );
1598         if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_YES ) {
1599             return_value = TRUE;
1600         } else {
1601             return_value = FALSE;
1602         }
1603         gtk_widget_destroy(dialog);
1604         g_free( baseName );
1605         g_free( dirName );
1606     } else {
1607         return_value = TRUE;
1608     }
1610     return return_value;
1613 static void
1614 sp_ui_menu_item_set_sensitive(SPAction */*action*/, unsigned int sensitive, void *data)
1616     return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
1619 static void
1620 sp_ui_menu_item_set_name(SPAction */*action*/, Glib::ustring name, void *data)
1622     void *child = GTK_BIN (data)->child;
1623     //child is either
1624     //- a GtkHBox, whose first child is a label displaying name if the menu
1625     //item has an accel key
1626     //- a GtkLabel if the menu has no accel key
1627     if (GTK_IS_LABEL(child)) {
1628         gtk_label_set_markup_with_mnemonic(GTK_LABEL (child), name.c_str());
1629     } else if (GTK_IS_HBOX(child)) {
1630         gtk_label_set_markup_with_mnemonic(
1631         GTK_LABEL (gtk_container_get_children(GTK_CONTAINER (child))->data),
1632         name.c_str());
1633     }//else sp_ui_menu_append_item_from_verb has been modified and can set
1634     //a menu item in yet another way...
1638 /*
1639   Local Variables:
1640   mode:c++
1641   c-file-style:"stroustrup"
1642   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1643   indent-tabs-mode:nil
1644   fill-column:99
1645   End:
1646 */
1647 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :