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 GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic(_("Open _Recent"));
996 gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), recent_menu);
998 gtk_menu_append(GTK_MENU(menu), GTK_WIDGET(recent_item));
999 // this will just sit and update the list's item count
1000 static MaxRecentObserver *mro = new MaxRecentObserver(recent_menu);
1001 prefs->addObserver(*mro);
1002 continue;
1003 }
1004 if (!strcmp(menu_pntr->name(), "objects-checkboxes")) {
1005 sp_ui_checkboxes_menus(GTK_MENU(menu), view);
1006 continue;
1007 }
1008 }
1009 }
1011 /** \brief Build the main tool bar
1012 \param view View to build the bar for
1014 Currently the main tool bar is built as a dynamic XML menu using
1015 \c sp_ui_build_dyn_menus. This function builds the bar, and then
1016 pass it to get items attached to it.
1017 */
1018 GtkWidget *
1019 sp_ui_main_menubar(Inkscape::UI::View::View *view)
1020 {
1021 GtkWidget *mbar = gtk_menu_bar_new();
1023 #ifdef GDK_WINDOWING_QUARTZ
1024 ige_mac_menu_set_menu_bar(GTK_MENU_SHELL(mbar));
1025 #endif
1027 sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view);
1029 #ifdef GDK_WINDOWING_QUARTZ
1030 return NULL;
1031 #else
1032 return mbar;
1033 #endif
1034 }
1036 static void leave_group(GtkMenuItem *, SPDesktop *desktop) {
1037 desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer()));
1038 }
1040 static void enter_group(GtkMenuItem *mi, SPDesktop *desktop) {
1041 desktop->setCurrentLayer(reinterpret_cast<SPObject *>(g_object_get_data(G_OBJECT(mi), "group")));
1042 sp_desktop_selection(desktop)->clear();
1043 }
1045 GtkWidget *
1046 sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item)
1047 {
1048 GtkWidget *m;
1049 SPDesktop *dt;
1051 dt = static_cast<SPDesktop*>(view);
1053 m = gtk_menu_new();
1055 /* Undo and Redo */
1056 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_UNDO), view);
1057 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_REDO), view);
1059 /* Separator */
1060 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1062 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_CUT), view);
1063 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_COPY), view);
1064 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_PASTE), view);
1066 /* Separator */
1067 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1069 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DUPLICATE), view);
1070 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DELETE), view);
1072 /* Item menu */
1073 if (item) {
1074 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1075 sp_object_menu((SPObject *) item, dt, GTK_MENU(m));
1076 }
1078 /* layer menu */
1079 SPGroup *group=NULL;
1080 if (item) {
1081 if (SP_IS_GROUP(item)) {
1082 group = SP_GROUP(item);
1083 } else if ( item != dt->currentRoot() && SP_IS_GROUP(SP_OBJECT_PARENT(item)) ) {
1084 group = SP_GROUP(SP_OBJECT_PARENT(item));
1085 }
1086 }
1088 if (( group && group != dt->currentLayer() ) ||
1089 ( dt->currentLayer() != dt->currentRoot() && SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) ) {
1090 /* Separator */
1091 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1092 }
1094 if ( group && group != dt->currentLayer() ) {
1095 /* TRANSLATORS: #%s is the id of the group e.g. <g id="#g7">, not a number. */
1096 gchar *label=g_strdup_printf(_("Enter group #%s"), SP_OBJECT_ID(group));
1097 GtkWidget *w = gtk_menu_item_new_with_label(label);
1098 g_free(label);
1099 g_object_set_data(G_OBJECT(w), "group", group);
1100 g_signal_connect(G_OBJECT(w), "activate", GCallback(enter_group), dt);
1101 gtk_widget_show(w);
1102 gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1103 }
1105 if ( dt->currentLayer() != dt->currentRoot() ) {
1106 if ( SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) {
1107 GtkWidget *w = gtk_menu_item_new_with_label(_("Go to parent"));
1108 g_signal_connect(G_OBJECT(w), "activate", GCallback(leave_group), dt);
1109 gtk_widget_show(w);
1110 gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1112 }
1113 }
1115 return m;
1116 }
1118 /* Drag and Drop */
1119 void
1120 sp_ui_drag_data_received(GtkWidget *widget,
1121 GdkDragContext *drag_context,
1122 gint x, gint y,
1123 GtkSelectionData *data,
1124 guint info,
1125 guint /*event_time*/,
1126 gpointer /*user_data*/)
1127 {
1128 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1129 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1131 switch (info) {
1132 #if ENABLE_MAGIC_COLORS
1133 case APP_X_INKY_COLOR:
1134 {
1135 int destX = 0;
1136 int destY = 0;
1137 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1138 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1140 SPItem *item = desktop->item_at_point( where, true );
1141 if ( item )
1142 {
1143 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1145 if ( data->length >= 8 ) {
1146 cmsHPROFILE srgbProf = cmsCreate_sRGBProfile();
1148 gchar c[64] = {0};
1149 // Careful about endian issues.
1150 guint16* dataVals = (guint16*)data->data;
1151 sp_svg_write_color( c, sizeof(c),
1152 SP_RGBA32_U_COMPOSE(
1153 0x0ff & (dataVals[0] >> 8),
1154 0x0ff & (dataVals[1] >> 8),
1155 0x0ff & (dataVals[2] >> 8),
1156 0xff // can't have transparency in the color itself
1157 //0x0ff & (data->data[3] >> 8),
1158 ));
1159 SPCSSAttr *css = sp_repr_css_attr_new();
1160 bool updatePerformed = false;
1162 if ( data->length > 14 ) {
1163 int flags = dataVals[4];
1165 // piggie-backed palette entry info
1166 int index = dataVals[5];
1167 Glib::ustring palName;
1168 for ( int i = 0; i < dataVals[6]; i++ ) {
1169 palName += (gunichar)dataVals[7+i];
1170 }
1172 // Now hook in a magic tag of some sort.
1173 if ( !palName.empty() && (flags & 1) ) {
1174 gchar* str = g_strdup_printf("%d|", index);
1175 palName.insert( 0, str );
1176 g_free(str);
1177 str = 0;
1179 sp_object_setAttribute( SP_OBJECT(item),
1180 fillnotstroke ? "inkscape:x-fill-tag":"inkscape:x-stroke-tag",
1181 palName.c_str(),
1182 false );
1183 item->updateRepr();
1185 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1186 updatePerformed = true;
1187 }
1188 }
1190 if ( !updatePerformed ) {
1191 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1192 }
1194 sp_desktop_apply_css_recursive( item, css, true );
1195 item->updateRepr();
1197 sp_document_done( doc , SP_VERB_NONE,
1198 _("Drop color"));
1200 if ( srgbProf ) {
1201 cmsCloseProfile( srgbProf );
1202 }
1203 }
1204 }
1205 }
1206 break;
1207 #endif // ENABLE_MAGIC_COLORS
1209 case APP_X_COLOR:
1210 {
1211 int destX = 0;
1212 int destY = 0;
1213 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1214 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1215 Geom::Point const button_dt(desktop->w2d(where));
1216 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1218 if ( data->length == 8 ) {
1219 gchar colorspec[64] = {0};
1220 // Careful about endian issues.
1221 guint16* dataVals = (guint16*)data->data;
1222 sp_svg_write_color( colorspec, sizeof(colorspec),
1223 SP_RGBA32_U_COMPOSE(
1224 0x0ff & (dataVals[0] >> 8),
1225 0x0ff & (dataVals[1] >> 8),
1226 0x0ff & (dataVals[2] >> 8),
1227 0xff // can't have transparency in the color itself
1228 //0x0ff & (data->data[3] >> 8),
1229 ));
1231 SPItem *item = desktop->item_at_point( where, true );
1233 bool consumed = false;
1234 if (desktop->event_context && desktop->event_context->get_drag()) {
1235 consumed = desktop->event_context->get_drag()->dropColor(item, colorspec, button_dt);
1236 if (consumed) {
1237 sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1238 desktop->event_context->get_drag()->updateDraggers();
1239 }
1240 }
1242 //if (!consumed && tools_active(desktop, TOOLS_TEXT)) {
1243 // consumed = sp_text_context_drop_color(c, button_doc);
1244 // if (consumed) {
1245 // sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient stop"));
1246 // }
1247 //}
1249 if (!consumed && item) {
1250 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1251 if (fillnotstroke &&
1252 (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1253 Path *livarot_path = Path_for_item(item, true, true);
1254 livarot_path->ConvertWithBackData(0.04);
1256 boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1257 if (position) {
1258 Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1259 Geom::Point delta = nearest - button_doc;
1260 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1261 delta = desktop->d2w(delta);
1262 double stroke_tolerance =
1263 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1264 desktop->current_zoom() *
1265 SP_OBJECT_STYLE (item)->stroke_width.computed *
1266 to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1267 : 0.0)
1268 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1270 if (Geom::L2 (delta) < stroke_tolerance) {
1271 fillnotstroke = false;
1272 }
1273 }
1274 delete livarot_path;
1275 }
1277 SPCSSAttr *css = sp_repr_css_attr_new();
1278 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec );
1280 sp_desktop_apply_css_recursive( item, css, true );
1281 item->updateRepr();
1283 sp_document_done( doc , SP_VERB_NONE,
1284 _("Drop color"));
1285 }
1286 }
1287 }
1288 break;
1290 case APP_OSWB_COLOR:
1291 {
1292 bool worked = false;
1293 Glib::ustring colorspec;
1294 if ( data->format == 8 ) {
1295 ege::PaintDef color;
1296 worked = color.fromMIMEData("application/x-oswb-color",
1297 reinterpret_cast<char*>(data->data),
1298 data->length,
1299 data->format);
1300 if ( worked ) {
1301 if ( color.getType() == ege::PaintDef::CLEAR ) {
1302 colorspec = ""; // TODO check if this is sufficient
1303 } else if ( color.getType() == ege::PaintDef::NONE ) {
1304 colorspec = "none";
1305 } else {
1306 unsigned int r = color.getR();
1307 unsigned int g = color.getG();
1308 unsigned int b = color.getB();
1310 SPGradient* matches = 0;
1311 const GSList *gradients = sp_document_get_resource_list(doc, "gradient");
1312 for (const GSList *item = gradients; item; item = item->next) {
1313 SPGradient* grad = SP_GRADIENT(item->data);
1314 if ( color.descr == grad->id ) {
1315 if ( grad->has_stops ) {
1316 matches = grad;
1317 break;
1318 }
1319 }
1320 }
1321 if (matches) {
1322 colorspec = "url(#";
1323 colorspec += matches->id;
1324 colorspec += ")";
1325 } else {
1326 gchar* tmp = g_strdup_printf("#%02x%02x%02x", r, g, b);
1327 colorspec = tmp;
1328 g_free(tmp);
1329 }
1330 }
1331 }
1332 }
1333 if ( worked ) {
1334 int destX = 0;
1335 int destY = 0;
1336 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1337 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1338 Geom::Point const button_dt(desktop->w2d(where));
1339 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1341 SPItem *item = desktop->item_at_point( where, true );
1343 bool consumed = false;
1344 if (desktop->event_context && desktop->event_context->get_drag()) {
1345 consumed = desktop->event_context->get_drag()->dropColor(item, colorspec.c_str(), button_dt);
1346 if (consumed) {
1347 sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1348 desktop->event_context->get_drag()->updateDraggers();
1349 }
1350 }
1352 if (!consumed && item) {
1353 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1354 if (fillnotstroke &&
1355 (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1356 Path *livarot_path = Path_for_item(item, true, true);
1357 livarot_path->ConvertWithBackData(0.04);
1359 boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1360 if (position) {
1361 Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1362 Geom::Point delta = nearest - button_doc;
1363 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1364 delta = desktop->d2w(delta);
1365 double stroke_tolerance =
1366 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1367 desktop->current_zoom() *
1368 SP_OBJECT_STYLE (item)->stroke_width.computed *
1369 to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1370 : 0.0)
1371 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1373 if (Geom::L2 (delta) < stroke_tolerance) {
1374 fillnotstroke = false;
1375 }
1376 }
1377 delete livarot_path;
1378 }
1380 SPCSSAttr *css = sp_repr_css_attr_new();
1381 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec.c_str() );
1383 sp_desktop_apply_css_recursive( item, css, true );
1384 item->updateRepr();
1386 sp_document_done( doc , SP_VERB_NONE,
1387 _("Drop color"));
1388 }
1389 }
1390 }
1391 break;
1393 case SVG_DATA:
1394 case SVG_XML_DATA: {
1395 gchar *svgdata = (gchar *)data->data;
1397 Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI);
1399 if (rnewdoc == NULL) {
1400 sp_ui_error_dialog(_("Could not parse SVG data"));
1401 return;
1402 }
1404 Inkscape::XML::Node *repr = rnewdoc->root();
1405 gchar const *style = repr->attribute("style");
1407 Inkscape::XML::Node *newgroup = rnewdoc->createElement("svg:g");
1408 newgroup->setAttribute("style", style);
1410 Inkscape::XML::Document * xml_doc = sp_document_repr_doc(doc);
1411 for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) {
1412 Inkscape::XML::Node *newchild = child->duplicate(xml_doc);
1413 newgroup->appendChild(newchild);
1414 }
1416 Inkscape::GC::release(rnewdoc);
1418 // Add it to the current layer
1420 // Greg's edits to add intelligent positioning of svg drops
1421 SPObject *new_obj = NULL;
1422 new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
1424 Inkscape::Selection *selection = sp_desktop_selection(desktop);
1425 selection->set(SP_ITEM(new_obj));
1427 // move to mouse pointer
1428 {
1429 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
1430 Geom::OptRect sel_bbox = selection->bounds();
1431 if (sel_bbox) {
1432 Geom::Point m( desktop->point() - sel_bbox->midpoint() );
1433 sp_selection_move_relative(selection, m, false);
1434 }
1435 }
1437 Inkscape::GC::release(newgroup);
1438 sp_document_done(doc, SP_VERB_NONE,
1439 _("Drop SVG"));
1440 break;
1441 }
1443 case URI_LIST: {
1444 gchar *uri = (gchar *)data->data;
1445 sp_ui_import_files(uri);
1446 break;
1447 }
1449 case PNG_DATA:
1450 case JPEG_DATA:
1451 case IMAGE_DATA: {
1452 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
1453 Inkscape::XML::Node *newImage = xml_doc->createElement("svg:image");
1454 gchar *atom_name = gdk_atom_name(data->type);
1456 // this formula taken from Glib docs
1457 guint needed_size = data->length * 4 / 3 + data->length * 4 / (3 * 72) + 7;
1458 needed_size += 5 + 8 + strlen(atom_name); // 5 bytes for data:, 8 for ;base64,
1460 gchar *buffer = (gchar *) g_malloc(needed_size), *buf_work = buffer;
1461 buf_work += g_sprintf(buffer, "data:%s;base64,", atom_name);
1463 gint state = 0, save = 0;
1464 g_base64_encode_step(data->data, data->length, TRUE, buf_work, &state, &save);
1465 g_base64_encode_close(TRUE, buf_work, &state, &save);
1467 newImage->setAttribute("xlink:href", buffer);
1468 g_free(buffer);
1470 GError *error = NULL;
1471 GdkPixbufLoader *loader = gdk_pixbuf_loader_new_with_mime_type( gdk_atom_name(data->type), &error );
1472 if ( loader ) {
1473 error = NULL;
1474 if ( gdk_pixbuf_loader_write( loader, data->data, data->length, &error) ) {
1475 GdkPixbuf *pbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1476 if ( pbuf ) {
1477 char tmp[1024];
1478 int width = gdk_pixbuf_get_width(pbuf);
1479 int height = gdk_pixbuf_get_height(pbuf);
1480 snprintf( tmp, sizeof(tmp), "%d", width );
1481 newImage->setAttribute("width", tmp);
1483 snprintf( tmp, sizeof(tmp), "%d", height );
1484 newImage->setAttribute("height", tmp);
1485 }
1486 }
1487 }
1488 g_free(atom_name);
1490 // Add it to the current layer
1491 desktop->currentLayer()->appendChildRepr(newImage);
1493 Inkscape::GC::release(newImage);
1494 sp_document_done( doc , SP_VERB_NONE,
1495 _("Drop bitmap image"));
1496 break;
1497 }
1498 }
1499 }
1501 #include "gradient-context.h"
1503 void sp_ui_drag_motion( GtkWidget */*widget*/,
1504 GdkDragContext */*drag_context*/,
1505 gint /*x*/, gint /*y*/,
1506 GtkSelectionData */*data*/,
1507 guint /*info*/,
1508 guint /*event_time*/,
1509 gpointer /*user_data*/)
1510 {
1511 // SPDocument *doc = SP_ACTIVE_DOCUMENT;
1512 // SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1515 // g_message("drag-n-drop motion (%4d, %4d) at %d", x, y, event_time);
1516 }
1518 static void sp_ui_drag_leave( GtkWidget */*widget*/,
1519 GdkDragContext */*drag_context*/,
1520 guint /*event_time*/,
1521 gpointer /*user_data*/ )
1522 {
1523 // g_message("drag-n-drop leave at %d", event_time);
1524 }
1526 static void
1527 sp_ui_import_files(gchar *buffer)
1528 {
1529 GList *list = gnome_uri_list_extract_filenames(buffer);
1530 if (!list)
1531 return;
1532 g_list_foreach(list, sp_ui_import_one_file_with_check, NULL);
1533 g_list_foreach(list, (GFunc) g_free, NULL);
1534 g_list_free(list);
1535 }
1537 static void
1538 sp_ui_import_one_file_with_check(gpointer filename, gpointer /*unused*/)
1539 {
1540 if (filename) {
1541 if (strlen((char const *)filename) > 2)
1542 sp_ui_import_one_file((char const *)filename);
1543 }
1544 }
1546 static void
1547 sp_ui_import_one_file(char const *filename)
1548 {
1549 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1550 if (!doc) return;
1552 if (filename == NULL) return;
1554 // Pass off to common implementation
1555 // TODO might need to get the proper type of Inkscape::Extension::Extension
1556 file_import( doc, filename, NULL );
1557 }
1559 void
1560 sp_ui_error_dialog(gchar const *message)
1561 {
1562 GtkWidget *dlg;
1563 gchar *safeMsg = Inkscape::IO::sanitizeString(message);
1565 dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
1566 GTK_BUTTONS_CLOSE, "%s", safeMsg);
1567 sp_transientize(dlg);
1568 gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE);
1569 gtk_dialog_run(GTK_DIALOG(dlg));
1570 gtk_widget_destroy(dlg);
1571 g_free(safeMsg);
1572 }
1574 bool
1575 sp_ui_overwrite_file(gchar const *filename)
1576 {
1577 bool return_value = FALSE;
1579 if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) {
1580 Gtk::Window *window = SP_ACTIVE_DESKTOP->getToplevel();
1581 gchar* baseName = g_path_get_basename( filename );
1582 gchar* dirName = g_path_get_dirname( filename );
1583 GtkWidget* dialog = gtk_message_dialog_new_with_markup( window->gobj(),
1584 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
1585 GTK_MESSAGE_QUESTION,
1586 GTK_BUTTONS_NONE,
1587 _( "<span weight=\"bold\" size=\"larger\">A file named \"%s\" already exists. Do you want to replace it?</span>\n\n"
1588 "The file already exists in \"%s\". Replacing it will overwrite its contents." ),
1589 baseName,
1590 dirName
1591 );
1592 gtk_dialog_add_buttons( GTK_DIALOG(dialog),
1593 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1594 _("Replace"), GTK_RESPONSE_YES,
1595 NULL );
1596 gtk_dialog_set_default_response( GTK_DIALOG(dialog), GTK_RESPONSE_YES );
1598 if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_YES ) {
1599 return_value = TRUE;
1600 } else {
1601 return_value = FALSE;
1602 }
1603 gtk_widget_destroy(dialog);
1604 g_free( baseName );
1605 g_free( dirName );
1606 } else {
1607 return_value = TRUE;
1608 }
1610 return return_value;
1611 }
1613 static void
1614 sp_ui_menu_item_set_sensitive(SPAction */*action*/, unsigned int sensitive, void *data)
1615 {
1616 return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
1617 }
1619 static void
1620 sp_ui_menu_item_set_name(SPAction */*action*/, Glib::ustring name, void *data)
1621 {
1622 void *child = GTK_BIN (data)->child;
1623 //child is either
1624 //- a GtkHBox, whose first child is a label displaying name if the menu
1625 //item has an accel key
1626 //- a GtkLabel if the menu has no accel key
1627 if (GTK_IS_LABEL(child)) {
1628 gtk_label_set_markup_with_mnemonic(GTK_LABEL (child), name.c_str());
1629 } else if (GTK_IS_HBOX(child)) {
1630 gtk_label_set_markup_with_mnemonic(
1631 GTK_LABEL (gtk_container_get_children(GTK_CONTAINER (child))->data),
1632 name.c_str());
1633 }//else sp_ui_menu_append_item_from_verb has been modified and can set
1634 //a menu item in yet another way...
1635 }
1638 /*
1639 Local Variables:
1640 mode:c++
1641 c-file-style:"stroustrup"
1642 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1643 indent-tabs-mode:nil
1644 fill-column:99
1645 End:
1646 */
1647 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :