Code

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