Code

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