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-flowtext.h"
44 #include "sp-namedview.h"
45 #include "ui/view/view.h"
46 #include "helper/action.h"
47 #include "helper/gnome-utils.h"
48 #include "helper/window.h"
49 #include "io/sys.h"
50 #include "dialogs/dialog-events.h"
51 #include "message-context.h"
53 // Added for color drag-n-drop
54 #if ENABLE_LCMS
55 #include "lcms.h"
56 #endif // ENABLE_LCMS
57 #include "display/sp-canvas.h"
58 #include "color.h"
59 #include "svg/svg-color.h"
60 #include "desktop-style.h"
61 #include "style.h"
62 #include "event-context.h"
63 #include "gradient-drag.h"
65 // Include Mac OS X menu synchronization on native OSX build
66 #ifdef GDK_WINDOWING_QUARTZ
67 #include "ige-mac-menu.h"
68 #endif
70 /* Drag and Drop */
71 typedef enum {
72 URI_LIST,
73 SVG_XML_DATA,
74 SVG_DATA,
75 PNG_DATA,
76 JPEG_DATA,
77 IMAGE_DATA,
78 APP_X_INKY_COLOR,
79 APP_X_COLOR,
80 APP_X_NOCOLOR
81 } ui_drop_target_info;
83 static GtkTargetEntry ui_drop_target_entries [] = {
84 {(gchar *)"text/uri-list", 0, URI_LIST },
85 {(gchar *)"image/svg+xml", 0, SVG_XML_DATA },
86 {(gchar *)"image/svg", 0, SVG_DATA },
87 {(gchar *)"image/png", 0, PNG_DATA },
88 {(gchar *)"image/jpeg", 0, JPEG_DATA },
89 #if ENABLE_MAGIC_COLORS
90 {(gchar *)"application/x-inkscape-color", 0, APP_X_INKY_COLOR},
91 #endif // ENABLE_MAGIC_COLORS
92 {(gchar *)"application/x-inkscape-nocolor", 0, APP_X_NOCOLOR },
93 {(gchar *)"application/x-color", 0, APP_X_COLOR }
94 };
96 static GtkTargetEntry *completeDropTargets = 0;
97 static int completeDropTargetsCount = 0;
99 #define ENTRIES_SIZE(n) sizeof(n)/sizeof(n[0])
100 static guint nui_drop_target_entries = ENTRIES_SIZE(ui_drop_target_entries);
101 static void sp_ui_import_files(gchar *buffer);
102 static void sp_ui_import_one_file(char const *filename);
103 static void sp_ui_import_one_file_with_check(gpointer filename, gpointer unused);
104 static void sp_ui_drag_data_received(GtkWidget *widget,
105 GdkDragContext *drag_context,
106 gint x, gint y,
107 GtkSelectionData *data,
108 guint info,
109 guint event_time,
110 gpointer user_data);
111 static void sp_ui_drag_motion( GtkWidget *widget,
112 GdkDragContext *drag_context,
113 gint x, gint y,
114 GtkSelectionData *data,
115 guint info,
116 guint event_time,
117 gpointer user_data );
118 static void sp_ui_drag_leave( GtkWidget *widget,
119 GdkDragContext *drag_context,
120 guint event_time,
121 gpointer user_data );
122 static void sp_ui_menu_item_set_sensitive(SPAction *action,
123 unsigned int sensitive,
124 void *data);
125 static void sp_ui_menu_item_set_name(SPAction *action,
126 Glib::ustring name,
127 void *data);
128 static void sp_recent_open(GtkRecentChooser *, gpointer);
130 SPActionEventVector menu_item_event_vector = {
131 {NULL},
132 NULL,
133 NULL, /* set_active */
134 sp_ui_menu_item_set_sensitive, /* set_sensitive */
135 NULL, /* set_shortcut */
136 sp_ui_menu_item_set_name /* set_name */
137 };
139 static const int MIN_ONSCREEN_DISTANCE = 50;
141 void
142 sp_create_window(SPViewWidget *vw, gboolean editable)
143 {
144 g_return_if_fail(vw != NULL);
145 g_return_if_fail(SP_IS_VIEW_WIDGET(vw));
147 Gtk::Window *win = Inkscape::UI::window_new("", TRUE);
149 gtk_container_add(GTK_CONTAINER(win->gobj()), GTK_WIDGET(vw));
150 gtk_widget_show(GTK_WIDGET(vw));
152 if (editable) {
153 g_object_set_data(G_OBJECT(vw), "window", win);
155 SPDesktopWidget *desktop_widget = reinterpret_cast<SPDesktopWidget*>(vw);
156 SPDesktop* desktop = desktop_widget->desktop;
158 desktop_widget->window = win;
160 win->set_data("desktop", desktop);
161 win->set_data("desktopwidget", desktop_widget);
163 win->signal_delete_event().connect(sigc::mem_fun(*(SPDesktop*)vw->view, &SPDesktop::onDeleteUI));
164 win->signal_window_state_event().connect(sigc::mem_fun(*desktop, &SPDesktop::onWindowStateEvent));
165 win->signal_focus_in_event().connect(sigc::mem_fun(*desktop_widget, &SPDesktopWidget::onFocusInEvent));
167 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
168 gint prefs_geometry =
169 (2==prefs->getInt("/options/savewindowgeometry/value", 0));
170 if (prefs_geometry) {
171 gint pw = prefs->getInt("/desktop/geometry/width", -1);
172 gint ph = prefs->getInt("/desktop/geometry/height", -1);
173 gint px = prefs->getInt("/desktop/geometry/x", -1);
174 gint py = prefs->getInt("/desktop/geometry/y", -1);
175 gint full = prefs->getBool("/desktop/geometry/fullscreen");
176 gint maxed = prefs->getBool("/desktop/geometry/maximized");
177 if (pw>0 && ph>0) {
178 gint w = MIN(gdk_screen_width(), pw);
179 gint h = MIN(gdk_screen_height(), ph);
180 gint x = MIN(gdk_screen_width() - MIN_ONSCREEN_DISTANCE, px);
181 gint y = MIN(gdk_screen_height() - MIN_ONSCREEN_DISTANCE, py);
182 if (w>0 && h>0 && x>0 && y>0) {
183 x = MIN(gdk_screen_width() - w, x);
184 y = MIN(gdk_screen_height() - h, y);
185 }
186 if (w>0 && h>0) {
187 desktop->setWindowSize(w, h);
188 }
190 // Only restore xy for the first window so subsequent windows don't overlap exactly
191 // with first. (Maybe rule should be only restore xy if it's different from xy of
192 // other desktops?)
194 // Empirically it seems that active_desktop==this desktop only the first time a
195 // desktop is created.
196 if (x>0 && y>0) {
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 }
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 if (SP_ACTIVE_DESKTOP == NULL) {
313 return;
314 }
315 if ((SP_ACTIVE_DESKTOP)->shutdown()) {
316 return;
317 }
318 SP_ACTIVE_DESKTOP->destroyWidget();
319 }
322 /**
323 * sp_ui_close_all
324 *
325 * This function is called to exit the program, and iterates through all
326 * open document view windows, attempting to close each in turn. If the
327 * view has unsaved information, the user will be prompted to save,
328 * discard, or cancel.
329 *
330 * Returns FALSE if the user cancels the close_all operation, TRUE
331 * otherwise.
332 */
333 unsigned int
334 sp_ui_close_all(void)
335 {
336 /* Iterate through all the windows, destroying each in the order they
337 become active */
338 while (SP_ACTIVE_DESKTOP) {
339 if ((SP_ACTIVE_DESKTOP)->shutdown()) {
340 /* The user cancelled the operation, so end doing the close */
341 return FALSE;
342 }
343 SP_ACTIVE_DESKTOP->destroyWidget();
344 }
346 return TRUE;
347 }
349 /*
350 * Some day when the right-click menus are ready to start working
351 * smarter with the verbs, we'll need to change this NULL being
352 * sent to sp_action_perform to something useful, or set some kind
353 * of global "right-clicked position" variable for actions to
354 * investigate when they're called.
355 */
356 static void
357 sp_ui_menu_activate(void */*object*/, SPAction *action)
358 {
359 sp_action_perform(action, NULL);
360 }
362 static void
363 sp_ui_menu_select_action(void */*object*/, SPAction *action)
364 {
365 action->view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, action->tip);
366 }
368 static void
369 sp_ui_menu_deselect_action(void */*object*/, SPAction *action)
370 {
371 action->view->tipsMessageContext()->clear();
372 }
374 static void
375 sp_ui_menu_select(gpointer object, gpointer tip)
376 {
377 Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view"));
378 view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, (gchar *)tip);
379 }
381 static void
382 sp_ui_menu_deselect(gpointer object)
383 {
384 Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view"));
385 view->tipsMessageContext()->clear();
386 }
388 /**
389 * sp_ui_menuitem_add_icon
390 *
391 * Creates and attaches a scaled icon to the given menu item.
392 *
393 */
394 void
395 sp_ui_menuitem_add_icon( GtkWidget *item, gchar *icon_name )
396 {
397 GtkWidget *icon;
399 icon = sp_icon_new( Inkscape::ICON_SIZE_MENU, icon_name );
400 gtk_widget_show(icon);
401 gtk_image_menu_item_set_image((GtkImageMenuItem *) item, icon);
402 } // end of sp_ui_menu_add_icon
404 /**
405 * sp_ui_menu_append_item
406 *
407 * Appends a UI item with specific info for Inkscape/Sodipodi.
408 *
409 */
410 static GtkWidget *
411 sp_ui_menu_append_item( GtkMenu *menu, gchar const *stock,
412 gchar const *label, gchar const *tip, Inkscape::UI::View::View *view, GCallback callback,
413 gpointer data, gboolean with_mnemonic = TRUE )
414 {
415 GtkWidget *item;
417 if (stock) {
418 item = gtk_image_menu_item_new_from_stock(stock, NULL);
419 } else if (label) {
420 item = (with_mnemonic)
421 ? gtk_image_menu_item_new_with_mnemonic(label) :
422 gtk_image_menu_item_new_with_label(label);
423 } else {
424 item = gtk_separator_menu_item_new();
425 }
427 gtk_widget_show(item);
429 if (callback) {
430 g_signal_connect(G_OBJECT(item), "activate", callback, data);
431 }
433 if (tip && view) {
434 g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
435 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) tip );
436 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
437 }
439 gtk_menu_append(GTK_MENU(menu), item);
441 return item;
443 } // end of sp_ui_menu_append_item()
445 /**
446 \brief a wrapper around gdk_keyval_name producing (when possible) characters, not names
447 */
448 static gchar const *
449 sp_key_name(guint keyval)
450 {
451 /* TODO: Compare with the definition of gtk_accel_label_refetch in gtk/gtkaccellabel.c (or
452 simply use GtkAccelLabel as the TODO comment in sp_ui_shortcut_string suggests). */
453 gchar const *n = gdk_keyval_name(gdk_keyval_to_upper(keyval));
455 if (!strcmp(n, "asciicircum")) return "^";
456 else if (!strcmp(n, "parenleft" )) return "(";
457 else if (!strcmp(n, "parenright" )) return ")";
458 else if (!strcmp(n, "plus" )) return "+";
459 else if (!strcmp(n, "minus" )) return "-";
460 else if (!strcmp(n, "asterisk" )) return "*";
461 else if (!strcmp(n, "KP_Multiply")) return "*";
462 else if (!strcmp(n, "Delete" )) return "Del";
463 else if (!strcmp(n, "Page_Up" )) return "PgUp";
464 else if (!strcmp(n, "Page_Down" )) return "PgDn";
465 else if (!strcmp(n, "grave" )) return "`";
466 else if (!strcmp(n, "numbersign" )) return "#";
467 else if (!strcmp(n, "bar" )) return "|";
468 else if (!strcmp(n, "slash" )) return "/";
469 else if (!strcmp(n, "exclam" )) return "!";
470 else if (!strcmp(n, "percent" )) return "%";
471 else return n;
472 }
475 /**
476 * \param shortcut A GDK keyval OR'd with SP_SHORTCUT_blah_MASK values.
477 * \param c Points to a buffer at least 256 bytes long.
478 */
479 void
480 sp_ui_shortcut_string(unsigned const shortcut, gchar *const c)
481 {
482 /* TODO: This function shouldn't exist. Our callers should use GtkAccelLabel instead of
483 * a generic GtkLabel containing this string, and should call gtk_widget_add_accelerator.
484 * Will probably need to change sp_shortcut_invoke callers.
485 *
486 * The existing gtk_label_new_with_mnemonic call can be replaced with
487 * g_object_new(GTK_TYPE_ACCEL_LABEL, NULL) followed by
488 * gtk_label_set_text_with_mnemonic(lbl, str).
489 */
490 static GtkAccelLabelClass const &accel_lbl_cls
491 = *(GtkAccelLabelClass const *) g_type_class_peek_static(GTK_TYPE_ACCEL_LABEL);
493 struct { unsigned test; char const *name; } const modifier_tbl[] = {
494 { SP_SHORTCUT_SHIFT_MASK, accel_lbl_cls.mod_name_shift },
495 { SP_SHORTCUT_CONTROL_MASK, accel_lbl_cls.mod_name_control },
496 { SP_SHORTCUT_ALT_MASK, accel_lbl_cls.mod_name_alt }
497 };
499 gchar *p = c;
500 gchar *end = p + 256;
502 for (unsigned i = 0; i < G_N_ELEMENTS(modifier_tbl); ++i) {
503 if ((shortcut & modifier_tbl[i].test)
504 && (p < end))
505 {
506 p += g_snprintf(p, end - p, "%s%s",
507 modifier_tbl[i].name,
508 accel_lbl_cls.mod_separator);
509 }
510 }
511 if (p < end) {
512 p += g_snprintf(p, end - p, "%s", sp_key_name(shortcut & 0xffffff));
513 }
514 end[-1] = '\0'; // snprintf doesn't guarantee to nul-terminate the string.
515 }
517 void
518 sp_ui_dialog_title_string(Inkscape::Verb *verb, gchar *c)
519 {
520 SPAction *action;
521 unsigned int shortcut;
522 gchar *s;
523 gchar key[256];
524 gchar *atitle;
526 action = verb->get_action(NULL);
527 if (!action)
528 return;
530 atitle = sp_action_get_title(action);
532 s = g_stpcpy(c, atitle);
534 g_free(atitle);
536 shortcut = sp_shortcut_get_primary(verb);
537 if (shortcut) {
538 s = g_stpcpy(s, " (");
539 sp_ui_shortcut_string(shortcut, key);
540 s = g_stpcpy(s, key);
541 s = g_stpcpy(s, ")");
542 }
543 }
546 /**
547 * sp_ui_menu_append_item_from_verb
548 *
549 * Appends a custom menu UI from a verb.
550 *
551 */
553 static GtkWidget *
554 sp_ui_menu_append_item_from_verb(GtkMenu *menu, Inkscape::Verb *verb, Inkscape::UI::View::View *view, bool radio = false, GSList *group = NULL)
555 {
556 SPAction *action;
557 GtkWidget *item;
559 if (verb->get_code() == SP_VERB_NONE) {
561 item = gtk_separator_menu_item_new();
563 } else {
564 unsigned int shortcut;
566 action = verb->get_action(view);
568 if (!action) return NULL;
570 shortcut = sp_shortcut_get_primary(verb);
571 if (shortcut) {
572 gchar c[256];
573 sp_ui_shortcut_string(shortcut, c);
574 GtkWidget *const hb = gtk_hbox_new(FALSE, 16);
575 GtkWidget *const name_lbl = gtk_label_new("");
576 gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
577 gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
578 gtk_box_pack_start((GtkBox *) hb, name_lbl, TRUE, TRUE, 0);
579 GtkWidget *const accel_lbl = gtk_label_new(c);
580 gtk_misc_set_alignment((GtkMisc *) accel_lbl, 1.0, 0.5);
581 gtk_box_pack_end((GtkBox *) hb, accel_lbl, FALSE, FALSE, 0);
582 gtk_widget_show_all(hb);
583 if (radio) {
584 item = gtk_radio_menu_item_new (group);
585 } else {
586 item = gtk_image_menu_item_new();
587 }
588 gtk_container_add((GtkContainer *) item, hb);
589 } else {
590 if (radio) {
591 item = gtk_radio_menu_item_new (group);
592 } else {
593 item = gtk_image_menu_item_new ();
594 }
595 GtkWidget *const name_lbl = gtk_label_new("");
596 gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
597 gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
598 gtk_container_add((GtkContainer *) item, name_lbl);
599 }
601 nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
602 if (!action->sensitive) {
603 gtk_widget_set_sensitive(item, FALSE);
604 }
606 if (action->image) {
607 sp_ui_menuitem_add_icon(item, action->image);
608 }
609 gtk_widget_set_events(item, GDK_KEY_PRESS_MASK);
610 g_signal_connect( G_OBJECT(item), "activate",
611 G_CALLBACK(sp_ui_menu_activate), action );
613 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select_action), action );
614 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect_action), action );
615 }
617 gtk_widget_show(item);
618 gtk_menu_append(GTK_MENU(menu), item);
620 return item;
622 } // end of sp_ui_menu_append_item_from_verb
625 static void
626 checkitem_toggled(GtkCheckMenuItem *menuitem, gpointer user_data)
627 {
628 gchar const *pref = (gchar const *) user_data;
629 Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
631 Glib::ustring pref_path;
632 if (reinterpret_cast<SPDesktop*>(view)->is_focusMode()) {
633 pref_path = "/focus/";
634 } else if (reinterpret_cast<SPDesktop*>(view)->is_fullscreen()) {
635 pref_path = "/fullscreen/";
636 } else {
637 pref_path = "/window/";
638 }
639 pref_path += pref;
640 pref_path += "/state";
642 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
643 gboolean checked = gtk_check_menu_item_get_active(menuitem);
644 prefs->setBool(pref_path, checked);
646 reinterpret_cast<SPDesktop*>(view)->layoutWidget();
647 }
649 static gboolean
650 checkitem_update(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_data)
651 {
652 GtkCheckMenuItem *menuitem=GTK_CHECK_MENU_ITEM(widget);
654 gchar const *pref = (gchar const *) user_data;
655 Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
657 Glib::ustring pref_path;
658 if ((static_cast<SPDesktop*>(view))->is_fullscreen()) {
659 pref_path = "/fullscreen/";
660 } else {
661 pref_path = "/window/";
662 }
663 pref_path += pref;
665 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
666 bool ison = prefs->getBool(pref_path + "/state", true);
668 g_signal_handlers_block_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
669 gtk_check_menu_item_set_active(menuitem, ison);
670 g_signal_handlers_unblock_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
672 return FALSE;
673 }
676 void
677 sp_ui_menu_append_check_item_from_verb(GtkMenu *menu, Inkscape::UI::View::View *view, gchar const *label, gchar const *tip, gchar const *pref,
678 void (*callback_toggle)(GtkCheckMenuItem *, gpointer user_data),
679 gboolean (*callback_update)(GtkWidget *widget, GdkEventExpose *event, gpointer user_data),
680 Inkscape::Verb *verb)
681 {
682 GtkWidget *item;
684 unsigned int shortcut = 0;
685 SPAction *action = NULL;
687 if (verb) {
688 shortcut = sp_shortcut_get_primary(verb);
689 action = verb->get_action(view);
690 }
692 if (verb && shortcut) {
693 gchar c[256];
694 sp_ui_shortcut_string(shortcut, c);
696 GtkWidget *hb = gtk_hbox_new(FALSE, 16);
698 {
699 GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
700 gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
701 gtk_box_pack_start((GtkBox *) hb, l, TRUE, TRUE, 0);
702 }
704 {
705 GtkWidget *l = gtk_label_new(c);
706 gtk_misc_set_alignment((GtkMisc *) l, 1.0, 0.5);
707 gtk_box_pack_end((GtkBox *) hb, l, FALSE, FALSE, 0);
708 }
710 gtk_widget_show_all(hb);
712 item = gtk_check_menu_item_new();
713 gtk_container_add((GtkContainer *) item, hb);
714 } else {
715 GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
716 gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
717 item = gtk_check_menu_item_new();
718 gtk_container_add((GtkContainer *) item, l);
719 }
720 #if 0
721 nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
722 if (!action->sensitive) {
723 gtk_widget_set_sensitive(item, FALSE);
724 }
725 #endif
726 gtk_widget_show(item);
728 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
730 g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
732 g_signal_connect( G_OBJECT(item), "toggled", (GCallback) callback_toggle, (void *) pref);
733 g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) callback_update, (void *) pref);
735 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) (action ? action->tip : tip));
736 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
737 }
739 static void
740 sp_recent_open(GtkRecentChooser *recent_menu, gpointer /*user_data*/)
741 {
742 // dealing with the bizarre filename convention in Inkscape for now
743 gchar *uri = gtk_recent_chooser_get_current_uri(GTK_RECENT_CHOOSER(recent_menu));
744 gchar *local_fn = g_filename_from_uri(uri, NULL, NULL);
745 gchar *utf8_fn = g_filename_to_utf8(local_fn, -1, NULL, NULL, NULL);
746 sp_file_open(utf8_fn, NULL);
747 g_free(utf8_fn);
748 g_free(local_fn);
749 g_free(uri);
750 }
752 static void
753 sp_file_new_from_template(GtkWidget */*widget*/, gchar const *uri)
754 {
755 sp_file_new(uri);
756 }
758 void
759 sp_menu_append_new_templates(GtkWidget *menu, Inkscape::UI::View::View *view)
760 {
761 std::list<gchar *> sources;
762 sources.push_back( profile_path("templates") ); // first try user's local dir
763 sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir
765 // Use this loop to iterate through a list of possible document locations.
766 while (!sources.empty()) {
767 gchar *dirname = sources.front();
769 if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) {
770 GError *err = 0;
771 GDir *dir = g_dir_open(dirname, 0, &err);
773 if (dir) {
774 for (gchar const *file = g_dir_read_name(dir); file != NULL; file = g_dir_read_name(dir)) {
775 if (!g_str_has_suffix(file, ".svg") && !g_str_has_suffix(file, ".svgz"))
776 continue; // skip non-svg files
778 gchar *basename = g_path_get_basename(file);
779 if (g_str_has_suffix(basename, ".svg") && g_str_has_prefix(basename, "default."))
780 continue; // skip default.*.svg (i.e. default.svg and translations) - it's in the menu already
782 gchar const *filepath = g_build_filename(dirname, file, NULL);
783 gchar *dupfile = g_strndup(file, strlen(file) - 4);
784 gchar *filename = g_filename_to_utf8(dupfile, -1, NULL, NULL, NULL);
785 g_free(dupfile);
786 GtkWidget *item = gtk_menu_item_new_with_label(filename);
787 g_free(filename);
789 gtk_widget_show(item);
790 // how does "filepath" ever get freed?
791 g_signal_connect(G_OBJECT(item),
792 "activate",
793 G_CALLBACK(sp_file_new_from_template),
794 (gpointer) filepath);
796 if (view) {
797 // set null tip for now; later use a description from the template file
798 g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
799 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) NULL );
800 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
801 }
803 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
804 }
805 g_dir_close(dir);
806 }
807 }
809 // toss the dirname
810 g_free(dirname);
811 sources.pop_front();
812 }
813 }
815 void
816 sp_ui_checkboxes_menus(GtkMenu *m, Inkscape::UI::View::View *view)
817 {
818 //sp_ui_menu_append_check_item_from_verb(m, view, _("_Menu"), _("Show or hide the menu bar"), "menu",
819 // checkitem_toggled, checkitem_update, 0);
820 sp_ui_menu_append_check_item_from_verb(m, view, _("Commands Bar"), _("Show or hide the Commands bar (under the menu)"), "commands",
821 checkitem_toggled, checkitem_update, 0);
822 sp_ui_menu_append_check_item_from_verb(m, view, _("Snap controls Bar"), _("Show or hide the snapping controls"), "snaptoolbox",
823 checkitem_toggled, checkitem_update, 0);
824 sp_ui_menu_append_check_item_from_verb(m, view, _("Tool Controls Bar"), _("Show or hide the Tool Controls bar"), "toppanel",
825 checkitem_toggled, checkitem_update, 0);
826 sp_ui_menu_append_check_item_from_verb(m, view, _("_Toolbox"), _("Show or hide the main toolbox (on the left)"), "toolbox",
827 checkitem_toggled, checkitem_update, 0);
828 sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "rulers",
829 checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_RULERS));
830 sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "scrollbars",
831 checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_SCROLLBARS));
832 sp_ui_menu_append_check_item_from_verb(m, view, _("_Palette"), _("Show or hide the color palette"), "panels",
833 checkitem_toggled, checkitem_update, 0);
834 sp_ui_menu_append_check_item_from_verb(m, view, _("_Statusbar"), _("Show or hide the statusbar (at the bottom of the window)"), "statusbar",
835 checkitem_toggled, checkitem_update, 0);
836 }
838 /** @brief Observer that updates the recent list's max document count */
839 class MaxRecentObserver : public Inkscape::Preferences::Observer {
840 public:
841 MaxRecentObserver(GtkWidget *recent_menu) :
842 Observer("/options/maxrecentdocuments/value"),
843 _rm(recent_menu)
844 {}
845 virtual void notify(Inkscape::Preferences::Entry const &e) {
846 gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(_rm), e.getInt());
847 // hack: the recent menu doesn't repopulate after changing the limit, so we force it
848 g_signal_emit_by_name((gpointer) gtk_recent_manager_get_default(), "changed");
849 }
850 private:
851 GtkWidget *_rm;
852 };
854 /** \brief This function turns XML into a menu
855 \param menus This is the XML that defines the menu
856 \param menu Menu to be added to
857 \param view The View that this menu is being built for
859 This function is realitively simple as it just goes through the XML
860 and parses the individual elements. In the case of a submenu, it
861 just calls itself recursively. Because it is only reasonable to have
862 a couple of submenus, it is unlikely this will go more than two or
863 three times.
865 In the case of an unreconginzed verb, a menu item is made to identify
866 the verb that is missing, and display that. The menu item is also made
867 insensitive.
868 */
869 void
870 sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI::View::View *view)
871 {
872 if (menus == NULL) return;
873 if (menu == NULL) return;
874 GSList *group = NULL;
876 for (Inkscape::XML::Node *menu_pntr = menus;
877 menu_pntr != NULL;
878 menu_pntr = menu_pntr->next()) {
879 if (!strcmp(menu_pntr->name(), "submenu")) {
880 GtkWidget *mitem = gtk_menu_item_new_with_mnemonic(_(menu_pntr->attribute("name")));
881 GtkWidget *submenu = gtk_menu_new();
882 sp_ui_build_dyn_menus(menu_pntr->firstChild(), submenu, view);
883 gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), GTK_WIDGET(submenu));
884 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
885 continue;
886 }
887 if (!strcmp(menu_pntr->name(), "verb")) {
888 gchar const *verb_name = menu_pntr->attribute("verb-id");
889 Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name);
891 if (verb != NULL) {
892 if (menu_pntr->attribute("radio") != NULL) {
893 GtkWidget *item = sp_ui_menu_append_item_from_verb (GTK_MENU(menu), verb, view, true, group);
894 group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item));
895 if (menu_pntr->attribute("default") != NULL) {
896 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
897 }
898 } else {
899 sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view);
900 group = NULL;
901 }
902 } else {
903 gchar string[120];
904 g_snprintf(string, 120, _("Verb \"%s\" Unknown"), verb_name);
905 string[119] = '\0'; /* may not be terminated */
906 GtkWidget *item = gtk_menu_item_new_with_label(string);
907 gtk_widget_set_sensitive(item, false);
908 gtk_widget_show(item);
909 gtk_menu_append(GTK_MENU(menu), item);
910 }
911 continue;
912 }
913 if (!strcmp(menu_pntr->name(), "separator")
914 // This was spelt wrong in the original version
915 // and so this is for backward compatibility. It can
916 // probably be dropped after the 0.44 release.
917 || !strcmp(menu_pntr->name(), "seperator")) {
918 GtkWidget *item = gtk_separator_menu_item_new();
919 gtk_widget_show(item);
920 gtk_menu_append(GTK_MENU(menu), item);
921 continue;
922 }
923 if (!strcmp(menu_pntr->name(), "template-list")) {
924 sp_menu_append_new_templates(menu, view);
925 continue;
926 }
927 if (!strcmp(menu_pntr->name(), "recent-file-list")) {
928 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
930 // create recent files menu
931 int max_recent = prefs->getInt("/options/maxrecentdocuments/value");
932 GtkWidget *recent_menu = gtk_recent_chooser_menu_new_for_manager(gtk_recent_manager_get_default());
933 gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(recent_menu), max_recent);
934 // sort most recently used documents first to preserve previous behavior
935 gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(recent_menu), GTK_RECENT_SORT_MRU);
936 g_signal_connect(G_OBJECT(recent_menu), "item-activated", G_CALLBACK(sp_recent_open), (gpointer) NULL);
938 // add filter to only open files added by Inkscape
939 GtkRecentFilter *inkscape_only_filter = gtk_recent_filter_new();
940 gtk_recent_filter_add_application(inkscape_only_filter, g_get_prgname());
941 gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_menu), inkscape_only_filter);
943 GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic(_("Open _Recent"));
944 gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), recent_menu);
946 gtk_menu_append(GTK_MENU(menu), GTK_WIDGET(recent_item));
947 // this will just sit and update the list's item count
948 static MaxRecentObserver *mro = new MaxRecentObserver(recent_menu);
949 prefs->addObserver(*mro);
950 continue;
951 }
952 if (!strcmp(menu_pntr->name(), "objects-checkboxes")) {
953 sp_ui_checkboxes_menus(GTK_MENU(menu), view);
954 continue;
955 }
956 }
957 }
959 /** \brief Build the main tool bar
960 \param view View to build the bar for
962 Currently the main tool bar is built as a dynamic XML menu using
963 \c sp_ui_build_dyn_menus. This function builds the bar, and then
964 pass it to get items attached to it.
965 */
966 GtkWidget *
967 sp_ui_main_menubar(Inkscape::UI::View::View *view)
968 {
969 GtkWidget *mbar = gtk_menu_bar_new();
971 #ifdef GDK_WINDOWING_QUARTZ
972 ige_mac_menu_set_menu_bar(GTK_MENU_SHELL(mbar));
973 #endif
975 sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view);
977 #ifdef GDK_WINDOWING_QUARTZ
978 return NULL;
979 #else
980 return mbar;
981 #endif
982 }
984 static void leave_group(GtkMenuItem *, SPDesktop *desktop) {
985 desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer()));
986 }
988 static void enter_group(GtkMenuItem *mi, SPDesktop *desktop) {
989 desktop->setCurrentLayer(reinterpret_cast<SPObject *>(g_object_get_data(G_OBJECT(mi), "group")));
990 sp_desktop_selection(desktop)->clear();
991 }
993 GtkWidget *
994 sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item)
995 {
996 GtkWidget *m;
997 SPDesktop *dt;
999 dt = static_cast<SPDesktop*>(view);
1001 m = gtk_menu_new();
1003 /* Undo and Redo */
1004 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_UNDO), view);
1005 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_REDO), view);
1007 /* Separator */
1008 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1010 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_CUT), view);
1011 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_COPY), view);
1012 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_PASTE), view);
1014 /* Separator */
1015 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1017 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DUPLICATE), view);
1018 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DELETE), view);
1020 /* Item menu */
1021 if (item) {
1022 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1023 sp_object_menu((SPObject *) item, dt, GTK_MENU(m));
1024 }
1026 /* layer menu */
1027 SPGroup *group=NULL;
1028 if (item) {
1029 if (SP_IS_GROUP(item)) {
1030 group = SP_GROUP(item);
1031 } else if ( item != dt->currentRoot() && SP_IS_GROUP(SP_OBJECT_PARENT(item)) ) {
1032 group = SP_GROUP(SP_OBJECT_PARENT(item));
1033 }
1034 }
1036 if (( group && group != dt->currentLayer() ) ||
1037 ( dt->currentLayer() != dt->currentRoot() && SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) ) {
1038 /* Separator */
1039 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1040 }
1042 if ( group && group != dt->currentLayer() ) {
1043 /* TRANSLATORS: #%s is the id of the group e.g. <g id="#g7">, not a number. */
1044 gchar *label=g_strdup_printf(_("Enter group #%s"), SP_OBJECT_ID(group));
1045 GtkWidget *w = gtk_menu_item_new_with_label(label);
1046 g_free(label);
1047 g_object_set_data(G_OBJECT(w), "group", group);
1048 g_signal_connect(G_OBJECT(w), "activate", GCallback(enter_group), dt);
1049 gtk_widget_show(w);
1050 gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1051 }
1053 if ( dt->currentLayer() != dt->currentRoot() ) {
1054 if ( SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) {
1055 GtkWidget *w = gtk_menu_item_new_with_label(_("Go to parent"));
1056 g_signal_connect(G_OBJECT(w), "activate", GCallback(leave_group), dt);
1057 gtk_widget_show(w);
1058 gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1060 }
1061 }
1063 return m;
1064 }
1066 /* Drag and Drop */
1067 void
1068 sp_ui_drag_data_received(GtkWidget *widget,
1069 GdkDragContext *drag_context,
1070 gint x, gint y,
1071 GtkSelectionData *data,
1072 guint info,
1073 guint /*event_time*/,
1074 gpointer /*user_data*/)
1075 {
1076 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1077 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1079 switch (info) {
1080 #if ENABLE_MAGIC_COLORS
1081 case APP_X_INKY_COLOR:
1082 {
1083 int destX = 0;
1084 int destY = 0;
1085 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1086 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1088 SPItem *item = desktop->item_at_point( where, true );
1089 if ( item )
1090 {
1091 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1093 if ( data->length >= 8 ) {
1094 cmsHPROFILE srgbProf = cmsCreate_sRGBProfile();
1096 gchar c[64] = {0};
1097 // Careful about endian issues.
1098 guint16* dataVals = (guint16*)data->data;
1099 sp_svg_write_color( c, sizeof(c),
1100 SP_RGBA32_U_COMPOSE(
1101 0x0ff & (dataVals[0] >> 8),
1102 0x0ff & (dataVals[1] >> 8),
1103 0x0ff & (dataVals[2] >> 8),
1104 0xff // can't have transparency in the color itself
1105 //0x0ff & (data->data[3] >> 8),
1106 ));
1107 SPCSSAttr *css = sp_repr_css_attr_new();
1108 bool updatePerformed = false;
1110 if ( data->length > 14 ) {
1111 int flags = dataVals[4];
1113 // piggie-backed palette entry info
1114 int index = dataVals[5];
1115 Glib::ustring palName;
1116 for ( int i = 0; i < dataVals[6]; i++ ) {
1117 palName += (gunichar)dataVals[7+i];
1118 }
1120 // Now hook in a magic tag of some sort.
1121 if ( !palName.empty() && (flags & 1) ) {
1122 gchar* str = g_strdup_printf("%d|", index);
1123 palName.insert( 0, str );
1124 g_free(str);
1125 str = 0;
1127 sp_object_setAttribute( SP_OBJECT(item),
1128 fillnotstroke ? "inkscape:x-fill-tag":"inkscape:x-stroke-tag",
1129 palName.c_str(),
1130 false );
1131 item->updateRepr();
1133 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1134 updatePerformed = true;
1135 }
1136 }
1138 if ( !updatePerformed ) {
1139 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1140 }
1142 sp_desktop_apply_css_recursive( item, css, true );
1143 item->updateRepr();
1145 sp_document_done( doc , SP_VERB_NONE,
1146 _("Drop color"));
1148 if ( srgbProf ) {
1149 cmsCloseProfile( srgbProf );
1150 }
1151 }
1152 }
1153 }
1154 break;
1155 #endif // ENABLE_MAGIC_COLORS
1157 case APP_X_COLOR:
1158 {
1159 int destX = 0;
1160 int destY = 0;
1161 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1162 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1163 Geom::Point const button_dt(desktop->w2d(where));
1164 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1166 if ( data->length == 8 ) {
1167 gchar c[64] = {0};
1168 // Careful about endian issues.
1169 guint16* dataVals = (guint16*)data->data;
1170 sp_svg_write_color( c, 64,
1171 SP_RGBA32_U_COMPOSE(
1172 0x0ff & (dataVals[0] >> 8),
1173 0x0ff & (dataVals[1] >> 8),
1174 0x0ff & (dataVals[2] >> 8),
1175 0xff // can't have transparency in the color itself
1176 //0x0ff & (data->data[3] >> 8),
1177 ));
1179 SPItem *item = desktop->item_at_point( where, true );
1181 bool consumed = false;
1182 if (desktop->event_context && desktop->event_context->get_drag()) {
1183 consumed = desktop->event_context->get_drag()->dropColor(item, c, button_dt);
1184 if (consumed) {
1185 sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1186 desktop->event_context->get_drag()->updateDraggers();
1187 }
1188 }
1190 //if (!consumed && tools_active(desktop, TOOLS_TEXT)) {
1191 // consumed = sp_text_context_drop_color(c, button_doc);
1192 // if (consumed) {
1193 // sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient stop"));
1194 // }
1195 //}
1197 if (!consumed && item) {
1198 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1199 if (fillnotstroke &&
1200 (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1201 Path *livarot_path = Path_for_item(item, true, true);
1202 livarot_path->ConvertWithBackData(0.04);
1204 boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1205 if (position) {
1206 Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1207 Geom::Point delta = nearest - button_doc;
1208 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1209 delta = desktop->d2w(delta);
1210 double stroke_tolerance =
1211 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1212 desktop->current_zoom() *
1213 SP_OBJECT_STYLE (item)->stroke_width.computed *
1214 to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1215 : 0.0)
1216 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1218 if (Geom::L2 (delta) < stroke_tolerance) {
1219 fillnotstroke = false;
1220 }
1221 }
1222 delete livarot_path;
1223 }
1225 SPCSSAttr *css = sp_repr_css_attr_new();
1226 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1228 sp_desktop_apply_css_recursive( item, css, true );
1229 item->updateRepr();
1231 sp_document_done( doc , SP_VERB_NONE,
1232 _("Drop color"));
1233 }
1234 }
1235 }
1236 break;
1238 case APP_X_NOCOLOR:
1239 {
1240 gchar* c = g_strdup("none"); // temp
1241 int destX = 0;
1242 int destY = 0;
1243 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1244 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1245 Geom::Point const button_dt(desktop->w2d(where));
1246 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1248 SPItem *item = desktop->item_at_point( where, true );
1250 bool consumed = false;
1251 if (desktop->event_context && desktop->event_context->get_drag()) {
1252 consumed = desktop->event_context->get_drag()->dropColor(item, c, button_dt);
1253 if (consumed) {
1254 sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1255 desktop->event_context->get_drag()->updateDraggers();
1256 }
1257 }
1259 if (!consumed && item) {
1260 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1261 if (fillnotstroke &&
1262 (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1263 Path *livarot_path = Path_for_item(item, true, true);
1264 livarot_path->ConvertWithBackData(0.04);
1266 boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1267 if (position) {
1268 Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1269 Geom::Point delta = nearest - button_doc;
1270 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1271 delta = desktop->d2w(delta);
1272 double stroke_tolerance =
1273 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1274 desktop->current_zoom() *
1275 SP_OBJECT_STYLE (item)->stroke_width.computed *
1276 to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1277 : 0.0)
1278 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1280 if (Geom::L2 (delta) < stroke_tolerance) {
1281 fillnotstroke = false;
1282 }
1283 }
1284 delete livarot_path;
1285 }
1287 SPCSSAttr *css = sp_repr_css_attr_new();
1288 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1290 sp_desktop_apply_css_recursive( item, css, true );
1291 item->updateRepr();
1293 sp_document_done( doc , SP_VERB_NONE,
1294 _("Drop color"));
1295 }
1296 g_free(c);
1297 }
1298 break;
1300 case SVG_DATA:
1301 case SVG_XML_DATA: {
1302 gchar *svgdata = (gchar *)data->data;
1304 Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI);
1306 if (rnewdoc == NULL) {
1307 sp_ui_error_dialog(_("Could not parse SVG data"));
1308 return;
1309 }
1311 Inkscape::XML::Node *repr = rnewdoc->root();
1312 gchar const *style = repr->attribute("style");
1314 Inkscape::XML::Node *newgroup = rnewdoc->createElement("svg:g");
1315 newgroup->setAttribute("style", style);
1317 Inkscape::XML::Document * xml_doc = sp_document_repr_doc(doc);
1318 for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) {
1319 Inkscape::XML::Node *newchild = child->duplicate(xml_doc);
1320 newgroup->appendChild(newchild);
1321 }
1323 Inkscape::GC::release(rnewdoc);
1325 // Add it to the current layer
1327 // Greg's edits to add intelligent positioning of svg drops
1328 SPObject *new_obj = NULL;
1329 new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
1331 Inkscape::Selection *selection = sp_desktop_selection(desktop);
1332 selection->set(SP_ITEM(new_obj));
1333 // To move the imported object, we must temporarily set the "transform pattern with
1334 // object" option.
1335 {
1336 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1337 bool const saved_pref = prefs->getBool("/options/transform/pattern", true);
1338 prefs->setBool("/options/transform/pattern", true);
1339 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
1340 Geom::OptRect sel_bbox = selection->bounds();
1341 if (sel_bbox) {
1342 Geom::Point m( desktop->point() - sel_bbox->midpoint() );
1343 sp_selection_move_relative(selection, m);
1344 }
1345 prefs->setBool("/options/transform/pattern", saved_pref);
1346 }
1348 Inkscape::GC::release(newgroup);
1349 sp_document_done(doc, SP_VERB_NONE,
1350 _("Drop SVG"));
1351 break;
1352 }
1354 case URI_LIST: {
1355 gchar *uri = (gchar *)data->data;
1356 sp_ui_import_files(uri);
1357 break;
1358 }
1360 case PNG_DATA:
1361 case JPEG_DATA:
1362 case IMAGE_DATA: {
1363 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
1364 Inkscape::XML::Node *newImage = xml_doc->createElement("svg:image");
1365 gchar *atom_name = gdk_atom_name(data->type);
1367 // this formula taken from Glib docs
1368 guint needed_size = data->length * 4 / 3 + data->length * 4 / (3 * 72) + 7;
1369 needed_size += 5 + 8 + strlen(atom_name); // 5 bytes for data:, 8 for ;base64,
1371 gchar *buffer = (gchar *) g_malloc(needed_size), *buf_work = buffer;
1372 buf_work += g_sprintf(buffer, "data:%s;base64,", atom_name);
1374 gint state = 0, save = 0;
1375 g_base64_encode_step(data->data, data->length, TRUE, buf_work, &state, &save);
1376 g_base64_encode_close(TRUE, buf_work, &state, &save);
1378 newImage->setAttribute("xlink:href", buffer);
1379 g_free(buffer);
1381 GError *error = NULL;
1382 GdkPixbufLoader *loader = gdk_pixbuf_loader_new_with_mime_type( gdk_atom_name(data->type), &error );
1383 if ( loader ) {
1384 error = NULL;
1385 if ( gdk_pixbuf_loader_write( loader, data->data, data->length, &error) ) {
1386 GdkPixbuf *pbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1387 if ( pbuf ) {
1388 char tmp[1024];
1389 int width = gdk_pixbuf_get_width(pbuf);
1390 int height = gdk_pixbuf_get_height(pbuf);
1391 snprintf( tmp, sizeof(tmp), "%d", width );
1392 newImage->setAttribute("width", tmp);
1394 snprintf( tmp, sizeof(tmp), "%d", height );
1395 newImage->setAttribute("height", tmp);
1396 }
1397 }
1398 }
1399 g_free(atom_name);
1401 // Add it to the current layer
1402 desktop->currentLayer()->appendChildRepr(newImage);
1404 Inkscape::GC::release(newImage);
1405 sp_document_done( doc , SP_VERB_NONE,
1406 _("Drop bitmap image"));
1407 break;
1408 }
1409 }
1410 }
1412 #include "gradient-context.h"
1414 void sp_ui_drag_motion( GtkWidget */*widget*/,
1415 GdkDragContext */*drag_context*/,
1416 gint /*x*/, gint /*y*/,
1417 GtkSelectionData */*data*/,
1418 guint /*info*/,
1419 guint /*event_time*/,
1420 gpointer /*user_data*/)
1421 {
1422 // SPDocument *doc = SP_ACTIVE_DOCUMENT;
1423 // SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1426 // g_message("drag-n-drop motion (%4d, %4d) at %d", x, y, event_time);
1427 }
1429 static void sp_ui_drag_leave( GtkWidget */*widget*/,
1430 GdkDragContext */*drag_context*/,
1431 guint /*event_time*/,
1432 gpointer /*user_data*/ )
1433 {
1434 // g_message("drag-n-drop leave at %d", event_time);
1435 }
1437 static void
1438 sp_ui_import_files(gchar *buffer)
1439 {
1440 GList *list = gnome_uri_list_extract_filenames(buffer);
1441 if (!list)
1442 return;
1443 g_list_foreach(list, sp_ui_import_one_file_with_check, NULL);
1444 g_list_foreach(list, (GFunc) g_free, NULL);
1445 g_list_free(list);
1446 }
1448 static void
1449 sp_ui_import_one_file_with_check(gpointer filename, gpointer /*unused*/)
1450 {
1451 if (filename) {
1452 if (strlen((char const *)filename) > 2)
1453 sp_ui_import_one_file((char const *)filename);
1454 }
1455 }
1457 static void
1458 sp_ui_import_one_file(char const *filename)
1459 {
1460 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1461 if (!doc) return;
1463 if (filename == NULL) return;
1465 // Pass off to common implementation
1466 // TODO might need to get the proper type of Inkscape::Extension::Extension
1467 file_import( doc, filename, NULL );
1468 }
1470 void
1471 sp_ui_error_dialog(gchar const *message)
1472 {
1473 GtkWidget *dlg;
1474 gchar *safeMsg = Inkscape::IO::sanitizeString(message);
1476 dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
1477 GTK_BUTTONS_CLOSE, "%s", safeMsg);
1478 sp_transientize(dlg);
1479 gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE);
1480 gtk_dialog_run(GTK_DIALOG(dlg));
1481 gtk_widget_destroy(dlg);
1482 g_free(safeMsg);
1483 }
1485 bool
1486 sp_ui_overwrite_file(gchar const *filename)
1487 {
1488 bool return_value = FALSE;
1490 if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) {
1491 Gtk::Window *window = SP_ACTIVE_DESKTOP->getToplevel();
1492 gchar* baseName = g_path_get_basename( filename );
1493 gchar* dirName = g_path_get_dirname( filename );
1494 GtkWidget* dialog = gtk_message_dialog_new_with_markup( window->gobj(),
1495 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
1496 GTK_MESSAGE_QUESTION,
1497 GTK_BUTTONS_NONE,
1498 _( "<span weight=\"bold\" size=\"larger\">A file named \"%s\" already exists. Do you want to replace it?</span>\n\n"
1499 "The file already exists in \"%s\". Replacing it will overwrite its contents." ),
1500 baseName,
1501 dirName
1502 );
1503 gtk_dialog_add_buttons( GTK_DIALOG(dialog),
1504 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1505 _("Replace"), GTK_RESPONSE_YES,
1506 NULL );
1507 gtk_dialog_set_default_response( GTK_DIALOG(dialog), GTK_RESPONSE_YES );
1509 if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_YES ) {
1510 return_value = TRUE;
1511 } else {
1512 return_value = FALSE;
1513 }
1514 gtk_widget_destroy(dialog);
1515 g_free( baseName );
1516 g_free( dirName );
1517 } else {
1518 return_value = TRUE;
1519 }
1521 return return_value;
1522 }
1524 static void
1525 sp_ui_menu_item_set_sensitive(SPAction */*action*/, unsigned int sensitive, void *data)
1526 {
1527 return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
1528 }
1530 static void
1531 sp_ui_menu_item_set_name(SPAction */*action*/, Glib::ustring name, void *data)
1532 {
1533 void *child = GTK_BIN (data)->child;
1534 //child is either
1535 //- a GtkHBox, whose first child is a label displaying name if the menu
1536 //item has an accel key
1537 //- a GtkLabel if the menu has no accel key
1538 if (GTK_IS_LABEL(child)) {
1539 gtk_label_set_markup_with_mnemonic(GTK_LABEL (child), name.c_str());
1540 } else if (GTK_IS_HBOX(child)) {
1541 gtk_label_set_markup_with_mnemonic(
1542 GTK_LABEL (gtk_container_get_children(GTK_CONTAINER (child))->data),
1543 name.c_str());
1544 }//else sp_ui_menu_append_item_from_verb has been modified and can set
1545 //a menu item in yet another way...
1546 }
1549 /*
1550 Local Variables:
1551 mode:c++
1552 c-file-style:"stroustrup"
1553 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1554 indent-tabs-mode:nil
1555 fill-column:99
1556 End:
1557 */
1558 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :