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