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, _("Tool Controls Bar"), _("Show or hide the Tool Controls bar"), "toppanel",
831 checkitem_toggled, checkitem_update, 0);
832 sp_ui_menu_append_check_item_from_verb(m, view, _("_Toolbox"), _("Show or hide the main toolbox (on the left)"), "toolbox",
833 checkitem_toggled, checkitem_update, 0);
834 sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "rulers",
835 checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_RULERS));
836 sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "scrollbars",
837 checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_SCROLLBARS));
838 sp_ui_menu_append_check_item_from_verb(m, view, _("_Palette"), _("Show or hide the color palette"), "panels",
839 checkitem_toggled, checkitem_update, 0);
840 sp_ui_menu_append_check_item_from_verb(m, view, _("_Statusbar"), _("Show or hide the statusbar (at the bottom of the window)"), "statusbar",
841 checkitem_toggled, checkitem_update, 0);
842 }
844 /** \brief This function turns XML into a menu
845 \param menus This is the XML that defines the menu
846 \param menu Menu to be added to
847 \param view The View that this menu is being built for
849 This function is realitively simple as it just goes through the XML
850 and parses the individual elements. In the case of a submenu, it
851 just calls itself recursively. Because it is only reasonable to have
852 a couple of submenus, it is unlikely this will go more than two or
853 three times.
855 In the case of an unreconginzed verb, a menu item is made to identify
856 the verb that is missing, and display that. The menu item is also made
857 insensitive.
858 */
859 void
860 sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI::View::View *view)
861 {
862 if (menus == NULL) return;
863 if (menu == NULL) return;
864 GSList *group = NULL;
866 for (Inkscape::XML::Node *menu_pntr = menus;
867 menu_pntr != NULL;
868 menu_pntr = menu_pntr->next()) {
869 if (!strcmp(menu_pntr->name(), "submenu")) {
870 GtkWidget *mitem = gtk_menu_item_new_with_mnemonic(_(menu_pntr->attribute("name")));
871 GtkWidget *submenu = gtk_menu_new();
872 sp_ui_build_dyn_menus(menu_pntr->firstChild(), submenu, view);
873 gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), GTK_WIDGET(submenu));
874 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
875 continue;
876 }
877 if (!strcmp(menu_pntr->name(), "verb")) {
878 gchar const *verb_name = menu_pntr->attribute("verb-id");
879 Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name);
881 if (verb != NULL) {
882 if (menu_pntr->attribute("radio") != NULL) {
883 GtkWidget *item = sp_ui_menu_append_item_from_verb (GTK_MENU(menu), verb, view, true, group);
884 group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item));
885 if (menu_pntr->attribute("default") != NULL) {
886 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
887 }
888 } else {
889 sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view);
890 group = NULL;
891 }
892 } else {
893 gchar string[120];
894 g_snprintf(string, 120, _("Verb \"%s\" Unknown"), verb_name);
895 string[119] = '\0'; /* may not be terminated */
896 GtkWidget *item = gtk_menu_item_new_with_label(string);
897 gtk_widget_set_sensitive(item, false);
898 gtk_widget_show(item);
899 gtk_menu_append(GTK_MENU(menu), item);
900 }
901 continue;
902 }
903 if (!strcmp(menu_pntr->name(), "separator")
904 // This was spelt wrong in the original version
905 // and so this is for backward compatibility. It can
906 // probably be dropped after the 0.44 release.
907 || !strcmp(menu_pntr->name(), "seperator")) {
908 GtkWidget *item = gtk_separator_menu_item_new();
909 gtk_widget_show(item);
910 gtk_menu_append(GTK_MENU(menu), item);
911 continue;
912 }
913 if (!strcmp(menu_pntr->name(), "template-list")) {
914 sp_menu_append_new_templates(menu, view);
915 continue;
916 }
917 if (!strcmp(menu_pntr->name(), "recent-file-list")) {
918 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
920 // create recent files menu
921 int max_recent = prefs->getInt("/options/maxrecentdocuments/value");
922 GtkWidget *recent_menu = gtk_recent_chooser_menu_new_for_manager(gtk_recent_manager_get_default());
923 gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(recent_menu), max_recent);
924 // sort most recently used documents first to preserve previous behavior
925 gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(recent_menu), GTK_RECENT_SORT_MRU);
926 g_signal_connect(G_OBJECT(recent_menu), "item-activated", G_CALLBACK(sp_recent_open), (gpointer) NULL);
928 // add filter to only open files added by Inkscape
929 GtkRecentFilter *inkscape_only_filter = gtk_recent_filter_new();
930 gtk_recent_filter_add_application(inkscape_only_filter, g_get_prgname());
931 gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_menu), inkscape_only_filter);
933 GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic(_("Open _Recent"));
934 gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), recent_menu);
936 gtk_menu_append(GTK_MENU(menu), GTK_WIDGET(recent_item));
937 continue;
938 }
939 if (!strcmp(menu_pntr->name(), "objects-checkboxes")) {
940 sp_ui_checkboxes_menus(GTK_MENU(menu), view);
941 continue;
942 }
943 }
944 }
946 /** \brief Build the main tool bar
947 \param view View to build the bar for
949 Currently the main tool bar is built as a dynamic XML menu using
950 \c sp_ui_build_dyn_menus. This function builds the bar, and then
951 pass it to get items attached to it.
952 */
953 GtkWidget *
954 sp_ui_main_menubar(Inkscape::UI::View::View *view)
955 {
956 GtkWidget *mbar = gtk_menu_bar_new();
958 #ifdef GDK_WINDOWING_QUARTZ
959 ige_mac_menu_set_menu_bar(GTK_MENU_SHELL(mbar));
960 #endif
962 sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view);
964 #ifdef GDK_WINDOWING_QUARTZ
965 return NULL;
966 #else
967 return mbar;
968 #endif
969 }
971 static void leave_group(GtkMenuItem *, SPDesktop *desktop) {
972 desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer()));
973 }
975 static void enter_group(GtkMenuItem *mi, SPDesktop *desktop) {
976 desktop->setCurrentLayer(reinterpret_cast<SPObject *>(g_object_get_data(G_OBJECT(mi), "group")));
977 sp_desktop_selection(desktop)->clear();
978 }
980 GtkWidget *
981 sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item)
982 {
983 GtkWidget *m;
984 SPDesktop *dt;
986 dt = static_cast<SPDesktop*>(view);
988 m = gtk_menu_new();
990 /* Undo and Redo */
991 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_UNDO), view);
992 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_REDO), view);
994 /* Separator */
995 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
997 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_CUT), view);
998 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_COPY), view);
999 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_PASTE), view);
1001 /* Separator */
1002 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1004 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DUPLICATE), view);
1005 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DELETE), view);
1007 /* Item menu */
1008 if (item) {
1009 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1010 sp_object_menu((SPObject *) item, dt, GTK_MENU(m));
1011 }
1013 /* layer menu */
1014 SPGroup *group=NULL;
1015 if (item) {
1016 if (SP_IS_GROUP(item)) {
1017 group = SP_GROUP(item);
1018 } else if ( item != dt->currentRoot() && SP_IS_GROUP(SP_OBJECT_PARENT(item)) ) {
1019 group = SP_GROUP(SP_OBJECT_PARENT(item));
1020 }
1021 }
1023 if (( group && group != dt->currentLayer() ) ||
1024 ( dt->currentLayer() != dt->currentRoot() && SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) ) {
1025 /* Separator */
1026 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1027 }
1029 if ( group && group != dt->currentLayer() ) {
1030 /* TRANSLATORS: #%s is the id of the group e.g. <g id="#g7">, not a number. */
1031 gchar *label=g_strdup_printf(_("Enter group #%s"), SP_OBJECT_ID(group));
1032 GtkWidget *w = gtk_menu_item_new_with_label(label);
1033 g_free(label);
1034 g_object_set_data(G_OBJECT(w), "group", group);
1035 g_signal_connect(G_OBJECT(w), "activate", GCallback(enter_group), dt);
1036 gtk_widget_show(w);
1037 gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1038 }
1040 if ( dt->currentLayer() != dt->currentRoot() ) {
1041 if ( SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) {
1042 GtkWidget *w = gtk_menu_item_new_with_label(_("Go to parent"));
1043 g_signal_connect(G_OBJECT(w), "activate", GCallback(leave_group), dt);
1044 gtk_widget_show(w);
1045 gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1047 }
1048 }
1050 return m;
1051 }
1053 /* Drag and Drop */
1054 void
1055 sp_ui_drag_data_received(GtkWidget *widget,
1056 GdkDragContext *drag_context,
1057 gint x, gint y,
1058 GtkSelectionData *data,
1059 guint info,
1060 guint /*event_time*/,
1061 gpointer /*user_data*/)
1062 {
1063 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1064 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1066 switch (info) {
1067 #if ENABLE_MAGIC_COLORS
1068 case APP_X_INKY_COLOR:
1069 {
1070 int destX = 0;
1071 int destY = 0;
1072 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1073 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1075 SPItem *item = desktop->item_at_point( where, true );
1076 if ( item )
1077 {
1078 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1080 if ( data->length >= 8 ) {
1081 cmsHPROFILE srgbProf = cmsCreate_sRGBProfile();
1083 gchar c[64] = {0};
1084 // Careful about endian issues.
1085 guint16* dataVals = (guint16*)data->data;
1086 sp_svg_write_color( c, sizeof(c),
1087 SP_RGBA32_U_COMPOSE(
1088 0x0ff & (dataVals[0] >> 8),
1089 0x0ff & (dataVals[1] >> 8),
1090 0x0ff & (dataVals[2] >> 8),
1091 0xff // can't have transparency in the color itself
1092 //0x0ff & (data->data[3] >> 8),
1093 ));
1094 SPCSSAttr *css = sp_repr_css_attr_new();
1095 bool updatePerformed = false;
1097 if ( data->length > 14 ) {
1098 int flags = dataVals[4];
1100 // piggie-backed palette entry info
1101 int index = dataVals[5];
1102 Glib::ustring palName;
1103 for ( int i = 0; i < dataVals[6]; i++ ) {
1104 palName += (gunichar)dataVals[7+i];
1105 }
1107 // Now hook in a magic tag of some sort.
1108 if ( !palName.empty() && (flags & 1) ) {
1109 gchar* str = g_strdup_printf("%d|", index);
1110 palName.insert( 0, str );
1111 g_free(str);
1112 str = 0;
1114 sp_object_setAttribute( SP_OBJECT(item),
1115 fillnotstroke ? "inkscape:x-fill-tag":"inkscape:x-stroke-tag",
1116 palName.c_str(),
1117 false );
1118 item->updateRepr();
1120 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1121 updatePerformed = true;
1122 }
1123 }
1125 if ( !updatePerformed ) {
1126 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1127 }
1129 sp_desktop_apply_css_recursive( item, css, true );
1130 item->updateRepr();
1132 sp_document_done( doc , SP_VERB_NONE,
1133 _("Drop color"));
1135 if ( srgbProf ) {
1136 cmsCloseProfile( srgbProf );
1137 }
1138 }
1139 }
1140 }
1141 break;
1142 #endif // ENABLE_MAGIC_COLORS
1144 case APP_X_COLOR:
1145 {
1146 int destX = 0;
1147 int destY = 0;
1148 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1149 NR::Point where( sp_canvas_window_to_world( desktop->canvas, NR::Point( destX, destY ) ) );
1150 NR::Point const button_dt(desktop->w2d(where));
1151 NR::Point const button_doc(desktop->dt2doc(button_dt));
1153 if ( data->length == 8 ) {
1154 gchar c[64] = {0};
1155 // Careful about endian issues.
1156 guint16* dataVals = (guint16*)data->data;
1157 sp_svg_write_color( c, 64,
1158 SP_RGBA32_U_COMPOSE(
1159 0x0ff & (dataVals[0] >> 8),
1160 0x0ff & (dataVals[1] >> 8),
1161 0x0ff & (dataVals[2] >> 8),
1162 0xff // can't have transparency in the color itself
1163 //0x0ff & (data->data[3] >> 8),
1164 ));
1166 SPItem *item = desktop->item_at_point( where, true );
1168 bool consumed = false;
1169 if (desktop->event_context && desktop->event_context->get_drag()) {
1170 consumed = desktop->event_context->get_drag()->dropColor(item, c, button_dt);
1171 if (consumed) {
1172 sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1173 desktop->event_context->get_drag()->updateDraggers();
1174 }
1175 }
1177 //if (!consumed && tools_active(desktop, TOOLS_TEXT)) {
1178 // consumed = sp_text_context_drop_color(c, button_doc);
1179 // if (consumed) {
1180 // sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient stop"));
1181 // }
1182 //}
1184 if (!consumed && item) {
1185 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1186 if (fillnotstroke &&
1187 (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1188 Path *livarot_path = Path_for_item(item, true, true);
1189 livarot_path->ConvertWithBackData(0.04);
1191 boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1192 if (position) {
1193 NR::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1194 NR::Point delta = nearest - button_doc;
1195 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1196 delta = desktop->d2w(delta);
1197 double stroke_tolerance =
1198 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1199 desktop->current_zoom() *
1200 SP_OBJECT_STYLE (item)->stroke_width.computed *
1201 to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1202 : 0.0)
1203 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1205 if (NR::L2 (delta) < stroke_tolerance) {
1206 fillnotstroke = false;
1207 }
1208 }
1209 delete livarot_path;
1210 }
1212 SPCSSAttr *css = sp_repr_css_attr_new();
1213 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1215 sp_desktop_apply_css_recursive( item, css, true );
1216 item->updateRepr();
1218 sp_document_done( doc , SP_VERB_NONE,
1219 _("Drop color"));
1220 }
1221 }
1222 }
1223 break;
1225 case SVG_DATA:
1226 case SVG_XML_DATA: {
1227 gchar *svgdata = (gchar *)data->data;
1229 Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI);
1231 if (rnewdoc == NULL) {
1232 sp_ui_error_dialog(_("Could not parse SVG data"));
1233 return;
1234 }
1236 Inkscape::XML::Node *repr = rnewdoc->root();
1237 gchar const *style = repr->attribute("style");
1239 Inkscape::XML::Node *newgroup = rnewdoc->createElement("svg:g");
1240 newgroup->setAttribute("style", style);
1242 Inkscape::XML::Document * xml_doc = sp_document_repr_doc(doc);
1243 for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) {
1244 Inkscape::XML::Node *newchild = child->duplicate(xml_doc);
1245 newgroup->appendChild(newchild);
1246 }
1248 Inkscape::GC::release(rnewdoc);
1250 // Add it to the current layer
1252 // Greg's edits to add intelligent positioning of svg drops
1253 SPObject *new_obj = NULL;
1254 new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
1256 Inkscape::Selection *selection = sp_desktop_selection(desktop);
1257 selection->set(SP_ITEM(new_obj));
1258 // To move the imported object, we must temporarily set the "transform pattern with
1259 // object" option.
1260 {
1261 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1262 bool const saved_pref = prefs->getBool("/options/transform/pattern", true);
1263 prefs->setBool("/options/transform/pattern", true);
1264 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
1265 Geom::OptRect sel_bbox = selection->bounds();
1266 if (sel_bbox) {
1267 Geom::Point m( desktop->point() - sel_bbox->midpoint() );
1268 sp_selection_move_relative(selection, m);
1269 }
1270 prefs->setBool("/options/transform/pattern", saved_pref);
1271 }
1273 Inkscape::GC::release(newgroup);
1274 sp_document_done(doc, SP_VERB_NONE,
1275 _("Drop SVG"));
1276 break;
1277 }
1279 case URI_LIST: {
1280 gchar *uri = (gchar *)data->data;
1281 sp_ui_import_files(uri);
1282 break;
1283 }
1285 case PNG_DATA:
1286 case JPEG_DATA:
1287 case IMAGE_DATA: {
1288 char tmp[1024];
1290 StringOutputStream outs;
1291 Base64OutputStream b64out(outs);
1292 b64out.setColumnWidth(0);
1294 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
1296 Inkscape::XML::Node *newImage = xml_doc->createElement("svg:image");
1298 for ( int i = 0; i < data->length; i++ ) {
1299 b64out.put( data->data[i] );
1300 }
1301 b64out.close();
1304 Glib::ustring str = outs.getString();
1306 snprintf( tmp, sizeof(tmp), "data:%s;base64,", gdk_atom_name(data->type) );
1307 str.insert( 0, tmp );
1308 newImage->setAttribute("xlink:href", str.c_str());
1310 GError *error = NULL;
1311 GdkPixbufLoader *loader = gdk_pixbuf_loader_new_with_mime_type( gdk_atom_name(data->type), &error );
1312 if ( loader ) {
1313 error = NULL;
1314 if ( gdk_pixbuf_loader_write( loader, data->data, data->length, &error) ) {
1315 GdkPixbuf *pbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1316 if ( pbuf ) {
1317 int width = gdk_pixbuf_get_width(pbuf);
1318 int height = gdk_pixbuf_get_height(pbuf);
1319 snprintf( tmp, sizeof(tmp), "%d", width );
1320 newImage->setAttribute("width", tmp);
1322 snprintf( tmp, sizeof(tmp), "%d", height );
1323 newImage->setAttribute("height", tmp);
1324 }
1325 }
1326 }
1328 // Add it to the current layer
1329 desktop->currentLayer()->appendChildRepr(newImage);
1331 Inkscape::GC::release(newImage);
1332 sp_document_done( doc , SP_VERB_NONE,
1333 _("Drop bitmap image"));
1334 break;
1335 }
1336 }
1337 }
1339 #include "gradient-context.h"
1341 void sp_ui_drag_motion( GtkWidget */*widget*/,
1342 GdkDragContext */*drag_context*/,
1343 gint /*x*/, gint /*y*/,
1344 GtkSelectionData */*data*/,
1345 guint /*info*/,
1346 guint /*event_time*/,
1347 gpointer /*user_data*/)
1348 {
1349 // SPDocument *doc = SP_ACTIVE_DOCUMENT;
1350 // SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1353 // g_message("drag-n-drop motion (%4d, %4d) at %d", x, y, event_time);
1354 }
1356 static void sp_ui_drag_leave( GtkWidget */*widget*/,
1357 GdkDragContext */*drag_context*/,
1358 guint /*event_time*/,
1359 gpointer /*user_data*/ )
1360 {
1361 // g_message("drag-n-drop leave at %d", event_time);
1362 }
1364 static void
1365 sp_ui_import_files(gchar *buffer)
1366 {
1367 GList *list = gnome_uri_list_extract_filenames(buffer);
1368 if (!list)
1369 return;
1370 g_list_foreach(list, sp_ui_import_one_file_with_check, NULL);
1371 g_list_foreach(list, (GFunc) g_free, NULL);
1372 g_list_free(list);
1373 }
1375 static void
1376 sp_ui_import_one_file_with_check(gpointer filename, gpointer /*unused*/)
1377 {
1378 if (filename) {
1379 if (strlen((char const *)filename) > 2)
1380 sp_ui_import_one_file((char const *)filename);
1381 }
1382 }
1384 static void
1385 sp_ui_import_one_file(char const *filename)
1386 {
1387 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1388 if (!doc) return;
1390 if (filename == NULL) return;
1392 // Pass off to common implementation
1393 // TODO might need to get the proper type of Inkscape::Extension::Extension
1394 file_import( doc, filename, NULL );
1395 }
1397 void
1398 sp_ui_error_dialog(gchar const *message)
1399 {
1400 GtkWidget *dlg;
1401 gchar *safeMsg = Inkscape::IO::sanitizeString(message);
1403 dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
1404 GTK_BUTTONS_CLOSE, "%s", safeMsg);
1405 sp_transientize(dlg);
1406 gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE);
1407 gtk_dialog_run(GTK_DIALOG(dlg));
1408 gtk_widget_destroy(dlg);
1409 g_free(safeMsg);
1410 }
1412 bool
1413 sp_ui_overwrite_file(gchar const *filename)
1414 {
1415 bool return_value = FALSE;
1417 if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) {
1418 Gtk::Window *window = SP_ACTIVE_DESKTOP->getToplevel();
1419 gchar* baseName = g_path_get_basename( filename );
1420 gchar* dirName = g_path_get_dirname( filename );
1421 GtkWidget* dialog = gtk_message_dialog_new_with_markup( window->gobj(),
1422 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
1423 GTK_MESSAGE_QUESTION,
1424 GTK_BUTTONS_NONE,
1425 _( "<span weight=\"bold\" size=\"larger\">A file named \"%s\" already exists. Do you want to replace it?</span>\n\n"
1426 "The file already exists in \"%s\". Replacing it will overwrite its contents." ),
1427 baseName,
1428 dirName
1429 );
1430 gtk_dialog_add_buttons( GTK_DIALOG(dialog),
1431 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1432 _("Replace"), GTK_RESPONSE_YES,
1433 NULL );
1434 gtk_dialog_set_default_response( GTK_DIALOG(dialog), GTK_RESPONSE_YES );
1436 if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_YES ) {
1437 return_value = TRUE;
1438 } else {
1439 return_value = FALSE;
1440 }
1441 gtk_widget_destroy(dialog);
1442 g_free( baseName );
1443 g_free( dirName );
1444 } else {
1445 return_value = TRUE;
1446 }
1448 return return_value;
1449 }
1451 static void
1452 sp_ui_menu_item_set_sensitive(SPAction */*action*/, unsigned int sensitive, void *data)
1453 {
1454 return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
1455 }
1457 static void
1458 sp_ui_menu_item_set_name(SPAction */*action*/, Glib::ustring name, void *data)
1459 {
1460 void *child = GTK_BIN (data)->child;
1461 //child is either
1462 //- a GtkHBox, whose first child is a label displaying name if the menu
1463 //item has an accel key
1464 //- a GtkLabel if the menu has no accel key
1465 if (GTK_IS_LABEL(child)) {
1466 gtk_label_set_markup_with_mnemonic(GTK_LABEL (child), name.c_str());
1467 } else if (GTK_IS_HBOX(child)) {
1468 gtk_label_set_markup_with_mnemonic(
1469 GTK_LABEL (gtk_container_get_children(GTK_CONTAINER (child))->data),
1470 name.c_str());
1471 }//else sp_ui_menu_append_item_from_verb has been modified and can set
1472 //a menu item in yet another way...
1473 }
1476 /*
1477 Local Variables:
1478 mode:c++
1479 c-file-style:"stroustrup"
1480 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1481 indent-tabs-mode:nil
1482 fill-column:99
1483 End:
1484 */
1485 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :