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;
102 #define ENTRIES_SIZE(n) sizeof(n)/sizeof(n[0])
103 static guint nui_drop_target_entries = ENTRIES_SIZE(ui_drop_target_entries);
104 static void sp_ui_import_files(gchar *buffer);
105 static void sp_ui_import_one_file(char const *filename);
106 static void sp_ui_import_one_file_with_check(gpointer filename, gpointer unused);
107 static void sp_ui_drag_data_received(GtkWidget *widget,
108 GdkDragContext *drag_context,
109 gint x, gint y,
110 GtkSelectionData *data,
111 guint info,
112 guint event_time,
113 gpointer user_data);
114 static void sp_ui_drag_motion( GtkWidget *widget,
115 GdkDragContext *drag_context,
116 gint x, gint y,
117 GtkSelectionData *data,
118 guint info,
119 guint event_time,
120 gpointer user_data );
121 static void sp_ui_drag_leave( GtkWidget *widget,
122 GdkDragContext *drag_context,
123 guint event_time,
124 gpointer user_data );
125 static void sp_ui_menu_item_set_sensitive(SPAction *action,
126 unsigned int sensitive,
127 void *data);
128 static void sp_ui_menu_item_set_name(SPAction *action,
129 Glib::ustring name,
130 void *data);
131 static void sp_recent_open(GtkRecentChooser *, gpointer);
133 SPActionEventVector menu_item_event_vector = {
134 {NULL},
135 NULL,
136 NULL, /* set_active */
137 sp_ui_menu_item_set_sensitive, /* set_sensitive */
138 NULL, /* set_shortcut */
139 sp_ui_menu_item_set_name /* set_name */
140 };
142 static const int MIN_ONSCREEN_DISTANCE = 50;
144 void
145 sp_create_window(SPViewWidget *vw, gboolean editable)
146 {
147 g_return_if_fail(vw != NULL);
148 g_return_if_fail(SP_IS_VIEW_WIDGET(vw));
150 Gtk::Window *win = Inkscape::UI::window_new("", TRUE);
152 gtk_container_add(GTK_CONTAINER(win->gobj()), GTK_WIDGET(vw));
153 gtk_widget_show(GTK_WIDGET(vw));
155 if (editable) {
156 g_object_set_data(G_OBJECT(vw), "window", win);
158 SPDesktopWidget *desktop_widget = reinterpret_cast<SPDesktopWidget*>(vw);
159 SPDesktop* desktop = desktop_widget->desktop;
161 desktop_widget->window = win;
163 win->set_data("desktop", desktop);
164 win->set_data("desktopwidget", desktop_widget);
166 win->signal_delete_event().connect(sigc::mem_fun(*(SPDesktop*)vw->view, &SPDesktop::onDeleteUI));
167 win->signal_window_state_event().connect(sigc::mem_fun(*desktop, &SPDesktop::onWindowStateEvent));
168 win->signal_focus_in_event().connect(sigc::mem_fun(*desktop_widget, &SPDesktopWidget::onFocusInEvent));
170 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
171 gint prefs_geometry =
172 (2==prefs->getInt("/options/savewindowgeometry/value", 0));
173 if (prefs_geometry) {
174 gint pw = prefs->getInt("/desktop/geometry/width", -1);
175 gint ph = prefs->getInt("/desktop/geometry/height", -1);
176 gint px = prefs->getInt("/desktop/geometry/x", -1);
177 gint py = prefs->getInt("/desktop/geometry/y", -1);
178 gint full = prefs->getBool("/desktop/geometry/fullscreen");
179 gint maxed = prefs->getBool("/desktop/geometry/maximized");
180 if (pw>0 && ph>0) {
181 gint w = MIN(gdk_screen_width(), pw);
182 gint h = MIN(gdk_screen_height(), ph);
183 gint x = MIN(gdk_screen_width() - MIN_ONSCREEN_DISTANCE, px);
184 gint y = MIN(gdk_screen_height() - MIN_ONSCREEN_DISTANCE, py);
185 if (w>0 && h>0) {
186 x = MIN(gdk_screen_width() - w, x);
187 y = MIN(gdk_screen_height() - h, y);
188 desktop->setWindowSize(w, h);
189 }
191 // Only restore xy for the first window so subsequent windows don't overlap exactly
192 // with first. (Maybe rule should be only restore xy if it's different from xy of
193 // other desktops?)
195 // Empirically it seems that active_desktop==this desktop only the first time a
196 // desktop is created.
197 SPDesktop *active_desktop = SP_ACTIVE_DESKTOP;
198 if (active_desktop == desktop || active_desktop==NULL) {
199 desktop->setWindowPosition(Geom::Point(x, y));
200 }
201 }
202 if (maxed) {
203 win->maximize();
204 }
205 if (full) {
206 win->fullscreen();
207 }
208 }
210 } else {
211 gtk_window_set_policy(GTK_WINDOW(win->gobj()), TRUE, TRUE, TRUE);
212 }
214 if ( completeDropTargets == 0 || completeDropTargetsCount == 0 )
215 {
216 std::vector<gchar*> types;
218 GSList *list = gdk_pixbuf_get_formats();
219 while ( list ) {
220 int i = 0;
221 GdkPixbufFormat *one = (GdkPixbufFormat*)list->data;
222 gchar** typesXX = gdk_pixbuf_format_get_mime_types(one);
223 for ( i = 0; typesXX[i]; i++ ) {
224 types.push_back(g_strdup(typesXX[i]));
225 }
226 g_strfreev(typesXX);
228 list = g_slist_next(list);
229 }
230 completeDropTargetsCount = nui_drop_target_entries + types.size();
231 completeDropTargets = new GtkTargetEntry[completeDropTargetsCount];
232 for ( int i = 0; i < (int)nui_drop_target_entries; i++ ) {
233 completeDropTargets[i] = ui_drop_target_entries[i];
234 }
235 int pos = nui_drop_target_entries;
237 for (std::vector<gchar*>::iterator it = types.begin() ; it != types.end() ; it++) {
238 completeDropTargets[pos].target = *it;
239 completeDropTargets[pos].flags = 0;
240 completeDropTargets[pos].info = IMAGE_DATA;
241 pos++;
242 }
243 }
245 gtk_drag_dest_set((GtkWidget*)win->gobj(),
246 GTK_DEST_DEFAULT_ALL,
247 completeDropTargets,
248 completeDropTargetsCount,
249 GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE));
252 g_signal_connect(G_OBJECT(win->gobj()),
253 "drag_data_received",
254 G_CALLBACK(sp_ui_drag_data_received),
255 NULL);
256 g_signal_connect(G_OBJECT(win->gobj()),
257 "drag_motion",
258 G_CALLBACK(sp_ui_drag_motion),
259 NULL);
260 g_signal_connect(G_OBJECT(win->gobj()),
261 "drag_leave",
262 G_CALLBACK(sp_ui_drag_leave),
263 NULL);
264 win->show();
266 // needed because the first ACTIVATE_DESKTOP was sent when there was no window yet
267 inkscape_reactivate_desktop(SP_DESKTOP_WIDGET(vw)->desktop);
268 }
270 void
271 sp_ui_new_view()
272 {
273 SPDocument *document;
274 SPViewWidget *dtw;
276 document = SP_ACTIVE_DOCUMENT;
277 if (!document) return;
279 dtw = sp_desktop_widget_new(sp_document_namedview(document, NULL));
280 g_return_if_fail(dtw != NULL);
282 sp_create_window(dtw, TRUE);
283 sp_namedview_window_from_document(static_cast<SPDesktop*>(dtw->view));
284 sp_namedview_update_layers_from_document(static_cast<SPDesktop*>(dtw->view));
285 }
287 /* TODO: not yet working */
288 /* To be re-enabled (by adding to menu) once it works. */
289 void
290 sp_ui_new_view_preview()
291 {
292 SPDocument *document;
293 SPViewWidget *dtw;
295 document = SP_ACTIVE_DOCUMENT;
296 if (!document) return;
298 dtw = (SPViewWidget *) sp_svg_view_widget_new(document);
299 g_return_if_fail(dtw != NULL);
300 sp_svg_view_widget_set_resize(SP_SVG_VIEW_WIDGET(dtw), TRUE, 400.0, 400.0);
302 sp_create_window(dtw, FALSE);
303 }
305 /**
306 * \param widget unused
307 */
308 void
309 sp_ui_close_view(GtkWidget */*widget*/)
310 {
311 SPDesktop *dt = SP_ACTIVE_DESKTOP;
313 if (dt == NULL) {
314 return;
315 }
317 if (dt->shutdown()) {
318 return; // Shutdown operation has been canceled, so do nothing
319 }
321 // Shutdown can proceed; use the stored reference to the desktop here instead of the current SP_ACTIVE_DESKTOP,
322 // because the user might have changed the focus in the meantime (see bug #381357 on Launchpad)
323 dt->destroyWidget();
324 }
327 /**
328 * sp_ui_close_all
329 *
330 * This function is called to exit the program, and iterates through all
331 * open document view windows, attempting to close each in turn. If the
332 * view has unsaved information, the user will be prompted to save,
333 * discard, or cancel.
334 *
335 * Returns FALSE if the user cancels the close_all operation, TRUE
336 * otherwise.
337 */
338 unsigned int
339 sp_ui_close_all(void)
340 {
341 /* Iterate through all the windows, destroying each in the order they
342 become active */
343 while (SP_ACTIVE_DESKTOP) {
344 SPDesktop *dt = SP_ACTIVE_DESKTOP;
345 if (dt->shutdown()) {
346 /* The user canceled the operation, so end doing the close */
347 return FALSE;
348 }
349 // Shutdown can proceed; use the stored reference to the desktop here instead of the current SP_ACTIVE_DESKTOP,
350 // because the user might have changed the focus in the meantime (see bug #381357 on Launchpad)
351 dt->destroyWidget();
352 }
354 return TRUE;
355 }
357 /*
358 * Some day when the right-click menus are ready to start working
359 * smarter with the verbs, we'll need to change this NULL being
360 * sent to sp_action_perform to something useful, or set some kind
361 * of global "right-clicked position" variable for actions to
362 * investigate when they're called.
363 */
364 static void
365 sp_ui_menu_activate(void */*object*/, SPAction *action)
366 {
367 sp_action_perform(action, NULL);
368 }
370 static void
371 sp_ui_menu_select_action(void */*object*/, SPAction *action)
372 {
373 action->view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, action->tip);
374 }
376 static void
377 sp_ui_menu_deselect_action(void */*object*/, SPAction *action)
378 {
379 action->view->tipsMessageContext()->clear();
380 }
382 static void
383 sp_ui_menu_select(gpointer object, gpointer tip)
384 {
385 Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view"));
386 view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, (gchar *)tip);
387 }
389 static void
390 sp_ui_menu_deselect(gpointer object)
391 {
392 Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view"));
393 view->tipsMessageContext()->clear();
394 }
396 /**
397 * sp_ui_menuitem_add_icon
398 *
399 * Creates and attaches a scaled icon to the given menu item.
400 *
401 */
402 void
403 sp_ui_menuitem_add_icon( GtkWidget *item, gchar *icon_name )
404 {
405 GtkWidget *icon;
407 icon = sp_icon_new( Inkscape::ICON_SIZE_MENU, icon_name );
408 gtk_widget_show(icon);
409 gtk_image_menu_item_set_image((GtkImageMenuItem *) item, icon);
410 } // end of sp_ui_menu_add_icon
412 /**
413 * sp_ui_menu_append_item
414 *
415 * Appends a UI item with specific info for Inkscape/Sodipodi.
416 *
417 */
418 static GtkWidget *
419 sp_ui_menu_append_item( GtkMenu *menu, gchar const *stock,
420 gchar const *label, gchar const *tip, Inkscape::UI::View::View *view, GCallback callback,
421 gpointer data, gboolean with_mnemonic = TRUE )
422 {
423 GtkWidget *item;
425 if (stock) {
426 item = gtk_image_menu_item_new_from_stock(stock, NULL);
427 } else if (label) {
428 item = (with_mnemonic)
429 ? gtk_image_menu_item_new_with_mnemonic(label) :
430 gtk_image_menu_item_new_with_label(label);
431 } else {
432 item = gtk_separator_menu_item_new();
433 }
435 gtk_widget_show(item);
437 if (callback) {
438 g_signal_connect(G_OBJECT(item), "activate", callback, data);
439 }
441 if (tip && view) {
442 g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
443 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) tip );
444 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
445 }
447 gtk_menu_append(GTK_MENU(menu), item);
449 return item;
451 } // end of sp_ui_menu_append_item()
453 /**
454 \brief a wrapper around gdk_keyval_name producing (when possible) characters, not names
455 */
456 static gchar const *
457 sp_key_name(guint keyval)
458 {
459 /* TODO: Compare with the definition of gtk_accel_label_refetch in gtk/gtkaccellabel.c (or
460 simply use GtkAccelLabel as the TODO comment in sp_ui_shortcut_string suggests). */
461 gchar const *n = gdk_keyval_name(gdk_keyval_to_upper(keyval));
463 if (!strcmp(n, "asciicircum")) return "^";
464 else if (!strcmp(n, "parenleft" )) return "(";
465 else if (!strcmp(n, "parenright" )) return ")";
466 else if (!strcmp(n, "plus" )) return "+";
467 else if (!strcmp(n, "minus" )) return "-";
468 else if (!strcmp(n, "asterisk" )) return "*";
469 else if (!strcmp(n, "KP_Multiply")) return "*";
470 else if (!strcmp(n, "Delete" )) return "Del";
471 else if (!strcmp(n, "Page_Up" )) return "PgUp";
472 else if (!strcmp(n, "Page_Down" )) return "PgDn";
473 else if (!strcmp(n, "grave" )) return "`";
474 else if (!strcmp(n, "numbersign" )) return "#";
475 else if (!strcmp(n, "bar" )) return "|";
476 else if (!strcmp(n, "slash" )) return "/";
477 else if (!strcmp(n, "exclam" )) return "!";
478 else if (!strcmp(n, "percent" )) return "%";
479 else return n;
480 }
483 /**
484 * \param shortcut A GDK keyval OR'd with SP_SHORTCUT_blah_MASK values.
485 * \param c Points to a buffer at least 256 bytes long.
486 */
487 void
488 sp_ui_shortcut_string(unsigned const shortcut, gchar *const c)
489 {
490 /* TODO: This function shouldn't exist. Our callers should use GtkAccelLabel instead of
491 * a generic GtkLabel containing this string, and should call gtk_widget_add_accelerator.
492 * Will probably need to change sp_shortcut_invoke callers.
493 *
494 * The existing gtk_label_new_with_mnemonic call can be replaced with
495 * g_object_new(GTK_TYPE_ACCEL_LABEL, NULL) followed by
496 * gtk_label_set_text_with_mnemonic(lbl, str).
497 */
498 static GtkAccelLabelClass const &accel_lbl_cls
499 = *(GtkAccelLabelClass const *) g_type_class_peek_static(GTK_TYPE_ACCEL_LABEL);
501 struct { unsigned test; char const *name; } const modifier_tbl[] = {
502 { SP_SHORTCUT_SHIFT_MASK, accel_lbl_cls.mod_name_shift },
503 { SP_SHORTCUT_CONTROL_MASK, accel_lbl_cls.mod_name_control },
504 { SP_SHORTCUT_ALT_MASK, accel_lbl_cls.mod_name_alt }
505 };
507 gchar *p = c;
508 gchar *end = p + 256;
510 for (unsigned i = 0; i < G_N_ELEMENTS(modifier_tbl); ++i) {
511 if ((shortcut & modifier_tbl[i].test)
512 && (p < end))
513 {
514 p += g_snprintf(p, end - p, "%s%s",
515 modifier_tbl[i].name,
516 accel_lbl_cls.mod_separator);
517 }
518 }
519 if (p < end) {
520 p += g_snprintf(p, end - p, "%s", sp_key_name(shortcut & 0xffffff));
521 }
522 end[-1] = '\0'; // snprintf doesn't guarantee to nul-terminate the string.
523 }
525 void
526 sp_ui_dialog_title_string(Inkscape::Verb *verb, gchar *c)
527 {
528 SPAction *action;
529 unsigned int shortcut;
530 gchar *s;
531 gchar key[256];
532 gchar *atitle;
534 action = verb->get_action(NULL);
535 if (!action)
536 return;
538 atitle = sp_action_get_title(action);
540 s = g_stpcpy(c, atitle);
542 g_free(atitle);
544 shortcut = sp_shortcut_get_primary(verb);
545 if (shortcut) {
546 s = g_stpcpy(s, " (");
547 sp_ui_shortcut_string(shortcut, key);
548 s = g_stpcpy(s, key);
549 s = g_stpcpy(s, ")");
550 }
551 }
554 /**
555 * sp_ui_menu_append_item_from_verb
556 *
557 * Appends a custom menu UI from a verb.
558 *
559 */
561 static GtkWidget *
562 sp_ui_menu_append_item_from_verb(GtkMenu *menu, Inkscape::Verb *verb, Inkscape::UI::View::View *view, bool radio = false, GSList *group = NULL)
563 {
564 SPAction *action;
565 GtkWidget *item;
567 if (verb->get_code() == SP_VERB_NONE) {
569 item = gtk_separator_menu_item_new();
571 } else {
572 unsigned int shortcut;
574 action = verb->get_action(view);
576 if (!action) return NULL;
578 shortcut = sp_shortcut_get_primary(verb);
579 if (shortcut) {
580 gchar c[256];
581 sp_ui_shortcut_string(shortcut, c);
582 GtkWidget *const hb = gtk_hbox_new(FALSE, 16);
583 GtkWidget *const name_lbl = gtk_label_new("");
584 gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
585 gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
586 gtk_box_pack_start((GtkBox *) hb, name_lbl, TRUE, TRUE, 0);
587 GtkWidget *const accel_lbl = gtk_label_new(c);
588 gtk_misc_set_alignment((GtkMisc *) accel_lbl, 1.0, 0.5);
589 gtk_box_pack_end((GtkBox *) hb, accel_lbl, FALSE, FALSE, 0);
590 gtk_widget_show_all(hb);
591 if (radio) {
592 item = gtk_radio_menu_item_new (group);
593 } else {
594 item = gtk_image_menu_item_new();
595 }
596 gtk_container_add((GtkContainer *) item, hb);
597 } else {
598 if (radio) {
599 item = gtk_radio_menu_item_new (group);
600 } else {
601 item = gtk_image_menu_item_new ();
602 }
603 GtkWidget *const name_lbl = gtk_label_new("");
604 gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
605 gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
606 gtk_container_add((GtkContainer *) item, name_lbl);
607 }
609 nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
610 if (!action->sensitive) {
611 gtk_widget_set_sensitive(item, FALSE);
612 }
614 if (action->image) {
615 sp_ui_menuitem_add_icon(item, action->image);
616 }
617 gtk_widget_set_events(item, GDK_KEY_PRESS_MASK);
618 g_signal_connect( G_OBJECT(item), "activate",
619 G_CALLBACK(sp_ui_menu_activate), action );
621 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select_action), action );
622 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect_action), action );
623 }
625 gtk_widget_show(item);
626 gtk_menu_append(GTK_MENU(menu), item);
628 return item;
630 } // end of sp_ui_menu_append_item_from_verb
633 static void
634 checkitem_toggled(GtkCheckMenuItem *menuitem, gpointer user_data)
635 {
636 gchar const *pref = (gchar const *) user_data;
637 Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
639 Glib::ustring pref_path;
640 if (reinterpret_cast<SPDesktop*>(view)->is_focusMode()) {
641 pref_path = "/focus/";
642 } else if (reinterpret_cast<SPDesktop*>(view)->is_fullscreen()) {
643 pref_path = "/fullscreen/";
644 } else {
645 pref_path = "/window/";
646 }
647 pref_path += pref;
648 pref_path += "/state";
650 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
651 gboolean checked = gtk_check_menu_item_get_active(menuitem);
652 prefs->setBool(pref_path, checked);
654 reinterpret_cast<SPDesktop*>(view)->layoutWidget();
655 }
657 static gboolean
658 checkitem_update(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_data)
659 {
660 GtkCheckMenuItem *menuitem=GTK_CHECK_MENU_ITEM(widget);
662 gchar const *pref = (gchar const *) user_data;
663 Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
665 Glib::ustring pref_path;
666 if ((static_cast<SPDesktop*>(view))->is_fullscreen()) {
667 pref_path = "/fullscreen/";
668 } else {
669 pref_path = "/window/";
670 }
671 pref_path += pref;
673 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
674 bool ison = prefs->getBool(pref_path + "/state", true);
676 g_signal_handlers_block_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
677 gtk_check_menu_item_set_active(menuitem, ison);
678 g_signal_handlers_unblock_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
680 return FALSE;
681 }
684 void
685 sp_ui_menu_append_check_item_from_verb(GtkMenu *menu, Inkscape::UI::View::View *view, gchar const *label, gchar const *tip, gchar const *pref,
686 void (*callback_toggle)(GtkCheckMenuItem *, gpointer user_data),
687 gboolean (*callback_update)(GtkWidget *widget, GdkEventExpose *event, gpointer user_data),
688 Inkscape::Verb *verb)
689 {
690 GtkWidget *item;
692 unsigned int shortcut = 0;
693 SPAction *action = NULL;
695 if (verb) {
696 shortcut = sp_shortcut_get_primary(verb);
697 action = verb->get_action(view);
698 }
700 if (verb && shortcut) {
701 gchar c[256];
702 sp_ui_shortcut_string(shortcut, c);
704 GtkWidget *hb = gtk_hbox_new(FALSE, 16);
706 {
707 GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
708 gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
709 gtk_box_pack_start((GtkBox *) hb, l, TRUE, TRUE, 0);
710 }
712 {
713 GtkWidget *l = gtk_label_new(c);
714 gtk_misc_set_alignment((GtkMisc *) l, 1.0, 0.5);
715 gtk_box_pack_end((GtkBox *) hb, l, FALSE, FALSE, 0);
716 }
718 gtk_widget_show_all(hb);
720 item = gtk_check_menu_item_new();
721 gtk_container_add((GtkContainer *) item, hb);
722 } else {
723 GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
724 gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
725 item = gtk_check_menu_item_new();
726 gtk_container_add((GtkContainer *) item, l);
727 }
728 #if 0
729 nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
730 if (!action->sensitive) {
731 gtk_widget_set_sensitive(item, FALSE);
732 }
733 #endif
734 gtk_widget_show(item);
736 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
738 g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
740 g_signal_connect( G_OBJECT(item), "toggled", (GCallback) callback_toggle, (void *) pref);
741 g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) callback_update, (void *) pref);
743 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) (action ? action->tip : tip));
744 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
745 }
747 static void
748 sp_recent_open(GtkRecentChooser *recent_menu, gpointer /*user_data*/)
749 {
750 // dealing with the bizarre filename convention in Inkscape for now
751 gchar *uri = gtk_recent_chooser_get_current_uri(GTK_RECENT_CHOOSER(recent_menu));
752 gchar *local_fn = g_filename_from_uri(uri, NULL, NULL);
753 gchar *utf8_fn = g_filename_to_utf8(local_fn, -1, NULL, NULL, NULL);
754 sp_file_open(utf8_fn, NULL);
755 g_free(utf8_fn);
756 g_free(local_fn);
757 g_free(uri);
758 }
760 static void
761 sp_file_new_from_template(GtkWidget */*widget*/, gchar const *uri)
762 {
763 sp_file_new(uri);
764 }
766 void
767 sp_menu_append_new_templates(GtkWidget *menu, Inkscape::UI::View::View *view)
768 {
769 std::list<gchar *> sources;
770 sources.push_back( profile_path("templates") ); // first try user's local dir
771 sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir
773 // Use this loop to iterate through a list of possible document locations.
774 while (!sources.empty()) {
775 gchar *dirname = sources.front();
777 if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) {
778 GError *err = 0;
779 GDir *dir = g_dir_open(dirname, 0, &err);
781 if (dir) {
782 for (gchar const *file = g_dir_read_name(dir); file != NULL; file = g_dir_read_name(dir)) {
783 if (!g_str_has_suffix(file, ".svg") && !g_str_has_suffix(file, ".svgz"))
784 continue; // skip non-svg files
786 gchar *basename = g_path_get_basename(file);
787 if (g_str_has_suffix(basename, ".svg") && g_str_has_prefix(basename, "default."))
788 continue; // skip default.*.svg (i.e. default.svg and translations) - it's in the menu already
790 gchar const *filepath = g_build_filename(dirname, file, NULL);
791 gchar *dupfile = g_strndup(file, strlen(file) - 4);
792 gchar *filename = g_filename_to_utf8(dupfile, -1, NULL, NULL, NULL);
793 g_free(dupfile);
794 GtkWidget *item = gtk_menu_item_new_with_label(filename);
795 g_free(filename);
797 gtk_widget_show(item);
798 // how does "filepath" ever get freed?
799 g_signal_connect(G_OBJECT(item),
800 "activate",
801 G_CALLBACK(sp_file_new_from_template),
802 (gpointer) filepath);
804 if (view) {
805 // set null tip for now; later use a description from the template file
806 g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
807 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) NULL );
808 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
809 }
811 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
812 }
813 g_dir_close(dir);
814 }
815 }
817 // toss the dirname
818 g_free(dirname);
819 sources.pop_front();
820 }
821 }
823 void
824 sp_ui_checkboxes_menus(GtkMenu *m, Inkscape::UI::View::View *view)
825 {
826 //sp_ui_menu_append_check_item_from_verb(m, view, _("_Menu"), _("Show or hide the menu bar"), "menu",
827 // checkitem_toggled, checkitem_update, 0);
828 sp_ui_menu_append_check_item_from_verb(m, view, _("Commands Bar"), _("Show or hide the Commands bar (under the menu)"), "commands",
829 checkitem_toggled, checkitem_update, 0);
830 sp_ui_menu_append_check_item_from_verb(m, view, _("Snap Controls Bar"), _("Show or hide the snapping controls"), "snaptoolbox",
831 checkitem_toggled, checkitem_update, 0);
832 sp_ui_menu_append_check_item_from_verb(m, view, _("Tool Controls Bar"), _("Show or hide the Tool Controls bar"), "toppanel",
833 checkitem_toggled, checkitem_update, 0);
834 sp_ui_menu_append_check_item_from_verb(m, view, _("_Toolbox"), _("Show or hide the main toolbox (on the left)"), "toolbox",
835 checkitem_toggled, checkitem_update, 0);
836 sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "rulers",
837 checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_RULERS));
838 sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "scrollbars",
839 checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_SCROLLBARS));
840 sp_ui_menu_append_check_item_from_verb(m, view, _("_Palette"), _("Show or hide the color palette"), "panels",
841 checkitem_toggled, checkitem_update, 0);
842 sp_ui_menu_append_check_item_from_verb(m, view, _("_Statusbar"), _("Show or hide the statusbar (at the bottom of the window)"), "statusbar",
843 checkitem_toggled, checkitem_update, 0);
844 }
846 /** @brief Observer that updates the recent list's max document count */
847 class MaxRecentObserver : public Inkscape::Preferences::Observer {
848 public:
849 MaxRecentObserver(GtkWidget *recent_menu) :
850 Observer("/options/maxrecentdocuments/value"),
851 _rm(recent_menu)
852 {}
853 virtual void notify(Inkscape::Preferences::Entry const &e) {
854 gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(_rm), e.getInt());
855 // hack: the recent menu doesn't repopulate after changing the limit, so we force it
856 g_signal_emit_by_name((gpointer) gtk_recent_manager_get_default(), "changed");
857 }
858 private:
859 GtkWidget *_rm;
860 };
862 /** \brief This function turns XML into a menu
863 \param menus This is the XML that defines the menu
864 \param menu Menu to be added to
865 \param view The View that this menu is being built for
867 This function is realitively simple as it just goes through the XML
868 and parses the individual elements. In the case of a submenu, it
869 just calls itself recursively. Because it is only reasonable to have
870 a couple of submenus, it is unlikely this will go more than two or
871 three times.
873 In the case of an unreconginzed verb, a menu item is made to identify
874 the verb that is missing, and display that. The menu item is also made
875 insensitive.
876 */
877 void
878 sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI::View::View *view)
879 {
880 if (menus == NULL) return;
881 if (menu == NULL) return;
882 GSList *group = NULL;
884 for (Inkscape::XML::Node *menu_pntr = menus;
885 menu_pntr != NULL;
886 menu_pntr = menu_pntr->next()) {
887 if (!strcmp(menu_pntr->name(), "submenu")) {
888 GtkWidget *mitem = gtk_menu_item_new_with_mnemonic(_(menu_pntr->attribute("name")));
889 GtkWidget *submenu = gtk_menu_new();
890 sp_ui_build_dyn_menus(menu_pntr->firstChild(), submenu, view);
891 gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), GTK_WIDGET(submenu));
892 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
893 continue;
894 }
895 if (!strcmp(menu_pntr->name(), "verb")) {
896 gchar const *verb_name = menu_pntr->attribute("verb-id");
897 Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name);
899 if (verb != NULL) {
900 if (menu_pntr->attribute("radio") != NULL) {
901 GtkWidget *item = sp_ui_menu_append_item_from_verb (GTK_MENU(menu), verb, view, true, group);
902 group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item));
903 if (menu_pntr->attribute("default") != NULL) {
904 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
905 }
906 } else {
907 sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view);
908 group = NULL;
909 }
910 } else {
911 gchar string[120];
912 g_snprintf(string, 120, _("Verb \"%s\" Unknown"), verb_name);
913 string[119] = '\0'; /* may not be terminated */
914 GtkWidget *item = gtk_menu_item_new_with_label(string);
915 gtk_widget_set_sensitive(item, false);
916 gtk_widget_show(item);
917 gtk_menu_append(GTK_MENU(menu), item);
918 }
919 continue;
920 }
921 if (!strcmp(menu_pntr->name(), "separator")
922 // This was spelt wrong in the original version
923 // and so this is for backward compatibility. It can
924 // probably be dropped after the 0.44 release.
925 || !strcmp(menu_pntr->name(), "seperator")) {
926 GtkWidget *item = gtk_separator_menu_item_new();
927 gtk_widget_show(item);
928 gtk_menu_append(GTK_MENU(menu), item);
929 continue;
930 }
931 if (!strcmp(menu_pntr->name(), "template-list")) {
932 sp_menu_append_new_templates(menu, view);
933 continue;
934 }
935 if (!strcmp(menu_pntr->name(), "recent-file-list")) {
936 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
938 // create recent files menu
939 int max_recent = prefs->getInt("/options/maxrecentdocuments/value");
940 GtkWidget *recent_menu = gtk_recent_chooser_menu_new_for_manager(gtk_recent_manager_get_default());
941 gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(recent_menu), max_recent);
942 // sort most recently used documents first to preserve previous behavior
943 gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(recent_menu), GTK_RECENT_SORT_MRU);
944 g_signal_connect(G_OBJECT(recent_menu), "item-activated", G_CALLBACK(sp_recent_open), (gpointer) NULL);
946 // add filter to only open files added by Inkscape
947 GtkRecentFilter *inkscape_only_filter = gtk_recent_filter_new();
948 gtk_recent_filter_add_application(inkscape_only_filter, g_get_prgname());
949 gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_menu), inkscape_only_filter);
951 GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic(_("Open _Recent"));
952 gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), recent_menu);
954 gtk_menu_append(GTK_MENU(menu), GTK_WIDGET(recent_item));
955 // this will just sit and update the list's item count
956 static MaxRecentObserver *mro = new MaxRecentObserver(recent_menu);
957 prefs->addObserver(*mro);
958 continue;
959 }
960 if (!strcmp(menu_pntr->name(), "objects-checkboxes")) {
961 sp_ui_checkboxes_menus(GTK_MENU(menu), view);
962 continue;
963 }
964 }
965 }
967 /** \brief Build the main tool bar
968 \param view View to build the bar for
970 Currently the main tool bar is built as a dynamic XML menu using
971 \c sp_ui_build_dyn_menus. This function builds the bar, and then
972 pass it to get items attached to it.
973 */
974 GtkWidget *
975 sp_ui_main_menubar(Inkscape::UI::View::View *view)
976 {
977 GtkWidget *mbar = gtk_menu_bar_new();
979 #ifdef GDK_WINDOWING_QUARTZ
980 ige_mac_menu_set_menu_bar(GTK_MENU_SHELL(mbar));
981 #endif
983 sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view);
985 #ifdef GDK_WINDOWING_QUARTZ
986 return NULL;
987 #else
988 return mbar;
989 #endif
990 }
992 static void leave_group(GtkMenuItem *, SPDesktop *desktop) {
993 desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer()));
994 }
996 static void enter_group(GtkMenuItem *mi, SPDesktop *desktop) {
997 desktop->setCurrentLayer(reinterpret_cast<SPObject *>(g_object_get_data(G_OBJECT(mi), "group")));
998 sp_desktop_selection(desktop)->clear();
999 }
1001 GtkWidget *
1002 sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item)
1003 {
1004 GtkWidget *m;
1005 SPDesktop *dt;
1007 dt = static_cast<SPDesktop*>(view);
1009 m = gtk_menu_new();
1011 /* Undo and Redo */
1012 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_UNDO), view);
1013 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_REDO), view);
1015 /* Separator */
1016 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1018 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_CUT), view);
1019 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_COPY), view);
1020 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_PASTE), view);
1022 /* Separator */
1023 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1025 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DUPLICATE), view);
1026 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DELETE), view);
1028 /* Item menu */
1029 if (item) {
1030 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1031 sp_object_menu((SPObject *) item, dt, GTK_MENU(m));
1032 }
1034 /* layer menu */
1035 SPGroup *group=NULL;
1036 if (item) {
1037 if (SP_IS_GROUP(item)) {
1038 group = SP_GROUP(item);
1039 } else if ( item != dt->currentRoot() && SP_IS_GROUP(SP_OBJECT_PARENT(item)) ) {
1040 group = SP_GROUP(SP_OBJECT_PARENT(item));
1041 }
1042 }
1044 if (( group && group != dt->currentLayer() ) ||
1045 ( dt->currentLayer() != dt->currentRoot() && SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) ) {
1046 /* Separator */
1047 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1048 }
1050 if ( group && group != dt->currentLayer() ) {
1051 /* TRANSLATORS: #%s is the id of the group e.g. <g id="#g7">, not a number. */
1052 gchar *label=g_strdup_printf(_("Enter group #%s"), SP_OBJECT_ID(group));
1053 GtkWidget *w = gtk_menu_item_new_with_label(label);
1054 g_free(label);
1055 g_object_set_data(G_OBJECT(w), "group", group);
1056 g_signal_connect(G_OBJECT(w), "activate", GCallback(enter_group), dt);
1057 gtk_widget_show(w);
1058 gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1059 }
1061 if ( dt->currentLayer() != dt->currentRoot() ) {
1062 if ( SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) {
1063 GtkWidget *w = gtk_menu_item_new_with_label(_("Go to parent"));
1064 g_signal_connect(G_OBJECT(w), "activate", GCallback(leave_group), dt);
1065 gtk_widget_show(w);
1066 gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1068 }
1069 }
1071 return m;
1072 }
1074 /* Drag and Drop */
1075 void
1076 sp_ui_drag_data_received(GtkWidget *widget,
1077 GdkDragContext *drag_context,
1078 gint x, gint y,
1079 GtkSelectionData *data,
1080 guint info,
1081 guint /*event_time*/,
1082 gpointer /*user_data*/)
1083 {
1084 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1085 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1087 switch (info) {
1088 #if ENABLE_MAGIC_COLORS
1089 case APP_X_INKY_COLOR:
1090 {
1091 int destX = 0;
1092 int destY = 0;
1093 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1094 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1096 SPItem *item = desktop->item_at_point( where, true );
1097 if ( item )
1098 {
1099 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1101 if ( data->length >= 8 ) {
1102 cmsHPROFILE srgbProf = cmsCreate_sRGBProfile();
1104 gchar c[64] = {0};
1105 // Careful about endian issues.
1106 guint16* dataVals = (guint16*)data->data;
1107 sp_svg_write_color( c, sizeof(c),
1108 SP_RGBA32_U_COMPOSE(
1109 0x0ff & (dataVals[0] >> 8),
1110 0x0ff & (dataVals[1] >> 8),
1111 0x0ff & (dataVals[2] >> 8),
1112 0xff // can't have transparency in the color itself
1113 //0x0ff & (data->data[3] >> 8),
1114 ));
1115 SPCSSAttr *css = sp_repr_css_attr_new();
1116 bool updatePerformed = false;
1118 if ( data->length > 14 ) {
1119 int flags = dataVals[4];
1121 // piggie-backed palette entry info
1122 int index = dataVals[5];
1123 Glib::ustring palName;
1124 for ( int i = 0; i < dataVals[6]; i++ ) {
1125 palName += (gunichar)dataVals[7+i];
1126 }
1128 // Now hook in a magic tag of some sort.
1129 if ( !palName.empty() && (flags & 1) ) {
1130 gchar* str = g_strdup_printf("%d|", index);
1131 palName.insert( 0, str );
1132 g_free(str);
1133 str = 0;
1135 sp_object_setAttribute( SP_OBJECT(item),
1136 fillnotstroke ? "inkscape:x-fill-tag":"inkscape:x-stroke-tag",
1137 palName.c_str(),
1138 false );
1139 item->updateRepr();
1141 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1142 updatePerformed = true;
1143 }
1144 }
1146 if ( !updatePerformed ) {
1147 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1148 }
1150 sp_desktop_apply_css_recursive( item, css, true );
1151 item->updateRepr();
1153 sp_document_done( doc , SP_VERB_NONE,
1154 _("Drop color"));
1156 if ( srgbProf ) {
1157 cmsCloseProfile( srgbProf );
1158 }
1159 }
1160 }
1161 }
1162 break;
1163 #endif // ENABLE_MAGIC_COLORS
1165 case APP_X_COLOR:
1166 {
1167 int destX = 0;
1168 int destY = 0;
1169 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1170 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1171 Geom::Point const button_dt(desktop->w2d(where));
1172 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1174 if ( data->length == 8 ) {
1175 gchar colorspec[64] = {0};
1176 // Careful about endian issues.
1177 guint16* dataVals = (guint16*)data->data;
1178 sp_svg_write_color( colorspec, sizeof(colorspec),
1179 SP_RGBA32_U_COMPOSE(
1180 0x0ff & (dataVals[0] >> 8),
1181 0x0ff & (dataVals[1] >> 8),
1182 0x0ff & (dataVals[2] >> 8),
1183 0xff // can't have transparency in the color itself
1184 //0x0ff & (data->data[3] >> 8),
1185 ));
1187 SPItem *item = desktop->item_at_point( where, true );
1189 bool consumed = false;
1190 if (desktop->event_context && desktop->event_context->get_drag()) {
1191 consumed = desktop->event_context->get_drag()->dropColor(item, colorspec, button_dt);
1192 if (consumed) {
1193 sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1194 desktop->event_context->get_drag()->updateDraggers();
1195 }
1196 }
1198 //if (!consumed && tools_active(desktop, TOOLS_TEXT)) {
1199 // consumed = sp_text_context_drop_color(c, button_doc);
1200 // if (consumed) {
1201 // sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient stop"));
1202 // }
1203 //}
1205 if (!consumed && item) {
1206 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1207 if (fillnotstroke &&
1208 (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1209 Path *livarot_path = Path_for_item(item, true, true);
1210 livarot_path->ConvertWithBackData(0.04);
1212 boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1213 if (position) {
1214 Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1215 Geom::Point delta = nearest - button_doc;
1216 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1217 delta = desktop->d2w(delta);
1218 double stroke_tolerance =
1219 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1220 desktop->current_zoom() *
1221 SP_OBJECT_STYLE (item)->stroke_width.computed *
1222 to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1223 : 0.0)
1224 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1226 if (Geom::L2 (delta) < stroke_tolerance) {
1227 fillnotstroke = false;
1228 }
1229 }
1230 delete livarot_path;
1231 }
1233 SPCSSAttr *css = sp_repr_css_attr_new();
1234 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec );
1236 sp_desktop_apply_css_recursive( item, css, true );
1237 item->updateRepr();
1239 sp_document_done( doc , SP_VERB_NONE,
1240 _("Drop color"));
1241 }
1242 }
1243 }
1244 break;
1246 case APP_OSWB_COLOR:
1247 {
1248 bool worked = false;
1249 Glib::ustring colorspec;
1250 if ( data->format == 8 ) {
1251 ege::PaintDef color;
1252 worked = color.fromMIMEData("application/x-oswb-color",
1253 reinterpret_cast<char*>(data->data),
1254 data->length,
1255 data->format);
1256 if ( worked ) {
1257 if ( color.getType() == ege::PaintDef::CLEAR ) {
1258 colorspec = ""; // TODO check if this is sufficient
1259 } else if ( color.getType() == ege::PaintDef::NONE ) {
1260 colorspec = "none";
1261 } else {
1262 unsigned int r = color.getR();
1263 unsigned int g = color.getG();
1264 unsigned int b = color.getB();
1266 SPGradient* matches = 0;
1267 const GSList *gradients = sp_document_get_resource_list(doc, "gradient");
1268 for (const GSList *item = gradients; item; item = item->next) {
1269 SPGradient* grad = SP_GRADIENT(item->data);
1270 if ( color.descr == grad->id ) {
1271 if ( grad->has_stops ) {
1272 matches = grad;
1273 break;
1274 }
1275 }
1276 }
1277 if (matches) {
1278 colorspec = "url(#";
1279 colorspec += matches->id;
1280 colorspec += ")";
1281 } else {
1282 gchar* tmp = g_strdup_printf("#%02x%02x%02x", r, g, b);
1283 colorspec = tmp;
1284 g_free(tmp);
1285 }
1286 }
1287 }
1288 }
1289 if ( worked ) {
1290 int destX = 0;
1291 int destY = 0;
1292 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1293 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1294 Geom::Point const button_dt(desktop->w2d(where));
1295 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1297 SPItem *item = desktop->item_at_point( where, true );
1299 bool consumed = false;
1300 if (desktop->event_context && desktop->event_context->get_drag()) {
1301 consumed = desktop->event_context->get_drag()->dropColor(item, colorspec.c_str(), button_dt);
1302 if (consumed) {
1303 sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1304 desktop->event_context->get_drag()->updateDraggers();
1305 }
1306 }
1308 if (!consumed && item) {
1309 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1310 if (fillnotstroke &&
1311 (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1312 Path *livarot_path = Path_for_item(item, true, true);
1313 livarot_path->ConvertWithBackData(0.04);
1315 boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1316 if (position) {
1317 Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1318 Geom::Point delta = nearest - button_doc;
1319 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1320 delta = desktop->d2w(delta);
1321 double stroke_tolerance =
1322 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1323 desktop->current_zoom() *
1324 SP_OBJECT_STYLE (item)->stroke_width.computed *
1325 to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1326 : 0.0)
1327 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1329 if (Geom::L2 (delta) < stroke_tolerance) {
1330 fillnotstroke = false;
1331 }
1332 }
1333 delete livarot_path;
1334 }
1336 SPCSSAttr *css = sp_repr_css_attr_new();
1337 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec.c_str() );
1339 sp_desktop_apply_css_recursive( item, css, true );
1340 item->updateRepr();
1342 sp_document_done( doc , SP_VERB_NONE,
1343 _("Drop color"));
1344 }
1345 }
1346 }
1347 break;
1349 case SVG_DATA:
1350 case SVG_XML_DATA: {
1351 gchar *svgdata = (gchar *)data->data;
1353 Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI);
1355 if (rnewdoc == NULL) {
1356 sp_ui_error_dialog(_("Could not parse SVG data"));
1357 return;
1358 }
1360 Inkscape::XML::Node *repr = rnewdoc->root();
1361 gchar const *style = repr->attribute("style");
1363 Inkscape::XML::Node *newgroup = rnewdoc->createElement("svg:g");
1364 newgroup->setAttribute("style", style);
1366 Inkscape::XML::Document * xml_doc = sp_document_repr_doc(doc);
1367 for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) {
1368 Inkscape::XML::Node *newchild = child->duplicate(xml_doc);
1369 newgroup->appendChild(newchild);
1370 }
1372 Inkscape::GC::release(rnewdoc);
1374 // Add it to the current layer
1376 // Greg's edits to add intelligent positioning of svg drops
1377 SPObject *new_obj = NULL;
1378 new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
1380 Inkscape::Selection *selection = sp_desktop_selection(desktop);
1381 selection->set(SP_ITEM(new_obj));
1383 // move to mouse pointer
1384 {
1385 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
1386 Geom::OptRect sel_bbox = selection->bounds();
1387 if (sel_bbox) {
1388 Geom::Point m( desktop->point() - sel_bbox->midpoint() );
1389 sp_selection_move_relative(selection, m, false);
1390 }
1391 }
1393 Inkscape::GC::release(newgroup);
1394 sp_document_done(doc, SP_VERB_NONE,
1395 _("Drop SVG"));
1396 break;
1397 }
1399 case URI_LIST: {
1400 gchar *uri = (gchar *)data->data;
1401 sp_ui_import_files(uri);
1402 break;
1403 }
1405 case PNG_DATA:
1406 case JPEG_DATA:
1407 case IMAGE_DATA: {
1408 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
1409 Inkscape::XML::Node *newImage = xml_doc->createElement("svg:image");
1410 gchar *atom_name = gdk_atom_name(data->type);
1412 // this formula taken from Glib docs
1413 guint needed_size = data->length * 4 / 3 + data->length * 4 / (3 * 72) + 7;
1414 needed_size += 5 + 8 + strlen(atom_name); // 5 bytes for data:, 8 for ;base64,
1416 gchar *buffer = (gchar *) g_malloc(needed_size), *buf_work = buffer;
1417 buf_work += g_sprintf(buffer, "data:%s;base64,", atom_name);
1419 gint state = 0, save = 0;
1420 g_base64_encode_step(data->data, data->length, TRUE, buf_work, &state, &save);
1421 g_base64_encode_close(TRUE, buf_work, &state, &save);
1423 newImage->setAttribute("xlink:href", buffer);
1424 g_free(buffer);
1426 GError *error = NULL;
1427 GdkPixbufLoader *loader = gdk_pixbuf_loader_new_with_mime_type( gdk_atom_name(data->type), &error );
1428 if ( loader ) {
1429 error = NULL;
1430 if ( gdk_pixbuf_loader_write( loader, data->data, data->length, &error) ) {
1431 GdkPixbuf *pbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1432 if ( pbuf ) {
1433 char tmp[1024];
1434 int width = gdk_pixbuf_get_width(pbuf);
1435 int height = gdk_pixbuf_get_height(pbuf);
1436 snprintf( tmp, sizeof(tmp), "%d", width );
1437 newImage->setAttribute("width", tmp);
1439 snprintf( tmp, sizeof(tmp), "%d", height );
1440 newImage->setAttribute("height", tmp);
1441 }
1442 }
1443 }
1444 g_free(atom_name);
1446 // Add it to the current layer
1447 desktop->currentLayer()->appendChildRepr(newImage);
1449 Inkscape::GC::release(newImage);
1450 sp_document_done( doc , SP_VERB_NONE,
1451 _("Drop bitmap image"));
1452 break;
1453 }
1454 }
1455 }
1457 #include "gradient-context.h"
1459 void sp_ui_drag_motion( GtkWidget */*widget*/,
1460 GdkDragContext */*drag_context*/,
1461 gint /*x*/, gint /*y*/,
1462 GtkSelectionData */*data*/,
1463 guint /*info*/,
1464 guint /*event_time*/,
1465 gpointer /*user_data*/)
1466 {
1467 // SPDocument *doc = SP_ACTIVE_DOCUMENT;
1468 // SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1471 // g_message("drag-n-drop motion (%4d, %4d) at %d", x, y, event_time);
1472 }
1474 static void sp_ui_drag_leave( GtkWidget */*widget*/,
1475 GdkDragContext */*drag_context*/,
1476 guint /*event_time*/,
1477 gpointer /*user_data*/ )
1478 {
1479 // g_message("drag-n-drop leave at %d", event_time);
1480 }
1482 static void
1483 sp_ui_import_files(gchar *buffer)
1484 {
1485 GList *list = gnome_uri_list_extract_filenames(buffer);
1486 if (!list)
1487 return;
1488 g_list_foreach(list, sp_ui_import_one_file_with_check, NULL);
1489 g_list_foreach(list, (GFunc) g_free, NULL);
1490 g_list_free(list);
1491 }
1493 static void
1494 sp_ui_import_one_file_with_check(gpointer filename, gpointer /*unused*/)
1495 {
1496 if (filename) {
1497 if (strlen((char const *)filename) > 2)
1498 sp_ui_import_one_file((char const *)filename);
1499 }
1500 }
1502 static void
1503 sp_ui_import_one_file(char const *filename)
1504 {
1505 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1506 if (!doc) return;
1508 if (filename == NULL) return;
1510 // Pass off to common implementation
1511 // TODO might need to get the proper type of Inkscape::Extension::Extension
1512 file_import( doc, filename, NULL );
1513 }
1515 void
1516 sp_ui_error_dialog(gchar const *message)
1517 {
1518 GtkWidget *dlg;
1519 gchar *safeMsg = Inkscape::IO::sanitizeString(message);
1521 dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
1522 GTK_BUTTONS_CLOSE, "%s", safeMsg);
1523 sp_transientize(dlg);
1524 gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE);
1525 gtk_dialog_run(GTK_DIALOG(dlg));
1526 gtk_widget_destroy(dlg);
1527 g_free(safeMsg);
1528 }
1530 bool
1531 sp_ui_overwrite_file(gchar const *filename)
1532 {
1533 bool return_value = FALSE;
1535 if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) {
1536 Gtk::Window *window = SP_ACTIVE_DESKTOP->getToplevel();
1537 gchar* baseName = g_path_get_basename( filename );
1538 gchar* dirName = g_path_get_dirname( filename );
1539 GtkWidget* dialog = gtk_message_dialog_new_with_markup( window->gobj(),
1540 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
1541 GTK_MESSAGE_QUESTION,
1542 GTK_BUTTONS_NONE,
1543 _( "<span weight=\"bold\" size=\"larger\">A file named \"%s\" already exists. Do you want to replace it?</span>\n\n"
1544 "The file already exists in \"%s\". Replacing it will overwrite its contents." ),
1545 baseName,
1546 dirName
1547 );
1548 gtk_dialog_add_buttons( GTK_DIALOG(dialog),
1549 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1550 _("Replace"), GTK_RESPONSE_YES,
1551 NULL );
1552 gtk_dialog_set_default_response( GTK_DIALOG(dialog), GTK_RESPONSE_YES );
1554 if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_YES ) {
1555 return_value = TRUE;
1556 } else {
1557 return_value = FALSE;
1558 }
1559 gtk_widget_destroy(dialog);
1560 g_free( baseName );
1561 g_free( dirName );
1562 } else {
1563 return_value = TRUE;
1564 }
1566 return return_value;
1567 }
1569 static void
1570 sp_ui_menu_item_set_sensitive(SPAction */*action*/, unsigned int sensitive, void *data)
1571 {
1572 return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
1573 }
1575 static void
1576 sp_ui_menu_item_set_name(SPAction */*action*/, Glib::ustring name, void *data)
1577 {
1578 void *child = GTK_BIN (data)->child;
1579 //child is either
1580 //- a GtkHBox, whose first child is a label displaying name if the menu
1581 //item has an accel key
1582 //- a GtkLabel if the menu has no accel key
1583 if (GTK_IS_LABEL(child)) {
1584 gtk_label_set_markup_with_mnemonic(GTK_LABEL (child), name.c_str());
1585 } else if (GTK_IS_HBOX(child)) {
1586 gtk_label_set_markup_with_mnemonic(
1587 GTK_LABEL (gtk_container_get_children(GTK_CONTAINER (child))->data),
1588 name.c_str());
1589 }//else sp_ui_menu_append_item_from_verb has been modified and can set
1590 //a menu item in yet another way...
1591 }
1594 /*
1595 Local Variables:
1596 mode:c++
1597 c-file-style:"stroustrup"
1598 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1599 indent-tabs-mode:nil
1600 fill-column:99
1601 End:
1602 */
1603 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :