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";
681 pref_path += pref;
683 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
684 bool ison = prefs->getBool(pref_path + "/state", true);
686 g_signal_handlers_block_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
687 gtk_check_menu_item_set_active(menuitem, ison);
688 g_signal_handlers_unblock_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
690 return FALSE;
691 }
693 static void taskToggled(GtkCheckMenuItem *menuitem, gpointer userData)
694 {
695 if ( gtk_check_menu_item_get_active(menuitem) ) {
696 gint taskNum = GPOINTER_TO_INT(userData);
697 taskNum = (taskNum < 0) ? 0 : (taskNum > 2) ? 2 : taskNum;
699 Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
701 // note: this will change once more options are in the task set support:
702 Inkscape::UI::UXManager::getInstance()->setTask( dynamic_cast<SPDesktop*>(view), taskNum );
703 }
704 }
707 /**
708 * \brief Callback function to update the status of the radio buttons in the View -> Display mode menu (Normal, No Filters, Outline)
709 */
711 static gboolean
712 update_view_menu(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_data)
713 {
714 SPAction *action = (SPAction *) user_data;
715 g_assert(action->id != NULL);
717 Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(widget), "view");
718 SPDesktop *dt = static_cast<SPDesktop*>(view);
719 Inkscape::RenderMode mode = dt->getMode();
721 bool new_state = false;
722 if (!strcmp(action->id, "ViewModeNormal")) {
723 new_state = mode == Inkscape::RENDERMODE_NORMAL;
724 } else if (!strcmp(action->id, "ViewModeNoFilters")) {
725 new_state = mode == Inkscape::RENDERMODE_NO_FILTERS;
726 } else if (!strcmp(action->id, "ViewModeOutline")) {
727 new_state = mode == Inkscape::RENDERMODE_OUTLINE;
728 } else if (!strcmp(action->id, "ViewModePrintColorsPreview")) {
729 new_state = mode == Inkscape::RENDERMODE_PRINT_COLORS_PREVIEW;
730 } else {
731 g_warning("update_view_menu does not handle this verb");
732 }
734 if (new_state) { //only one of the radio buttons has to be activated; the others will automatically be deactivated
735 if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) {
736 // When the GtkMenuItem version of the "activate" signal has been emitted by a GtkRadioMenuItem, there is a second
737 // emission as the most recently active item is toggled to inactive. This is dealt with before the original signal is handled.
738 // This emission however should not invoke any actions, hence we block it here:
739 temporarily_block_actions = true;
740 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (widget), TRUE);
741 temporarily_block_actions = false;
742 }
743 }
745 return FALSE;
746 }
748 void
749 sp_ui_menu_append_check_item_from_verb(GtkMenu *menu, Inkscape::UI::View::View *view, gchar const *label, gchar const *tip, gchar const *pref,
750 void (*callback_toggle)(GtkCheckMenuItem *, gpointer user_data),
751 gboolean (*callback_update)(GtkWidget *widget, GdkEventExpose *event, gpointer user_data),
752 Inkscape::Verb *verb)
753 {
754 unsigned int shortcut = (verb) ? sp_shortcut_get_primary(verb) : 0;
755 SPAction *action = (verb) ? verb->get_action(view) : 0;
756 GtkWidget *item = gtk_check_menu_item_new();
758 if (verb && shortcut) {
759 gchar c[256];
760 sp_ui_shortcut_string(shortcut, c);
762 GtkWidget *hb = gtk_hbox_new(FALSE, 16);
764 {
765 GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
766 gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
767 gtk_box_pack_start((GtkBox *) hb, l, TRUE, TRUE, 0);
768 }
770 {
771 GtkWidget *l = gtk_label_new(c);
772 gtk_misc_set_alignment((GtkMisc *) l, 1.0, 0.5);
773 gtk_box_pack_end((GtkBox *) hb, l, FALSE, FALSE, 0);
774 }
776 gtk_widget_show_all(hb);
778 gtk_container_add((GtkContainer *) item, hb);
779 } else {
780 GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
781 gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
782 gtk_container_add((GtkContainer *) item, l);
783 }
784 #if 0
785 nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
786 if (!action->sensitive) {
787 gtk_widget_set_sensitive(item, FALSE);
788 }
789 #endif
790 gtk_widget_show(item);
792 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
794 g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
796 g_signal_connect( G_OBJECT(item), "toggled", (GCallback) callback_toggle, (void *) pref);
797 g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) callback_update, (void *) pref);
799 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) (action ? action->tip : tip));
800 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
801 }
803 static void
804 sp_recent_open(GtkRecentChooser *recent_menu, gpointer /*user_data*/)
805 {
806 // dealing with the bizarre filename convention in Inkscape for now
807 gchar *uri = gtk_recent_chooser_get_current_uri(GTK_RECENT_CHOOSER(recent_menu));
808 gchar *local_fn = g_filename_from_uri(uri, NULL, NULL);
809 gchar *utf8_fn = g_filename_to_utf8(local_fn, -1, NULL, NULL, NULL);
810 sp_file_open(utf8_fn, NULL);
811 g_free(utf8_fn);
812 g_free(local_fn);
813 g_free(uri);
814 }
816 static void
817 sp_file_new_from_template(GtkWidget */*widget*/, gchar const *uri)
818 {
819 sp_file_new(uri);
820 }
822 void
823 sp_menu_append_new_templates(GtkWidget *menu, Inkscape::UI::View::View *view)
824 {
825 std::list<gchar *> sources;
826 sources.push_back( profile_path("templates") ); // first try user's local dir
827 sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir
829 // Use this loop to iterate through a list of possible document locations.
830 while (!sources.empty()) {
831 gchar *dirname = sources.front();
833 if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) {
834 GError *err = 0;
835 GDir *dir = g_dir_open(dirname, 0, &err);
837 if (dir) {
838 for (gchar const *file = g_dir_read_name(dir); file != NULL; file = g_dir_read_name(dir)) {
839 if (!g_str_has_suffix(file, ".svg") && !g_str_has_suffix(file, ".svgz"))
840 continue; // skip non-svg files
842 gchar *basename = g_path_get_basename(file);
843 if (g_str_has_suffix(basename, ".svg") && g_str_has_prefix(basename, "default."))
844 continue; // skip default.*.svg (i.e. default.svg and translations) - it's in the menu already
846 gchar const *filepath = g_build_filename(dirname, file, NULL);
847 gchar *dupfile = g_strndup(file, strlen(file) - 4);
848 gchar *filename = g_filename_to_utf8(dupfile, -1, NULL, NULL, NULL);
849 g_free(dupfile);
850 GtkWidget *item = gtk_menu_item_new_with_label(filename);
851 g_free(filename);
853 gtk_widget_show(item);
854 // how does "filepath" ever get freed?
855 g_signal_connect(G_OBJECT(item),
856 "activate",
857 G_CALLBACK(sp_file_new_from_template),
858 (gpointer) filepath);
860 if (view) {
861 // set null tip for now; later use a description from the template file
862 g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
863 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) NULL );
864 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
865 }
867 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
868 }
869 g_dir_close(dir);
870 }
871 }
873 // toss the dirname
874 g_free(dirname);
875 sources.pop_front();
876 }
877 }
879 void
880 sp_ui_checkboxes_menus(GtkMenu *m, Inkscape::UI::View::View *view)
881 {
882 //sp_ui_menu_append_check_item_from_verb(m, view, _("_Menu"), _("Show or hide the menu bar"), "menu",
883 // checkitem_toggled, checkitem_update, 0);
884 sp_ui_menu_append_check_item_from_verb(m, view, _("Commands Bar"), _("Show or hide the Commands bar (under the menu)"), "commands",
885 checkitem_toggled, checkitem_update, 0);
886 sp_ui_menu_append_check_item_from_verb(m, view, _("Snap Controls Bar"), _("Show or hide the snapping controls"), "snaptoolbox",
887 checkitem_toggled, checkitem_update, 0);
888 sp_ui_menu_append_check_item_from_verb(m, view, _("Tool Controls Bar"), _("Show or hide the Tool Controls bar"), "toppanel",
889 checkitem_toggled, checkitem_update, 0);
890 sp_ui_menu_append_check_item_from_verb(m, view, _("_Toolbox"), _("Show or hide the main toolbox (on the left)"), "toolbox",
891 checkitem_toggled, checkitem_update, 0);
892 sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "rulers",
893 checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_RULERS));
894 sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "scrollbars",
895 checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_SCROLLBARS));
896 sp_ui_menu_append_check_item_from_verb(m, view, _("_Palette"), _("Show or hide the color palette"), "panels",
897 checkitem_toggled, checkitem_update, 0);
898 sp_ui_menu_append_check_item_from_verb(m, view, _("_Statusbar"), _("Show or hide the statusbar (at the bottom of the window)"), "statusbar",
899 checkitem_toggled, checkitem_update, 0);
900 }
903 void addTaskMenuItems(GtkMenu *menu, Inkscape::UI::View::View *view)
904 {
905 gchar const* data[] = {
906 _("Default"), _("Default interface setup"),
907 _("Custom"), _("Set the custom task"),
908 _("Wide"), _("Setup for widescreen work."),
909 0, 0
910 };
912 GSList *group = 0;
913 int count = 0;
914 gint active = Inkscape::UI::UXManager::getInstance()->getDefaultTask( dynamic_cast<SPDesktop*>(view) );
915 for (gchar const **strs = data; strs[0]; strs += 2, count++)
916 {
917 GtkWidget *item = gtk_radio_menu_item_new_with_label( group, strs[0] );
918 group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item) );
919 if ( count == active )
920 {
921 gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(item), TRUE );
922 }
924 g_object_set_data( G_OBJECT(item), "view", view );
925 g_signal_connect( G_OBJECT(item), "toggled", reinterpret_cast<GCallback>(taskToggled), GINT_TO_POINTER(count) );
926 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), const_cast<gchar*>(strs[1]) );
927 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), 0 );
929 gtk_widget_show( item );
930 gtk_menu_shell_append( GTK_MENU_SHELL(menu), item );
931 }
932 }
935 /** @brief Observer that updates the recent list's max document count */
936 class MaxRecentObserver : public Inkscape::Preferences::Observer {
937 public:
938 MaxRecentObserver(GtkWidget *recent_menu) :
939 Observer("/options/maxrecentdocuments/value"),
940 _rm(recent_menu)
941 {}
942 virtual void notify(Inkscape::Preferences::Entry const &e) {
943 gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(_rm), e.getInt());
944 // hack: the recent menu doesn't repopulate after changing the limit, so we force it
945 g_signal_emit_by_name((gpointer) gtk_recent_manager_get_default(), "changed");
946 }
947 private:
948 GtkWidget *_rm;
949 };
951 /** \brief This function turns XML into a menu
952 \param menus This is the XML that defines the menu
953 \param menu Menu to be added to
954 \param view The View that this menu is being built for
956 This function is realitively simple as it just goes through the XML
957 and parses the individual elements. In the case of a submenu, it
958 just calls itself recursively. Because it is only reasonable to have
959 a couple of submenus, it is unlikely this will go more than two or
960 three times.
962 In the case of an unrecognized verb, a menu item is made to identify
963 the verb that is missing, and display that. The menu item is also made
964 insensitive.
965 */
966 void
967 sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI::View::View *view)
968 {
969 if (menus == NULL) return;
970 if (menu == NULL) return;
971 GSList *group = NULL;
973 for (Inkscape::XML::Node *menu_pntr = menus;
974 menu_pntr != NULL;
975 menu_pntr = menu_pntr->next()) {
976 if (!strcmp(menu_pntr->name(), "submenu")) {
977 GtkWidget *mitem = gtk_menu_item_new_with_mnemonic(_(menu_pntr->attribute("name")));
978 GtkWidget *submenu = gtk_menu_new();
979 sp_ui_build_dyn_menus(menu_pntr->firstChild(), submenu, view);
980 gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), GTK_WIDGET(submenu));
981 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
982 continue;
983 }
984 if (!strcmp(menu_pntr->name(), "verb")) {
985 gchar const *verb_name = menu_pntr->attribute("verb-id");
986 Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name);
988 if (verb != NULL) {
989 if (menu_pntr->attribute("radio") != NULL) {
990 GtkWidget *item = sp_ui_menu_append_item_from_verb (GTK_MENU(menu), verb, view, true, group);
991 group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item));
992 if (menu_pntr->attribute("default") != NULL) {
993 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
994 }
995 if (verb->get_code() != SP_VERB_NONE) {
996 SPAction *action = verb->get_action(view);
997 g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) update_view_menu, (void *) action);
998 }
999 } else {
1000 sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view);
1001 group = NULL;
1002 }
1003 } else {
1004 gchar string[120];
1005 g_snprintf(string, 120, _("Verb \"%s\" Unknown"), verb_name);
1006 string[119] = '\0'; /* may not be terminated */
1007 GtkWidget *item = gtk_menu_item_new_with_label(string);
1008 gtk_widget_set_sensitive(item, false);
1009 gtk_widget_show(item);
1010 gtk_menu_append(GTK_MENU(menu), item);
1011 }
1012 continue;
1013 }
1014 if (!strcmp(menu_pntr->name(), "separator")
1015 // This was spelt wrong in the original version
1016 // and so this is for backward compatibility. It can
1017 // probably be dropped after the 0.44 release.
1018 || !strcmp(menu_pntr->name(), "seperator")) {
1019 GtkWidget *item = gtk_separator_menu_item_new();
1020 gtk_widget_show(item);
1021 gtk_menu_append(GTK_MENU(menu), item);
1022 continue;
1023 }
1024 if (!strcmp(menu_pntr->name(), "template-list")) {
1025 sp_menu_append_new_templates(menu, view);
1026 continue;
1027 }
1028 if (!strcmp(menu_pntr->name(), "recent-file-list")) {
1029 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1031 // create recent files menu
1032 int max_recent = prefs->getInt("/options/maxrecentdocuments/value");
1033 GtkWidget *recent_menu = gtk_recent_chooser_menu_new_for_manager(gtk_recent_manager_get_default());
1034 gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(recent_menu), max_recent);
1035 // sort most recently used documents first to preserve previous behavior
1036 gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(recent_menu), GTK_RECENT_SORT_MRU);
1037 g_signal_connect(G_OBJECT(recent_menu), "item-activated", G_CALLBACK(sp_recent_open), (gpointer) NULL);
1039 // add filter to only open files added by Inkscape
1040 GtkRecentFilter *inkscape_only_filter = gtk_recent_filter_new();
1041 gtk_recent_filter_add_application(inkscape_only_filter, g_get_prgname());
1042 gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_menu), inkscape_only_filter);
1044 gtk_recent_chooser_set_show_tips (GTK_RECENT_CHOOSER(recent_menu), TRUE);
1045 gtk_recent_chooser_set_show_not_found (GTK_RECENT_CHOOSER(recent_menu), FALSE);
1047 GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic(_("Open _Recent"));
1048 gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), recent_menu);
1050 gtk_menu_append(GTK_MENU(menu), GTK_WIDGET(recent_item));
1051 // this will just sit and update the list's item count
1052 static MaxRecentObserver *mro = new MaxRecentObserver(recent_menu);
1053 prefs->addObserver(*mro);
1054 continue;
1055 }
1056 if (!strcmp(menu_pntr->name(), "objects-checkboxes")) {
1057 sp_ui_checkboxes_menus(GTK_MENU(menu), view);
1058 continue;
1059 }
1060 if (!strcmp(menu_pntr->name(), "task-checkboxes")) {
1061 addTaskMenuItems(GTK_MENU(menu), view);
1062 continue;
1063 }
1064 }
1065 }
1067 /** \brief Build the main tool bar
1068 \param view View to build the bar for
1070 Currently the main tool bar is built as a dynamic XML menu using
1071 \c sp_ui_build_dyn_menus. This function builds the bar, and then
1072 pass it to get items attached to it.
1073 */
1074 GtkWidget *
1075 sp_ui_main_menubar(Inkscape::UI::View::View *view)
1076 {
1077 GtkWidget *mbar = gtk_menu_bar_new();
1079 #ifdef GDK_WINDOWING_QUARTZ
1080 ige_mac_menu_set_menu_bar(GTK_MENU_SHELL(mbar));
1081 #endif
1083 sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view);
1085 #ifdef GDK_WINDOWING_QUARTZ
1086 return NULL;
1087 #else
1088 return mbar;
1089 #endif
1090 }
1092 static void leave_group(GtkMenuItem *, SPDesktop *desktop) {
1093 desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer()));
1094 }
1096 static void enter_group(GtkMenuItem *mi, SPDesktop *desktop) {
1097 desktop->setCurrentLayer(reinterpret_cast<SPObject *>(g_object_get_data(G_OBJECT(mi), "group")));
1098 sp_desktop_selection(desktop)->clear();
1099 }
1101 GtkWidget *
1102 sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item)
1103 {
1104 GtkWidget *m;
1105 SPDesktop *dt;
1107 dt = static_cast<SPDesktop*>(view);
1109 m = gtk_menu_new();
1111 /* Undo and Redo */
1112 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_UNDO), view);
1113 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_REDO), view);
1115 /* Separator */
1116 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1118 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_CUT), view);
1119 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_COPY), view);
1120 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_PASTE), view);
1122 /* Separator */
1123 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1125 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DUPLICATE), view);
1126 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DELETE), view);
1128 /* Item menu */
1129 if (item) {
1130 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1131 sp_object_menu((SPObject *) item, dt, GTK_MENU(m));
1132 }
1134 /* layer menu */
1135 SPGroup *group=NULL;
1136 if (item) {
1137 if (SP_IS_GROUP(item)) {
1138 group = SP_GROUP(item);
1139 } else if ( item != dt->currentRoot() && SP_IS_GROUP(SP_OBJECT_PARENT(item)) ) {
1140 group = SP_GROUP(SP_OBJECT_PARENT(item));
1141 }
1142 }
1144 if (( group && group != dt->currentLayer() ) ||
1145 ( dt->currentLayer() != dt->currentRoot() && SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) ) {
1146 /* Separator */
1147 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1148 }
1150 if ( group && group != dt->currentLayer() ) {
1151 /* TRANSLATORS: #%s is the id of the group e.g. <g id="#g7">, not a number. */
1152 gchar *label=g_strdup_printf(_("Enter group #%s"), group->getId());
1153 GtkWidget *w = gtk_menu_item_new_with_label(label);
1154 g_free(label);
1155 g_object_set_data(G_OBJECT(w), "group", group);
1156 g_signal_connect(G_OBJECT(w), "activate", GCallback(enter_group), dt);
1157 gtk_widget_show(w);
1158 gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1159 }
1161 if ( dt->currentLayer() != dt->currentRoot() ) {
1162 if ( SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) {
1163 GtkWidget *w = gtk_menu_item_new_with_label(_("Go to parent"));
1164 g_signal_connect(G_OBJECT(w), "activate", GCallback(leave_group), dt);
1165 gtk_widget_show(w);
1166 gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1168 }
1169 }
1171 return m;
1172 }
1174 /* Drag and Drop */
1175 void
1176 sp_ui_drag_data_received(GtkWidget *widget,
1177 GdkDragContext *drag_context,
1178 gint x, gint y,
1179 GtkSelectionData *data,
1180 guint info,
1181 guint /*event_time*/,
1182 gpointer /*user_data*/)
1183 {
1184 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1185 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1187 switch (info) {
1188 #if ENABLE_MAGIC_COLORS
1189 case APP_X_INKY_COLOR:
1190 {
1191 int destX = 0;
1192 int destY = 0;
1193 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1194 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1196 SPItem *item = desktop->item_at_point( where, true );
1197 if ( item )
1198 {
1199 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1201 if ( data->length >= 8 ) {
1202 cmsHPROFILE srgbProf = cmsCreate_sRGBProfile();
1204 gchar c[64] = {0};
1205 // Careful about endian issues.
1206 guint16* dataVals = (guint16*)data->data;
1207 sp_svg_write_color( c, sizeof(c),
1208 SP_RGBA32_U_COMPOSE(
1209 0x0ff & (dataVals[0] >> 8),
1210 0x0ff & (dataVals[1] >> 8),
1211 0x0ff & (dataVals[2] >> 8),
1212 0xff // can't have transparency in the color itself
1213 //0x0ff & (data->data[3] >> 8),
1214 ));
1215 SPCSSAttr *css = sp_repr_css_attr_new();
1216 bool updatePerformed = false;
1218 if ( data->length > 14 ) {
1219 int flags = dataVals[4];
1221 // piggie-backed palette entry info
1222 int index = dataVals[5];
1223 Glib::ustring palName;
1224 for ( int i = 0; i < dataVals[6]; i++ ) {
1225 palName += (gunichar)dataVals[7+i];
1226 }
1228 // Now hook in a magic tag of some sort.
1229 if ( !palName.empty() && (flags & 1) ) {
1230 gchar* str = g_strdup_printf("%d|", index);
1231 palName.insert( 0, str );
1232 g_free(str);
1233 str = 0;
1235 sp_object_setAttribute( SP_OBJECT(item),
1236 fillnotstroke ? "inkscape:x-fill-tag":"inkscape:x-stroke-tag",
1237 palName.c_str(),
1238 false );
1239 item->updateRepr();
1241 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1242 updatePerformed = true;
1243 }
1244 }
1246 if ( !updatePerformed ) {
1247 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1248 }
1250 sp_desktop_apply_css_recursive( item, css, true );
1251 item->updateRepr();
1253 sp_document_done( doc , SP_VERB_NONE,
1254 _("Drop color"));
1256 if ( srgbProf ) {
1257 cmsCloseProfile( srgbProf );
1258 }
1259 }
1260 }
1261 }
1262 break;
1263 #endif // ENABLE_MAGIC_COLORS
1265 case APP_X_COLOR:
1266 {
1267 int destX = 0;
1268 int destY = 0;
1269 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1270 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1271 Geom::Point const button_dt(desktop->w2d(where));
1272 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1274 if ( data->length == 8 ) {
1275 gchar colorspec[64] = {0};
1276 // Careful about endian issues.
1277 guint16* dataVals = (guint16*)data->data;
1278 sp_svg_write_color( colorspec, sizeof(colorspec),
1279 SP_RGBA32_U_COMPOSE(
1280 0x0ff & (dataVals[0] >> 8),
1281 0x0ff & (dataVals[1] >> 8),
1282 0x0ff & (dataVals[2] >> 8),
1283 0xff // can't have transparency in the color itself
1284 //0x0ff & (data->data[3] >> 8),
1285 ));
1287 SPItem *item = desktop->item_at_point( where, true );
1289 bool consumed = false;
1290 if (desktop->event_context && desktop->event_context->get_drag()) {
1291 consumed = desktop->event_context->get_drag()->dropColor(item, colorspec, button_dt);
1292 if (consumed) {
1293 sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1294 desktop->event_context->get_drag()->updateDraggers();
1295 }
1296 }
1298 //if (!consumed && tools_active(desktop, TOOLS_TEXT)) {
1299 // consumed = sp_text_context_drop_color(c, button_doc);
1300 // if (consumed) {
1301 // sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient stop"));
1302 // }
1303 //}
1305 if (!consumed && item) {
1306 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1307 if (fillnotstroke &&
1308 (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1309 Path *livarot_path = Path_for_item(item, true, true);
1310 livarot_path->ConvertWithBackData(0.04);
1312 boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1313 if (position) {
1314 Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1315 Geom::Point delta = nearest - button_doc;
1316 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1317 delta = desktop->d2w(delta);
1318 double stroke_tolerance =
1319 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1320 desktop->current_zoom() *
1321 SP_OBJECT_STYLE (item)->stroke_width.computed *
1322 to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1323 : 0.0)
1324 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1326 if (Geom::L2 (delta) < stroke_tolerance) {
1327 fillnotstroke = false;
1328 }
1329 }
1330 delete livarot_path;
1331 }
1333 SPCSSAttr *css = sp_repr_css_attr_new();
1334 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec );
1336 sp_desktop_apply_css_recursive( item, css, true );
1337 item->updateRepr();
1339 sp_document_done( doc , SP_VERB_NONE,
1340 _("Drop color"));
1341 }
1342 }
1343 }
1344 break;
1346 case APP_OSWB_COLOR:
1347 {
1348 bool worked = false;
1349 Glib::ustring colorspec;
1350 if ( data->format == 8 ) {
1351 ege::PaintDef color;
1352 worked = color.fromMIMEData("application/x-oswb-color",
1353 reinterpret_cast<char*>(data->data),
1354 data->length,
1355 data->format);
1356 if ( worked ) {
1357 if ( color.getType() == ege::PaintDef::CLEAR ) {
1358 colorspec = ""; // TODO check if this is sufficient
1359 } else if ( color.getType() == ege::PaintDef::NONE ) {
1360 colorspec = "none";
1361 } else {
1362 unsigned int r = color.getR();
1363 unsigned int g = color.getG();
1364 unsigned int b = color.getB();
1366 SPGradient* matches = 0;
1367 const GSList *gradients = sp_document_get_resource_list(doc, "gradient");
1368 for (const GSList *item = gradients; item; item = item->next) {
1369 SPGradient* grad = SP_GRADIENT(item->data);
1370 if ( color.descr == grad->getId() ) {
1371 if ( grad->has_stops ) {
1372 matches = grad;
1373 break;
1374 }
1375 }
1376 }
1377 if (matches) {
1378 colorspec = "url(#";
1379 colorspec += matches->getId();
1380 colorspec += ")";
1381 } else {
1382 gchar* tmp = g_strdup_printf("#%02x%02x%02x", r, g, b);
1383 colorspec = tmp;
1384 g_free(tmp);
1385 }
1386 }
1387 }
1388 }
1389 if ( worked ) {
1390 int destX = 0;
1391 int destY = 0;
1392 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1393 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1394 Geom::Point const button_dt(desktop->w2d(where));
1395 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1397 SPItem *item = desktop->item_at_point( where, true );
1399 bool consumed = false;
1400 if (desktop->event_context && desktop->event_context->get_drag()) {
1401 consumed = desktop->event_context->get_drag()->dropColor(item, colorspec.c_str(), button_dt);
1402 if (consumed) {
1403 sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1404 desktop->event_context->get_drag()->updateDraggers();
1405 }
1406 }
1408 if (!consumed && item) {
1409 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1410 if (fillnotstroke &&
1411 (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1412 Path *livarot_path = Path_for_item(item, true, true);
1413 livarot_path->ConvertWithBackData(0.04);
1415 boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1416 if (position) {
1417 Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1418 Geom::Point delta = nearest - button_doc;
1419 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1420 delta = desktop->d2w(delta);
1421 double stroke_tolerance =
1422 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1423 desktop->current_zoom() *
1424 SP_OBJECT_STYLE (item)->stroke_width.computed *
1425 to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1426 : 0.0)
1427 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1429 if (Geom::L2 (delta) < stroke_tolerance) {
1430 fillnotstroke = false;
1431 }
1432 }
1433 delete livarot_path;
1434 }
1436 SPCSSAttr *css = sp_repr_css_attr_new();
1437 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec.c_str() );
1439 sp_desktop_apply_css_recursive( item, css, true );
1440 item->updateRepr();
1442 sp_document_done( doc , SP_VERB_NONE,
1443 _("Drop color"));
1444 }
1445 }
1446 }
1447 break;
1449 case SVG_DATA:
1450 case SVG_XML_DATA: {
1451 gchar *svgdata = (gchar *)data->data;
1453 Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI);
1455 if (rnewdoc == NULL) {
1456 sp_ui_error_dialog(_("Could not parse SVG data"));
1457 return;
1458 }
1460 Inkscape::XML::Node *repr = rnewdoc->root();
1461 gchar const *style = repr->attribute("style");
1463 Inkscape::XML::Node *newgroup = rnewdoc->createElement("svg:g");
1464 newgroup->setAttribute("style", style);
1466 Inkscape::XML::Document * xml_doc = sp_document_repr_doc(doc);
1467 for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) {
1468 Inkscape::XML::Node *newchild = child->duplicate(xml_doc);
1469 newgroup->appendChild(newchild);
1470 }
1472 Inkscape::GC::release(rnewdoc);
1474 // Add it to the current layer
1476 // Greg's edits to add intelligent positioning of svg drops
1477 SPObject *new_obj = NULL;
1478 new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
1480 Inkscape::Selection *selection = sp_desktop_selection(desktop);
1481 selection->set(SP_ITEM(new_obj));
1483 // move to mouse pointer
1484 {
1485 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
1486 Geom::OptRect sel_bbox = selection->bounds();
1487 if (sel_bbox) {
1488 Geom::Point m( desktop->point() - sel_bbox->midpoint() );
1489 sp_selection_move_relative(selection, m, false);
1490 }
1491 }
1493 Inkscape::GC::release(newgroup);
1494 sp_document_done(doc, SP_VERB_NONE,
1495 _("Drop SVG"));
1496 break;
1497 }
1499 case URI_LIST: {
1500 gchar *uri = (gchar *)data->data;
1501 sp_ui_import_files(uri);
1502 break;
1503 }
1505 case PNG_DATA:
1506 case JPEG_DATA:
1507 case IMAGE_DATA: {
1508 const char *mime = (info == JPEG_DATA ? "image/jpeg" : "image/png");
1510 Inkscape::Extension::DB::InputList o;
1511 Inkscape::Extension::db.get_input_list(o);
1512 Inkscape::Extension::DB::InputList::const_iterator i = o.begin();
1513 while (i != o.end() && strcmp( (*i)->get_mimetype(), mime ) != 0) {
1514 ++i;
1515 }
1516 Inkscape::Extension::Extension *ext = *i;
1517 bool save = (strcmp(ext->get_param_optiongroup("link"), "embed") == 0);
1518 ext->set_param_optiongroup("link", "embed");
1519 ext->set_gui(false);
1521 gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-dnd-import", NULL );
1522 g_file_set_contents(filename, reinterpret_cast<gchar const *>(data->data), data->length, NULL);
1523 file_import(doc, filename, ext);
1524 g_free(filename);
1526 ext->set_param_optiongroup("link", save ? "embed" : "link");
1527 ext->set_gui(true);
1528 sp_document_done( doc , SP_VERB_NONE,
1529 _("Drop bitmap image"));
1530 break;
1531 }
1532 }
1533 }
1535 #include "gradient-context.h"
1537 void sp_ui_drag_motion( GtkWidget */*widget*/,
1538 GdkDragContext */*drag_context*/,
1539 gint /*x*/, gint /*y*/,
1540 GtkSelectionData */*data*/,
1541 guint /*info*/,
1542 guint /*event_time*/,
1543 gpointer /*user_data*/)
1544 {
1545 // SPDocument *doc = SP_ACTIVE_DOCUMENT;
1546 // SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1549 // g_message("drag-n-drop motion (%4d, %4d) at %d", x, y, event_time);
1550 }
1552 static void sp_ui_drag_leave( GtkWidget */*widget*/,
1553 GdkDragContext */*drag_context*/,
1554 guint /*event_time*/,
1555 gpointer /*user_data*/ )
1556 {
1557 // g_message("drag-n-drop leave at %d", event_time);
1558 }
1560 static void
1561 sp_ui_import_files(gchar *buffer)
1562 {
1563 GList *list = gnome_uri_list_extract_filenames(buffer);
1564 if (!list)
1565 return;
1566 g_list_foreach(list, sp_ui_import_one_file_with_check, NULL);
1567 g_list_foreach(list, (GFunc) g_free, NULL);
1568 g_list_free(list);
1569 }
1571 static void
1572 sp_ui_import_one_file_with_check(gpointer filename, gpointer /*unused*/)
1573 {
1574 if (filename) {
1575 if (strlen((char const *)filename) > 2)
1576 sp_ui_import_one_file((char const *)filename);
1577 }
1578 }
1580 static void
1581 sp_ui_import_one_file(char const *filename)
1582 {
1583 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1584 if (!doc) return;
1586 if (filename == NULL) return;
1588 // Pass off to common implementation
1589 // TODO might need to get the proper type of Inkscape::Extension::Extension
1590 file_import( doc, filename, NULL );
1591 }
1593 void
1594 sp_ui_error_dialog(gchar const *message)
1595 {
1596 GtkWidget *dlg;
1597 gchar *safeMsg = Inkscape::IO::sanitizeString(message);
1599 dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
1600 GTK_BUTTONS_CLOSE, "%s", safeMsg);
1601 sp_transientize(dlg);
1602 gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE);
1603 gtk_dialog_run(GTK_DIALOG(dlg));
1604 gtk_widget_destroy(dlg);
1605 g_free(safeMsg);
1606 }
1608 bool
1609 sp_ui_overwrite_file(gchar const *filename)
1610 {
1611 bool return_value = FALSE;
1613 if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) {
1614 Gtk::Window *window = SP_ACTIVE_DESKTOP->getToplevel();
1615 gchar* baseName = g_path_get_basename( filename );
1616 gchar* dirName = g_path_get_dirname( filename );
1617 GtkWidget* dialog = gtk_message_dialog_new_with_markup( window->gobj(),
1618 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
1619 GTK_MESSAGE_QUESTION,
1620 GTK_BUTTONS_NONE,
1621 _( "<span weight=\"bold\" size=\"larger\">A file named \"%s\" already exists. Do you want to replace it?</span>\n\n"
1622 "The file already exists in \"%s\". Replacing it will overwrite its contents." ),
1623 baseName,
1624 dirName
1625 );
1626 gtk_dialog_add_buttons( GTK_DIALOG(dialog),
1627 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1628 _("Replace"), GTK_RESPONSE_YES,
1629 NULL );
1630 gtk_dialog_set_default_response( GTK_DIALOG(dialog), GTK_RESPONSE_YES );
1632 if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_YES ) {
1633 return_value = TRUE;
1634 } else {
1635 return_value = FALSE;
1636 }
1637 gtk_widget_destroy(dialog);
1638 g_free( baseName );
1639 g_free( dirName );
1640 } else {
1641 return_value = TRUE;
1642 }
1644 return return_value;
1645 }
1647 static void
1648 sp_ui_menu_item_set_sensitive(SPAction */*action*/, unsigned int sensitive, void *data)
1649 {
1650 return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
1651 }
1653 static void
1654 sp_ui_menu_item_set_name(SPAction */*action*/, Glib::ustring name, void *data)
1655 {
1656 void *child = GTK_BIN (data)->child;
1657 //child is either
1658 //- a GtkHBox, whose first child is a label displaying name if the menu
1659 //item has an accel key
1660 //- a GtkLabel if the menu has no accel key
1661 if (GTK_IS_LABEL(child)) {
1662 gtk_label_set_markup_with_mnemonic(GTK_LABEL (child), name.c_str());
1663 } else if (GTK_IS_HBOX(child)) {
1664 gtk_label_set_markup_with_mnemonic(
1665 GTK_LABEL (gtk_container_get_children(GTK_CONTAINER (child))->data),
1666 name.c_str());
1667 }//else sp_ui_menu_append_item_from_verb has been modified and can set
1668 //a menu item in yet another way...
1669 }
1672 /*
1673 Local Variables:
1674 mode:c++
1675 c-file-style:"stroustrup"
1676 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1677 indent-tabs-mode:nil
1678 fill-column:99
1679 End:
1680 */
1681 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :