bdec992919cc2478448a93b77e9846c3f465b53d
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/db.h"
27 #include "extension/effect.h"
28 #include "extension/input.h"
29 #include "widgets/icon.h"
30 #include "preferences.h"
31 #include "path-prefix.h"
32 #include "shortcuts.h"
33 #include "document.h"
34 #include "desktop-handles.h"
35 #include "file.h"
36 #include "interface.h"
37 #include "desktop.h"
38 #include "ui/context-menu.h"
39 #include "selection.h"
40 #include "selection-chemistry.h"
41 #include "svg-view-widget.h"
42 #include "widgets/desktop-widget.h"
43 #include "sp-item-group.h"
44 #include "sp-text.h"
45 #include "sp-gradient-fns.h"
46 #include "sp-gradient.h"
47 #include "sp-flowtext.h"
48 #include "sp-namedview.h"
49 #include "ui/view/view.h"
50 #include "helper/action.h"
51 #include "helper/gnome-utils.h"
52 #include "helper/window.h"
53 #include "io/sys.h"
54 #include "dialogs/dialog-events.h"
55 #include "message-context.h"
56 #include "ui/uxmanager.h"
58 // Added for color drag-n-drop
59 #if ENABLE_LCMS
60 #include "lcms.h"
61 #endif // ENABLE_LCMS
62 #include "display/sp-canvas.h"
63 #include "color.h"
64 #include "svg/svg-color.h"
65 #include "desktop-style.h"
66 #include "style.h"
67 #include "event-context.h"
68 #include "gradient-drag.h"
69 #include "widgets/ege-paint-def.h"
71 // Include Mac OS X menu synchronization on native OSX build
72 #ifdef GDK_WINDOWING_QUARTZ
73 #include "ige-mac-menu.h"
74 #endif
76 /* Drag and Drop */
77 typedef enum {
78 URI_LIST,
79 SVG_XML_DATA,
80 SVG_DATA,
81 PNG_DATA,
82 JPEG_DATA,
83 IMAGE_DATA,
84 APP_X_INKY_COLOR,
85 APP_X_COLOR,
86 APP_OSWB_COLOR,
87 } ui_drop_target_info;
89 static GtkTargetEntry ui_drop_target_entries [] = {
90 {(gchar *)"text/uri-list", 0, URI_LIST },
91 {(gchar *)"image/svg+xml", 0, SVG_XML_DATA },
92 {(gchar *)"image/svg", 0, SVG_DATA },
93 {(gchar *)"image/png", 0, PNG_DATA },
94 {(gchar *)"image/jpeg", 0, JPEG_DATA },
95 #if ENABLE_MAGIC_COLORS
96 {(gchar *)"application/x-inkscape-color", 0, APP_X_INKY_COLOR},
97 #endif // ENABLE_MAGIC_COLORS
98 {(gchar *)"application/x-oswb-color", 0, APP_OSWB_COLOR },
99 {(gchar *)"application/x-color", 0, APP_X_COLOR }
100 };
102 static GtkTargetEntry *completeDropTargets = 0;
103 static int completeDropTargetsCount = 0;
104 static bool temporarily_block_actions = false;
106 #define ENTRIES_SIZE(n) sizeof(n)/sizeof(n[0])
107 static guint nui_drop_target_entries = ENTRIES_SIZE(ui_drop_target_entries);
108 static void sp_ui_import_files(gchar *buffer);
109 static void sp_ui_import_one_file(char const *filename);
110 static void sp_ui_import_one_file_with_check(gpointer filename, gpointer unused);
111 static void sp_ui_drag_data_received(GtkWidget *widget,
112 GdkDragContext *drag_context,
113 gint x, gint y,
114 GtkSelectionData *data,
115 guint info,
116 guint event_time,
117 gpointer user_data);
118 static void sp_ui_drag_motion( GtkWidget *widget,
119 GdkDragContext *drag_context,
120 gint x, gint y,
121 GtkSelectionData *data,
122 guint info,
123 guint event_time,
124 gpointer user_data );
125 static void sp_ui_drag_leave( GtkWidget *widget,
126 GdkDragContext *drag_context,
127 guint event_time,
128 gpointer user_data );
129 static void sp_ui_menu_item_set_sensitive(SPAction *action,
130 unsigned int sensitive,
131 void *data);
132 static void sp_ui_menu_item_set_name(SPAction *action,
133 Glib::ustring name,
134 void *data);
135 static void sp_recent_open(GtkRecentChooser *, gpointer);
137 SPActionEventVector menu_item_event_vector = {
138 {NULL},
139 NULL,
140 NULL, /* set_active */
141 sp_ui_menu_item_set_sensitive, /* set_sensitive */
142 NULL, /* set_shortcut */
143 sp_ui_menu_item_set_name /* set_name */
144 };
146 static const int MIN_ONSCREEN_DISTANCE = 50;
148 void
149 sp_create_window(SPViewWidget *vw, gboolean editable)
150 {
151 g_return_if_fail(vw != NULL);
152 g_return_if_fail(SP_IS_VIEW_WIDGET(vw));
154 Gtk::Window *win = Inkscape::UI::window_new("", TRUE);
156 gtk_container_add(GTK_CONTAINER(win->gobj()), GTK_WIDGET(vw));
157 gtk_widget_show(GTK_WIDGET(vw));
159 if (editable) {
160 g_object_set_data(G_OBJECT(vw), "window", win);
162 SPDesktopWidget *desktop_widget = reinterpret_cast<SPDesktopWidget*>(vw);
163 SPDesktop* desktop = desktop_widget->desktop;
165 desktop_widget->window = win;
167 win->set_data("desktop", desktop);
168 win->set_data("desktopwidget", desktop_widget);
170 win->signal_delete_event().connect(sigc::mem_fun(*(SPDesktop*)vw->view, &SPDesktop::onDeleteUI));
171 win->signal_window_state_event().connect(sigc::mem_fun(*desktop, &SPDesktop::onWindowStateEvent));
172 win->signal_focus_in_event().connect(sigc::mem_fun(*desktop_widget, &SPDesktopWidget::onFocusInEvent));
174 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
175 gint prefs_geometry =
176 (2==prefs->getInt("/options/savewindowgeometry/value", 0));
177 if (prefs_geometry) {
178 gint pw = prefs->getInt("/desktop/geometry/width", -1);
179 gint ph = prefs->getInt("/desktop/geometry/height", -1);
180 gint px = prefs->getInt("/desktop/geometry/x", -1);
181 gint py = prefs->getInt("/desktop/geometry/y", -1);
182 gint full = prefs->getBool("/desktop/geometry/fullscreen");
183 gint maxed = prefs->getBool("/desktop/geometry/maximized");
184 if (pw>0 && ph>0) {
185 gint w = MIN(gdk_screen_width(), pw);
186 gint h = MIN(gdk_screen_height(), ph);
187 gint x = MIN(gdk_screen_width() - MIN_ONSCREEN_DISTANCE, px);
188 gint y = MIN(gdk_screen_height() - MIN_ONSCREEN_DISTANCE, py);
189 if (w>0 && h>0) {
190 x = MIN(gdk_screen_width() - w, x);
191 y = MIN(gdk_screen_height() - h, y);
192 desktop->setWindowSize(w, h);
193 }
195 // Only restore xy for the first window so subsequent windows don't overlap exactly
196 // with first. (Maybe rule should be only restore xy if it's different from xy of
197 // other desktops?)
199 // Empirically it seems that active_desktop==this desktop only the first time a
200 // desktop is created.
201 SPDesktop *active_desktop = SP_ACTIVE_DESKTOP;
202 if (active_desktop == desktop || active_desktop==NULL) {
203 desktop->setWindowPosition(Geom::Point(x, y));
204 }
205 }
206 if (maxed) {
207 win->maximize();
208 }
209 if (full) {
210 win->fullscreen();
211 }
212 }
214 } else {
215 gtk_window_set_policy(GTK_WINDOW(win->gobj()), TRUE, TRUE, TRUE);
216 }
218 if ( completeDropTargets == 0 || completeDropTargetsCount == 0 )
219 {
220 std::vector<gchar*> types;
222 GSList *list = gdk_pixbuf_get_formats();
223 while ( list ) {
224 int i = 0;
225 GdkPixbufFormat *one = (GdkPixbufFormat*)list->data;
226 gchar** typesXX = gdk_pixbuf_format_get_mime_types(one);
227 for ( i = 0; typesXX[i]; i++ ) {
228 types.push_back(g_strdup(typesXX[i]));
229 }
230 g_strfreev(typesXX);
232 list = g_slist_next(list);
233 }
234 completeDropTargetsCount = nui_drop_target_entries + types.size();
235 completeDropTargets = new GtkTargetEntry[completeDropTargetsCount];
236 for ( int i = 0; i < (int)nui_drop_target_entries; i++ ) {
237 completeDropTargets[i] = ui_drop_target_entries[i];
238 }
239 int pos = nui_drop_target_entries;
241 for (std::vector<gchar*>::iterator it = types.begin() ; it != types.end() ; it++) {
242 completeDropTargets[pos].target = *it;
243 completeDropTargets[pos].flags = 0;
244 completeDropTargets[pos].info = IMAGE_DATA;
245 pos++;
246 }
247 }
249 gtk_drag_dest_set((GtkWidget*)win->gobj(),
250 GTK_DEST_DEFAULT_ALL,
251 completeDropTargets,
252 completeDropTargetsCount,
253 GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE));
256 g_signal_connect(G_OBJECT(win->gobj()),
257 "drag_data_received",
258 G_CALLBACK(sp_ui_drag_data_received),
259 NULL);
260 g_signal_connect(G_OBJECT(win->gobj()),
261 "drag_motion",
262 G_CALLBACK(sp_ui_drag_motion),
263 NULL);
264 g_signal_connect(G_OBJECT(win->gobj()),
265 "drag_leave",
266 G_CALLBACK(sp_ui_drag_leave),
267 NULL);
268 win->show();
270 // needed because the first ACTIVATE_DESKTOP was sent when there was no window yet
271 inkscape_reactivate_desktop(SP_DESKTOP_WIDGET(vw)->desktop);
272 }
274 void
275 sp_ui_new_view()
276 {
277 SPDocument *document;
278 SPViewWidget *dtw;
280 document = SP_ACTIVE_DOCUMENT;
281 if (!document) return;
283 dtw = sp_desktop_widget_new(sp_document_namedview(document, NULL));
284 g_return_if_fail(dtw != NULL);
286 sp_create_window(dtw, TRUE);
287 sp_namedview_window_from_document(static_cast<SPDesktop*>(dtw->view));
288 sp_namedview_update_layers_from_document(static_cast<SPDesktop*>(dtw->view));
289 }
291 /* TODO: not yet working */
292 /* To be re-enabled (by adding to menu) once it works. */
293 void
294 sp_ui_new_view_preview()
295 {
296 SPDocument *document;
297 SPViewWidget *dtw;
299 document = SP_ACTIVE_DOCUMENT;
300 if (!document) return;
302 dtw = (SPViewWidget *) sp_svg_view_widget_new(document);
303 g_return_if_fail(dtw != NULL);
304 sp_svg_view_widget_set_resize(SP_SVG_VIEW_WIDGET(dtw), TRUE, 400.0, 400.0);
306 sp_create_window(dtw, FALSE);
307 }
309 /**
310 * \param widget unused
311 */
312 void
313 sp_ui_close_view(GtkWidget */*widget*/)
314 {
315 SPDesktop *dt = SP_ACTIVE_DESKTOP;
317 if (dt == NULL) {
318 return;
319 }
321 if (dt->shutdown()) {
322 return; // Shutdown operation has been canceled, so do nothing
323 }
325 // Shutdown can proceed; use the stored reference to the desktop here instead of the current SP_ACTIVE_DESKTOP,
326 // because the user might have changed the focus in the meantime (see bug #381357 on Launchpad)
327 dt->destroyWidget();
328 }
331 /**
332 * sp_ui_close_all
333 *
334 * This function is called to exit the program, and iterates through all
335 * open document view windows, attempting to close each in turn. If the
336 * view has unsaved information, the user will be prompted to save,
337 * discard, or cancel.
338 *
339 * Returns FALSE if the user cancels the close_all operation, TRUE
340 * otherwise.
341 */
342 unsigned int
343 sp_ui_close_all(void)
344 {
345 /* Iterate through all the windows, destroying each in the order they
346 become active */
347 while (SP_ACTIVE_DESKTOP) {
348 SPDesktop *dt = SP_ACTIVE_DESKTOP;
349 if (dt->shutdown()) {
350 /* The user canceled the operation, so end doing the close */
351 return FALSE;
352 }
353 // Shutdown can proceed; use the stored reference to the desktop here instead of the current SP_ACTIVE_DESKTOP,
354 // because the user might have changed the focus in the meantime (see bug #381357 on Launchpad)
355 dt->destroyWidget();
356 }
358 return TRUE;
359 }
361 /*
362 * Some day when the right-click menus are ready to start working
363 * smarter with the verbs, we'll need to change this NULL being
364 * sent to sp_action_perform to something useful, or set some kind
365 * of global "right-clicked position" variable for actions to
366 * investigate when they're called.
367 */
368 static void
369 sp_ui_menu_activate(void */*object*/, SPAction *action)
370 {
371 if (!temporarily_block_actions) {
372 sp_action_perform(action, NULL);
373 }
374 }
376 static void
377 sp_ui_menu_select_action(void */*object*/, SPAction *action)
378 {
379 action->view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, action->tip);
380 }
382 static void
383 sp_ui_menu_deselect_action(void */*object*/, SPAction *action)
384 {
385 action->view->tipsMessageContext()->clear();
386 }
388 static void
389 sp_ui_menu_select(gpointer object, gpointer tip)
390 {
391 Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view"));
392 view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, (gchar *)tip);
393 }
395 static void
396 sp_ui_menu_deselect(gpointer object)
397 {
398 Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view"));
399 view->tipsMessageContext()->clear();
400 }
402 /**
403 * sp_ui_menuitem_add_icon
404 *
405 * Creates and attaches a scaled icon to the given menu item.
406 *
407 */
408 void
409 sp_ui_menuitem_add_icon( GtkWidget *item, gchar *icon_name )
410 {
411 GtkWidget *icon;
413 icon = sp_icon_new( Inkscape::ICON_SIZE_MENU, icon_name );
414 gtk_widget_show(icon);
415 gtk_image_menu_item_set_image((GtkImageMenuItem *) item, icon);
416 } // end of sp_ui_menu_add_icon
418 /**
419 * sp_ui_menu_append_item
420 *
421 * Appends a UI item with specific info for Inkscape/Sodipodi.
422 *
423 */
424 static GtkWidget *
425 sp_ui_menu_append_item( GtkMenu *menu, gchar const *stock,
426 gchar const *label, gchar const *tip, Inkscape::UI::View::View *view, GCallback callback,
427 gpointer data, gboolean with_mnemonic = TRUE )
428 {
429 GtkWidget *item;
431 if (stock) {
432 item = gtk_image_menu_item_new_from_stock(stock, NULL);
433 } else if (label) {
434 item = (with_mnemonic)
435 ? gtk_image_menu_item_new_with_mnemonic(label) :
436 gtk_image_menu_item_new_with_label(label);
437 } else {
438 item = gtk_separator_menu_item_new();
439 }
441 gtk_widget_show(item);
443 if (callback) {
444 g_signal_connect(G_OBJECT(item), "activate", callback, data);
445 }
447 if (tip && view) {
448 g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
449 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) tip );
450 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
451 }
453 gtk_menu_append(GTK_MENU(menu), item);
455 return item;
457 } // end of sp_ui_menu_append_item()
459 /**
460 \brief a wrapper around gdk_keyval_name producing (when possible) characters, not names
461 */
462 static gchar const *
463 sp_key_name(guint keyval)
464 {
465 /* TODO: Compare with the definition of gtk_accel_label_refetch in gtk/gtkaccellabel.c (or
466 simply use GtkAccelLabel as the TODO comment in sp_ui_shortcut_string suggests). */
467 gchar const *n = gdk_keyval_name(gdk_keyval_to_upper(keyval));
469 if (!strcmp(n, "asciicircum")) return "^";
470 else if (!strcmp(n, "parenleft" )) return "(";
471 else if (!strcmp(n, "parenright" )) return ")";
472 else if (!strcmp(n, "plus" )) return "+";
473 else if (!strcmp(n, "minus" )) return "-";
474 else if (!strcmp(n, "asterisk" )) return "*";
475 else if (!strcmp(n, "KP_Multiply")) return "*";
476 else if (!strcmp(n, "Delete" )) return "Del";
477 else if (!strcmp(n, "Page_Up" )) return "PgUp";
478 else if (!strcmp(n, "Page_Down" )) return "PgDn";
479 else if (!strcmp(n, "grave" )) return "`";
480 else if (!strcmp(n, "numbersign" )) return "#";
481 else if (!strcmp(n, "bar" )) return "|";
482 else if (!strcmp(n, "slash" )) return "/";
483 else if (!strcmp(n, "exclam" )) return "!";
484 else if (!strcmp(n, "percent" )) return "%";
485 else return n;
486 }
489 /**
490 * \param shortcut A GDK keyval OR'd with SP_SHORTCUT_blah_MASK values.
491 * \param c Points to a buffer at least 256 bytes long.
492 */
493 void
494 sp_ui_shortcut_string(unsigned const shortcut, gchar *const c)
495 {
496 /* TODO: This function shouldn't exist. Our callers should use GtkAccelLabel instead of
497 * a generic GtkLabel containing this string, and should call gtk_widget_add_accelerator.
498 * Will probably need to change sp_shortcut_invoke callers.
499 *
500 * The existing gtk_label_new_with_mnemonic call can be replaced with
501 * g_object_new(GTK_TYPE_ACCEL_LABEL, NULL) followed by
502 * gtk_label_set_text_with_mnemonic(lbl, str).
503 */
504 static GtkAccelLabelClass const &accel_lbl_cls
505 = *(GtkAccelLabelClass const *) g_type_class_peek_static(GTK_TYPE_ACCEL_LABEL);
507 struct { unsigned test; char const *name; } const modifier_tbl[] = {
508 { SP_SHORTCUT_SHIFT_MASK, accel_lbl_cls.mod_name_shift },
509 { SP_SHORTCUT_CONTROL_MASK, accel_lbl_cls.mod_name_control },
510 { SP_SHORTCUT_ALT_MASK, accel_lbl_cls.mod_name_alt }
511 };
513 gchar *p = c;
514 gchar *end = p + 256;
516 for (unsigned i = 0; i < G_N_ELEMENTS(modifier_tbl); ++i) {
517 if ((shortcut & modifier_tbl[i].test)
518 && (p < end))
519 {
520 p += g_snprintf(p, end - p, "%s%s",
521 modifier_tbl[i].name,
522 accel_lbl_cls.mod_separator);
523 }
524 }
525 if (p < end) {
526 p += g_snprintf(p, end - p, "%s", sp_key_name(shortcut & 0xffffff));
527 }
528 end[-1] = '\0'; // snprintf doesn't guarantee to nul-terminate the string.
529 }
531 void
532 sp_ui_dialog_title_string(Inkscape::Verb *verb, gchar *c)
533 {
534 SPAction *action;
535 unsigned int shortcut;
536 gchar *s;
537 gchar key[256];
538 gchar *atitle;
540 action = verb->get_action(NULL);
541 if (!action)
542 return;
544 atitle = sp_action_get_title(action);
546 s = g_stpcpy(c, atitle);
548 g_free(atitle);
550 shortcut = sp_shortcut_get_primary(verb);
551 if (shortcut) {
552 s = g_stpcpy(s, " (");
553 sp_ui_shortcut_string(shortcut, key);
554 s = g_stpcpy(s, key);
555 s = g_stpcpy(s, ")");
556 }
557 }
560 /**
561 * sp_ui_menu_append_item_from_verb
562 *
563 * Appends a custom menu UI from a verb.
564 *
565 */
567 static GtkWidget *
568 sp_ui_menu_append_item_from_verb(GtkMenu *menu, Inkscape::Verb *verb, Inkscape::UI::View::View *view, bool radio = false, GSList *group = NULL)
569 {
570 SPAction *action;
571 GtkWidget *item;
573 if (verb->get_code() == SP_VERB_NONE) {
575 item = gtk_separator_menu_item_new();
577 } else {
578 unsigned int shortcut;
580 action = verb->get_action(view);
582 if (!action) return NULL;
584 shortcut = sp_shortcut_get_primary(verb);
585 if (shortcut) {
586 gchar c[256];
587 sp_ui_shortcut_string(shortcut, c);
588 GtkWidget *const hb = gtk_hbox_new(FALSE, 16);
589 GtkWidget *const name_lbl = gtk_label_new("");
590 gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
591 gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
592 gtk_box_pack_start((GtkBox *) hb, name_lbl, TRUE, TRUE, 0);
593 GtkWidget *const accel_lbl = gtk_label_new(c);
594 gtk_misc_set_alignment((GtkMisc *) accel_lbl, 1.0, 0.5);
595 gtk_box_pack_end((GtkBox *) hb, accel_lbl, FALSE, FALSE, 0);
596 gtk_widget_show_all(hb);
597 if (radio) {
598 item = gtk_radio_menu_item_new (group);
599 } else {
600 item = gtk_image_menu_item_new();
601 }
602 gtk_container_add((GtkContainer *) item, hb);
603 } else {
604 if (radio) {
605 item = gtk_radio_menu_item_new (group);
606 } else {
607 item = gtk_image_menu_item_new ();
608 }
609 GtkWidget *const name_lbl = gtk_label_new("");
610 gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
611 gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
612 gtk_container_add((GtkContainer *) item, name_lbl);
613 }
615 nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
616 if (!action->sensitive) {
617 gtk_widget_set_sensitive(item, FALSE);
618 }
620 if (action->image) {
621 sp_ui_menuitem_add_icon(item, action->image);
622 }
623 gtk_widget_set_events(item, GDK_KEY_PRESS_MASK);
624 g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
625 g_signal_connect( G_OBJECT(item), "activate", G_CALLBACK(sp_ui_menu_activate), action );
626 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select_action), action );
627 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect_action), action );
628 }
630 gtk_widget_show(item);
631 gtk_menu_append(GTK_MENU(menu), item);
633 return item;
635 } // end of sp_ui_menu_append_item_from_verb
638 static Glib::ustring getLayoutPrefPath( Inkscape::UI::View::View *view )
639 {
640 Glib::ustring prefPath;
642 if (reinterpret_cast<SPDesktop*>(view)->is_focusMode()) {
643 prefPath = "/focus/";
644 } else if (reinterpret_cast<SPDesktop*>(view)->is_fullscreen()) {
645 prefPath = "/fullscreen/";
646 } else {
647 prefPath = "/window/";
648 }
650 return prefPath;
651 }
653 static void
654 checkitem_toggled(GtkCheckMenuItem *menuitem, gpointer user_data)
655 {
656 gchar const *pref = (gchar const *) user_data;
657 Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
659 Glib::ustring pref_path = getLayoutPrefPath( view );
660 pref_path += pref;
661 pref_path += "/state";
663 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
664 gboolean checked = gtk_check_menu_item_get_active(menuitem);
665 prefs->setBool(pref_path, checked);
667 reinterpret_cast<SPDesktop*>(view)->layoutWidget();
668 }
670 static gboolean
671 checkitem_update(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_data)
672 {
673 GtkCheckMenuItem *menuitem=GTK_CHECK_MENU_ITEM(widget);
675 gchar const *pref = (gchar const *) user_data;
676 Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
678 Glib::ustring pref_path = getLayoutPrefPath( view );
679 pref_path += pref;
680 pref_path += "/state";
682 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
683 bool ison = prefs->getBool(pref_path, true);
685 g_signal_handlers_block_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
686 gtk_check_menu_item_set_active(menuitem, ison);
687 g_signal_handlers_unblock_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
689 return FALSE;
690 }
692 static void taskToggled(GtkCheckMenuItem *menuitem, gpointer userData)
693 {
694 if ( gtk_check_menu_item_get_active(menuitem) ) {
695 gint taskNum = GPOINTER_TO_INT(userData);
696 taskNum = (taskNum < 0) ? 0 : (taskNum > 2) ? 2 : taskNum;
698 Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
700 // note: this will change once more options are in the task set support:
701 Inkscape::UI::UXManager::getInstance()->setTask( dynamic_cast<SPDesktop*>(view), taskNum );
702 }
703 }
706 /**
707 * \brief Callback function to update the status of the radio buttons in the View -> Display mode menu (Normal, No Filters, Outline)
708 */
710 static gboolean
711 update_view_menu(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_data)
712 {
713 SPAction *action = (SPAction *) user_data;
714 g_assert(action->id != NULL);
716 Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(widget), "view");
717 SPDesktop *dt = static_cast<SPDesktop*>(view);
718 Inkscape::RenderMode mode = dt->getMode();
720 bool new_state = false;
721 if (!strcmp(action->id, "ViewModeNormal")) {
722 new_state = mode == Inkscape::RENDERMODE_NORMAL;
723 } else if (!strcmp(action->id, "ViewModeNoFilters")) {
724 new_state = mode == Inkscape::RENDERMODE_NO_FILTERS;
725 } else if (!strcmp(action->id, "ViewModeOutline")) {
726 new_state = mode == Inkscape::RENDERMODE_OUTLINE;
727 } else if (!strcmp(action->id, "ViewModePrintColorsPreview")) {
728 new_state = mode == Inkscape::RENDERMODE_PRINT_COLORS_PREVIEW;
729 } else {
730 g_warning("update_view_menu does not handle this verb");
731 }
733 if (new_state) { //only one of the radio buttons has to be activated; the others will automatically be deactivated
734 if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) {
735 // When the GtkMenuItem version of the "activate" signal has been emitted by a GtkRadioMenuItem, there is a second
736 // emission as the most recently active item is toggled to inactive. This is dealt with before the original signal is handled.
737 // This emission however should not invoke any actions, hence we block it here:
738 temporarily_block_actions = true;
739 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (widget), TRUE);
740 temporarily_block_actions = false;
741 }
742 }
744 return FALSE;
745 }
747 void
748 sp_ui_menu_append_check_item_from_verb(GtkMenu *menu, Inkscape::UI::View::View *view, gchar const *label, gchar const *tip, gchar const *pref,
749 void (*callback_toggle)(GtkCheckMenuItem *, gpointer user_data),
750 gboolean (*callback_update)(GtkWidget *widget, GdkEventExpose *event, gpointer user_data),
751 Inkscape::Verb *verb)
752 {
753 unsigned int shortcut = (verb) ? sp_shortcut_get_primary(verb) : 0;
754 SPAction *action = (verb) ? verb->get_action(view) : 0;
755 GtkWidget *item = gtk_check_menu_item_new();
757 if (verb && shortcut) {
758 gchar c[256];
759 sp_ui_shortcut_string(shortcut, c);
761 GtkWidget *hb = gtk_hbox_new(FALSE, 16);
763 {
764 GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
765 gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
766 gtk_box_pack_start((GtkBox *) hb, l, TRUE, TRUE, 0);
767 }
769 {
770 GtkWidget *l = gtk_label_new(c);
771 gtk_misc_set_alignment((GtkMisc *) l, 1.0, 0.5);
772 gtk_box_pack_end((GtkBox *) hb, l, FALSE, FALSE, 0);
773 }
775 gtk_widget_show_all(hb);
777 gtk_container_add((GtkContainer *) item, hb);
778 } else {
779 GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
780 gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
781 gtk_container_add((GtkContainer *) item, l);
782 }
783 #if 0
784 nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
785 if (!action->sensitive) {
786 gtk_widget_set_sensitive(item, FALSE);
787 }
788 #endif
789 gtk_widget_show(item);
791 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
793 g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
795 g_signal_connect( G_OBJECT(item), "toggled", (GCallback) callback_toggle, (void *) pref);
796 g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) callback_update, (void *) pref);
798 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) (action ? action->tip : tip));
799 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
800 }
802 static void
803 sp_recent_open(GtkRecentChooser *recent_menu, gpointer /*user_data*/)
804 {
805 // dealing with the bizarre filename convention in Inkscape for now
806 gchar *uri = gtk_recent_chooser_get_current_uri(GTK_RECENT_CHOOSER(recent_menu));
807 gchar *local_fn = g_filename_from_uri(uri, NULL, NULL);
808 gchar *utf8_fn = g_filename_to_utf8(local_fn, -1, NULL, NULL, NULL);
809 sp_file_open(utf8_fn, NULL);
810 g_free(utf8_fn);
811 g_free(local_fn);
812 g_free(uri);
813 }
815 static void
816 sp_file_new_from_template(GtkWidget */*widget*/, gchar const *uri)
817 {
818 sp_file_new(uri);
819 }
821 void
822 sp_menu_append_new_templates(GtkWidget *menu, Inkscape::UI::View::View *view)
823 {
824 std::list<gchar *> sources;
825 sources.push_back( profile_path("templates") ); // first try user's local dir
826 sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir
828 // Use this loop to iterate through a list of possible document locations.
829 while (!sources.empty()) {
830 gchar *dirname = sources.front();
832 if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) {
833 GError *err = 0;
834 GDir *dir = g_dir_open(dirname, 0, &err);
836 if (dir) {
837 for (gchar const *file = g_dir_read_name(dir); file != NULL; file = g_dir_read_name(dir)) {
838 if (!g_str_has_suffix(file, ".svg") && !g_str_has_suffix(file, ".svgz"))
839 continue; // skip non-svg files
841 gchar *basename = g_path_get_basename(file);
842 if (g_str_has_suffix(basename, ".svg") && g_str_has_prefix(basename, "default."))
843 continue; // skip default.*.svg (i.e. default.svg and translations) - it's in the menu already
845 gchar const *filepath = g_build_filename(dirname, file, NULL);
846 gchar *dupfile = g_strndup(file, strlen(file) - 4);
847 gchar *filename = g_filename_to_utf8(dupfile, -1, NULL, NULL, NULL);
848 g_free(dupfile);
849 GtkWidget *item = gtk_menu_item_new_with_label(filename);
850 g_free(filename);
852 gtk_widget_show(item);
853 // how does "filepath" ever get freed?
854 g_signal_connect(G_OBJECT(item),
855 "activate",
856 G_CALLBACK(sp_file_new_from_template),
857 (gpointer) filepath);
859 if (view) {
860 // set null tip for now; later use a description from the template file
861 g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
862 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) NULL );
863 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
864 }
866 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
867 }
868 g_dir_close(dir);
869 }
870 }
872 // toss the dirname
873 g_free(dirname);
874 sources.pop_front();
875 }
876 }
878 void
879 sp_ui_checkboxes_menus(GtkMenu *m, Inkscape::UI::View::View *view)
880 {
881 //sp_ui_menu_append_check_item_from_verb(m, view, _("_Menu"), _("Show or hide the menu bar"), "menu",
882 // checkitem_toggled, checkitem_update, 0);
883 sp_ui_menu_append_check_item_from_verb(m, view, _("Commands Bar"), _("Show or hide the Commands bar (under the menu)"), "commands",
884 checkitem_toggled, checkitem_update, 0);
885 sp_ui_menu_append_check_item_from_verb(m, view, _("Snap Controls Bar"), _("Show or hide the snapping controls"), "snaptoolbox",
886 checkitem_toggled, checkitem_update, 0);
887 sp_ui_menu_append_check_item_from_verb(m, view, _("Tool Controls Bar"), _("Show or hide the Tool Controls bar"), "toppanel",
888 checkitem_toggled, checkitem_update, 0);
889 sp_ui_menu_append_check_item_from_verb(m, view, _("_Toolbox"), _("Show or hide the main toolbox (on the left)"), "toolbox",
890 checkitem_toggled, checkitem_update, 0);
891 sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "rulers",
892 checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_RULERS));
893 sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "scrollbars",
894 checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_SCROLLBARS));
895 sp_ui_menu_append_check_item_from_verb(m, view, _("_Palette"), _("Show or hide the color palette"), "panels",
896 checkitem_toggled, checkitem_update, 0);
897 sp_ui_menu_append_check_item_from_verb(m, view, _("_Statusbar"), _("Show or hide the statusbar (at the bottom of the window)"), "statusbar",
898 checkitem_toggled, checkitem_update, 0);
899 }
902 void addTaskMenuItems(GtkMenu *menu, Inkscape::UI::View::View *view)
903 {
904 gchar const* data[] = {
905 _("Default"), _("Default interface setup"),
906 _("Custom"), _("Set the custom task"),
907 _("Wide"), _("Setup for widescreen work."),
908 0, 0
909 };
911 GSList *group = 0;
912 int count = 0;
913 gint active = Inkscape::UI::UXManager::getInstance()->getDefaultTask( dynamic_cast<SPDesktop*>(view) );
914 for (gchar const **strs = data; strs[0]; strs += 2, count++)
915 {
916 GtkWidget *item = gtk_radio_menu_item_new_with_label( group, strs[0] );
917 group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item) );
918 if ( count == active )
919 {
920 gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(item), TRUE );
921 }
923 g_object_set_data( G_OBJECT(item), "view", view );
924 g_signal_connect( G_OBJECT(item), "toggled", reinterpret_cast<GCallback>(taskToggled), GINT_TO_POINTER(count) );
925 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), const_cast<gchar*>(strs[1]) );
926 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), 0 );
928 gtk_widget_show( item );
929 gtk_menu_shell_append( GTK_MENU_SHELL(menu), item );
930 }
931 }
934 /** @brief Observer that updates the recent list's max document count */
935 class MaxRecentObserver : public Inkscape::Preferences::Observer {
936 public:
937 MaxRecentObserver(GtkWidget *recent_menu) :
938 Observer("/options/maxrecentdocuments/value"),
939 _rm(recent_menu)
940 {}
941 virtual void notify(Inkscape::Preferences::Entry const &e) {
942 gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(_rm), e.getInt());
943 // hack: the recent menu doesn't repopulate after changing the limit, so we force it
944 g_signal_emit_by_name((gpointer) gtk_recent_manager_get_default(), "changed");
945 }
946 private:
947 GtkWidget *_rm;
948 };
950 /** \brief This function turns XML into a menu
951 \param menus This is the XML that defines the menu
952 \param menu Menu to be added to
953 \param view The View that this menu is being built for
955 This function is realitively simple as it just goes through the XML
956 and parses the individual elements. In the case of a submenu, it
957 just calls itself recursively. Because it is only reasonable to have
958 a couple of submenus, it is unlikely this will go more than two or
959 three times.
961 In the case of an unrecognized verb, a menu item is made to identify
962 the verb that is missing, and display that. The menu item is also made
963 insensitive.
964 */
965 void
966 sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI::View::View *view)
967 {
968 if (menus == NULL) return;
969 if (menu == NULL) return;
970 GSList *group = NULL;
972 for (Inkscape::XML::Node *menu_pntr = menus;
973 menu_pntr != NULL;
974 menu_pntr = menu_pntr->next()) {
975 if (!strcmp(menu_pntr->name(), "submenu")) {
976 GtkWidget *mitem = gtk_menu_item_new_with_mnemonic(_(menu_pntr->attribute("name")));
977 GtkWidget *submenu = gtk_menu_new();
978 sp_ui_build_dyn_menus(menu_pntr->firstChild(), submenu, view);
979 gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), GTK_WIDGET(submenu));
980 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
981 continue;
982 }
983 if (!strcmp(menu_pntr->name(), "verb")) {
984 gchar const *verb_name = menu_pntr->attribute("verb-id");
985 Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name);
987 if (verb != NULL) {
988 if (menu_pntr->attribute("radio") != NULL) {
989 GtkWidget *item = sp_ui_menu_append_item_from_verb (GTK_MENU(menu), verb, view, true, group);
990 group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item));
991 if (menu_pntr->attribute("default") != NULL) {
992 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
993 }
994 if (verb->get_code() != SP_VERB_NONE) {
995 SPAction *action = verb->get_action(view);
996 g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) update_view_menu, (void *) action);
997 }
998 } else {
999 sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view);
1000 group = NULL;
1001 }
1002 } else {
1003 gchar string[120];
1004 g_snprintf(string, 120, _("Verb \"%s\" Unknown"), verb_name);
1005 string[119] = '\0'; /* may not be terminated */
1006 GtkWidget *item = gtk_menu_item_new_with_label(string);
1007 gtk_widget_set_sensitive(item, false);
1008 gtk_widget_show(item);
1009 gtk_menu_append(GTK_MENU(menu), item);
1010 }
1011 continue;
1012 }
1013 if (!strcmp(menu_pntr->name(), "separator")
1014 // This was spelt wrong in the original version
1015 // and so this is for backward compatibility. It can
1016 // probably be dropped after the 0.44 release.
1017 || !strcmp(menu_pntr->name(), "seperator")) {
1018 GtkWidget *item = gtk_separator_menu_item_new();
1019 gtk_widget_show(item);
1020 gtk_menu_append(GTK_MENU(menu), item);
1021 continue;
1022 }
1023 if (!strcmp(menu_pntr->name(), "template-list")) {
1024 sp_menu_append_new_templates(menu, view);
1025 continue;
1026 }
1027 if (!strcmp(menu_pntr->name(), "recent-file-list")) {
1028 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1030 // create recent files menu
1031 int max_recent = prefs->getInt("/options/maxrecentdocuments/value");
1032 GtkWidget *recent_menu = gtk_recent_chooser_menu_new_for_manager(gtk_recent_manager_get_default());
1033 gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(recent_menu), max_recent);
1034 // sort most recently used documents first to preserve previous behavior
1035 gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(recent_menu), GTK_RECENT_SORT_MRU);
1036 g_signal_connect(G_OBJECT(recent_menu), "item-activated", G_CALLBACK(sp_recent_open), (gpointer) NULL);
1038 // add filter to only open files added by Inkscape
1039 GtkRecentFilter *inkscape_only_filter = gtk_recent_filter_new();
1040 gtk_recent_filter_add_application(inkscape_only_filter, g_get_prgname());
1041 gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_menu), inkscape_only_filter);
1043 gtk_recent_chooser_set_show_tips (GTK_RECENT_CHOOSER(recent_menu), TRUE);
1044 gtk_recent_chooser_set_show_not_found (GTK_RECENT_CHOOSER(recent_menu), FALSE);
1046 GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic(_("Open _Recent"));
1047 gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), recent_menu);
1049 gtk_menu_append(GTK_MENU(menu), GTK_WIDGET(recent_item));
1050 // this will just sit and update the list's item count
1051 static MaxRecentObserver *mro = new MaxRecentObserver(recent_menu);
1052 prefs->addObserver(*mro);
1053 continue;
1054 }
1055 if (!strcmp(menu_pntr->name(), "objects-checkboxes")) {
1056 sp_ui_checkboxes_menus(GTK_MENU(menu), view);
1057 continue;
1058 }
1059 if (!strcmp(menu_pntr->name(), "task-checkboxes")) {
1060 addTaskMenuItems(GTK_MENU(menu), view);
1061 continue;
1062 }
1063 }
1064 }
1066 /** \brief Build the main tool bar
1067 \param view View to build the bar for
1069 Currently the main tool bar is built as a dynamic XML menu using
1070 \c sp_ui_build_dyn_menus. This function builds the bar, and then
1071 pass it to get items attached to it.
1072 */
1073 GtkWidget *
1074 sp_ui_main_menubar(Inkscape::UI::View::View *view)
1075 {
1076 GtkWidget *mbar = gtk_menu_bar_new();
1078 #ifdef GDK_WINDOWING_QUARTZ
1079 ige_mac_menu_set_menu_bar(GTK_MENU_SHELL(mbar));
1080 #endif
1082 sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view);
1084 #ifdef GDK_WINDOWING_QUARTZ
1085 return NULL;
1086 #else
1087 return mbar;
1088 #endif
1089 }
1091 static void leave_group(GtkMenuItem *, SPDesktop *desktop) {
1092 desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer()));
1093 }
1095 static void enter_group(GtkMenuItem *mi, SPDesktop *desktop) {
1096 desktop->setCurrentLayer(reinterpret_cast<SPObject *>(g_object_get_data(G_OBJECT(mi), "group")));
1097 sp_desktop_selection(desktop)->clear();
1098 }
1100 GtkWidget *
1101 sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item)
1102 {
1103 GtkWidget *m;
1104 SPDesktop *dt;
1106 dt = static_cast<SPDesktop*>(view);
1108 m = gtk_menu_new();
1110 /* Undo and Redo */
1111 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_UNDO), view);
1112 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_REDO), view);
1114 /* Separator */
1115 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1117 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_CUT), view);
1118 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_COPY), view);
1119 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_PASTE), view);
1121 /* Separator */
1122 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1124 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DUPLICATE), view);
1125 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DELETE), view);
1127 /* Item menu */
1128 if (item) {
1129 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1130 sp_object_menu((SPObject *) item, dt, GTK_MENU(m));
1131 }
1133 /* layer menu */
1134 SPGroup *group=NULL;
1135 if (item) {
1136 if (SP_IS_GROUP(item)) {
1137 group = SP_GROUP(item);
1138 } else if ( item != dt->currentRoot() && SP_IS_GROUP(SP_OBJECT_PARENT(item)) ) {
1139 group = SP_GROUP(SP_OBJECT_PARENT(item));
1140 }
1141 }
1143 if (( group && group != dt->currentLayer() ) ||
1144 ( dt->currentLayer() != dt->currentRoot() && SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) ) {
1145 /* Separator */
1146 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1147 }
1149 if ( group && group != dt->currentLayer() ) {
1150 /* TRANSLATORS: #%s is the id of the group e.g. <g id="#g7">, not a number. */
1151 gchar *label=g_strdup_printf(_("Enter group #%s"), group->getId());
1152 GtkWidget *w = gtk_menu_item_new_with_label(label);
1153 g_free(label);
1154 g_object_set_data(G_OBJECT(w), "group", group);
1155 g_signal_connect(G_OBJECT(w), "activate", GCallback(enter_group), dt);
1156 gtk_widget_show(w);
1157 gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1158 }
1160 if ( dt->currentLayer() != dt->currentRoot() ) {
1161 if ( SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) {
1162 GtkWidget *w = gtk_menu_item_new_with_label(_("Go to parent"));
1163 g_signal_connect(G_OBJECT(w), "activate", GCallback(leave_group), dt);
1164 gtk_widget_show(w);
1165 gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1167 }
1168 }
1170 return m;
1171 }
1173 /* Drag and Drop */
1174 void
1175 sp_ui_drag_data_received(GtkWidget *widget,
1176 GdkDragContext *drag_context,
1177 gint x, gint y,
1178 GtkSelectionData *data,
1179 guint info,
1180 guint /*event_time*/,
1181 gpointer /*user_data*/)
1182 {
1183 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1184 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1186 switch (info) {
1187 #if ENABLE_MAGIC_COLORS
1188 case APP_X_INKY_COLOR:
1189 {
1190 int destX = 0;
1191 int destY = 0;
1192 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1193 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1195 SPItem *item = desktop->item_at_point( where, true );
1196 if ( item )
1197 {
1198 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1200 if ( data->length >= 8 ) {
1201 cmsHPROFILE srgbProf = cmsCreate_sRGBProfile();
1203 gchar c[64] = {0};
1204 // Careful about endian issues.
1205 guint16* dataVals = (guint16*)data->data;
1206 sp_svg_write_color( c, sizeof(c),
1207 SP_RGBA32_U_COMPOSE(
1208 0x0ff & (dataVals[0] >> 8),
1209 0x0ff & (dataVals[1] >> 8),
1210 0x0ff & (dataVals[2] >> 8),
1211 0xff // can't have transparency in the color itself
1212 //0x0ff & (data->data[3] >> 8),
1213 ));
1214 SPCSSAttr *css = sp_repr_css_attr_new();
1215 bool updatePerformed = false;
1217 if ( data->length > 14 ) {
1218 int flags = dataVals[4];
1220 // piggie-backed palette entry info
1221 int index = dataVals[5];
1222 Glib::ustring palName;
1223 for ( int i = 0; i < dataVals[6]; i++ ) {
1224 palName += (gunichar)dataVals[7+i];
1225 }
1227 // Now hook in a magic tag of some sort.
1228 if ( !palName.empty() && (flags & 1) ) {
1229 gchar* str = g_strdup_printf("%d|", index);
1230 palName.insert( 0, str );
1231 g_free(str);
1232 str = 0;
1234 sp_object_setAttribute( SP_OBJECT(item),
1235 fillnotstroke ? "inkscape:x-fill-tag":"inkscape:x-stroke-tag",
1236 palName.c_str(),
1237 false );
1238 item->updateRepr();
1240 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1241 updatePerformed = true;
1242 }
1243 }
1245 if ( !updatePerformed ) {
1246 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1247 }
1249 sp_desktop_apply_css_recursive( item, css, true );
1250 item->updateRepr();
1252 sp_document_done( doc , SP_VERB_NONE,
1253 _("Drop color"));
1255 if ( srgbProf ) {
1256 cmsCloseProfile( srgbProf );
1257 }
1258 }
1259 }
1260 }
1261 break;
1262 #endif // ENABLE_MAGIC_COLORS
1264 case APP_X_COLOR:
1265 {
1266 int destX = 0;
1267 int destY = 0;
1268 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1269 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1270 Geom::Point const button_dt(desktop->w2d(where));
1271 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1273 if ( data->length == 8 ) {
1274 gchar colorspec[64] = {0};
1275 // Careful about endian issues.
1276 guint16* dataVals = (guint16*)data->data;
1277 sp_svg_write_color( colorspec, sizeof(colorspec),
1278 SP_RGBA32_U_COMPOSE(
1279 0x0ff & (dataVals[0] >> 8),
1280 0x0ff & (dataVals[1] >> 8),
1281 0x0ff & (dataVals[2] >> 8),
1282 0xff // can't have transparency in the color itself
1283 //0x0ff & (data->data[3] >> 8),
1284 ));
1286 SPItem *item = desktop->item_at_point( where, true );
1288 bool consumed = false;
1289 if (desktop->event_context && desktop->event_context->get_drag()) {
1290 consumed = desktop->event_context->get_drag()->dropColor(item, colorspec, button_dt);
1291 if (consumed) {
1292 sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1293 desktop->event_context->get_drag()->updateDraggers();
1294 }
1295 }
1297 //if (!consumed && tools_active(desktop, TOOLS_TEXT)) {
1298 // consumed = sp_text_context_drop_color(c, button_doc);
1299 // if (consumed) {
1300 // sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient stop"));
1301 // }
1302 //}
1304 if (!consumed && item) {
1305 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1306 if (fillnotstroke &&
1307 (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1308 Path *livarot_path = Path_for_item(item, true, true);
1309 livarot_path->ConvertWithBackData(0.04);
1311 boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1312 if (position) {
1313 Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1314 Geom::Point delta = nearest - button_doc;
1315 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1316 delta = desktop->d2w(delta);
1317 double stroke_tolerance =
1318 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1319 desktop->current_zoom() *
1320 SP_OBJECT_STYLE (item)->stroke_width.computed *
1321 to_2geom(item->i2d_affine()).descrim() * 0.5
1322 : 0.0)
1323 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1325 if (Geom::L2 (delta) < stroke_tolerance) {
1326 fillnotstroke = false;
1327 }
1328 }
1329 delete livarot_path;
1330 }
1332 SPCSSAttr *css = sp_repr_css_attr_new();
1333 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec );
1335 sp_desktop_apply_css_recursive( item, css, true );
1336 item->updateRepr();
1338 sp_document_done( doc , SP_VERB_NONE,
1339 _("Drop color"));
1340 }
1341 }
1342 }
1343 break;
1345 case APP_OSWB_COLOR:
1346 {
1347 bool worked = false;
1348 Glib::ustring colorspec;
1349 if ( data->format == 8 ) {
1350 ege::PaintDef color;
1351 worked = color.fromMIMEData("application/x-oswb-color",
1352 reinterpret_cast<char*>(data->data),
1353 data->length,
1354 data->format);
1355 if ( worked ) {
1356 if ( color.getType() == ege::PaintDef::CLEAR ) {
1357 colorspec = ""; // TODO check if this is sufficient
1358 } else if ( color.getType() == ege::PaintDef::NONE ) {
1359 colorspec = "none";
1360 } else {
1361 unsigned int r = color.getR();
1362 unsigned int g = color.getG();
1363 unsigned int b = color.getB();
1365 SPGradient* matches = 0;
1366 const GSList *gradients = doc->get_resource_list("gradient");
1367 for (const GSList *item = gradients; item; item = item->next) {
1368 SPGradient* grad = SP_GRADIENT(item->data);
1369 if ( color.descr == grad->getId() ) {
1370 if ( grad->hasStops() ) {
1371 matches = grad;
1372 break;
1373 }
1374 }
1375 }
1376 if (matches) {
1377 colorspec = "url(#";
1378 colorspec += matches->getId();
1379 colorspec += ")";
1380 } else {
1381 gchar* tmp = g_strdup_printf("#%02x%02x%02x", r, g, b);
1382 colorspec = tmp;
1383 g_free(tmp);
1384 }
1385 }
1386 }
1387 }
1388 if ( worked ) {
1389 int destX = 0;
1390 int destY = 0;
1391 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1392 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1393 Geom::Point const button_dt(desktop->w2d(where));
1394 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1396 SPItem *item = desktop->item_at_point( where, true );
1398 bool consumed = false;
1399 if (desktop->event_context && desktop->event_context->get_drag()) {
1400 consumed = desktop->event_context->get_drag()->dropColor(item, colorspec.c_str(), button_dt);
1401 if (consumed) {
1402 sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1403 desktop->event_context->get_drag()->updateDraggers();
1404 }
1405 }
1407 if (!consumed && item) {
1408 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1409 if (fillnotstroke &&
1410 (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1411 Path *livarot_path = Path_for_item(item, true, true);
1412 livarot_path->ConvertWithBackData(0.04);
1414 boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1415 if (position) {
1416 Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1417 Geom::Point delta = nearest - button_doc;
1418 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1419 delta = desktop->d2w(delta);
1420 double stroke_tolerance =
1421 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1422 desktop->current_zoom() *
1423 SP_OBJECT_STYLE (item)->stroke_width.computed *
1424 to_2geom(item->i2d_affine()).descrim() * 0.5
1425 : 0.0)
1426 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1428 if (Geom::L2 (delta) < stroke_tolerance) {
1429 fillnotstroke = false;
1430 }
1431 }
1432 delete livarot_path;
1433 }
1435 SPCSSAttr *css = sp_repr_css_attr_new();
1436 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec.c_str() );
1438 sp_desktop_apply_css_recursive( item, css, true );
1439 item->updateRepr();
1441 sp_document_done( doc , SP_VERB_NONE,
1442 _("Drop color"));
1443 }
1444 }
1445 }
1446 break;
1448 case SVG_DATA:
1449 case SVG_XML_DATA: {
1450 gchar *svgdata = (gchar *)data->data;
1452 Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI);
1454 if (rnewdoc == NULL) {
1455 sp_ui_error_dialog(_("Could not parse SVG data"));
1456 return;
1457 }
1459 Inkscape::XML::Node *repr = rnewdoc->root();
1460 gchar const *style = repr->attribute("style");
1462 Inkscape::XML::Node *newgroup = rnewdoc->createElement("svg:g");
1463 newgroup->setAttribute("style", style);
1465 Inkscape::XML::Document * xml_doc = sp_document_repr_doc(doc);
1466 for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) {
1467 Inkscape::XML::Node *newchild = child->duplicate(xml_doc);
1468 newgroup->appendChild(newchild);
1469 }
1471 Inkscape::GC::release(rnewdoc);
1473 // Add it to the current layer
1475 // Greg's edits to add intelligent positioning of svg drops
1476 SPObject *new_obj = NULL;
1477 new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
1479 Inkscape::Selection *selection = sp_desktop_selection(desktop);
1480 selection->set(SP_ITEM(new_obj));
1482 // move to mouse pointer
1483 {
1484 sp_desktop_document(desktop)->ensure_up_to_date();
1485 Geom::OptRect sel_bbox = selection->bounds();
1486 if (sel_bbox) {
1487 Geom::Point m( desktop->point() - sel_bbox->midpoint() );
1488 sp_selection_move_relative(selection, m, false);
1489 }
1490 }
1492 Inkscape::GC::release(newgroup);
1493 sp_document_done(doc, SP_VERB_NONE,
1494 _("Drop SVG"));
1495 break;
1496 }
1498 case URI_LIST: {
1499 gchar *uri = (gchar *)data->data;
1500 sp_ui_import_files(uri);
1501 break;
1502 }
1504 case PNG_DATA:
1505 case JPEG_DATA:
1506 case IMAGE_DATA: {
1507 const char *mime = (info == JPEG_DATA ? "image/jpeg" : "image/png");
1509 Inkscape::Extension::DB::InputList o;
1510 Inkscape::Extension::db.get_input_list(o);
1511 Inkscape::Extension::DB::InputList::const_iterator i = o.begin();
1512 while (i != o.end() && strcmp( (*i)->get_mimetype(), mime ) != 0) {
1513 ++i;
1514 }
1515 Inkscape::Extension::Extension *ext = *i;
1516 bool save = (strcmp(ext->get_param_optiongroup("link"), "embed") == 0);
1517 ext->set_param_optiongroup("link", "embed");
1518 ext->set_gui(false);
1520 gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-dnd-import", NULL );
1521 g_file_set_contents(filename, reinterpret_cast<gchar const *>(data->data), data->length, NULL);
1522 file_import(doc, filename, ext);
1523 g_free(filename);
1525 ext->set_param_optiongroup("link", save ? "embed" : "link");
1526 ext->set_gui(true);
1527 sp_document_done( doc , SP_VERB_NONE,
1528 _("Drop bitmap image"));
1529 break;
1530 }
1531 }
1532 }
1534 #include "gradient-context.h"
1536 void sp_ui_drag_motion( GtkWidget */*widget*/,
1537 GdkDragContext */*drag_context*/,
1538 gint /*x*/, gint /*y*/,
1539 GtkSelectionData */*data*/,
1540 guint /*info*/,
1541 guint /*event_time*/,
1542 gpointer /*user_data*/)
1543 {
1544 // SPDocument *doc = SP_ACTIVE_DOCUMENT;
1545 // SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1548 // g_message("drag-n-drop motion (%4d, %4d) at %d", x, y, event_time);
1549 }
1551 static void sp_ui_drag_leave( GtkWidget */*widget*/,
1552 GdkDragContext */*drag_context*/,
1553 guint /*event_time*/,
1554 gpointer /*user_data*/ )
1555 {
1556 // g_message("drag-n-drop leave at %d", event_time);
1557 }
1559 static void
1560 sp_ui_import_files(gchar *buffer)
1561 {
1562 GList *list = gnome_uri_list_extract_filenames(buffer);
1563 if (!list)
1564 return;
1565 g_list_foreach(list, sp_ui_import_one_file_with_check, NULL);
1566 g_list_foreach(list, (GFunc) g_free, NULL);
1567 g_list_free(list);
1568 }
1570 static void
1571 sp_ui_import_one_file_with_check(gpointer filename, gpointer /*unused*/)
1572 {
1573 if (filename) {
1574 if (strlen((char const *)filename) > 2)
1575 sp_ui_import_one_file((char const *)filename);
1576 }
1577 }
1579 static void
1580 sp_ui_import_one_file(char const *filename)
1581 {
1582 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1583 if (!doc) return;
1585 if (filename == NULL) return;
1587 // Pass off to common implementation
1588 // TODO might need to get the proper type of Inkscape::Extension::Extension
1589 file_import( doc, filename, NULL );
1590 }
1592 void
1593 sp_ui_error_dialog(gchar const *message)
1594 {
1595 GtkWidget *dlg;
1596 gchar *safeMsg = Inkscape::IO::sanitizeString(message);
1598 dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
1599 GTK_BUTTONS_CLOSE, "%s", safeMsg);
1600 sp_transientize(dlg);
1601 gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE);
1602 gtk_dialog_run(GTK_DIALOG(dlg));
1603 gtk_widget_destroy(dlg);
1604 g_free(safeMsg);
1605 }
1607 bool
1608 sp_ui_overwrite_file(gchar const *filename)
1609 {
1610 bool return_value = FALSE;
1612 if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) {
1613 Gtk::Window *window = SP_ACTIVE_DESKTOP->getToplevel();
1614 gchar* baseName = g_path_get_basename( filename );
1615 gchar* dirName = g_path_get_dirname( filename );
1616 GtkWidget* dialog = gtk_message_dialog_new_with_markup( window->gobj(),
1617 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
1618 GTK_MESSAGE_QUESTION,
1619 GTK_BUTTONS_NONE,
1620 _( "<span weight=\"bold\" size=\"larger\">A file named \"%s\" already exists. Do you want to replace it?</span>\n\n"
1621 "The file already exists in \"%s\". Replacing it will overwrite its contents." ),
1622 baseName,
1623 dirName
1624 );
1625 gtk_dialog_add_buttons( GTK_DIALOG(dialog),
1626 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1627 _("Replace"), GTK_RESPONSE_YES,
1628 NULL );
1629 gtk_dialog_set_default_response( GTK_DIALOG(dialog), GTK_RESPONSE_YES );
1631 if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_YES ) {
1632 return_value = TRUE;
1633 } else {
1634 return_value = FALSE;
1635 }
1636 gtk_widget_destroy(dialog);
1637 g_free( baseName );
1638 g_free( dirName );
1639 } else {
1640 return_value = TRUE;
1641 }
1643 return return_value;
1644 }
1646 static void
1647 sp_ui_menu_item_set_sensitive(SPAction */*action*/, unsigned int sensitive, void *data)
1648 {
1649 return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
1650 }
1652 static void
1653 sp_ui_menu_item_set_name(SPAction */*action*/, Glib::ustring name, void *data)
1654 {
1655 void *child = GTK_BIN (data)->child;
1656 //child is either
1657 //- a GtkHBox, whose first child is a label displaying name if the menu
1658 //item has an accel key
1659 //- a GtkLabel if the menu has no accel key
1660 if (GTK_IS_LABEL(child)) {
1661 gtk_label_set_markup_with_mnemonic(GTK_LABEL (child), name.c_str());
1662 } else if (GTK_IS_HBOX(child)) {
1663 gtk_label_set_markup_with_mnemonic(
1664 GTK_LABEL (gtk_container_get_children(GTK_CONTAINER (child))->data),
1665 name.c_str());
1666 }//else sp_ui_menu_append_item_from_verb has been modified and can set
1667 //a menu item in yet another way...
1668 }
1671 /*
1672 Local Variables:
1673 mode:c++
1674 c-file-style:"stroustrup"
1675 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1676 indent-tabs-mode:nil
1677 fill-column:99
1678 End:
1679 */
1680 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :