Code

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