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