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