c162ea6d68fef6b384672db7f72c72f1f335465f
1 #define __SP_INTERFACE_C__
3 /** @file
4 * @brief Main UI stuff
5 */
6 /* Authors:
7 * Lauris Kaplinski <lauris@kaplinski.com>
8 * Frank Felfe <innerspace@iname.com>
9 * bulia byak <buliabyak@users.sf.net>
10 *
11 * Copyright (C) 1999-2005 authors
12 * Copyright (C) 2001-2002 Ximian, Inc.
13 * Copyright (C) 2004 David Turner
14 *
15 * Released under GNU GPL, read the file 'COPYING' for more information
16 */
18 #ifdef HAVE_CONFIG_H
19 # include "config.h"
20 #endif
22 #include <gtk/gtk.h>
23 #include <glib.h>
25 #include "inkscape-private.h"
26 #include "extension/effect.h"
27 #include "widgets/icon.h"
28 #include "preferences.h"
29 #include "path-prefix.h"
30 #include "shortcuts.h"
31 #include "document.h"
32 #include "desktop-handles.h"
33 #include "file.h"
34 #include "interface.h"
35 #include "desktop.h"
36 #include "ui/context-menu.h"
37 #include "selection.h"
38 #include "selection-chemistry.h"
39 #include "svg-view-widget.h"
40 #include "widgets/desktop-widget.h"
41 #include "sp-item-group.h"
42 #include "sp-text.h"
43 #include "sp-gradient-fns.h"
44 #include "sp-gradient.h"
45 #include "sp-flowtext.h"
46 #include "sp-namedview.h"
47 #include "ui/view/view.h"
48 #include "helper/action.h"
49 #include "helper/gnome-utils.h"
50 #include "helper/window.h"
51 #include "io/sys.h"
52 #include "dialogs/dialog-events.h"
53 #include "message-context.h"
55 // Added for color drag-n-drop
56 #if ENABLE_LCMS
57 #include "lcms.h"
58 #endif // ENABLE_LCMS
59 #include "display/sp-canvas.h"
60 #include "color.h"
61 #include "svg/svg-color.h"
62 #include "desktop-style.h"
63 #include "style.h"
64 #include "event-context.h"
65 #include "gradient-drag.h"
66 #include "widgets/eek-color-def.h"
68 // Include Mac OS X menu synchronization on native OSX build
69 #ifdef GDK_WINDOWING_QUARTZ
70 #include "ige-mac-menu.h"
71 #endif
73 /* Drag and Drop */
74 typedef enum {
75 URI_LIST,
76 SVG_XML_DATA,
77 SVG_DATA,
78 PNG_DATA,
79 JPEG_DATA,
80 IMAGE_DATA,
81 APP_X_INKY_COLOR,
82 APP_X_COLOR,
83 APP_OSWB_COLOR,
84 } ui_drop_target_info;
86 static GtkTargetEntry ui_drop_target_entries [] = {
87 {(gchar *)"text/uri-list", 0, URI_LIST },
88 {(gchar *)"image/svg+xml", 0, SVG_XML_DATA },
89 {(gchar *)"image/svg", 0, SVG_DATA },
90 {(gchar *)"image/png", 0, PNG_DATA },
91 {(gchar *)"image/jpeg", 0, JPEG_DATA },
92 #if ENABLE_MAGIC_COLORS
93 {(gchar *)"application/x-inkscape-color", 0, APP_X_INKY_COLOR},
94 #endif // ENABLE_MAGIC_COLORS
95 {(gchar *)"application/x-oswb-color", 0, APP_OSWB_COLOR },
96 {(gchar *)"application/x-color", 0, APP_X_COLOR }
97 };
99 static GtkTargetEntry *completeDropTargets = 0;
100 static int completeDropTargetsCount = 0;
102 #define ENTRIES_SIZE(n) sizeof(n)/sizeof(n[0])
103 static guint nui_drop_target_entries = ENTRIES_SIZE(ui_drop_target_entries);
104 static void sp_ui_import_files(gchar *buffer);
105 static void sp_ui_import_one_file(char const *filename);
106 static void sp_ui_import_one_file_with_check(gpointer filename, gpointer unused);
107 static void sp_ui_drag_data_received(GtkWidget *widget,
108 GdkDragContext *drag_context,
109 gint x, gint y,
110 GtkSelectionData *data,
111 guint info,
112 guint event_time,
113 gpointer user_data);
114 static void sp_ui_drag_motion( GtkWidget *widget,
115 GdkDragContext *drag_context,
116 gint x, gint y,
117 GtkSelectionData *data,
118 guint info,
119 guint event_time,
120 gpointer user_data );
121 static void sp_ui_drag_leave( GtkWidget *widget,
122 GdkDragContext *drag_context,
123 guint event_time,
124 gpointer user_data );
125 static void sp_ui_menu_item_set_sensitive(SPAction *action,
126 unsigned int sensitive,
127 void *data);
128 static void sp_ui_menu_item_set_name(SPAction *action,
129 Glib::ustring name,
130 void *data);
131 static void sp_recent_open(GtkRecentChooser *, gpointer);
133 SPActionEventVector menu_item_event_vector = {
134 {NULL},
135 NULL,
136 NULL, /* set_active */
137 sp_ui_menu_item_set_sensitive, /* set_sensitive */
138 NULL, /* set_shortcut */
139 sp_ui_menu_item_set_name /* set_name */
140 };
142 static const int MIN_ONSCREEN_DISTANCE = 50;
144 void
145 sp_create_window(SPViewWidget *vw, gboolean editable)
146 {
147 g_return_if_fail(vw != NULL);
148 g_return_if_fail(SP_IS_VIEW_WIDGET(vw));
150 Gtk::Window *win = Inkscape::UI::window_new("", TRUE);
152 gtk_container_add(GTK_CONTAINER(win->gobj()), GTK_WIDGET(vw));
153 gtk_widget_show(GTK_WIDGET(vw));
155 if (editable) {
156 g_object_set_data(G_OBJECT(vw), "window", win);
158 SPDesktopWidget *desktop_widget = reinterpret_cast<SPDesktopWidget*>(vw);
159 SPDesktop* desktop = desktop_widget->desktop;
161 desktop_widget->window = win;
163 win->set_data("desktop", desktop);
164 win->set_data("desktopwidget", desktop_widget);
166 win->signal_delete_event().connect(sigc::mem_fun(*(SPDesktop*)vw->view, &SPDesktop::onDeleteUI));
167 win->signal_window_state_event().connect(sigc::mem_fun(*desktop, &SPDesktop::onWindowStateEvent));
168 win->signal_focus_in_event().connect(sigc::mem_fun(*desktop_widget, &SPDesktopWidget::onFocusInEvent));
170 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
171 gint prefs_geometry =
172 (2==prefs->getInt("/options/savewindowgeometry/value", 0));
173 if (prefs_geometry) {
174 gint pw = prefs->getInt("/desktop/geometry/width", -1);
175 gint ph = prefs->getInt("/desktop/geometry/height", -1);
176 gint px = prefs->getInt("/desktop/geometry/x", -1);
177 gint py = prefs->getInt("/desktop/geometry/y", -1);
178 gint full = prefs->getBool("/desktop/geometry/fullscreen");
179 gint maxed = prefs->getBool("/desktop/geometry/maximized");
180 if (pw>0 && ph>0) {
181 gint w = MIN(gdk_screen_width(), pw);
182 gint h = MIN(gdk_screen_height(), ph);
183 gint x = MIN(gdk_screen_width() - MIN_ONSCREEN_DISTANCE, px);
184 gint y = MIN(gdk_screen_height() - MIN_ONSCREEN_DISTANCE, py);
185 if (w>0 && h>0 && x>0 && y>0) {
186 x = MIN(gdk_screen_width() - w, x);
187 y = MIN(gdk_screen_height() - h, y);
188 }
189 if (w>0 && h>0) {
190 desktop->setWindowSize(w, h);
191 }
193 // Only restore xy for the first window so subsequent windows don't overlap exactly
194 // with first. (Maybe rule should be only restore xy if it's different from xy of
195 // other desktops?)
197 // Empirically it seems that active_desktop==this desktop only the first time a
198 // desktop is created.
199 if (x>0 && y>0) {
200 SPDesktop *active_desktop = SP_ACTIVE_DESKTOP;
201 if (active_desktop == desktop || active_desktop==NULL) {
202 desktop->setWindowPosition(Geom::Point(x, y));
203 }
204 }
205 }
206 if (maxed) {
207 win->maximize();
208 }
209 if (full) {
210 win->fullscreen();
211 }
212 }
214 } else {
215 gtk_window_set_policy(GTK_WINDOW(win->gobj()), TRUE, TRUE, TRUE);
216 }
218 if ( completeDropTargets == 0 || completeDropTargetsCount == 0 )
219 {
220 std::vector<gchar*> types;
222 GSList *list = gdk_pixbuf_get_formats();
223 while ( list ) {
224 int i = 0;
225 GdkPixbufFormat *one = (GdkPixbufFormat*)list->data;
226 gchar** typesXX = gdk_pixbuf_format_get_mime_types(one);
227 for ( i = 0; typesXX[i]; i++ ) {
228 types.push_back(g_strdup(typesXX[i]));
229 }
230 g_strfreev(typesXX);
232 list = g_slist_next(list);
233 }
234 completeDropTargetsCount = nui_drop_target_entries + types.size();
235 completeDropTargets = new GtkTargetEntry[completeDropTargetsCount];
236 for ( int i = 0; i < (int)nui_drop_target_entries; i++ ) {
237 completeDropTargets[i] = ui_drop_target_entries[i];
238 }
239 int pos = nui_drop_target_entries;
241 for (std::vector<gchar*>::iterator it = types.begin() ; it != types.end() ; it++) {
242 completeDropTargets[pos].target = *it;
243 completeDropTargets[pos].flags = 0;
244 completeDropTargets[pos].info = IMAGE_DATA;
245 pos++;
246 }
247 }
249 gtk_drag_dest_set((GtkWidget*)win->gobj(),
250 GTK_DEST_DEFAULT_ALL,
251 completeDropTargets,
252 completeDropTargetsCount,
253 GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE));
256 g_signal_connect(G_OBJECT(win->gobj()),
257 "drag_data_received",
258 G_CALLBACK(sp_ui_drag_data_received),
259 NULL);
260 g_signal_connect(G_OBJECT(win->gobj()),
261 "drag_motion",
262 G_CALLBACK(sp_ui_drag_motion),
263 NULL);
264 g_signal_connect(G_OBJECT(win->gobj()),
265 "drag_leave",
266 G_CALLBACK(sp_ui_drag_leave),
267 NULL);
268 win->show();
270 // needed because the first ACTIVATE_DESKTOP was sent when there was no window yet
271 inkscape_reactivate_desktop(SP_DESKTOP_WIDGET(vw)->desktop);
272 }
274 void
275 sp_ui_new_view()
276 {
277 SPDocument *document;
278 SPViewWidget *dtw;
280 document = SP_ACTIVE_DOCUMENT;
281 if (!document) return;
283 dtw = sp_desktop_widget_new(sp_document_namedview(document, NULL));
284 g_return_if_fail(dtw != NULL);
286 sp_create_window(dtw, TRUE);
287 sp_namedview_window_from_document(static_cast<SPDesktop*>(dtw->view));
288 sp_namedview_update_layers_from_document(static_cast<SPDesktop*>(dtw->view));
289 }
291 /* TODO: not yet working */
292 /* To be re-enabled (by adding to menu) once it works. */
293 void
294 sp_ui_new_view_preview()
295 {
296 SPDocument *document;
297 SPViewWidget *dtw;
299 document = SP_ACTIVE_DOCUMENT;
300 if (!document) return;
302 dtw = (SPViewWidget *) sp_svg_view_widget_new(document);
303 g_return_if_fail(dtw != NULL);
304 sp_svg_view_widget_set_resize(SP_SVG_VIEW_WIDGET(dtw), TRUE, 400.0, 400.0);
306 sp_create_window(dtw, FALSE);
307 }
309 /**
310 * \param widget unused
311 */
312 void
313 sp_ui_close_view(GtkWidget */*widget*/)
314 {
315 if (SP_ACTIVE_DESKTOP == NULL) {
316 return;
317 }
318 if ((SP_ACTIVE_DESKTOP)->shutdown()) {
319 return;
320 }
321 SP_ACTIVE_DESKTOP->destroyWidget();
322 }
325 /**
326 * sp_ui_close_all
327 *
328 * This function is called to exit the program, and iterates through all
329 * open document view windows, attempting to close each in turn. If the
330 * view has unsaved information, the user will be prompted to save,
331 * discard, or cancel.
332 *
333 * Returns FALSE if the user cancels the close_all operation, TRUE
334 * otherwise.
335 */
336 unsigned int
337 sp_ui_close_all(void)
338 {
339 /* Iterate through all the windows, destroying each in the order they
340 become active */
341 while (SP_ACTIVE_DESKTOP) {
342 if ((SP_ACTIVE_DESKTOP)->shutdown()) {
343 /* The user cancelled the operation, so end doing the close */
344 return FALSE;
345 }
346 SP_ACTIVE_DESKTOP->destroyWidget();
347 }
349 return TRUE;
350 }
352 /*
353 * Some day when the right-click menus are ready to start working
354 * smarter with the verbs, we'll need to change this NULL being
355 * sent to sp_action_perform to something useful, or set some kind
356 * of global "right-clicked position" variable for actions to
357 * investigate when they're called.
358 */
359 static void
360 sp_ui_menu_activate(void */*object*/, SPAction *action)
361 {
362 sp_action_perform(action, NULL);
363 }
365 static void
366 sp_ui_menu_select_action(void */*object*/, SPAction *action)
367 {
368 action->view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, action->tip);
369 }
371 static void
372 sp_ui_menu_deselect_action(void */*object*/, SPAction *action)
373 {
374 action->view->tipsMessageContext()->clear();
375 }
377 static void
378 sp_ui_menu_select(gpointer object, gpointer tip)
379 {
380 Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view"));
381 view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, (gchar *)tip);
382 }
384 static void
385 sp_ui_menu_deselect(gpointer object)
386 {
387 Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view"));
388 view->tipsMessageContext()->clear();
389 }
391 /**
392 * sp_ui_menuitem_add_icon
393 *
394 * Creates and attaches a scaled icon to the given menu item.
395 *
396 */
397 void
398 sp_ui_menuitem_add_icon( GtkWidget *item, gchar *icon_name )
399 {
400 GtkWidget *icon;
402 icon = sp_icon_new( Inkscape::ICON_SIZE_MENU, icon_name );
403 gtk_widget_show(icon);
404 gtk_image_menu_item_set_image((GtkImageMenuItem *) item, icon);
405 } // end of sp_ui_menu_add_icon
407 /**
408 * sp_ui_menu_append_item
409 *
410 * Appends a UI item with specific info for Inkscape/Sodipodi.
411 *
412 */
413 static GtkWidget *
414 sp_ui_menu_append_item( GtkMenu *menu, gchar const *stock,
415 gchar const *label, gchar const *tip, Inkscape::UI::View::View *view, GCallback callback,
416 gpointer data, gboolean with_mnemonic = TRUE )
417 {
418 GtkWidget *item;
420 if (stock) {
421 item = gtk_image_menu_item_new_from_stock(stock, NULL);
422 } else if (label) {
423 item = (with_mnemonic)
424 ? gtk_image_menu_item_new_with_mnemonic(label) :
425 gtk_image_menu_item_new_with_label(label);
426 } else {
427 item = gtk_separator_menu_item_new();
428 }
430 gtk_widget_show(item);
432 if (callback) {
433 g_signal_connect(G_OBJECT(item), "activate", callback, data);
434 }
436 if (tip && view) {
437 g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
438 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) tip );
439 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
440 }
442 gtk_menu_append(GTK_MENU(menu), item);
444 return item;
446 } // end of sp_ui_menu_append_item()
448 /**
449 \brief a wrapper around gdk_keyval_name producing (when possible) characters, not names
450 */
451 static gchar const *
452 sp_key_name(guint keyval)
453 {
454 /* TODO: Compare with the definition of gtk_accel_label_refetch in gtk/gtkaccellabel.c (or
455 simply use GtkAccelLabel as the TODO comment in sp_ui_shortcut_string suggests). */
456 gchar const *n = gdk_keyval_name(gdk_keyval_to_upper(keyval));
458 if (!strcmp(n, "asciicircum")) return "^";
459 else if (!strcmp(n, "parenleft" )) return "(";
460 else if (!strcmp(n, "parenright" )) return ")";
461 else if (!strcmp(n, "plus" )) return "+";
462 else if (!strcmp(n, "minus" )) return "-";
463 else if (!strcmp(n, "asterisk" )) return "*";
464 else if (!strcmp(n, "KP_Multiply")) return "*";
465 else if (!strcmp(n, "Delete" )) return "Del";
466 else if (!strcmp(n, "Page_Up" )) return "PgUp";
467 else if (!strcmp(n, "Page_Down" )) return "PgDn";
468 else if (!strcmp(n, "grave" )) return "`";
469 else if (!strcmp(n, "numbersign" )) return "#";
470 else if (!strcmp(n, "bar" )) return "|";
471 else if (!strcmp(n, "slash" )) return "/";
472 else if (!strcmp(n, "exclam" )) return "!";
473 else if (!strcmp(n, "percent" )) return "%";
474 else return n;
475 }
478 /**
479 * \param shortcut A GDK keyval OR'd with SP_SHORTCUT_blah_MASK values.
480 * \param c Points to a buffer at least 256 bytes long.
481 */
482 void
483 sp_ui_shortcut_string(unsigned const shortcut, gchar *const c)
484 {
485 /* TODO: This function shouldn't exist. Our callers should use GtkAccelLabel instead of
486 * a generic GtkLabel containing this string, and should call gtk_widget_add_accelerator.
487 * Will probably need to change sp_shortcut_invoke callers.
488 *
489 * The existing gtk_label_new_with_mnemonic call can be replaced with
490 * g_object_new(GTK_TYPE_ACCEL_LABEL, NULL) followed by
491 * gtk_label_set_text_with_mnemonic(lbl, str).
492 */
493 static GtkAccelLabelClass const &accel_lbl_cls
494 = *(GtkAccelLabelClass const *) g_type_class_peek_static(GTK_TYPE_ACCEL_LABEL);
496 struct { unsigned test; char const *name; } const modifier_tbl[] = {
497 { SP_SHORTCUT_SHIFT_MASK, accel_lbl_cls.mod_name_shift },
498 { SP_SHORTCUT_CONTROL_MASK, accel_lbl_cls.mod_name_control },
499 { SP_SHORTCUT_ALT_MASK, accel_lbl_cls.mod_name_alt }
500 };
502 gchar *p = c;
503 gchar *end = p + 256;
505 for (unsigned i = 0; i < G_N_ELEMENTS(modifier_tbl); ++i) {
506 if ((shortcut & modifier_tbl[i].test)
507 && (p < end))
508 {
509 p += g_snprintf(p, end - p, "%s%s",
510 modifier_tbl[i].name,
511 accel_lbl_cls.mod_separator);
512 }
513 }
514 if (p < end) {
515 p += g_snprintf(p, end - p, "%s", sp_key_name(shortcut & 0xffffff));
516 }
517 end[-1] = '\0'; // snprintf doesn't guarantee to nul-terminate the string.
518 }
520 void
521 sp_ui_dialog_title_string(Inkscape::Verb *verb, gchar *c)
522 {
523 SPAction *action;
524 unsigned int shortcut;
525 gchar *s;
526 gchar key[256];
527 gchar *atitle;
529 action = verb->get_action(NULL);
530 if (!action)
531 return;
533 atitle = sp_action_get_title(action);
535 s = g_stpcpy(c, atitle);
537 g_free(atitle);
539 shortcut = sp_shortcut_get_primary(verb);
540 if (shortcut) {
541 s = g_stpcpy(s, " (");
542 sp_ui_shortcut_string(shortcut, key);
543 s = g_stpcpy(s, key);
544 s = g_stpcpy(s, ")");
545 }
546 }
549 /**
550 * sp_ui_menu_append_item_from_verb
551 *
552 * Appends a custom menu UI from a verb.
553 *
554 */
556 static GtkWidget *
557 sp_ui_menu_append_item_from_verb(GtkMenu *menu, Inkscape::Verb *verb, Inkscape::UI::View::View *view, bool radio = false, GSList *group = NULL)
558 {
559 SPAction *action;
560 GtkWidget *item;
562 if (verb->get_code() == SP_VERB_NONE) {
564 item = gtk_separator_menu_item_new();
566 } else {
567 unsigned int shortcut;
569 action = verb->get_action(view);
571 if (!action) return NULL;
573 shortcut = sp_shortcut_get_primary(verb);
574 if (shortcut) {
575 gchar c[256];
576 sp_ui_shortcut_string(shortcut, c);
577 GtkWidget *const hb = gtk_hbox_new(FALSE, 16);
578 GtkWidget *const name_lbl = gtk_label_new("");
579 gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
580 gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
581 gtk_box_pack_start((GtkBox *) hb, name_lbl, TRUE, TRUE, 0);
582 GtkWidget *const accel_lbl = gtk_label_new(c);
583 gtk_misc_set_alignment((GtkMisc *) accel_lbl, 1.0, 0.5);
584 gtk_box_pack_end((GtkBox *) hb, accel_lbl, FALSE, FALSE, 0);
585 gtk_widget_show_all(hb);
586 if (radio) {
587 item = gtk_radio_menu_item_new (group);
588 } else {
589 item = gtk_image_menu_item_new();
590 }
591 gtk_container_add((GtkContainer *) item, hb);
592 } else {
593 if (radio) {
594 item = gtk_radio_menu_item_new (group);
595 } else {
596 item = gtk_image_menu_item_new ();
597 }
598 GtkWidget *const name_lbl = gtk_label_new("");
599 gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
600 gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
601 gtk_container_add((GtkContainer *) item, name_lbl);
602 }
604 nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
605 if (!action->sensitive) {
606 gtk_widget_set_sensitive(item, FALSE);
607 }
609 if (action->image) {
610 sp_ui_menuitem_add_icon(item, action->image);
611 }
612 gtk_widget_set_events(item, GDK_KEY_PRESS_MASK);
613 g_signal_connect( G_OBJECT(item), "activate",
614 G_CALLBACK(sp_ui_menu_activate), action );
616 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select_action), action );
617 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect_action), action );
618 }
620 gtk_widget_show(item);
621 gtk_menu_append(GTK_MENU(menu), item);
623 return item;
625 } // end of sp_ui_menu_append_item_from_verb
628 static void
629 checkitem_toggled(GtkCheckMenuItem *menuitem, gpointer user_data)
630 {
631 gchar const *pref = (gchar const *) user_data;
632 Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
634 Glib::ustring pref_path;
635 if (reinterpret_cast<SPDesktop*>(view)->is_focusMode()) {
636 pref_path = "/focus/";
637 } else if (reinterpret_cast<SPDesktop*>(view)->is_fullscreen()) {
638 pref_path = "/fullscreen/";
639 } else {
640 pref_path = "/window/";
641 }
642 pref_path += pref;
643 pref_path += "/state";
645 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
646 gboolean checked = gtk_check_menu_item_get_active(menuitem);
647 prefs->setBool(pref_path, checked);
649 reinterpret_cast<SPDesktop*>(view)->layoutWidget();
650 }
652 static gboolean
653 checkitem_update(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_data)
654 {
655 GtkCheckMenuItem *menuitem=GTK_CHECK_MENU_ITEM(widget);
657 gchar const *pref = (gchar const *) user_data;
658 Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
660 Glib::ustring pref_path;
661 if ((static_cast<SPDesktop*>(view))->is_fullscreen()) {
662 pref_path = "/fullscreen/";
663 } else {
664 pref_path = "/window/";
665 }
666 pref_path += pref;
668 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
669 bool ison = prefs->getBool(pref_path + "/state", true);
671 g_signal_handlers_block_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
672 gtk_check_menu_item_set_active(menuitem, ison);
673 g_signal_handlers_unblock_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
675 return FALSE;
676 }
679 void
680 sp_ui_menu_append_check_item_from_verb(GtkMenu *menu, Inkscape::UI::View::View *view, gchar const *label, gchar const *tip, gchar const *pref,
681 void (*callback_toggle)(GtkCheckMenuItem *, gpointer user_data),
682 gboolean (*callback_update)(GtkWidget *widget, GdkEventExpose *event, gpointer user_data),
683 Inkscape::Verb *verb)
684 {
685 GtkWidget *item;
687 unsigned int shortcut = 0;
688 SPAction *action = NULL;
690 if (verb) {
691 shortcut = sp_shortcut_get_primary(verb);
692 action = verb->get_action(view);
693 }
695 if (verb && shortcut) {
696 gchar c[256];
697 sp_ui_shortcut_string(shortcut, c);
699 GtkWidget *hb = gtk_hbox_new(FALSE, 16);
701 {
702 GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
703 gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
704 gtk_box_pack_start((GtkBox *) hb, l, TRUE, TRUE, 0);
705 }
707 {
708 GtkWidget *l = gtk_label_new(c);
709 gtk_misc_set_alignment((GtkMisc *) l, 1.0, 0.5);
710 gtk_box_pack_end((GtkBox *) hb, l, FALSE, FALSE, 0);
711 }
713 gtk_widget_show_all(hb);
715 item = gtk_check_menu_item_new();
716 gtk_container_add((GtkContainer *) item, hb);
717 } else {
718 GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
719 gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
720 item = gtk_check_menu_item_new();
721 gtk_container_add((GtkContainer *) item, l);
722 }
723 #if 0
724 nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
725 if (!action->sensitive) {
726 gtk_widget_set_sensitive(item, FALSE);
727 }
728 #endif
729 gtk_widget_show(item);
731 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
733 g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
735 g_signal_connect( G_OBJECT(item), "toggled", (GCallback) callback_toggle, (void *) pref);
736 g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) callback_update, (void *) pref);
738 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) (action ? action->tip : tip));
739 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
740 }
742 static void
743 sp_recent_open(GtkRecentChooser *recent_menu, gpointer /*user_data*/)
744 {
745 // dealing with the bizarre filename convention in Inkscape for now
746 gchar *uri = gtk_recent_chooser_get_current_uri(GTK_RECENT_CHOOSER(recent_menu));
747 gchar *local_fn = g_filename_from_uri(uri, NULL, NULL);
748 gchar *utf8_fn = g_filename_to_utf8(local_fn, -1, NULL, NULL, NULL);
749 sp_file_open(utf8_fn, NULL);
750 g_free(utf8_fn);
751 g_free(local_fn);
752 g_free(uri);
753 }
755 static void
756 sp_file_new_from_template(GtkWidget */*widget*/, gchar const *uri)
757 {
758 sp_file_new(uri);
759 }
761 void
762 sp_menu_append_new_templates(GtkWidget *menu, Inkscape::UI::View::View *view)
763 {
764 std::list<gchar *> sources;
765 sources.push_back( profile_path("templates") ); // first try user's local dir
766 sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir
768 // Use this loop to iterate through a list of possible document locations.
769 while (!sources.empty()) {
770 gchar *dirname = sources.front();
772 if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) {
773 GError *err = 0;
774 GDir *dir = g_dir_open(dirname, 0, &err);
776 if (dir) {
777 for (gchar const *file = g_dir_read_name(dir); file != NULL; file = g_dir_read_name(dir)) {
778 if (!g_str_has_suffix(file, ".svg") && !g_str_has_suffix(file, ".svgz"))
779 continue; // skip non-svg files
781 gchar *basename = g_path_get_basename(file);
782 if (g_str_has_suffix(basename, ".svg") && g_str_has_prefix(basename, "default."))
783 continue; // skip default.*.svg (i.e. default.svg and translations) - it's in the menu already
785 gchar const *filepath = g_build_filename(dirname, file, NULL);
786 gchar *dupfile = g_strndup(file, strlen(file) - 4);
787 gchar *filename = g_filename_to_utf8(dupfile, -1, NULL, NULL, NULL);
788 g_free(dupfile);
789 GtkWidget *item = gtk_menu_item_new_with_label(filename);
790 g_free(filename);
792 gtk_widget_show(item);
793 // how does "filepath" ever get freed?
794 g_signal_connect(G_OBJECT(item),
795 "activate",
796 G_CALLBACK(sp_file_new_from_template),
797 (gpointer) filepath);
799 if (view) {
800 // set null tip for now; later use a description from the template file
801 g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
802 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) NULL );
803 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
804 }
806 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
807 }
808 g_dir_close(dir);
809 }
810 }
812 // toss the dirname
813 g_free(dirname);
814 sources.pop_front();
815 }
816 }
818 void
819 sp_ui_checkboxes_menus(GtkMenu *m, Inkscape::UI::View::View *view)
820 {
821 //sp_ui_menu_append_check_item_from_verb(m, view, _("_Menu"), _("Show or hide the menu bar"), "menu",
822 // checkitem_toggled, checkitem_update, 0);
823 sp_ui_menu_append_check_item_from_verb(m, view, _("Commands Bar"), _("Show or hide the Commands bar (under the menu)"), "commands",
824 checkitem_toggled, checkitem_update, 0);
825 sp_ui_menu_append_check_item_from_verb(m, view, _("Snap controls Bar"), _("Show or hide the snapping controls"), "snaptoolbox",
826 checkitem_toggled, checkitem_update, 0);
827 sp_ui_menu_append_check_item_from_verb(m, view, _("Tool Controls Bar"), _("Show or hide the Tool Controls bar"), "toppanel",
828 checkitem_toggled, checkitem_update, 0);
829 sp_ui_menu_append_check_item_from_verb(m, view, _("_Toolbox"), _("Show or hide the main toolbox (on the left)"), "toolbox",
830 checkitem_toggled, checkitem_update, 0);
831 sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "rulers",
832 checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_RULERS));
833 sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "scrollbars",
834 checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_SCROLLBARS));
835 sp_ui_menu_append_check_item_from_verb(m, view, _("_Palette"), _("Show or hide the color palette"), "panels",
836 checkitem_toggled, checkitem_update, 0);
837 sp_ui_menu_append_check_item_from_verb(m, view, _("_Statusbar"), _("Show or hide the statusbar (at the bottom of the window)"), "statusbar",
838 checkitem_toggled, checkitem_update, 0);
839 }
841 /** @brief Observer that updates the recent list's max document count */
842 class MaxRecentObserver : public Inkscape::Preferences::Observer {
843 public:
844 MaxRecentObserver(GtkWidget *recent_menu) :
845 Observer("/options/maxrecentdocuments/value"),
846 _rm(recent_menu)
847 {}
848 virtual void notify(Inkscape::Preferences::Entry const &e) {
849 gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(_rm), e.getInt());
850 // hack: the recent menu doesn't repopulate after changing the limit, so we force it
851 g_signal_emit_by_name((gpointer) gtk_recent_manager_get_default(), "changed");
852 }
853 private:
854 GtkWidget *_rm;
855 };
857 /** \brief This function turns XML into a menu
858 \param menus This is the XML that defines the menu
859 \param menu Menu to be added to
860 \param view The View that this menu is being built for
862 This function is realitively simple as it just goes through the XML
863 and parses the individual elements. In the case of a submenu, it
864 just calls itself recursively. Because it is only reasonable to have
865 a couple of submenus, it is unlikely this will go more than two or
866 three times.
868 In the case of an unreconginzed verb, a menu item is made to identify
869 the verb that is missing, and display that. The menu item is also made
870 insensitive.
871 */
872 void
873 sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI::View::View *view)
874 {
875 if (menus == NULL) return;
876 if (menu == NULL) return;
877 GSList *group = NULL;
879 for (Inkscape::XML::Node *menu_pntr = menus;
880 menu_pntr != NULL;
881 menu_pntr = menu_pntr->next()) {
882 if (!strcmp(menu_pntr->name(), "submenu")) {
883 GtkWidget *mitem = gtk_menu_item_new_with_mnemonic(_(menu_pntr->attribute("name")));
884 GtkWidget *submenu = gtk_menu_new();
885 sp_ui_build_dyn_menus(menu_pntr->firstChild(), submenu, view);
886 gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), GTK_WIDGET(submenu));
887 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
888 continue;
889 }
890 if (!strcmp(menu_pntr->name(), "verb")) {
891 gchar const *verb_name = menu_pntr->attribute("verb-id");
892 Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name);
894 if (verb != NULL) {
895 if (menu_pntr->attribute("radio") != NULL) {
896 GtkWidget *item = sp_ui_menu_append_item_from_verb (GTK_MENU(menu), verb, view, true, group);
897 group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item));
898 if (menu_pntr->attribute("default") != NULL) {
899 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
900 }
901 } else {
902 sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view);
903 group = NULL;
904 }
905 } else {
906 gchar string[120];
907 g_snprintf(string, 120, _("Verb \"%s\" Unknown"), verb_name);
908 string[119] = '\0'; /* may not be terminated */
909 GtkWidget *item = gtk_menu_item_new_with_label(string);
910 gtk_widget_set_sensitive(item, false);
911 gtk_widget_show(item);
912 gtk_menu_append(GTK_MENU(menu), item);
913 }
914 continue;
915 }
916 if (!strcmp(menu_pntr->name(), "separator")
917 // This was spelt wrong in the original version
918 // and so this is for backward compatibility. It can
919 // probably be dropped after the 0.44 release.
920 || !strcmp(menu_pntr->name(), "seperator")) {
921 GtkWidget *item = gtk_separator_menu_item_new();
922 gtk_widget_show(item);
923 gtk_menu_append(GTK_MENU(menu), item);
924 continue;
925 }
926 if (!strcmp(menu_pntr->name(), "template-list")) {
927 sp_menu_append_new_templates(menu, view);
928 continue;
929 }
930 if (!strcmp(menu_pntr->name(), "recent-file-list")) {
931 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
933 // create recent files menu
934 int max_recent = prefs->getInt("/options/maxrecentdocuments/value");
935 GtkWidget *recent_menu = gtk_recent_chooser_menu_new_for_manager(gtk_recent_manager_get_default());
936 gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(recent_menu), max_recent);
937 // sort most recently used documents first to preserve previous behavior
938 gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(recent_menu), GTK_RECENT_SORT_MRU);
939 g_signal_connect(G_OBJECT(recent_menu), "item-activated", G_CALLBACK(sp_recent_open), (gpointer) NULL);
941 // add filter to only open files added by Inkscape
942 GtkRecentFilter *inkscape_only_filter = gtk_recent_filter_new();
943 gtk_recent_filter_add_application(inkscape_only_filter, g_get_prgname());
944 gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_menu), inkscape_only_filter);
946 GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic(_("Open _Recent"));
947 gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), recent_menu);
949 gtk_menu_append(GTK_MENU(menu), GTK_WIDGET(recent_item));
950 // this will just sit and update the list's item count
951 static MaxRecentObserver *mro = new MaxRecentObserver(recent_menu);
952 prefs->addObserver(*mro);
953 continue;
954 }
955 if (!strcmp(menu_pntr->name(), "objects-checkboxes")) {
956 sp_ui_checkboxes_menus(GTK_MENU(menu), view);
957 continue;
958 }
959 }
960 }
962 /** \brief Build the main tool bar
963 \param view View to build the bar for
965 Currently the main tool bar is built as a dynamic XML menu using
966 \c sp_ui_build_dyn_menus. This function builds the bar, and then
967 pass it to get items attached to it.
968 */
969 GtkWidget *
970 sp_ui_main_menubar(Inkscape::UI::View::View *view)
971 {
972 GtkWidget *mbar = gtk_menu_bar_new();
974 #ifdef GDK_WINDOWING_QUARTZ
975 ige_mac_menu_set_menu_bar(GTK_MENU_SHELL(mbar));
976 #endif
978 sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view);
980 #ifdef GDK_WINDOWING_QUARTZ
981 return NULL;
982 #else
983 return mbar;
984 #endif
985 }
987 static void leave_group(GtkMenuItem *, SPDesktop *desktop) {
988 desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer()));
989 }
991 static void enter_group(GtkMenuItem *mi, SPDesktop *desktop) {
992 desktop->setCurrentLayer(reinterpret_cast<SPObject *>(g_object_get_data(G_OBJECT(mi), "group")));
993 sp_desktop_selection(desktop)->clear();
994 }
996 GtkWidget *
997 sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item)
998 {
999 GtkWidget *m;
1000 SPDesktop *dt;
1002 dt = static_cast<SPDesktop*>(view);
1004 m = gtk_menu_new();
1006 /* Undo and Redo */
1007 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_UNDO), view);
1008 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_REDO), view);
1010 /* Separator */
1011 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1013 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_CUT), view);
1014 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_COPY), view);
1015 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_PASTE), 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_DUPLICATE), view);
1021 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DELETE), view);
1023 /* Item menu */
1024 if (item) {
1025 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1026 sp_object_menu((SPObject *) item, dt, GTK_MENU(m));
1027 }
1029 /* layer menu */
1030 SPGroup *group=NULL;
1031 if (item) {
1032 if (SP_IS_GROUP(item)) {
1033 group = SP_GROUP(item);
1034 } else if ( item != dt->currentRoot() && SP_IS_GROUP(SP_OBJECT_PARENT(item)) ) {
1035 group = SP_GROUP(SP_OBJECT_PARENT(item));
1036 }
1037 }
1039 if (( group && group != dt->currentLayer() ) ||
1040 ( dt->currentLayer() != dt->currentRoot() && SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) ) {
1041 /* Separator */
1042 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1043 }
1045 if ( group && group != dt->currentLayer() ) {
1046 /* TRANSLATORS: #%s is the id of the group e.g. <g id="#g7">, not a number. */
1047 gchar *label=g_strdup_printf(_("Enter group #%s"), SP_OBJECT_ID(group));
1048 GtkWidget *w = gtk_menu_item_new_with_label(label);
1049 g_free(label);
1050 g_object_set_data(G_OBJECT(w), "group", group);
1051 g_signal_connect(G_OBJECT(w), "activate", GCallback(enter_group), dt);
1052 gtk_widget_show(w);
1053 gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1054 }
1056 if ( dt->currentLayer() != dt->currentRoot() ) {
1057 if ( SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) {
1058 GtkWidget *w = gtk_menu_item_new_with_label(_("Go to parent"));
1059 g_signal_connect(G_OBJECT(w), "activate", GCallback(leave_group), dt);
1060 gtk_widget_show(w);
1061 gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1063 }
1064 }
1066 return m;
1067 }
1069 /* Drag and Drop */
1070 void
1071 sp_ui_drag_data_received(GtkWidget *widget,
1072 GdkDragContext *drag_context,
1073 gint x, gint y,
1074 GtkSelectionData *data,
1075 guint info,
1076 guint /*event_time*/,
1077 gpointer /*user_data*/)
1078 {
1079 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1080 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1082 switch (info) {
1083 #if ENABLE_MAGIC_COLORS
1084 case APP_X_INKY_COLOR:
1085 {
1086 int destX = 0;
1087 int destY = 0;
1088 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1089 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1091 SPItem *item = desktop->item_at_point( where, true );
1092 if ( item )
1093 {
1094 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1096 if ( data->length >= 8 ) {
1097 cmsHPROFILE srgbProf = cmsCreate_sRGBProfile();
1099 gchar c[64] = {0};
1100 // Careful about endian issues.
1101 guint16* dataVals = (guint16*)data->data;
1102 sp_svg_write_color( c, sizeof(c),
1103 SP_RGBA32_U_COMPOSE(
1104 0x0ff & (dataVals[0] >> 8),
1105 0x0ff & (dataVals[1] >> 8),
1106 0x0ff & (dataVals[2] >> 8),
1107 0xff // can't have transparency in the color itself
1108 //0x0ff & (data->data[3] >> 8),
1109 ));
1110 SPCSSAttr *css = sp_repr_css_attr_new();
1111 bool updatePerformed = false;
1113 if ( data->length > 14 ) {
1114 int flags = dataVals[4];
1116 // piggie-backed palette entry info
1117 int index = dataVals[5];
1118 Glib::ustring palName;
1119 for ( int i = 0; i < dataVals[6]; i++ ) {
1120 palName += (gunichar)dataVals[7+i];
1121 }
1123 // Now hook in a magic tag of some sort.
1124 if ( !palName.empty() && (flags & 1) ) {
1125 gchar* str = g_strdup_printf("%d|", index);
1126 palName.insert( 0, str );
1127 g_free(str);
1128 str = 0;
1130 sp_object_setAttribute( SP_OBJECT(item),
1131 fillnotstroke ? "inkscape:x-fill-tag":"inkscape:x-stroke-tag",
1132 palName.c_str(),
1133 false );
1134 item->updateRepr();
1136 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1137 updatePerformed = true;
1138 }
1139 }
1141 if ( !updatePerformed ) {
1142 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1143 }
1145 sp_desktop_apply_css_recursive( item, css, true );
1146 item->updateRepr();
1148 sp_document_done( doc , SP_VERB_NONE,
1149 _("Drop color"));
1151 if ( srgbProf ) {
1152 cmsCloseProfile( srgbProf );
1153 }
1154 }
1155 }
1156 }
1157 break;
1158 #endif // ENABLE_MAGIC_COLORS
1160 case APP_X_COLOR:
1161 {
1162 int destX = 0;
1163 int destY = 0;
1164 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1165 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1166 Geom::Point const button_dt(desktop->w2d(where));
1167 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1169 if ( data->length == 8 ) {
1170 gchar colorspec[64] = {0};
1171 // Careful about endian issues.
1172 guint16* dataVals = (guint16*)data->data;
1173 sp_svg_write_color( colorspec, sizeof(colorspec),
1174 SP_RGBA32_U_COMPOSE(
1175 0x0ff & (dataVals[0] >> 8),
1176 0x0ff & (dataVals[1] >> 8),
1177 0x0ff & (dataVals[2] >> 8),
1178 0xff // can't have transparency in the color itself
1179 //0x0ff & (data->data[3] >> 8),
1180 ));
1182 SPItem *item = desktop->item_at_point( where, true );
1184 bool consumed = false;
1185 if (desktop->event_context && desktop->event_context->get_drag()) {
1186 consumed = desktop->event_context->get_drag()->dropColor(item, colorspec, button_dt);
1187 if (consumed) {
1188 sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1189 desktop->event_context->get_drag()->updateDraggers();
1190 }
1191 }
1193 //if (!consumed && tools_active(desktop, TOOLS_TEXT)) {
1194 // consumed = sp_text_context_drop_color(c, button_doc);
1195 // if (consumed) {
1196 // sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient stop"));
1197 // }
1198 //}
1200 if (!consumed && item) {
1201 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1202 if (fillnotstroke &&
1203 (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1204 Path *livarot_path = Path_for_item(item, true, true);
1205 livarot_path->ConvertWithBackData(0.04);
1207 boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1208 if (position) {
1209 Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1210 Geom::Point delta = nearest - button_doc;
1211 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1212 delta = desktop->d2w(delta);
1213 double stroke_tolerance =
1214 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1215 desktop->current_zoom() *
1216 SP_OBJECT_STYLE (item)->stroke_width.computed *
1217 to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1218 : 0.0)
1219 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1221 if (Geom::L2 (delta) < stroke_tolerance) {
1222 fillnotstroke = false;
1223 }
1224 }
1225 delete livarot_path;
1226 }
1228 SPCSSAttr *css = sp_repr_css_attr_new();
1229 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec );
1231 sp_desktop_apply_css_recursive( item, css, true );
1232 item->updateRepr();
1234 sp_document_done( doc , SP_VERB_NONE,
1235 _("Drop color"));
1236 }
1237 }
1238 }
1239 break;
1241 case APP_OSWB_COLOR:
1242 {
1243 bool worked = false;
1244 Glib::ustring colorspec;
1245 if ( data->format == 8 ) {
1246 eek::ColorDef color;
1247 worked = color.fromMIMEData("application/x-oswb-color",
1248 reinterpret_cast<char*>(data->data),
1249 data->length,
1250 data->format);
1251 if ( worked ) {
1252 if ( color.getType() == eek::ColorDef::CLEAR ) {
1253 colorspec = ""; // TODO check if this is sufficient
1254 } else if ( color.getType() == eek::ColorDef::NONE ) {
1255 colorspec = "none";
1256 } else {
1257 unsigned int r = color.getR();
1258 unsigned int g = color.getG();
1259 unsigned int b = color.getB();
1261 SPGradient* matches = 0;
1262 const GSList *gradients = sp_document_get_resource_list(doc, "gradient");
1263 for (const GSList *item = gradients; item; item = item->next) {
1264 SPGradient* grad = SP_GRADIENT(item->data);
1265 if ( color.descr == grad->id ) {
1266 if ( grad->has_stops ) {
1267 matches = grad;
1268 break;
1269 }
1270 }
1271 }
1272 if (matches) {
1273 colorspec = "url(#";
1274 colorspec += matches->id;
1275 colorspec += ")";
1276 } else {
1277 gchar* tmp = g_strdup_printf("#%02x%02x%02x", r, g, b);
1278 colorspec = tmp;
1279 g_free(tmp);
1280 }
1281 }
1282 }
1283 }
1284 if ( worked ) {
1285 int destX = 0;
1286 int destY = 0;
1287 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1288 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1289 Geom::Point const button_dt(desktop->w2d(where));
1290 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1292 SPItem *item = desktop->item_at_point( where, true );
1294 bool consumed = false;
1295 if (desktop->event_context && desktop->event_context->get_drag()) {
1296 consumed = desktop->event_context->get_drag()->dropColor(item, colorspec.c_str(), button_dt);
1297 if (consumed) {
1298 sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1299 desktop->event_context->get_drag()->updateDraggers();
1300 }
1301 }
1303 if (!consumed && item) {
1304 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1305 if (fillnotstroke &&
1306 (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1307 Path *livarot_path = Path_for_item(item, true, true);
1308 livarot_path->ConvertWithBackData(0.04);
1310 boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1311 if (position) {
1312 Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1313 Geom::Point delta = nearest - button_doc;
1314 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1315 delta = desktop->d2w(delta);
1316 double stroke_tolerance =
1317 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1318 desktop->current_zoom() *
1319 SP_OBJECT_STYLE (item)->stroke_width.computed *
1320 to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1321 : 0.0)
1322 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1324 if (Geom::L2 (delta) < stroke_tolerance) {
1325 fillnotstroke = false;
1326 }
1327 }
1328 delete livarot_path;
1329 }
1331 SPCSSAttr *css = sp_repr_css_attr_new();
1332 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec.c_str() );
1334 sp_desktop_apply_css_recursive( item, css, true );
1335 item->updateRepr();
1337 sp_document_done( doc , SP_VERB_NONE,
1338 _("Drop color"));
1339 }
1340 }
1341 }
1342 break;
1344 case SVG_DATA:
1345 case SVG_XML_DATA: {
1346 gchar *svgdata = (gchar *)data->data;
1348 Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI);
1350 if (rnewdoc == NULL) {
1351 sp_ui_error_dialog(_("Could not parse SVG data"));
1352 return;
1353 }
1355 Inkscape::XML::Node *repr = rnewdoc->root();
1356 gchar const *style = repr->attribute("style");
1358 Inkscape::XML::Node *newgroup = rnewdoc->createElement("svg:g");
1359 newgroup->setAttribute("style", style);
1361 Inkscape::XML::Document * xml_doc = sp_document_repr_doc(doc);
1362 for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) {
1363 Inkscape::XML::Node *newchild = child->duplicate(xml_doc);
1364 newgroup->appendChild(newchild);
1365 }
1367 Inkscape::GC::release(rnewdoc);
1369 // Add it to the current layer
1371 // Greg's edits to add intelligent positioning of svg drops
1372 SPObject *new_obj = NULL;
1373 new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
1375 Inkscape::Selection *selection = sp_desktop_selection(desktop);
1376 selection->set(SP_ITEM(new_obj));
1377 // To move the imported object, we must temporarily set the "transform pattern with
1378 // object" option.
1379 {
1380 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1381 bool const saved_pref = prefs->getBool("/options/transform/pattern", true);
1382 prefs->setBool("/options/transform/pattern", true);
1383 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
1384 Geom::OptRect sel_bbox = selection->bounds();
1385 if (sel_bbox) {
1386 Geom::Point m( desktop->point() - sel_bbox->midpoint() );
1387 sp_selection_move_relative(selection, m);
1388 }
1389 prefs->setBool("/options/transform/pattern", saved_pref);
1390 }
1392 Inkscape::GC::release(newgroup);
1393 sp_document_done(doc, SP_VERB_NONE,
1394 _("Drop SVG"));
1395 break;
1396 }
1398 case URI_LIST: {
1399 gchar *uri = (gchar *)data->data;
1400 sp_ui_import_files(uri);
1401 break;
1402 }
1404 case PNG_DATA:
1405 case JPEG_DATA:
1406 case IMAGE_DATA: {
1407 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
1408 Inkscape::XML::Node *newImage = xml_doc->createElement("svg:image");
1409 gchar *atom_name = gdk_atom_name(data->type);
1411 // this formula taken from Glib docs
1412 guint needed_size = data->length * 4 / 3 + data->length * 4 / (3 * 72) + 7;
1413 needed_size += 5 + 8 + strlen(atom_name); // 5 bytes for data:, 8 for ;base64,
1415 gchar *buffer = (gchar *) g_malloc(needed_size), *buf_work = buffer;
1416 buf_work += g_sprintf(buffer, "data:%s;base64,", atom_name);
1418 gint state = 0, save = 0;
1419 g_base64_encode_step(data->data, data->length, TRUE, buf_work, &state, &save);
1420 g_base64_encode_close(TRUE, buf_work, &state, &save);
1422 newImage->setAttribute("xlink:href", buffer);
1423 g_free(buffer);
1425 GError *error = NULL;
1426 GdkPixbufLoader *loader = gdk_pixbuf_loader_new_with_mime_type( gdk_atom_name(data->type), &error );
1427 if ( loader ) {
1428 error = NULL;
1429 if ( gdk_pixbuf_loader_write( loader, data->data, data->length, &error) ) {
1430 GdkPixbuf *pbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1431 if ( pbuf ) {
1432 char tmp[1024];
1433 int width = gdk_pixbuf_get_width(pbuf);
1434 int height = gdk_pixbuf_get_height(pbuf);
1435 snprintf( tmp, sizeof(tmp), "%d", width );
1436 newImage->setAttribute("width", tmp);
1438 snprintf( tmp, sizeof(tmp), "%d", height );
1439 newImage->setAttribute("height", tmp);
1440 }
1441 }
1442 }
1443 g_free(atom_name);
1445 // Add it to the current layer
1446 desktop->currentLayer()->appendChildRepr(newImage);
1448 Inkscape::GC::release(newImage);
1449 sp_document_done( doc , SP_VERB_NONE,
1450 _("Drop bitmap image"));
1451 break;
1452 }
1453 }
1454 }
1456 #include "gradient-context.h"
1458 void sp_ui_drag_motion( GtkWidget */*widget*/,
1459 GdkDragContext */*drag_context*/,
1460 gint /*x*/, gint /*y*/,
1461 GtkSelectionData */*data*/,
1462 guint /*info*/,
1463 guint /*event_time*/,
1464 gpointer /*user_data*/)
1465 {
1466 // SPDocument *doc = SP_ACTIVE_DOCUMENT;
1467 // SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1470 // g_message("drag-n-drop motion (%4d, %4d) at %d", x, y, event_time);
1471 }
1473 static void sp_ui_drag_leave( GtkWidget */*widget*/,
1474 GdkDragContext */*drag_context*/,
1475 guint /*event_time*/,
1476 gpointer /*user_data*/ )
1477 {
1478 // g_message("drag-n-drop leave at %d", event_time);
1479 }
1481 static void
1482 sp_ui_import_files(gchar *buffer)
1483 {
1484 GList *list = gnome_uri_list_extract_filenames(buffer);
1485 if (!list)
1486 return;
1487 g_list_foreach(list, sp_ui_import_one_file_with_check, NULL);
1488 g_list_foreach(list, (GFunc) g_free, NULL);
1489 g_list_free(list);
1490 }
1492 static void
1493 sp_ui_import_one_file_with_check(gpointer filename, gpointer /*unused*/)
1494 {
1495 if (filename) {
1496 if (strlen((char const *)filename) > 2)
1497 sp_ui_import_one_file((char const *)filename);
1498 }
1499 }
1501 static void
1502 sp_ui_import_one_file(char const *filename)
1503 {
1504 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1505 if (!doc) return;
1507 if (filename == NULL) return;
1509 // Pass off to common implementation
1510 // TODO might need to get the proper type of Inkscape::Extension::Extension
1511 file_import( doc, filename, NULL );
1512 }
1514 void
1515 sp_ui_error_dialog(gchar const *message)
1516 {
1517 GtkWidget *dlg;
1518 gchar *safeMsg = Inkscape::IO::sanitizeString(message);
1520 dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
1521 GTK_BUTTONS_CLOSE, "%s", safeMsg);
1522 sp_transientize(dlg);
1523 gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE);
1524 gtk_dialog_run(GTK_DIALOG(dlg));
1525 gtk_widget_destroy(dlg);
1526 g_free(safeMsg);
1527 }
1529 bool
1530 sp_ui_overwrite_file(gchar const *filename)
1531 {
1532 bool return_value = FALSE;
1534 if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) {
1535 Gtk::Window *window = SP_ACTIVE_DESKTOP->getToplevel();
1536 gchar* baseName = g_path_get_basename( filename );
1537 gchar* dirName = g_path_get_dirname( filename );
1538 GtkWidget* dialog = gtk_message_dialog_new_with_markup( window->gobj(),
1539 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
1540 GTK_MESSAGE_QUESTION,
1541 GTK_BUTTONS_NONE,
1542 _( "<span weight=\"bold\" size=\"larger\">A file named \"%s\" already exists. Do you want to replace it?</span>\n\n"
1543 "The file already exists in \"%s\". Replacing it will overwrite its contents." ),
1544 baseName,
1545 dirName
1546 );
1547 gtk_dialog_add_buttons( GTK_DIALOG(dialog),
1548 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1549 _("Replace"), GTK_RESPONSE_YES,
1550 NULL );
1551 gtk_dialog_set_default_response( GTK_DIALOG(dialog), GTK_RESPONSE_YES );
1553 if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_YES ) {
1554 return_value = TRUE;
1555 } else {
1556 return_value = FALSE;
1557 }
1558 gtk_widget_destroy(dialog);
1559 g_free( baseName );
1560 g_free( dirName );
1561 } else {
1562 return_value = TRUE;
1563 }
1565 return return_value;
1566 }
1568 static void
1569 sp_ui_menu_item_set_sensitive(SPAction */*action*/, unsigned int sensitive, void *data)
1570 {
1571 return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
1572 }
1574 static void
1575 sp_ui_menu_item_set_name(SPAction */*action*/, Glib::ustring name, void *data)
1576 {
1577 void *child = GTK_BIN (data)->child;
1578 //child is either
1579 //- a GtkHBox, whose first child is a label displaying name if the menu
1580 //item has an accel key
1581 //- a GtkLabel if the menu has no accel key
1582 if (GTK_IS_LABEL(child)) {
1583 gtk_label_set_markup_with_mnemonic(GTK_LABEL (child), name.c_str());
1584 } else if (GTK_IS_HBOX(child)) {
1585 gtk_label_set_markup_with_mnemonic(
1586 GTK_LABEL (gtk_container_get_children(GTK_CONTAINER (child))->data),
1587 name.c_str());
1588 }//else sp_ui_menu_append_item_from_verb has been modified and can set
1589 //a menu item in yet another way...
1590 }
1593 /*
1594 Local Variables:
1595 mode:c++
1596 c-file-style:"stroustrup"
1597 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1598 indent-tabs-mode:nil
1599 fill-column:99
1600 End:
1601 */
1602 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :