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