d628d964d3bc10bfc5e2e62546101d91de8fe7e4
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/ege-paint-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) {
186 x = MIN(gdk_screen_width() - w, x);
187 y = MIN(gdk_screen_height() - h, y);
188 desktop->setWindowSize(w, h);
189 }
191 // Only restore xy for the first window so subsequent windows don't overlap exactly
192 // with first. (Maybe rule should be only restore xy if it's different from xy of
193 // other desktops?)
195 // Empirically it seems that active_desktop==this desktop only the first time a
196 // desktop is created.
197 SPDesktop *active_desktop = SP_ACTIVE_DESKTOP;
198 if (active_desktop == desktop || active_desktop==NULL) {
199 desktop->setWindowPosition(Geom::Point(x, y));
200 }
201 }
202 if (maxed) {
203 win->maximize();
204 }
205 if (full) {
206 win->fullscreen();
207 }
208 }
210 } else {
211 gtk_window_set_policy(GTK_WINDOW(win->gobj()), TRUE, TRUE, TRUE);
212 }
214 if ( completeDropTargets == 0 || completeDropTargetsCount == 0 )
215 {
216 std::vector<gchar*> types;
218 GSList *list = gdk_pixbuf_get_formats();
219 while ( list ) {
220 int i = 0;
221 GdkPixbufFormat *one = (GdkPixbufFormat*)list->data;
222 gchar** typesXX = gdk_pixbuf_format_get_mime_types(one);
223 for ( i = 0; typesXX[i]; i++ ) {
224 types.push_back(g_strdup(typesXX[i]));
225 }
226 g_strfreev(typesXX);
228 list = g_slist_next(list);
229 }
230 completeDropTargetsCount = nui_drop_target_entries + types.size();
231 completeDropTargets = new GtkTargetEntry[completeDropTargetsCount];
232 for ( int i = 0; i < (int)nui_drop_target_entries; i++ ) {
233 completeDropTargets[i] = ui_drop_target_entries[i];
234 }
235 int pos = nui_drop_target_entries;
237 for (std::vector<gchar*>::iterator it = types.begin() ; it != types.end() ; it++) {
238 completeDropTargets[pos].target = *it;
239 completeDropTargets[pos].flags = 0;
240 completeDropTargets[pos].info = IMAGE_DATA;
241 pos++;
242 }
243 }
245 gtk_drag_dest_set((GtkWidget*)win->gobj(),
246 GTK_DEST_DEFAULT_ALL,
247 completeDropTargets,
248 completeDropTargetsCount,
249 GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE));
252 g_signal_connect(G_OBJECT(win->gobj()),
253 "drag_data_received",
254 G_CALLBACK(sp_ui_drag_data_received),
255 NULL);
256 g_signal_connect(G_OBJECT(win->gobj()),
257 "drag_motion",
258 G_CALLBACK(sp_ui_drag_motion),
259 NULL);
260 g_signal_connect(G_OBJECT(win->gobj()),
261 "drag_leave",
262 G_CALLBACK(sp_ui_drag_leave),
263 NULL);
264 win->show();
266 // needed because the first ACTIVATE_DESKTOP was sent when there was no window yet
267 inkscape_reactivate_desktop(SP_DESKTOP_WIDGET(vw)->desktop);
268 }
270 void
271 sp_ui_new_view()
272 {
273 SPDocument *document;
274 SPViewWidget *dtw;
276 document = SP_ACTIVE_DOCUMENT;
277 if (!document) return;
279 dtw = sp_desktop_widget_new(sp_document_namedview(document, NULL));
280 g_return_if_fail(dtw != NULL);
282 sp_create_window(dtw, TRUE);
283 sp_namedview_window_from_document(static_cast<SPDesktop*>(dtw->view));
284 sp_namedview_update_layers_from_document(static_cast<SPDesktop*>(dtw->view));
285 }
287 /* TODO: not yet working */
288 /* To be re-enabled (by adding to menu) once it works. */
289 void
290 sp_ui_new_view_preview()
291 {
292 SPDocument *document;
293 SPViewWidget *dtw;
295 document = SP_ACTIVE_DOCUMENT;
296 if (!document) return;
298 dtw = (SPViewWidget *) sp_svg_view_widget_new(document);
299 g_return_if_fail(dtw != NULL);
300 sp_svg_view_widget_set_resize(SP_SVG_VIEW_WIDGET(dtw), TRUE, 400.0, 400.0);
302 sp_create_window(dtw, FALSE);
303 }
305 /**
306 * \param widget unused
307 */
308 void
309 sp_ui_close_view(GtkWidget */*widget*/)
310 {
311 if (SP_ACTIVE_DESKTOP == NULL) {
312 return;
313 }
314 if ((SP_ACTIVE_DESKTOP)->shutdown()) {
315 return;
316 }
317 SP_ACTIVE_DESKTOP->destroyWidget();
318 }
321 /**
322 * sp_ui_close_all
323 *
324 * This function is called to exit the program, and iterates through all
325 * open document view windows, attempting to close each in turn. If the
326 * view has unsaved information, the user will be prompted to save,
327 * discard, or cancel.
328 *
329 * Returns FALSE if the user cancels the close_all operation, TRUE
330 * otherwise.
331 */
332 unsigned int
333 sp_ui_close_all(void)
334 {
335 /* Iterate through all the windows, destroying each in the order they
336 become active */
337 while (SP_ACTIVE_DESKTOP) {
338 if ((SP_ACTIVE_DESKTOP)->shutdown()) {
339 /* The user cancelled the operation, so end doing the close */
340 return FALSE;
341 }
342 SP_ACTIVE_DESKTOP->destroyWidget();
343 }
345 return TRUE;
346 }
348 /*
349 * Some day when the right-click menus are ready to start working
350 * smarter with the verbs, we'll need to change this NULL being
351 * sent to sp_action_perform to something useful, or set some kind
352 * of global "right-clicked position" variable for actions to
353 * investigate when they're called.
354 */
355 static void
356 sp_ui_menu_activate(void */*object*/, SPAction *action)
357 {
358 sp_action_perform(action, NULL);
359 }
361 static void
362 sp_ui_menu_select_action(void */*object*/, SPAction *action)
363 {
364 action->view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, action->tip);
365 }
367 static void
368 sp_ui_menu_deselect_action(void */*object*/, SPAction *action)
369 {
370 action->view->tipsMessageContext()->clear();
371 }
373 static void
374 sp_ui_menu_select(gpointer object, gpointer tip)
375 {
376 Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view"));
377 view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, (gchar *)tip);
378 }
380 static void
381 sp_ui_menu_deselect(gpointer object)
382 {
383 Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view"));
384 view->tipsMessageContext()->clear();
385 }
387 /**
388 * sp_ui_menuitem_add_icon
389 *
390 * Creates and attaches a scaled icon to the given menu item.
391 *
392 */
393 void
394 sp_ui_menuitem_add_icon( GtkWidget *item, gchar *icon_name )
395 {
396 GtkWidget *icon;
398 icon = sp_icon_new( Inkscape::ICON_SIZE_MENU, icon_name );
399 gtk_widget_show(icon);
400 gtk_image_menu_item_set_image((GtkImageMenuItem *) item, icon);
401 } // end of sp_ui_menu_add_icon
403 /**
404 * sp_ui_menu_append_item
405 *
406 * Appends a UI item with specific info for Inkscape/Sodipodi.
407 *
408 */
409 static GtkWidget *
410 sp_ui_menu_append_item( GtkMenu *menu, gchar const *stock,
411 gchar const *label, gchar const *tip, Inkscape::UI::View::View *view, GCallback callback,
412 gpointer data, gboolean with_mnemonic = TRUE )
413 {
414 GtkWidget *item;
416 if (stock) {
417 item = gtk_image_menu_item_new_from_stock(stock, NULL);
418 } else if (label) {
419 item = (with_mnemonic)
420 ? gtk_image_menu_item_new_with_mnemonic(label) :
421 gtk_image_menu_item_new_with_label(label);
422 } else {
423 item = gtk_separator_menu_item_new();
424 }
426 gtk_widget_show(item);
428 if (callback) {
429 g_signal_connect(G_OBJECT(item), "activate", callback, data);
430 }
432 if (tip && view) {
433 g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
434 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) tip );
435 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
436 }
438 gtk_menu_append(GTK_MENU(menu), item);
440 return item;
442 } // end of sp_ui_menu_append_item()
444 /**
445 \brief a wrapper around gdk_keyval_name producing (when possible) characters, not names
446 */
447 static gchar const *
448 sp_key_name(guint keyval)
449 {
450 /* TODO: Compare with the definition of gtk_accel_label_refetch in gtk/gtkaccellabel.c (or
451 simply use GtkAccelLabel as the TODO comment in sp_ui_shortcut_string suggests). */
452 gchar const *n = gdk_keyval_name(gdk_keyval_to_upper(keyval));
454 if (!strcmp(n, "asciicircum")) return "^";
455 else if (!strcmp(n, "parenleft" )) return "(";
456 else if (!strcmp(n, "parenright" )) return ")";
457 else if (!strcmp(n, "plus" )) return "+";
458 else if (!strcmp(n, "minus" )) return "-";
459 else if (!strcmp(n, "asterisk" )) return "*";
460 else if (!strcmp(n, "KP_Multiply")) return "*";
461 else if (!strcmp(n, "Delete" )) return "Del";
462 else if (!strcmp(n, "Page_Up" )) return "PgUp";
463 else if (!strcmp(n, "Page_Down" )) return "PgDn";
464 else if (!strcmp(n, "grave" )) return "`";
465 else if (!strcmp(n, "numbersign" )) return "#";
466 else if (!strcmp(n, "bar" )) return "|";
467 else if (!strcmp(n, "slash" )) return "/";
468 else if (!strcmp(n, "exclam" )) return "!";
469 else if (!strcmp(n, "percent" )) return "%";
470 else return n;
471 }
474 /**
475 * \param shortcut A GDK keyval OR'd with SP_SHORTCUT_blah_MASK values.
476 * \param c Points to a buffer at least 256 bytes long.
477 */
478 void
479 sp_ui_shortcut_string(unsigned const shortcut, gchar *const c)
480 {
481 /* TODO: This function shouldn't exist. Our callers should use GtkAccelLabel instead of
482 * a generic GtkLabel containing this string, and should call gtk_widget_add_accelerator.
483 * Will probably need to change sp_shortcut_invoke callers.
484 *
485 * The existing gtk_label_new_with_mnemonic call can be replaced with
486 * g_object_new(GTK_TYPE_ACCEL_LABEL, NULL) followed by
487 * gtk_label_set_text_with_mnemonic(lbl, str).
488 */
489 static GtkAccelLabelClass const &accel_lbl_cls
490 = *(GtkAccelLabelClass const *) g_type_class_peek_static(GTK_TYPE_ACCEL_LABEL);
492 struct { unsigned test; char const *name; } const modifier_tbl[] = {
493 { SP_SHORTCUT_SHIFT_MASK, accel_lbl_cls.mod_name_shift },
494 { SP_SHORTCUT_CONTROL_MASK, accel_lbl_cls.mod_name_control },
495 { SP_SHORTCUT_ALT_MASK, accel_lbl_cls.mod_name_alt }
496 };
498 gchar *p = c;
499 gchar *end = p + 256;
501 for (unsigned i = 0; i < G_N_ELEMENTS(modifier_tbl); ++i) {
502 if ((shortcut & modifier_tbl[i].test)
503 && (p < end))
504 {
505 p += g_snprintf(p, end - p, "%s%s",
506 modifier_tbl[i].name,
507 accel_lbl_cls.mod_separator);
508 }
509 }
510 if (p < end) {
511 p += g_snprintf(p, end - p, "%s", sp_key_name(shortcut & 0xffffff));
512 }
513 end[-1] = '\0'; // snprintf doesn't guarantee to nul-terminate the string.
514 }
516 void
517 sp_ui_dialog_title_string(Inkscape::Verb *verb, gchar *c)
518 {
519 SPAction *action;
520 unsigned int shortcut;
521 gchar *s;
522 gchar key[256];
523 gchar *atitle;
525 action = verb->get_action(NULL);
526 if (!action)
527 return;
529 atitle = sp_action_get_title(action);
531 s = g_stpcpy(c, atitle);
533 g_free(atitle);
535 shortcut = sp_shortcut_get_primary(verb);
536 if (shortcut) {
537 s = g_stpcpy(s, " (");
538 sp_ui_shortcut_string(shortcut, key);
539 s = g_stpcpy(s, key);
540 s = g_stpcpy(s, ")");
541 }
542 }
545 /**
546 * sp_ui_menu_append_item_from_verb
547 *
548 * Appends a custom menu UI from a verb.
549 *
550 */
552 static GtkWidget *
553 sp_ui_menu_append_item_from_verb(GtkMenu *menu, Inkscape::Verb *verb, Inkscape::UI::View::View *view, bool radio = false, GSList *group = NULL)
554 {
555 SPAction *action;
556 GtkWidget *item;
558 if (verb->get_code() == SP_VERB_NONE) {
560 item = gtk_separator_menu_item_new();
562 } else {
563 unsigned int shortcut;
565 action = verb->get_action(view);
567 if (!action) return NULL;
569 shortcut = sp_shortcut_get_primary(verb);
570 if (shortcut) {
571 gchar c[256];
572 sp_ui_shortcut_string(shortcut, c);
573 GtkWidget *const hb = gtk_hbox_new(FALSE, 16);
574 GtkWidget *const name_lbl = gtk_label_new("");
575 gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
576 gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
577 gtk_box_pack_start((GtkBox *) hb, name_lbl, TRUE, TRUE, 0);
578 GtkWidget *const accel_lbl = gtk_label_new(c);
579 gtk_misc_set_alignment((GtkMisc *) accel_lbl, 1.0, 0.5);
580 gtk_box_pack_end((GtkBox *) hb, accel_lbl, FALSE, FALSE, 0);
581 gtk_widget_show_all(hb);
582 if (radio) {
583 item = gtk_radio_menu_item_new (group);
584 } else {
585 item = gtk_image_menu_item_new();
586 }
587 gtk_container_add((GtkContainer *) item, hb);
588 } else {
589 if (radio) {
590 item = gtk_radio_menu_item_new (group);
591 } else {
592 item = gtk_image_menu_item_new ();
593 }
594 GtkWidget *const name_lbl = gtk_label_new("");
595 gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
596 gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
597 gtk_container_add((GtkContainer *) item, name_lbl);
598 }
600 nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
601 if (!action->sensitive) {
602 gtk_widget_set_sensitive(item, FALSE);
603 }
605 if (action->image) {
606 sp_ui_menuitem_add_icon(item, action->image);
607 }
608 gtk_widget_set_events(item, GDK_KEY_PRESS_MASK);
609 g_signal_connect( G_OBJECT(item), "activate",
610 G_CALLBACK(sp_ui_menu_activate), action );
612 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select_action), action );
613 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect_action), action );
614 }
616 gtk_widget_show(item);
617 gtk_menu_append(GTK_MENU(menu), item);
619 return item;
621 } // end of sp_ui_menu_append_item_from_verb
624 static void
625 checkitem_toggled(GtkCheckMenuItem *menuitem, gpointer user_data)
626 {
627 gchar const *pref = (gchar const *) user_data;
628 Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
630 Glib::ustring pref_path;
631 if (reinterpret_cast<SPDesktop*>(view)->is_focusMode()) {
632 pref_path = "/focus/";
633 } else if (reinterpret_cast<SPDesktop*>(view)->is_fullscreen()) {
634 pref_path = "/fullscreen/";
635 } else {
636 pref_path = "/window/";
637 }
638 pref_path += pref;
639 pref_path += "/state";
641 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
642 gboolean checked = gtk_check_menu_item_get_active(menuitem);
643 prefs->setBool(pref_path, checked);
645 reinterpret_cast<SPDesktop*>(view)->layoutWidget();
646 }
648 static gboolean
649 checkitem_update(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_data)
650 {
651 GtkCheckMenuItem *menuitem=GTK_CHECK_MENU_ITEM(widget);
653 gchar const *pref = (gchar const *) user_data;
654 Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
656 Glib::ustring pref_path;
657 if ((static_cast<SPDesktop*>(view))->is_fullscreen()) {
658 pref_path = "/fullscreen/";
659 } else {
660 pref_path = "/window/";
661 }
662 pref_path += pref;
664 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
665 bool ison = prefs->getBool(pref_path + "/state", true);
667 g_signal_handlers_block_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
668 gtk_check_menu_item_set_active(menuitem, ison);
669 g_signal_handlers_unblock_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
671 return FALSE;
672 }
675 void
676 sp_ui_menu_append_check_item_from_verb(GtkMenu *menu, Inkscape::UI::View::View *view, gchar const *label, gchar const *tip, gchar const *pref,
677 void (*callback_toggle)(GtkCheckMenuItem *, gpointer user_data),
678 gboolean (*callback_update)(GtkWidget *widget, GdkEventExpose *event, gpointer user_data),
679 Inkscape::Verb *verb)
680 {
681 GtkWidget *item;
683 unsigned int shortcut = 0;
684 SPAction *action = NULL;
686 if (verb) {
687 shortcut = sp_shortcut_get_primary(verb);
688 action = verb->get_action(view);
689 }
691 if (verb && shortcut) {
692 gchar c[256];
693 sp_ui_shortcut_string(shortcut, c);
695 GtkWidget *hb = gtk_hbox_new(FALSE, 16);
697 {
698 GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
699 gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
700 gtk_box_pack_start((GtkBox *) hb, l, TRUE, TRUE, 0);
701 }
703 {
704 GtkWidget *l = gtk_label_new(c);
705 gtk_misc_set_alignment((GtkMisc *) l, 1.0, 0.5);
706 gtk_box_pack_end((GtkBox *) hb, l, FALSE, FALSE, 0);
707 }
709 gtk_widget_show_all(hb);
711 item = gtk_check_menu_item_new();
712 gtk_container_add((GtkContainer *) item, hb);
713 } else {
714 GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
715 gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
716 item = gtk_check_menu_item_new();
717 gtk_container_add((GtkContainer *) item, l);
718 }
719 #if 0
720 nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
721 if (!action->sensitive) {
722 gtk_widget_set_sensitive(item, FALSE);
723 }
724 #endif
725 gtk_widget_show(item);
727 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
729 g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
731 g_signal_connect( G_OBJECT(item), "toggled", (GCallback) callback_toggle, (void *) pref);
732 g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) callback_update, (void *) pref);
734 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) (action ? action->tip : tip));
735 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
736 }
738 static void
739 sp_recent_open(GtkRecentChooser *recent_menu, gpointer /*user_data*/)
740 {
741 // dealing with the bizarre filename convention in Inkscape for now
742 gchar *uri = gtk_recent_chooser_get_current_uri(GTK_RECENT_CHOOSER(recent_menu));
743 gchar *local_fn = g_filename_from_uri(uri, NULL, NULL);
744 gchar *utf8_fn = g_filename_to_utf8(local_fn, -1, NULL, NULL, NULL);
745 sp_file_open(utf8_fn, NULL);
746 g_free(utf8_fn);
747 g_free(local_fn);
748 g_free(uri);
749 }
751 static void
752 sp_file_new_from_template(GtkWidget */*widget*/, gchar const *uri)
753 {
754 sp_file_new(uri);
755 }
757 void
758 sp_menu_append_new_templates(GtkWidget *menu, Inkscape::UI::View::View *view)
759 {
760 std::list<gchar *> sources;
761 sources.push_back( profile_path("templates") ); // first try user's local dir
762 sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir
764 // Use this loop to iterate through a list of possible document locations.
765 while (!sources.empty()) {
766 gchar *dirname = sources.front();
768 if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) {
769 GError *err = 0;
770 GDir *dir = g_dir_open(dirname, 0, &err);
772 if (dir) {
773 for (gchar const *file = g_dir_read_name(dir); file != NULL; file = g_dir_read_name(dir)) {
774 if (!g_str_has_suffix(file, ".svg") && !g_str_has_suffix(file, ".svgz"))
775 continue; // skip non-svg files
777 gchar *basename = g_path_get_basename(file);
778 if (g_str_has_suffix(basename, ".svg") && g_str_has_prefix(basename, "default."))
779 continue; // skip default.*.svg (i.e. default.svg and translations) - it's in the menu already
781 gchar const *filepath = g_build_filename(dirname, file, NULL);
782 gchar *dupfile = g_strndup(file, strlen(file) - 4);
783 gchar *filename = g_filename_to_utf8(dupfile, -1, NULL, NULL, NULL);
784 g_free(dupfile);
785 GtkWidget *item = gtk_menu_item_new_with_label(filename);
786 g_free(filename);
788 gtk_widget_show(item);
789 // how does "filepath" ever get freed?
790 g_signal_connect(G_OBJECT(item),
791 "activate",
792 G_CALLBACK(sp_file_new_from_template),
793 (gpointer) filepath);
795 if (view) {
796 // set null tip for now; later use a description from the template file
797 g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
798 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) NULL );
799 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
800 }
802 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
803 }
804 g_dir_close(dir);
805 }
806 }
808 // toss the dirname
809 g_free(dirname);
810 sources.pop_front();
811 }
812 }
814 void
815 sp_ui_checkboxes_menus(GtkMenu *m, Inkscape::UI::View::View *view)
816 {
817 //sp_ui_menu_append_check_item_from_verb(m, view, _("_Menu"), _("Show or hide the menu bar"), "menu",
818 // checkitem_toggled, checkitem_update, 0);
819 sp_ui_menu_append_check_item_from_verb(m, view, _("Commands Bar"), _("Show or hide the Commands bar (under the menu)"), "commands",
820 checkitem_toggled, checkitem_update, 0);
821 sp_ui_menu_append_check_item_from_verb(m, view, _("Snap Controls Bar"), _("Show or hide the snapping controls"), "snaptoolbox",
822 checkitem_toggled, checkitem_update, 0);
823 sp_ui_menu_append_check_item_from_verb(m, view, _("Tool Controls Bar"), _("Show or hide the Tool Controls bar"), "toppanel",
824 checkitem_toggled, checkitem_update, 0);
825 sp_ui_menu_append_check_item_from_verb(m, view, _("_Toolbox"), _("Show or hide the main toolbox (on the left)"), "toolbox",
826 checkitem_toggled, checkitem_update, 0);
827 sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "rulers",
828 checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_RULERS));
829 sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "scrollbars",
830 checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_SCROLLBARS));
831 sp_ui_menu_append_check_item_from_verb(m, view, _("_Palette"), _("Show or hide the color palette"), "panels",
832 checkitem_toggled, checkitem_update, 0);
833 sp_ui_menu_append_check_item_from_verb(m, view, _("_Statusbar"), _("Show or hide the statusbar (at the bottom of the window)"), "statusbar",
834 checkitem_toggled, checkitem_update, 0);
835 }
837 /** @brief Observer that updates the recent list's max document count */
838 class MaxRecentObserver : public Inkscape::Preferences::Observer {
839 public:
840 MaxRecentObserver(GtkWidget *recent_menu) :
841 Observer("/options/maxrecentdocuments/value"),
842 _rm(recent_menu)
843 {}
844 virtual void notify(Inkscape::Preferences::Entry const &e) {
845 gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(_rm), e.getInt());
846 // hack: the recent menu doesn't repopulate after changing the limit, so we force it
847 g_signal_emit_by_name((gpointer) gtk_recent_manager_get_default(), "changed");
848 }
849 private:
850 GtkWidget *_rm;
851 };
853 /** \brief This function turns XML into a menu
854 \param menus This is the XML that defines the menu
855 \param menu Menu to be added to
856 \param view The View that this menu is being built for
858 This function is realitively simple as it just goes through the XML
859 and parses the individual elements. In the case of a submenu, it
860 just calls itself recursively. Because it is only reasonable to have
861 a couple of submenus, it is unlikely this will go more than two or
862 three times.
864 In the case of an unreconginzed verb, a menu item is made to identify
865 the verb that is missing, and display that. The menu item is also made
866 insensitive.
867 */
868 void
869 sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI::View::View *view)
870 {
871 if (menus == NULL) return;
872 if (menu == NULL) return;
873 GSList *group = NULL;
875 for (Inkscape::XML::Node *menu_pntr = menus;
876 menu_pntr != NULL;
877 menu_pntr = menu_pntr->next()) {
878 if (!strcmp(menu_pntr->name(), "submenu")) {
879 GtkWidget *mitem = gtk_menu_item_new_with_mnemonic(_(menu_pntr->attribute("name")));
880 GtkWidget *submenu = gtk_menu_new();
881 sp_ui_build_dyn_menus(menu_pntr->firstChild(), submenu, view);
882 gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), GTK_WIDGET(submenu));
883 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
884 continue;
885 }
886 if (!strcmp(menu_pntr->name(), "verb")) {
887 gchar const *verb_name = menu_pntr->attribute("verb-id");
888 Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name);
890 if (verb != NULL) {
891 if (menu_pntr->attribute("radio") != NULL) {
892 GtkWidget *item = sp_ui_menu_append_item_from_verb (GTK_MENU(menu), verb, view, true, group);
893 group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item));
894 if (menu_pntr->attribute("default") != NULL) {
895 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
896 }
897 } else {
898 sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view);
899 group = NULL;
900 }
901 } else {
902 gchar string[120];
903 g_snprintf(string, 120, _("Verb \"%s\" Unknown"), verb_name);
904 string[119] = '\0'; /* may not be terminated */
905 GtkWidget *item = gtk_menu_item_new_with_label(string);
906 gtk_widget_set_sensitive(item, false);
907 gtk_widget_show(item);
908 gtk_menu_append(GTK_MENU(menu), item);
909 }
910 continue;
911 }
912 if (!strcmp(menu_pntr->name(), "separator")
913 // This was spelt wrong in the original version
914 // and so this is for backward compatibility. It can
915 // probably be dropped after the 0.44 release.
916 || !strcmp(menu_pntr->name(), "seperator")) {
917 GtkWidget *item = gtk_separator_menu_item_new();
918 gtk_widget_show(item);
919 gtk_menu_append(GTK_MENU(menu), item);
920 continue;
921 }
922 if (!strcmp(menu_pntr->name(), "template-list")) {
923 sp_menu_append_new_templates(menu, view);
924 continue;
925 }
926 if (!strcmp(menu_pntr->name(), "recent-file-list")) {
927 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
929 // create recent files menu
930 int max_recent = prefs->getInt("/options/maxrecentdocuments/value");
931 GtkWidget *recent_menu = gtk_recent_chooser_menu_new_for_manager(gtk_recent_manager_get_default());
932 gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(recent_menu), max_recent);
933 // sort most recently used documents first to preserve previous behavior
934 gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(recent_menu), GTK_RECENT_SORT_MRU);
935 g_signal_connect(G_OBJECT(recent_menu), "item-activated", G_CALLBACK(sp_recent_open), (gpointer) NULL);
937 // add filter to only open files added by Inkscape
938 GtkRecentFilter *inkscape_only_filter = gtk_recent_filter_new();
939 gtk_recent_filter_add_application(inkscape_only_filter, g_get_prgname());
940 gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_menu), inkscape_only_filter);
942 GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic(_("Open _Recent"));
943 gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), recent_menu);
945 gtk_menu_append(GTK_MENU(menu), GTK_WIDGET(recent_item));
946 // this will just sit and update the list's item count
947 static MaxRecentObserver *mro = new MaxRecentObserver(recent_menu);
948 prefs->addObserver(*mro);
949 continue;
950 }
951 if (!strcmp(menu_pntr->name(), "objects-checkboxes")) {
952 sp_ui_checkboxes_menus(GTK_MENU(menu), view);
953 continue;
954 }
955 }
956 }
958 /** \brief Build the main tool bar
959 \param view View to build the bar for
961 Currently the main tool bar is built as a dynamic XML menu using
962 \c sp_ui_build_dyn_menus. This function builds the bar, and then
963 pass it to get items attached to it.
964 */
965 GtkWidget *
966 sp_ui_main_menubar(Inkscape::UI::View::View *view)
967 {
968 GtkWidget *mbar = gtk_menu_bar_new();
970 #ifdef GDK_WINDOWING_QUARTZ
971 ige_mac_menu_set_menu_bar(GTK_MENU_SHELL(mbar));
972 #endif
974 sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view);
976 #ifdef GDK_WINDOWING_QUARTZ
977 return NULL;
978 #else
979 return mbar;
980 #endif
981 }
983 static void leave_group(GtkMenuItem *, SPDesktop *desktop) {
984 desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer()));
985 }
987 static void enter_group(GtkMenuItem *mi, SPDesktop *desktop) {
988 desktop->setCurrentLayer(reinterpret_cast<SPObject *>(g_object_get_data(G_OBJECT(mi), "group")));
989 sp_desktop_selection(desktop)->clear();
990 }
992 GtkWidget *
993 sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item)
994 {
995 GtkWidget *m;
996 SPDesktop *dt;
998 dt = static_cast<SPDesktop*>(view);
1000 m = gtk_menu_new();
1002 /* Undo and Redo */
1003 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_UNDO), view);
1004 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_REDO), view);
1006 /* Separator */
1007 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1009 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_CUT), view);
1010 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_COPY), view);
1011 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_PASTE), view);
1013 /* Separator */
1014 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1016 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DUPLICATE), view);
1017 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DELETE), view);
1019 /* Item menu */
1020 if (item) {
1021 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1022 sp_object_menu((SPObject *) item, dt, GTK_MENU(m));
1023 }
1025 /* layer menu */
1026 SPGroup *group=NULL;
1027 if (item) {
1028 if (SP_IS_GROUP(item)) {
1029 group = SP_GROUP(item);
1030 } else if ( item != dt->currentRoot() && SP_IS_GROUP(SP_OBJECT_PARENT(item)) ) {
1031 group = SP_GROUP(SP_OBJECT_PARENT(item));
1032 }
1033 }
1035 if (( group && group != dt->currentLayer() ) ||
1036 ( dt->currentLayer() != dt->currentRoot() && SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) ) {
1037 /* Separator */
1038 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1039 }
1041 if ( group && group != dt->currentLayer() ) {
1042 /* TRANSLATORS: #%s is the id of the group e.g. <g id="#g7">, not a number. */
1043 gchar *label=g_strdup_printf(_("Enter group #%s"), SP_OBJECT_ID(group));
1044 GtkWidget *w = gtk_menu_item_new_with_label(label);
1045 g_free(label);
1046 g_object_set_data(G_OBJECT(w), "group", group);
1047 g_signal_connect(G_OBJECT(w), "activate", GCallback(enter_group), dt);
1048 gtk_widget_show(w);
1049 gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1050 }
1052 if ( dt->currentLayer() != dt->currentRoot() ) {
1053 if ( SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) {
1054 GtkWidget *w = gtk_menu_item_new_with_label(_("Go to parent"));
1055 g_signal_connect(G_OBJECT(w), "activate", GCallback(leave_group), dt);
1056 gtk_widget_show(w);
1057 gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1059 }
1060 }
1062 return m;
1063 }
1065 /* Drag and Drop */
1066 void
1067 sp_ui_drag_data_received(GtkWidget *widget,
1068 GdkDragContext *drag_context,
1069 gint x, gint y,
1070 GtkSelectionData *data,
1071 guint info,
1072 guint /*event_time*/,
1073 gpointer /*user_data*/)
1074 {
1075 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1076 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1078 switch (info) {
1079 #if ENABLE_MAGIC_COLORS
1080 case APP_X_INKY_COLOR:
1081 {
1082 int destX = 0;
1083 int destY = 0;
1084 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1085 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1087 SPItem *item = desktop->item_at_point( where, true );
1088 if ( item )
1089 {
1090 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1092 if ( data->length >= 8 ) {
1093 cmsHPROFILE srgbProf = cmsCreate_sRGBProfile();
1095 gchar c[64] = {0};
1096 // Careful about endian issues.
1097 guint16* dataVals = (guint16*)data->data;
1098 sp_svg_write_color( c, sizeof(c),
1099 SP_RGBA32_U_COMPOSE(
1100 0x0ff & (dataVals[0] >> 8),
1101 0x0ff & (dataVals[1] >> 8),
1102 0x0ff & (dataVals[2] >> 8),
1103 0xff // can't have transparency in the color itself
1104 //0x0ff & (data->data[3] >> 8),
1105 ));
1106 SPCSSAttr *css = sp_repr_css_attr_new();
1107 bool updatePerformed = false;
1109 if ( data->length > 14 ) {
1110 int flags = dataVals[4];
1112 // piggie-backed palette entry info
1113 int index = dataVals[5];
1114 Glib::ustring palName;
1115 for ( int i = 0; i < dataVals[6]; i++ ) {
1116 palName += (gunichar)dataVals[7+i];
1117 }
1119 // Now hook in a magic tag of some sort.
1120 if ( !palName.empty() && (flags & 1) ) {
1121 gchar* str = g_strdup_printf("%d|", index);
1122 palName.insert( 0, str );
1123 g_free(str);
1124 str = 0;
1126 sp_object_setAttribute( SP_OBJECT(item),
1127 fillnotstroke ? "inkscape:x-fill-tag":"inkscape:x-stroke-tag",
1128 palName.c_str(),
1129 false );
1130 item->updateRepr();
1132 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1133 updatePerformed = true;
1134 }
1135 }
1137 if ( !updatePerformed ) {
1138 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1139 }
1141 sp_desktop_apply_css_recursive( item, css, true );
1142 item->updateRepr();
1144 sp_document_done( doc , SP_VERB_NONE,
1145 _("Drop color"));
1147 if ( srgbProf ) {
1148 cmsCloseProfile( srgbProf );
1149 }
1150 }
1151 }
1152 }
1153 break;
1154 #endif // ENABLE_MAGIC_COLORS
1156 case APP_X_COLOR:
1157 {
1158 int destX = 0;
1159 int destY = 0;
1160 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1161 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1162 Geom::Point const button_dt(desktop->w2d(where));
1163 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1165 if ( data->length == 8 ) {
1166 gchar colorspec[64] = {0};
1167 // Careful about endian issues.
1168 guint16* dataVals = (guint16*)data->data;
1169 sp_svg_write_color( colorspec, sizeof(colorspec),
1170 SP_RGBA32_U_COMPOSE(
1171 0x0ff & (dataVals[0] >> 8),
1172 0x0ff & (dataVals[1] >> 8),
1173 0x0ff & (dataVals[2] >> 8),
1174 0xff // can't have transparency in the color itself
1175 //0x0ff & (data->data[3] >> 8),
1176 ));
1178 SPItem *item = desktop->item_at_point( where, true );
1180 bool consumed = false;
1181 if (desktop->event_context && desktop->event_context->get_drag()) {
1182 consumed = desktop->event_context->get_drag()->dropColor(item, colorspec, button_dt);
1183 if (consumed) {
1184 sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1185 desktop->event_context->get_drag()->updateDraggers();
1186 }
1187 }
1189 //if (!consumed && tools_active(desktop, TOOLS_TEXT)) {
1190 // consumed = sp_text_context_drop_color(c, button_doc);
1191 // if (consumed) {
1192 // sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient stop"));
1193 // }
1194 //}
1196 if (!consumed && item) {
1197 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1198 if (fillnotstroke &&
1199 (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1200 Path *livarot_path = Path_for_item(item, true, true);
1201 livarot_path->ConvertWithBackData(0.04);
1203 boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1204 if (position) {
1205 Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1206 Geom::Point delta = nearest - button_doc;
1207 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1208 delta = desktop->d2w(delta);
1209 double stroke_tolerance =
1210 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1211 desktop->current_zoom() *
1212 SP_OBJECT_STYLE (item)->stroke_width.computed *
1213 to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1214 : 0.0)
1215 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1217 if (Geom::L2 (delta) < stroke_tolerance) {
1218 fillnotstroke = false;
1219 }
1220 }
1221 delete livarot_path;
1222 }
1224 SPCSSAttr *css = sp_repr_css_attr_new();
1225 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec );
1227 sp_desktop_apply_css_recursive( item, css, true );
1228 item->updateRepr();
1230 sp_document_done( doc , SP_VERB_NONE,
1231 _("Drop color"));
1232 }
1233 }
1234 }
1235 break;
1237 case APP_OSWB_COLOR:
1238 {
1239 bool worked = false;
1240 Glib::ustring colorspec;
1241 if ( data->format == 8 ) {
1242 ege::PaintDef color;
1243 worked = color.fromMIMEData("application/x-oswb-color",
1244 reinterpret_cast<char*>(data->data),
1245 data->length,
1246 data->format);
1247 if ( worked ) {
1248 if ( color.getType() == ege::PaintDef::CLEAR ) {
1249 colorspec = ""; // TODO check if this is sufficient
1250 } else if ( color.getType() == ege::PaintDef::NONE ) {
1251 colorspec = "none";
1252 } else {
1253 unsigned int r = color.getR();
1254 unsigned int g = color.getG();
1255 unsigned int b = color.getB();
1257 SPGradient* matches = 0;
1258 const GSList *gradients = sp_document_get_resource_list(doc, "gradient");
1259 for (const GSList *item = gradients; item; item = item->next) {
1260 SPGradient* grad = SP_GRADIENT(item->data);
1261 if ( color.descr == grad->id ) {
1262 if ( grad->has_stops ) {
1263 matches = grad;
1264 break;
1265 }
1266 }
1267 }
1268 if (matches) {
1269 colorspec = "url(#";
1270 colorspec += matches->id;
1271 colorspec += ")";
1272 } else {
1273 gchar* tmp = g_strdup_printf("#%02x%02x%02x", r, g, b);
1274 colorspec = tmp;
1275 g_free(tmp);
1276 }
1277 }
1278 }
1279 }
1280 if ( worked ) {
1281 int destX = 0;
1282 int destY = 0;
1283 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1284 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1285 Geom::Point const button_dt(desktop->w2d(where));
1286 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1288 SPItem *item = desktop->item_at_point( where, true );
1290 bool consumed = false;
1291 if (desktop->event_context && desktop->event_context->get_drag()) {
1292 consumed = desktop->event_context->get_drag()->dropColor(item, colorspec.c_str(), button_dt);
1293 if (consumed) {
1294 sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1295 desktop->event_context->get_drag()->updateDraggers();
1296 }
1297 }
1299 if (!consumed && item) {
1300 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1301 if (fillnotstroke &&
1302 (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1303 Path *livarot_path = Path_for_item(item, true, true);
1304 livarot_path->ConvertWithBackData(0.04);
1306 boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1307 if (position) {
1308 Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1309 Geom::Point delta = nearest - button_doc;
1310 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1311 delta = desktop->d2w(delta);
1312 double stroke_tolerance =
1313 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1314 desktop->current_zoom() *
1315 SP_OBJECT_STYLE (item)->stroke_width.computed *
1316 to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1317 : 0.0)
1318 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1320 if (Geom::L2 (delta) < stroke_tolerance) {
1321 fillnotstroke = false;
1322 }
1323 }
1324 delete livarot_path;
1325 }
1327 SPCSSAttr *css = sp_repr_css_attr_new();
1328 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec.c_str() );
1330 sp_desktop_apply_css_recursive( item, css, true );
1331 item->updateRepr();
1333 sp_document_done( doc , SP_VERB_NONE,
1334 _("Drop color"));
1335 }
1336 }
1337 }
1338 break;
1340 case SVG_DATA:
1341 case SVG_XML_DATA: {
1342 gchar *svgdata = (gchar *)data->data;
1344 Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI);
1346 if (rnewdoc == NULL) {
1347 sp_ui_error_dialog(_("Could not parse SVG data"));
1348 return;
1349 }
1351 Inkscape::XML::Node *repr = rnewdoc->root();
1352 gchar const *style = repr->attribute("style");
1354 Inkscape::XML::Node *newgroup = rnewdoc->createElement("svg:g");
1355 newgroup->setAttribute("style", style);
1357 Inkscape::XML::Document * xml_doc = sp_document_repr_doc(doc);
1358 for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) {
1359 Inkscape::XML::Node *newchild = child->duplicate(xml_doc);
1360 newgroup->appendChild(newchild);
1361 }
1363 Inkscape::GC::release(rnewdoc);
1365 // Add it to the current layer
1367 // Greg's edits to add intelligent positioning of svg drops
1368 SPObject *new_obj = NULL;
1369 new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
1371 Inkscape::Selection *selection = sp_desktop_selection(desktop);
1372 selection->set(SP_ITEM(new_obj));
1374 // move to mouse pointer
1375 {
1376 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
1377 Geom::OptRect sel_bbox = selection->bounds();
1378 if (sel_bbox) {
1379 Geom::Point m( desktop->point() - sel_bbox->midpoint() );
1380 sp_selection_move_relative(selection, m, false);
1381 }
1382 }
1384 Inkscape::GC::release(newgroup);
1385 sp_document_done(doc, SP_VERB_NONE,
1386 _("Drop SVG"));
1387 break;
1388 }
1390 case URI_LIST: {
1391 gchar *uri = (gchar *)data->data;
1392 sp_ui_import_files(uri);
1393 break;
1394 }
1396 case PNG_DATA:
1397 case JPEG_DATA:
1398 case IMAGE_DATA: {
1399 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
1400 Inkscape::XML::Node *newImage = xml_doc->createElement("svg:image");
1401 gchar *atom_name = gdk_atom_name(data->type);
1403 // this formula taken from Glib docs
1404 guint needed_size = data->length * 4 / 3 + data->length * 4 / (3 * 72) + 7;
1405 needed_size += 5 + 8 + strlen(atom_name); // 5 bytes for data:, 8 for ;base64,
1407 gchar *buffer = (gchar *) g_malloc(needed_size), *buf_work = buffer;
1408 buf_work += g_sprintf(buffer, "data:%s;base64,", atom_name);
1410 gint state = 0, save = 0;
1411 g_base64_encode_step(data->data, data->length, TRUE, buf_work, &state, &save);
1412 g_base64_encode_close(TRUE, buf_work, &state, &save);
1414 newImage->setAttribute("xlink:href", buffer);
1415 g_free(buffer);
1417 GError *error = NULL;
1418 GdkPixbufLoader *loader = gdk_pixbuf_loader_new_with_mime_type( gdk_atom_name(data->type), &error );
1419 if ( loader ) {
1420 error = NULL;
1421 if ( gdk_pixbuf_loader_write( loader, data->data, data->length, &error) ) {
1422 GdkPixbuf *pbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1423 if ( pbuf ) {
1424 char tmp[1024];
1425 int width = gdk_pixbuf_get_width(pbuf);
1426 int height = gdk_pixbuf_get_height(pbuf);
1427 snprintf( tmp, sizeof(tmp), "%d", width );
1428 newImage->setAttribute("width", tmp);
1430 snprintf( tmp, sizeof(tmp), "%d", height );
1431 newImage->setAttribute("height", tmp);
1432 }
1433 }
1434 }
1435 g_free(atom_name);
1437 // Add it to the current layer
1438 desktop->currentLayer()->appendChildRepr(newImage);
1440 Inkscape::GC::release(newImage);
1441 sp_document_done( doc , SP_VERB_NONE,
1442 _("Drop bitmap image"));
1443 break;
1444 }
1445 }
1446 }
1448 #include "gradient-context.h"
1450 void sp_ui_drag_motion( GtkWidget */*widget*/,
1451 GdkDragContext */*drag_context*/,
1452 gint /*x*/, gint /*y*/,
1453 GtkSelectionData */*data*/,
1454 guint /*info*/,
1455 guint /*event_time*/,
1456 gpointer /*user_data*/)
1457 {
1458 // SPDocument *doc = SP_ACTIVE_DOCUMENT;
1459 // SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1462 // g_message("drag-n-drop motion (%4d, %4d) at %d", x, y, event_time);
1463 }
1465 static void sp_ui_drag_leave( GtkWidget */*widget*/,
1466 GdkDragContext */*drag_context*/,
1467 guint /*event_time*/,
1468 gpointer /*user_data*/ )
1469 {
1470 // g_message("drag-n-drop leave at %d", event_time);
1471 }
1473 static void
1474 sp_ui_import_files(gchar *buffer)
1475 {
1476 GList *list = gnome_uri_list_extract_filenames(buffer);
1477 if (!list)
1478 return;
1479 g_list_foreach(list, sp_ui_import_one_file_with_check, NULL);
1480 g_list_foreach(list, (GFunc) g_free, NULL);
1481 g_list_free(list);
1482 }
1484 static void
1485 sp_ui_import_one_file_with_check(gpointer filename, gpointer /*unused*/)
1486 {
1487 if (filename) {
1488 if (strlen((char const *)filename) > 2)
1489 sp_ui_import_one_file((char const *)filename);
1490 }
1491 }
1493 static void
1494 sp_ui_import_one_file(char const *filename)
1495 {
1496 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1497 if (!doc) return;
1499 if (filename == NULL) return;
1501 // Pass off to common implementation
1502 // TODO might need to get the proper type of Inkscape::Extension::Extension
1503 file_import( doc, filename, NULL );
1504 }
1506 void
1507 sp_ui_error_dialog(gchar const *message)
1508 {
1509 GtkWidget *dlg;
1510 gchar *safeMsg = Inkscape::IO::sanitizeString(message);
1512 dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
1513 GTK_BUTTONS_CLOSE, "%s", safeMsg);
1514 sp_transientize(dlg);
1515 gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE);
1516 gtk_dialog_run(GTK_DIALOG(dlg));
1517 gtk_widget_destroy(dlg);
1518 g_free(safeMsg);
1519 }
1521 bool
1522 sp_ui_overwrite_file(gchar const *filename)
1523 {
1524 bool return_value = FALSE;
1526 if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) {
1527 Gtk::Window *window = SP_ACTIVE_DESKTOP->getToplevel();
1528 gchar* baseName = g_path_get_basename( filename );
1529 gchar* dirName = g_path_get_dirname( filename );
1530 GtkWidget* dialog = gtk_message_dialog_new_with_markup( window->gobj(),
1531 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
1532 GTK_MESSAGE_QUESTION,
1533 GTK_BUTTONS_NONE,
1534 _( "<span weight=\"bold\" size=\"larger\">A file named \"%s\" already exists. Do you want to replace it?</span>\n\n"
1535 "The file already exists in \"%s\". Replacing it will overwrite its contents." ),
1536 baseName,
1537 dirName
1538 );
1539 gtk_dialog_add_buttons( GTK_DIALOG(dialog),
1540 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1541 _("Replace"), GTK_RESPONSE_YES,
1542 NULL );
1543 gtk_dialog_set_default_response( GTK_DIALOG(dialog), GTK_RESPONSE_YES );
1545 if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_YES ) {
1546 return_value = TRUE;
1547 } else {
1548 return_value = FALSE;
1549 }
1550 gtk_widget_destroy(dialog);
1551 g_free( baseName );
1552 g_free( dirName );
1553 } else {
1554 return_value = TRUE;
1555 }
1557 return return_value;
1558 }
1560 static void
1561 sp_ui_menu_item_set_sensitive(SPAction */*action*/, unsigned int sensitive, void *data)
1562 {
1563 return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
1564 }
1566 static void
1567 sp_ui_menu_item_set_name(SPAction */*action*/, Glib::ustring name, void *data)
1568 {
1569 void *child = GTK_BIN (data)->child;
1570 //child is either
1571 //- a GtkHBox, whose first child is a label displaying name if the menu
1572 //item has an accel key
1573 //- a GtkLabel if the menu has no accel key
1574 if (GTK_IS_LABEL(child)) {
1575 gtk_label_set_markup_with_mnemonic(GTK_LABEL (child), name.c_str());
1576 } else if (GTK_IS_HBOX(child)) {
1577 gtk_label_set_markup_with_mnemonic(
1578 GTK_LABEL (gtk_container_get_children(GTK_CONTAINER (child))->data),
1579 name.c_str());
1580 }//else sp_ui_menu_append_item_from_verb has been modified and can set
1581 //a menu item in yet another way...
1582 }
1585 /*
1586 Local Variables:
1587 mode:c++
1588 c-file-style:"stroustrup"
1589 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1590 indent-tabs-mode:nil
1591 fill-column:99
1592 End:
1593 */
1594 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :