Code

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