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