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>
24 #include "inkscape-private.h"
25 #include "extension/effect.h"
26 #include "widgets/icon.h"
27 #include "preferences.h"
28 #include "path-prefix.h"
30 #include "shortcuts.h"
32 #include "document.h"
33 #include "desktop-handles.h"
34 #include "file.h"
35 #include "interface.h"
36 #include "desktop.h"
37 #include "ui/context-menu.h"
38 #include "selection.h"
39 #include "selection-chemistry.h"
40 #include "svg-view-widget.h"
41 #include "widgets/desktop-widget.h"
42 #include "sp-item-group.h"
43 #include "sp-text.h"
44 #include "sp-flowtext.h"
45 #include "sp-namedview.h"
46 #include "ui/view/view.h"
48 #include "helper/action.h"
49 #include "helper/gnome-utils.h"
50 #include "helper/window.h"
52 #include "io/sys.h"
53 #include "io/stringstream.h"
54 #include "io/base64stream.h"
56 #include "dialogs/dialog-events.h"
58 #include "message-context.h"
60 // Added for color drag-n-drop
61 #if ENABLE_LCMS
62 #include "lcms.h"
63 #endif // ENABLE_LCMS
64 #include "display/sp-canvas.h"
65 #include "color.h"
66 #include "svg/svg-color.h"
67 #include "desktop-style.h"
68 #include "style.h"
69 #include "event-context.h"
70 #include "gradient-drag.h"
72 // Include Mac OS X menu synchronization on native OSX build
73 #ifdef GDK_WINDOWING_QUARTZ
74 #include "ige-mac-menu.h"
75 #endif
77 using Inkscape::IO::StringOutputStream;
78 using Inkscape::IO::Base64OutputStream;
80 /* Drag and Drop */
81 typedef enum {
82 URI_LIST,
83 SVG_XML_DATA,
84 SVG_DATA,
85 PNG_DATA,
86 JPEG_DATA,
87 IMAGE_DATA,
88 APP_X_INKY_COLOR,
89 APP_X_COLOR
90 } ui_drop_target_info;
92 static GtkTargetEntry ui_drop_target_entries [] = {
93 {(gchar *)"text/uri-list", 0, URI_LIST },
94 {(gchar *)"image/svg+xml", 0, SVG_XML_DATA },
95 {(gchar *)"image/svg", 0, SVG_DATA },
96 {(gchar *)"image/png", 0, PNG_DATA },
97 {(gchar *)"image/jpeg", 0, JPEG_DATA },
98 #if ENABLE_MAGIC_COLORS
99 {(gchar *)"application/x-inkscape-color", 0, APP_X_INKY_COLOR},
100 #endif // ENABLE_MAGIC_COLORS
101 {(gchar *)"application/x-color", 0, APP_X_COLOR }
102 };
104 static GtkTargetEntry *completeDropTargets = 0;
105 static int completeDropTargetsCount = 0;
107 #define ENTRIES_SIZE(n) sizeof(n)/sizeof(n[0])
108 static guint nui_drop_target_entries = ENTRIES_SIZE(ui_drop_target_entries);
109 static void sp_ui_import_files(gchar *buffer);
110 static void sp_ui_import_one_file(char const *filename);
111 static void sp_ui_import_one_file_with_check(gpointer filename, gpointer unused);
112 static void sp_ui_drag_data_received(GtkWidget *widget,
113 GdkDragContext *drag_context,
114 gint x, gint y,
115 GtkSelectionData *data,
116 guint info,
117 guint event_time,
118 gpointer user_data);
119 static void sp_ui_drag_motion( GtkWidget *widget,
120 GdkDragContext *drag_context,
121 gint x, gint y,
122 GtkSelectionData *data,
123 guint info,
124 guint event_time,
125 gpointer user_data );
126 static void sp_ui_drag_leave( GtkWidget *widget,
127 GdkDragContext *drag_context,
128 guint event_time,
129 gpointer user_data );
130 static void sp_ui_menu_item_set_sensitive(SPAction *action,
131 unsigned int sensitive,
132 void *data);
133 static void sp_ui_menu_item_set_name(SPAction *action,
134 Glib::ustring name,
135 void *data);
136 static void sp_recent_open(GtkRecentChooser *, gpointer);
138 SPActionEventVector menu_item_event_vector = {
139 {NULL},
140 NULL,
141 NULL, /* set_active */
142 sp_ui_menu_item_set_sensitive, /* set_sensitive */
143 NULL, /* set_shortcut */
144 sp_ui_menu_item_set_name /* set_name */
145 };
147 static const int MIN_ONSCREEN_DISTANCE = 50;
149 void
150 sp_create_window(SPViewWidget *vw, gboolean editable)
151 {
152 g_return_if_fail(vw != NULL);
153 g_return_if_fail(SP_IS_VIEW_WIDGET(vw));
155 Gtk::Window *win = Inkscape::UI::window_new("", TRUE);
157 gtk_container_add(GTK_CONTAINER(win->gobj()), GTK_WIDGET(vw));
158 gtk_widget_show(GTK_WIDGET(vw));
160 if (editable) {
161 g_object_set_data(G_OBJECT(vw), "window", win);
163 SPDesktopWidget *desktop_widget = reinterpret_cast<SPDesktopWidget*>(vw);
164 SPDesktop* desktop = desktop_widget->desktop;
166 desktop_widget->window = win;
168 win->set_data("desktop", desktop);
169 win->set_data("desktopwidget", desktop_widget);
171 win->signal_delete_event().connect(sigc::mem_fun(*(SPDesktop*)vw->view, &SPDesktop::onDeleteUI));
172 win->signal_window_state_event().connect(sigc::mem_fun(*desktop, &SPDesktop::onWindowStateEvent));
173 win->signal_focus_in_event().connect(sigc::mem_fun(*desktop_widget, &SPDesktopWidget::onFocusInEvent));
175 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
176 gint prefs_geometry =
177 (2==prefs->getInt("/options/savewindowgeometry/value", 0));
178 if (prefs_geometry) {
179 gint pw = prefs->getInt("/desktop/geometry/width", -1);
180 gint ph = prefs->getInt("/desktop/geometry/height", -1);
181 gint px = prefs->getInt("/desktop/geometry/x", -1);
182 gint py = prefs->getInt("/desktop/geometry/y", -1);
183 gint full = prefs->getBool("/desktop/geometry/fullscreen");
184 gint maxed = prefs->getBool("/desktop/geometry/maximized");
185 if (pw>0 && ph>0) {
186 gint w = MIN(gdk_screen_width(), pw);
187 gint h = MIN(gdk_screen_height(), ph);
188 gint x = MIN(gdk_screen_width() - MIN_ONSCREEN_DISTANCE, px);
189 gint y = MIN(gdk_screen_height() - MIN_ONSCREEN_DISTANCE, py);
190 if (w>0 && h>0 && x>0 && y>0) {
191 x = MIN(gdk_screen_width() - w, x);
192 y = MIN(gdk_screen_height() - h, y);
193 }
194 if (w>0 && h>0) {
195 desktop->setWindowSize(w, h);
196 }
198 // Only restore xy for the first window so subsequent windows don't overlap exactly
199 // with first. (Maybe rule should be only restore xy if it's different from xy of
200 // other desktops?)
202 // Empirically it seems that active_desktop==this desktop only the first time a
203 // desktop is created.
204 if (x>0 && y>0) {
205 SPDesktop *active_desktop = SP_ACTIVE_DESKTOP;
206 if (active_desktop == desktop || active_desktop==NULL) {
207 desktop->setWindowPosition(Geom::Point(x, y));
208 }
209 }
210 }
211 if (maxed) {
212 win->maximize();
213 }
214 if (full) {
215 win->fullscreen();
216 }
217 }
219 } else {
220 gtk_window_set_policy(GTK_WINDOW(win->gobj()), TRUE, TRUE, TRUE);
221 }
223 if ( completeDropTargets == 0 || completeDropTargetsCount == 0 )
224 {
225 std::vector<gchar*> types;
227 GSList *list = gdk_pixbuf_get_formats();
228 while ( list ) {
229 int i = 0;
230 GdkPixbufFormat *one = (GdkPixbufFormat*)list->data;
231 gchar** typesXX = gdk_pixbuf_format_get_mime_types(one);
232 for ( i = 0; typesXX[i]; i++ ) {
233 types.push_back(g_strdup(typesXX[i]));
234 }
235 g_strfreev(typesXX);
237 list = g_slist_next(list);
238 }
239 completeDropTargetsCount = nui_drop_target_entries + types.size();
240 completeDropTargets = new GtkTargetEntry[completeDropTargetsCount];
241 for ( int i = 0; i < (int)nui_drop_target_entries; i++ ) {
242 completeDropTargets[i] = ui_drop_target_entries[i];
243 }
244 int pos = nui_drop_target_entries;
246 for (std::vector<gchar*>::iterator it = types.begin() ; it != types.end() ; it++) {
247 completeDropTargets[pos].target = *it;
248 completeDropTargets[pos].flags = 0;
249 completeDropTargets[pos].info = IMAGE_DATA;
250 pos++;
251 }
252 }
254 gtk_drag_dest_set((GtkWidget*)win->gobj(),
255 GTK_DEST_DEFAULT_ALL,
256 completeDropTargets,
257 completeDropTargetsCount,
258 GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE));
261 g_signal_connect(G_OBJECT(win->gobj()),
262 "drag_data_received",
263 G_CALLBACK(sp_ui_drag_data_received),
264 NULL);
265 g_signal_connect(G_OBJECT(win->gobj()),
266 "drag_motion",
267 G_CALLBACK(sp_ui_drag_motion),
268 NULL);
269 g_signal_connect(G_OBJECT(win->gobj()),
270 "drag_leave",
271 G_CALLBACK(sp_ui_drag_leave),
272 NULL);
273 win->show();
275 // needed because the first ACTIVATE_DESKTOP was sent when there was no window yet
276 inkscape_reactivate_desktop(SP_DESKTOP_WIDGET(vw)->desktop);
277 }
279 void
280 sp_ui_new_view()
281 {
282 SPDocument *document;
283 SPViewWidget *dtw;
285 document = SP_ACTIVE_DOCUMENT;
286 if (!document) return;
288 dtw = sp_desktop_widget_new(sp_document_namedview(document, NULL));
289 g_return_if_fail(dtw != NULL);
291 sp_create_window(dtw, TRUE);
292 sp_namedview_window_from_document(static_cast<SPDesktop*>(dtw->view));
293 sp_namedview_update_layers_from_document(static_cast<SPDesktop*>(dtw->view));
294 }
296 /* TODO: not yet working */
297 /* To be re-enabled (by adding to menu) once it works. */
298 void
299 sp_ui_new_view_preview()
300 {
301 SPDocument *document;
302 SPViewWidget *dtw;
304 document = SP_ACTIVE_DOCUMENT;
305 if (!document) return;
307 dtw = (SPViewWidget *) sp_svg_view_widget_new(document);
308 g_return_if_fail(dtw != NULL);
309 sp_svg_view_widget_set_resize(SP_SVG_VIEW_WIDGET(dtw), TRUE, 400.0, 400.0);
311 sp_create_window(dtw, FALSE);
312 }
314 /**
315 * \param widget unused
316 */
317 void
318 sp_ui_close_view(GtkWidget */*widget*/)
319 {
320 if (SP_ACTIVE_DESKTOP == NULL) {
321 return;
322 }
323 if ((SP_ACTIVE_DESKTOP)->shutdown()) {
324 return;
325 }
326 SP_ACTIVE_DESKTOP->destroyWidget();
327 }
330 /**
331 * sp_ui_close_all
332 *
333 * This function is called to exit the program, and iterates through all
334 * open document view windows, attempting to close each in turn. If the
335 * view has unsaved information, the user will be prompted to save,
336 * discard, or cancel.
337 *
338 * Returns FALSE if the user cancels the close_all operation, TRUE
339 * otherwise.
340 */
341 unsigned int
342 sp_ui_close_all(void)
343 {
344 /* Iterate through all the windows, destroying each in the order they
345 become active */
346 while (SP_ACTIVE_DESKTOP) {
347 if ((SP_ACTIVE_DESKTOP)->shutdown()) {
348 /* The user cancelled the operation, so end doing the close */
349 return FALSE;
350 }
351 SP_ACTIVE_DESKTOP->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 This function turns XML into a menu
847 \param menus This is the XML that defines the menu
848 \param menu Menu to be added to
849 \param view The View that this menu is being built for
851 This function is realitively simple as it just goes through the XML
852 and parses the individual elements. In the case of a submenu, it
853 just calls itself recursively. Because it is only reasonable to have
854 a couple of submenus, it is unlikely this will go more than two or
855 three times.
857 In the case of an unreconginzed verb, a menu item is made to identify
858 the verb that is missing, and display that. The menu item is also made
859 insensitive.
860 */
861 void
862 sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI::View::View *view)
863 {
864 if (menus == NULL) return;
865 if (menu == NULL) return;
866 GSList *group = NULL;
868 for (Inkscape::XML::Node *menu_pntr = menus;
869 menu_pntr != NULL;
870 menu_pntr = menu_pntr->next()) {
871 if (!strcmp(menu_pntr->name(), "submenu")) {
872 GtkWidget *mitem = gtk_menu_item_new_with_mnemonic(_(menu_pntr->attribute("name")));
873 GtkWidget *submenu = gtk_menu_new();
874 sp_ui_build_dyn_menus(menu_pntr->firstChild(), submenu, view);
875 gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), GTK_WIDGET(submenu));
876 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
877 continue;
878 }
879 if (!strcmp(menu_pntr->name(), "verb")) {
880 gchar const *verb_name = menu_pntr->attribute("verb-id");
881 Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name);
883 if (verb != NULL) {
884 if (menu_pntr->attribute("radio") != NULL) {
885 GtkWidget *item = sp_ui_menu_append_item_from_verb (GTK_MENU(menu), verb, view, true, group);
886 group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item));
887 if (menu_pntr->attribute("default") != NULL) {
888 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
889 }
890 } else {
891 sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view);
892 group = NULL;
893 }
894 } else {
895 gchar string[120];
896 g_snprintf(string, 120, _("Verb \"%s\" Unknown"), verb_name);
897 string[119] = '\0'; /* may not be terminated */
898 GtkWidget *item = gtk_menu_item_new_with_label(string);
899 gtk_widget_set_sensitive(item, false);
900 gtk_widget_show(item);
901 gtk_menu_append(GTK_MENU(menu), item);
902 }
903 continue;
904 }
905 if (!strcmp(menu_pntr->name(), "separator")
906 // This was spelt wrong in the original version
907 // and so this is for backward compatibility. It can
908 // probably be dropped after the 0.44 release.
909 || !strcmp(menu_pntr->name(), "seperator")) {
910 GtkWidget *item = gtk_separator_menu_item_new();
911 gtk_widget_show(item);
912 gtk_menu_append(GTK_MENU(menu), item);
913 continue;
914 }
915 if (!strcmp(menu_pntr->name(), "template-list")) {
916 sp_menu_append_new_templates(menu, view);
917 continue;
918 }
919 if (!strcmp(menu_pntr->name(), "recent-file-list")) {
920 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
922 // create recent files menu
923 int max_recent = prefs->getInt("/options/maxrecentdocuments/value");
924 GtkWidget *recent_menu = gtk_recent_chooser_menu_new_for_manager(gtk_recent_manager_get_default());
925 gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(recent_menu), max_recent);
926 // sort most recently used documents first to preserve previous behavior
927 gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(recent_menu), GTK_RECENT_SORT_MRU);
928 g_signal_connect(G_OBJECT(recent_menu), "item-activated", G_CALLBACK(sp_recent_open), (gpointer) NULL);
930 // add filter to only open files added by Inkscape
931 GtkRecentFilter *inkscape_only_filter = gtk_recent_filter_new();
932 gtk_recent_filter_add_application(inkscape_only_filter, g_get_prgname());
933 gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_menu), inkscape_only_filter);
935 GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic(_("Open _Recent"));
936 gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), recent_menu);
938 gtk_menu_append(GTK_MENU(menu), GTK_WIDGET(recent_item));
939 continue;
940 }
941 if (!strcmp(menu_pntr->name(), "objects-checkboxes")) {
942 sp_ui_checkboxes_menus(GTK_MENU(menu), view);
943 continue;
944 }
945 }
946 }
948 /** \brief Build the main tool bar
949 \param view View to build the bar for
951 Currently the main tool bar is built as a dynamic XML menu using
952 \c sp_ui_build_dyn_menus. This function builds the bar, and then
953 pass it to get items attached to it.
954 */
955 GtkWidget *
956 sp_ui_main_menubar(Inkscape::UI::View::View *view)
957 {
958 GtkWidget *mbar = gtk_menu_bar_new();
960 #ifdef GDK_WINDOWING_QUARTZ
961 ige_mac_menu_set_menu_bar(GTK_MENU_SHELL(mbar));
962 #endif
964 sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view);
966 #ifdef GDK_WINDOWING_QUARTZ
967 return NULL;
968 #else
969 return mbar;
970 #endif
971 }
973 static void leave_group(GtkMenuItem *, SPDesktop *desktop) {
974 desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer()));
975 }
977 static void enter_group(GtkMenuItem *mi, SPDesktop *desktop) {
978 desktop->setCurrentLayer(reinterpret_cast<SPObject *>(g_object_get_data(G_OBJECT(mi), "group")));
979 sp_desktop_selection(desktop)->clear();
980 }
982 GtkWidget *
983 sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item)
984 {
985 GtkWidget *m;
986 SPDesktop *dt;
988 dt = static_cast<SPDesktop*>(view);
990 m = gtk_menu_new();
992 /* Undo and Redo */
993 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_UNDO), view);
994 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_REDO), view);
996 /* Separator */
997 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
999 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_CUT), view);
1000 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_COPY), view);
1001 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_PASTE), view);
1003 /* Separator */
1004 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1006 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DUPLICATE), view);
1007 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DELETE), view);
1009 /* Item menu */
1010 if (item) {
1011 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1012 sp_object_menu((SPObject *) item, dt, GTK_MENU(m));
1013 }
1015 /* layer menu */
1016 SPGroup *group=NULL;
1017 if (item) {
1018 if (SP_IS_GROUP(item)) {
1019 group = SP_GROUP(item);
1020 } else if ( item != dt->currentRoot() && SP_IS_GROUP(SP_OBJECT_PARENT(item)) ) {
1021 group = SP_GROUP(SP_OBJECT_PARENT(item));
1022 }
1023 }
1025 if (( group && group != dt->currentLayer() ) ||
1026 ( dt->currentLayer() != dt->currentRoot() && SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) ) {
1027 /* Separator */
1028 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1029 }
1031 if ( group && group != dt->currentLayer() ) {
1032 /* TRANSLATORS: #%s is the id of the group e.g. <g id="#g7">, not a number. */
1033 gchar *label=g_strdup_printf(_("Enter group #%s"), SP_OBJECT_ID(group));
1034 GtkWidget *w = gtk_menu_item_new_with_label(label);
1035 g_free(label);
1036 g_object_set_data(G_OBJECT(w), "group", group);
1037 g_signal_connect(G_OBJECT(w), "activate", GCallback(enter_group), dt);
1038 gtk_widget_show(w);
1039 gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1040 }
1042 if ( dt->currentLayer() != dt->currentRoot() ) {
1043 if ( SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) {
1044 GtkWidget *w = gtk_menu_item_new_with_label(_("Go to parent"));
1045 g_signal_connect(G_OBJECT(w), "activate", GCallback(leave_group), dt);
1046 gtk_widget_show(w);
1047 gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1049 }
1050 }
1052 return m;
1053 }
1055 /* Drag and Drop */
1056 void
1057 sp_ui_drag_data_received(GtkWidget *widget,
1058 GdkDragContext *drag_context,
1059 gint x, gint y,
1060 GtkSelectionData *data,
1061 guint info,
1062 guint /*event_time*/,
1063 gpointer /*user_data*/)
1064 {
1065 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1066 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1068 switch (info) {
1069 #if ENABLE_MAGIC_COLORS
1070 case APP_X_INKY_COLOR:
1071 {
1072 int destX = 0;
1073 int destY = 0;
1074 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1075 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1077 SPItem *item = desktop->item_at_point( where, true );
1078 if ( item )
1079 {
1080 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1082 if ( data->length >= 8 ) {
1083 cmsHPROFILE srgbProf = cmsCreate_sRGBProfile();
1085 gchar c[64] = {0};
1086 // Careful about endian issues.
1087 guint16* dataVals = (guint16*)data->data;
1088 sp_svg_write_color( c, sizeof(c),
1089 SP_RGBA32_U_COMPOSE(
1090 0x0ff & (dataVals[0] >> 8),
1091 0x0ff & (dataVals[1] >> 8),
1092 0x0ff & (dataVals[2] >> 8),
1093 0xff // can't have transparency in the color itself
1094 //0x0ff & (data->data[3] >> 8),
1095 ));
1096 SPCSSAttr *css = sp_repr_css_attr_new();
1097 bool updatePerformed = false;
1099 if ( data->length > 14 ) {
1100 int flags = dataVals[4];
1102 // piggie-backed palette entry info
1103 int index = dataVals[5];
1104 Glib::ustring palName;
1105 for ( int i = 0; i < dataVals[6]; i++ ) {
1106 palName += (gunichar)dataVals[7+i];
1107 }
1109 // Now hook in a magic tag of some sort.
1110 if ( !palName.empty() && (flags & 1) ) {
1111 gchar* str = g_strdup_printf("%d|", index);
1112 palName.insert( 0, str );
1113 g_free(str);
1114 str = 0;
1116 sp_object_setAttribute( SP_OBJECT(item),
1117 fillnotstroke ? "inkscape:x-fill-tag":"inkscape:x-stroke-tag",
1118 palName.c_str(),
1119 false );
1120 item->updateRepr();
1122 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1123 updatePerformed = true;
1124 }
1125 }
1127 if ( !updatePerformed ) {
1128 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1129 }
1131 sp_desktop_apply_css_recursive( item, css, true );
1132 item->updateRepr();
1134 sp_document_done( doc , SP_VERB_NONE,
1135 _("Drop color"));
1137 if ( srgbProf ) {
1138 cmsCloseProfile( srgbProf );
1139 }
1140 }
1141 }
1142 }
1143 break;
1144 #endif // ENABLE_MAGIC_COLORS
1146 case APP_X_COLOR:
1147 {
1148 int destX = 0;
1149 int destY = 0;
1150 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1151 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1152 Geom::Point const button_dt(desktop->w2d(where));
1153 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1155 if ( data->length == 8 ) {
1156 gchar c[64] = {0};
1157 // Careful about endian issues.
1158 guint16* dataVals = (guint16*)data->data;
1159 sp_svg_write_color( c, 64,
1160 SP_RGBA32_U_COMPOSE(
1161 0x0ff & (dataVals[0] >> 8),
1162 0x0ff & (dataVals[1] >> 8),
1163 0x0ff & (dataVals[2] >> 8),
1164 0xff // can't have transparency in the color itself
1165 //0x0ff & (data->data[3] >> 8),
1166 ));
1168 SPItem *item = desktop->item_at_point( where, true );
1170 bool consumed = false;
1171 if (desktop->event_context && desktop->event_context->get_drag()) {
1172 consumed = desktop->event_context->get_drag()->dropColor(item, c, button_dt);
1173 if (consumed) {
1174 sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1175 desktop->event_context->get_drag()->updateDraggers();
1176 }
1177 }
1179 //if (!consumed && tools_active(desktop, TOOLS_TEXT)) {
1180 // consumed = sp_text_context_drop_color(c, button_doc);
1181 // if (consumed) {
1182 // sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient stop"));
1183 // }
1184 //}
1186 if (!consumed && item) {
1187 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1188 if (fillnotstroke &&
1189 (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1190 Path *livarot_path = Path_for_item(item, true, true);
1191 livarot_path->ConvertWithBackData(0.04);
1193 boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1194 if (position) {
1195 Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1196 Geom::Point delta = nearest - button_doc;
1197 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1198 delta = desktop->d2w(delta);
1199 double stroke_tolerance =
1200 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1201 desktop->current_zoom() *
1202 SP_OBJECT_STYLE (item)->stroke_width.computed *
1203 to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1204 : 0.0)
1205 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1207 if (Geom::L2 (delta) < stroke_tolerance) {
1208 fillnotstroke = false;
1209 }
1210 }
1211 delete livarot_path;
1212 }
1214 SPCSSAttr *css = sp_repr_css_attr_new();
1215 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1217 sp_desktop_apply_css_recursive( item, css, true );
1218 item->updateRepr();
1220 sp_document_done( doc , SP_VERB_NONE,
1221 _("Drop color"));
1222 }
1223 }
1224 }
1225 break;
1227 case SVG_DATA:
1228 case SVG_XML_DATA: {
1229 gchar *svgdata = (gchar *)data->data;
1231 Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI);
1233 if (rnewdoc == NULL) {
1234 sp_ui_error_dialog(_("Could not parse SVG data"));
1235 return;
1236 }
1238 Inkscape::XML::Node *repr = rnewdoc->root();
1239 gchar const *style = repr->attribute("style");
1241 Inkscape::XML::Node *newgroup = rnewdoc->createElement("svg:g");
1242 newgroup->setAttribute("style", style);
1244 Inkscape::XML::Document * xml_doc = sp_document_repr_doc(doc);
1245 for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) {
1246 Inkscape::XML::Node *newchild = child->duplicate(xml_doc);
1247 newgroup->appendChild(newchild);
1248 }
1250 Inkscape::GC::release(rnewdoc);
1252 // Add it to the current layer
1254 // Greg's edits to add intelligent positioning of svg drops
1255 SPObject *new_obj = NULL;
1256 new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
1258 Inkscape::Selection *selection = sp_desktop_selection(desktop);
1259 selection->set(SP_ITEM(new_obj));
1260 // To move the imported object, we must temporarily set the "transform pattern with
1261 // object" option.
1262 {
1263 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1264 bool const saved_pref = prefs->getBool("/options/transform/pattern", true);
1265 prefs->setBool("/options/transform/pattern", true);
1266 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
1267 Geom::OptRect sel_bbox = selection->bounds();
1268 if (sel_bbox) {
1269 Geom::Point m( desktop->point() - sel_bbox->midpoint() );
1270 sp_selection_move_relative(selection, m);
1271 }
1272 prefs->setBool("/options/transform/pattern", saved_pref);
1273 }
1275 Inkscape::GC::release(newgroup);
1276 sp_document_done(doc, SP_VERB_NONE,
1277 _("Drop SVG"));
1278 break;
1279 }
1281 case URI_LIST: {
1282 gchar *uri = (gchar *)data->data;
1283 sp_ui_import_files(uri);
1284 break;
1285 }
1287 case PNG_DATA:
1288 case JPEG_DATA:
1289 case IMAGE_DATA: {
1290 char tmp[1024];
1292 StringOutputStream outs;
1293 Base64OutputStream b64out(outs);
1294 b64out.setColumnWidth(0);
1296 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
1298 Inkscape::XML::Node *newImage = xml_doc->createElement("svg:image");
1300 for ( int i = 0; i < data->length; i++ ) {
1301 b64out.put( data->data[i] );
1302 }
1303 b64out.close();
1306 Glib::ustring str = outs.getString();
1308 snprintf( tmp, sizeof(tmp), "data:%s;base64,", gdk_atom_name(data->type) );
1309 str.insert( 0, tmp );
1310 newImage->setAttribute("xlink:href", str.c_str());
1312 GError *error = NULL;
1313 GdkPixbufLoader *loader = gdk_pixbuf_loader_new_with_mime_type( gdk_atom_name(data->type), &error );
1314 if ( loader ) {
1315 error = NULL;
1316 if ( gdk_pixbuf_loader_write( loader, data->data, data->length, &error) ) {
1317 GdkPixbuf *pbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1318 if ( pbuf ) {
1319 int width = gdk_pixbuf_get_width(pbuf);
1320 int height = gdk_pixbuf_get_height(pbuf);
1321 snprintf( tmp, sizeof(tmp), "%d", width );
1322 newImage->setAttribute("width", tmp);
1324 snprintf( tmp, sizeof(tmp), "%d", height );
1325 newImage->setAttribute("height", tmp);
1326 }
1327 }
1328 }
1330 // Add it to the current layer
1331 desktop->currentLayer()->appendChildRepr(newImage);
1333 Inkscape::GC::release(newImage);
1334 sp_document_done( doc , SP_VERB_NONE,
1335 _("Drop bitmap image"));
1336 break;
1337 }
1338 }
1339 }
1341 #include "gradient-context.h"
1343 void sp_ui_drag_motion( GtkWidget */*widget*/,
1344 GdkDragContext */*drag_context*/,
1345 gint /*x*/, gint /*y*/,
1346 GtkSelectionData */*data*/,
1347 guint /*info*/,
1348 guint /*event_time*/,
1349 gpointer /*user_data*/)
1350 {
1351 // SPDocument *doc = SP_ACTIVE_DOCUMENT;
1352 // SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1355 // g_message("drag-n-drop motion (%4d, %4d) at %d", x, y, event_time);
1356 }
1358 static void sp_ui_drag_leave( GtkWidget */*widget*/,
1359 GdkDragContext */*drag_context*/,
1360 guint /*event_time*/,
1361 gpointer /*user_data*/ )
1362 {
1363 // g_message("drag-n-drop leave at %d", event_time);
1364 }
1366 static void
1367 sp_ui_import_files(gchar *buffer)
1368 {
1369 GList *list = gnome_uri_list_extract_filenames(buffer);
1370 if (!list)
1371 return;
1372 g_list_foreach(list, sp_ui_import_one_file_with_check, NULL);
1373 g_list_foreach(list, (GFunc) g_free, NULL);
1374 g_list_free(list);
1375 }
1377 static void
1378 sp_ui_import_one_file_with_check(gpointer filename, gpointer /*unused*/)
1379 {
1380 if (filename) {
1381 if (strlen((char const *)filename) > 2)
1382 sp_ui_import_one_file((char const *)filename);
1383 }
1384 }
1386 static void
1387 sp_ui_import_one_file(char const *filename)
1388 {
1389 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1390 if (!doc) return;
1392 if (filename == NULL) return;
1394 // Pass off to common implementation
1395 // TODO might need to get the proper type of Inkscape::Extension::Extension
1396 file_import( doc, filename, NULL );
1397 }
1399 void
1400 sp_ui_error_dialog(gchar const *message)
1401 {
1402 GtkWidget *dlg;
1403 gchar *safeMsg = Inkscape::IO::sanitizeString(message);
1405 dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
1406 GTK_BUTTONS_CLOSE, "%s", safeMsg);
1407 sp_transientize(dlg);
1408 gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE);
1409 gtk_dialog_run(GTK_DIALOG(dlg));
1410 gtk_widget_destroy(dlg);
1411 g_free(safeMsg);
1412 }
1414 bool
1415 sp_ui_overwrite_file(gchar const *filename)
1416 {
1417 bool return_value = FALSE;
1419 if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) {
1420 Gtk::Window *window = SP_ACTIVE_DESKTOP->getToplevel();
1421 gchar* baseName = g_path_get_basename( filename );
1422 gchar* dirName = g_path_get_dirname( filename );
1423 GtkWidget* dialog = gtk_message_dialog_new_with_markup( window->gobj(),
1424 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
1425 GTK_MESSAGE_QUESTION,
1426 GTK_BUTTONS_NONE,
1427 _( "<span weight=\"bold\" size=\"larger\">A file named \"%s\" already exists. Do you want to replace it?</span>\n\n"
1428 "The file already exists in \"%s\". Replacing it will overwrite its contents." ),
1429 baseName,
1430 dirName
1431 );
1432 gtk_dialog_add_buttons( GTK_DIALOG(dialog),
1433 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1434 _("Replace"), GTK_RESPONSE_YES,
1435 NULL );
1436 gtk_dialog_set_default_response( GTK_DIALOG(dialog), GTK_RESPONSE_YES );
1438 if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_YES ) {
1439 return_value = TRUE;
1440 } else {
1441 return_value = FALSE;
1442 }
1443 gtk_widget_destroy(dialog);
1444 g_free( baseName );
1445 g_free( dirName );
1446 } else {
1447 return_value = TRUE;
1448 }
1450 return return_value;
1451 }
1453 static void
1454 sp_ui_menu_item_set_sensitive(SPAction */*action*/, unsigned int sensitive, void *data)
1455 {
1456 return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
1457 }
1459 static void
1460 sp_ui_menu_item_set_name(SPAction */*action*/, Glib::ustring name, void *data)
1461 {
1462 void *child = GTK_BIN (data)->child;
1463 //child is either
1464 //- a GtkHBox, whose first child is a label displaying name if the menu
1465 //item has an accel key
1466 //- a GtkLabel if the menu has no accel key
1467 if (GTK_IS_LABEL(child)) {
1468 gtk_label_set_markup_with_mnemonic(GTK_LABEL (child), name.c_str());
1469 } else if (GTK_IS_HBOX(child)) {
1470 gtk_label_set_markup_with_mnemonic(
1471 GTK_LABEL (gtk_container_get_children(GTK_CONTAINER (child))->data),
1472 name.c_str());
1473 }//else sp_ui_menu_append_item_from_verb has been modified and can set
1474 //a menu item in yet another way...
1475 }
1478 /*
1479 Local Variables:
1480 mode:c++
1481 c-file-style:"stroustrup"
1482 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1483 indent-tabs-mode:nil
1484 fill-column:99
1485 End:
1486 */
1487 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :