164b66f3388213c7e8eaf2960bd9870c82d84942
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-flowtext.h"
44 #include "sp-namedview.h"
45 #include "ui/view/view.h"
46 #include "helper/action.h"
47 #include "helper/gnome-utils.h"
48 #include "helper/window.h"
49 #include "io/sys.h"
50 #include "dialogs/dialog-events.h"
51 #include "message-context.h"
53 // Added for color drag-n-drop
54 #if ENABLE_LCMS
55 #include "lcms.h"
56 #endif // ENABLE_LCMS
57 #include "display/sp-canvas.h"
58 #include "color.h"
59 #include "svg/svg-color.h"
60 #include "desktop-style.h"
61 #include "style.h"
62 #include "event-context.h"
63 #include "gradient-drag.h"
64 #include "widgets/eek-color-def.h"
66 // Include Mac OS X menu synchronization on native OSX build
67 #ifdef GDK_WINDOWING_QUARTZ
68 #include "ige-mac-menu.h"
69 #endif
71 /* Drag and Drop */
72 typedef enum {
73 URI_LIST,
74 SVG_XML_DATA,
75 SVG_DATA,
76 PNG_DATA,
77 JPEG_DATA,
78 IMAGE_DATA,
79 APP_X_INKY_COLOR,
80 APP_X_COLOR,
81 APP_OSWB_COLOR,
82 } ui_drop_target_info;
84 static GtkTargetEntry ui_drop_target_entries [] = {
85 {(gchar *)"text/uri-list", 0, URI_LIST },
86 {(gchar *)"image/svg+xml", 0, SVG_XML_DATA },
87 {(gchar *)"image/svg", 0, SVG_DATA },
88 {(gchar *)"image/png", 0, PNG_DATA },
89 {(gchar *)"image/jpeg", 0, JPEG_DATA },
90 #if ENABLE_MAGIC_COLORS
91 {(gchar *)"application/x-inkscape-color", 0, APP_X_INKY_COLOR},
92 #endif // ENABLE_MAGIC_COLORS
93 {(gchar *)"application/x-oswb-color", 0, APP_OSWB_COLOR },
94 {(gchar *)"application/x-color", 0, APP_X_COLOR }
95 };
97 static GtkTargetEntry *completeDropTargets = 0;
98 static int completeDropTargetsCount = 0;
100 #define ENTRIES_SIZE(n) sizeof(n)/sizeof(n[0])
101 static guint nui_drop_target_entries = ENTRIES_SIZE(ui_drop_target_entries);
102 static void sp_ui_import_files(gchar *buffer);
103 static void sp_ui_import_one_file(char const *filename);
104 static void sp_ui_import_one_file_with_check(gpointer filename, gpointer unused);
105 static void sp_ui_drag_data_received(GtkWidget *widget,
106 GdkDragContext *drag_context,
107 gint x, gint y,
108 GtkSelectionData *data,
109 guint info,
110 guint event_time,
111 gpointer user_data);
112 static void sp_ui_drag_motion( GtkWidget *widget,
113 GdkDragContext *drag_context,
114 gint x, gint y,
115 GtkSelectionData *data,
116 guint info,
117 guint event_time,
118 gpointer user_data );
119 static void sp_ui_drag_leave( GtkWidget *widget,
120 GdkDragContext *drag_context,
121 guint event_time,
122 gpointer user_data );
123 static void sp_ui_menu_item_set_sensitive(SPAction *action,
124 unsigned int sensitive,
125 void *data);
126 static void sp_ui_menu_item_set_name(SPAction *action,
127 Glib::ustring name,
128 void *data);
129 static void sp_recent_open(GtkRecentChooser *, gpointer);
131 SPActionEventVector menu_item_event_vector = {
132 {NULL},
133 NULL,
134 NULL, /* set_active */
135 sp_ui_menu_item_set_sensitive, /* set_sensitive */
136 NULL, /* set_shortcut */
137 sp_ui_menu_item_set_name /* set_name */
138 };
140 static const int MIN_ONSCREEN_DISTANCE = 50;
142 void
143 sp_create_window(SPViewWidget *vw, gboolean editable)
144 {
145 g_return_if_fail(vw != NULL);
146 g_return_if_fail(SP_IS_VIEW_WIDGET(vw));
148 Gtk::Window *win = Inkscape::UI::window_new("", TRUE);
150 gtk_container_add(GTK_CONTAINER(win->gobj()), GTK_WIDGET(vw));
151 gtk_widget_show(GTK_WIDGET(vw));
153 if (editable) {
154 g_object_set_data(G_OBJECT(vw), "window", win);
156 SPDesktopWidget *desktop_widget = reinterpret_cast<SPDesktopWidget*>(vw);
157 SPDesktop* desktop = desktop_widget->desktop;
159 desktop_widget->window = win;
161 win->set_data("desktop", desktop);
162 win->set_data("desktopwidget", desktop_widget);
164 win->signal_delete_event().connect(sigc::mem_fun(*(SPDesktop*)vw->view, &SPDesktop::onDeleteUI));
165 win->signal_window_state_event().connect(sigc::mem_fun(*desktop, &SPDesktop::onWindowStateEvent));
166 win->signal_focus_in_event().connect(sigc::mem_fun(*desktop_widget, &SPDesktopWidget::onFocusInEvent));
168 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
169 gint prefs_geometry =
170 (2==prefs->getInt("/options/savewindowgeometry/value", 0));
171 if (prefs_geometry) {
172 gint pw = prefs->getInt("/desktop/geometry/width", -1);
173 gint ph = prefs->getInt("/desktop/geometry/height", -1);
174 gint px = prefs->getInt("/desktop/geometry/x", -1);
175 gint py = prefs->getInt("/desktop/geometry/y", -1);
176 gint full = prefs->getBool("/desktop/geometry/fullscreen");
177 gint maxed = prefs->getBool("/desktop/geometry/maximized");
178 if (pw>0 && ph>0) {
179 gint w = MIN(gdk_screen_width(), pw);
180 gint h = MIN(gdk_screen_height(), ph);
181 gint x = MIN(gdk_screen_width() - MIN_ONSCREEN_DISTANCE, px);
182 gint y = MIN(gdk_screen_height() - MIN_ONSCREEN_DISTANCE, py);
183 if (w>0 && h>0 && x>0 && y>0) {
184 x = MIN(gdk_screen_width() - w, x);
185 y = MIN(gdk_screen_height() - h, y);
186 }
187 if (w>0 && h>0) {
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 if (x>0 && y>0) {
198 SPDesktop *active_desktop = SP_ACTIVE_DESKTOP;
199 if (active_desktop == desktop || active_desktop==NULL) {
200 desktop->setWindowPosition(Geom::Point(x, y));
201 }
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 eek::ColorDef 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() == eek::ColorDef::CLEAR ) {
1251 colorspec = ""; // TODO check if this is sufficient
1252 } else if ( color.getType() == eek::ColorDef::NONE ) {
1253 colorspec = "none";
1254 } else {
1255 gchar* tmp = g_strdup_printf("#%02x%02x%02x", color.getR(), color.getG(), color.getB());
1256 colorspec = tmp;
1257 g_free(tmp);
1258 }
1259 }
1260 }
1261 if ( worked ) {
1262 int destX = 0;
1263 int destY = 0;
1264 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1265 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1266 Geom::Point const button_dt(desktop->w2d(where));
1267 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1269 SPItem *item = desktop->item_at_point( where, true );
1271 bool consumed = false;
1272 if (desktop->event_context && desktop->event_context->get_drag()) {
1273 consumed = desktop->event_context->get_drag()->dropColor(item, colorspec.c_str(), button_dt);
1274 if (consumed) {
1275 sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1276 desktop->event_context->get_drag()->updateDraggers();
1277 }
1278 }
1280 if (!consumed && item) {
1281 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1282 if (fillnotstroke &&
1283 (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1284 Path *livarot_path = Path_for_item(item, true, true);
1285 livarot_path->ConvertWithBackData(0.04);
1287 boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1288 if (position) {
1289 Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1290 Geom::Point delta = nearest - button_doc;
1291 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1292 delta = desktop->d2w(delta);
1293 double stroke_tolerance =
1294 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1295 desktop->current_zoom() *
1296 SP_OBJECT_STYLE (item)->stroke_width.computed *
1297 to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1298 : 0.0)
1299 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1301 if (Geom::L2 (delta) < stroke_tolerance) {
1302 fillnotstroke = false;
1303 }
1304 }
1305 delete livarot_path;
1306 }
1308 SPCSSAttr *css = sp_repr_css_attr_new();
1309 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec.c_str() );
1311 sp_desktop_apply_css_recursive( item, css, true );
1312 item->updateRepr();
1314 sp_document_done( doc , SP_VERB_NONE,
1315 _("Drop color"));
1316 }
1317 }
1318 }
1319 break;
1321 case SVG_DATA:
1322 case SVG_XML_DATA: {
1323 gchar *svgdata = (gchar *)data->data;
1325 Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI);
1327 if (rnewdoc == NULL) {
1328 sp_ui_error_dialog(_("Could not parse SVG data"));
1329 return;
1330 }
1332 Inkscape::XML::Node *repr = rnewdoc->root();
1333 gchar const *style = repr->attribute("style");
1335 Inkscape::XML::Node *newgroup = rnewdoc->createElement("svg:g");
1336 newgroup->setAttribute("style", style);
1338 Inkscape::XML::Document * xml_doc = sp_document_repr_doc(doc);
1339 for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) {
1340 Inkscape::XML::Node *newchild = child->duplicate(xml_doc);
1341 newgroup->appendChild(newchild);
1342 }
1344 Inkscape::GC::release(rnewdoc);
1346 // Add it to the current layer
1348 // Greg's edits to add intelligent positioning of svg drops
1349 SPObject *new_obj = NULL;
1350 new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
1352 Inkscape::Selection *selection = sp_desktop_selection(desktop);
1353 selection->set(SP_ITEM(new_obj));
1354 // To move the imported object, we must temporarily set the "transform pattern with
1355 // object" option.
1356 {
1357 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1358 bool const saved_pref = prefs->getBool("/options/transform/pattern", true);
1359 prefs->setBool("/options/transform/pattern", true);
1360 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
1361 Geom::OptRect sel_bbox = selection->bounds();
1362 if (sel_bbox) {
1363 Geom::Point m( desktop->point() - sel_bbox->midpoint() );
1364 sp_selection_move_relative(selection, m);
1365 }
1366 prefs->setBool("/options/transform/pattern", saved_pref);
1367 }
1369 Inkscape::GC::release(newgroup);
1370 sp_document_done(doc, SP_VERB_NONE,
1371 _("Drop SVG"));
1372 break;
1373 }
1375 case URI_LIST: {
1376 gchar *uri = (gchar *)data->data;
1377 sp_ui_import_files(uri);
1378 break;
1379 }
1381 case PNG_DATA:
1382 case JPEG_DATA:
1383 case IMAGE_DATA: {
1384 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
1385 Inkscape::XML::Node *newImage = xml_doc->createElement("svg:image");
1386 gchar *atom_name = gdk_atom_name(data->type);
1388 // this formula taken from Glib docs
1389 guint needed_size = data->length * 4 / 3 + data->length * 4 / (3 * 72) + 7;
1390 needed_size += 5 + 8 + strlen(atom_name); // 5 bytes for data:, 8 for ;base64,
1392 gchar *buffer = (gchar *) g_malloc(needed_size), *buf_work = buffer;
1393 buf_work += g_sprintf(buffer, "data:%s;base64,", atom_name);
1395 gint state = 0, save = 0;
1396 g_base64_encode_step(data->data, data->length, TRUE, buf_work, &state, &save);
1397 g_base64_encode_close(TRUE, buf_work, &state, &save);
1399 newImage->setAttribute("xlink:href", buffer);
1400 g_free(buffer);
1402 GError *error = NULL;
1403 GdkPixbufLoader *loader = gdk_pixbuf_loader_new_with_mime_type( gdk_atom_name(data->type), &error );
1404 if ( loader ) {
1405 error = NULL;
1406 if ( gdk_pixbuf_loader_write( loader, data->data, data->length, &error) ) {
1407 GdkPixbuf *pbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1408 if ( pbuf ) {
1409 char tmp[1024];
1410 int width = gdk_pixbuf_get_width(pbuf);
1411 int height = gdk_pixbuf_get_height(pbuf);
1412 snprintf( tmp, sizeof(tmp), "%d", width );
1413 newImage->setAttribute("width", tmp);
1415 snprintf( tmp, sizeof(tmp), "%d", height );
1416 newImage->setAttribute("height", tmp);
1417 }
1418 }
1419 }
1420 g_free(atom_name);
1422 // Add it to the current layer
1423 desktop->currentLayer()->appendChildRepr(newImage);
1425 Inkscape::GC::release(newImage);
1426 sp_document_done( doc , SP_VERB_NONE,
1427 _("Drop bitmap image"));
1428 break;
1429 }
1430 }
1431 }
1433 #include "gradient-context.h"
1435 void sp_ui_drag_motion( GtkWidget */*widget*/,
1436 GdkDragContext */*drag_context*/,
1437 gint /*x*/, gint /*y*/,
1438 GtkSelectionData */*data*/,
1439 guint /*info*/,
1440 guint /*event_time*/,
1441 gpointer /*user_data*/)
1442 {
1443 // SPDocument *doc = SP_ACTIVE_DOCUMENT;
1444 // SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1447 // g_message("drag-n-drop motion (%4d, %4d) at %d", x, y, event_time);
1448 }
1450 static void sp_ui_drag_leave( GtkWidget */*widget*/,
1451 GdkDragContext */*drag_context*/,
1452 guint /*event_time*/,
1453 gpointer /*user_data*/ )
1454 {
1455 // g_message("drag-n-drop leave at %d", event_time);
1456 }
1458 static void
1459 sp_ui_import_files(gchar *buffer)
1460 {
1461 GList *list = gnome_uri_list_extract_filenames(buffer);
1462 if (!list)
1463 return;
1464 g_list_foreach(list, sp_ui_import_one_file_with_check, NULL);
1465 g_list_foreach(list, (GFunc) g_free, NULL);
1466 g_list_free(list);
1467 }
1469 static void
1470 sp_ui_import_one_file_with_check(gpointer filename, gpointer /*unused*/)
1471 {
1472 if (filename) {
1473 if (strlen((char const *)filename) > 2)
1474 sp_ui_import_one_file((char const *)filename);
1475 }
1476 }
1478 static void
1479 sp_ui_import_one_file(char const *filename)
1480 {
1481 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1482 if (!doc) return;
1484 if (filename == NULL) return;
1486 // Pass off to common implementation
1487 // TODO might need to get the proper type of Inkscape::Extension::Extension
1488 file_import( doc, filename, NULL );
1489 }
1491 void
1492 sp_ui_error_dialog(gchar const *message)
1493 {
1494 GtkWidget *dlg;
1495 gchar *safeMsg = Inkscape::IO::sanitizeString(message);
1497 dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
1498 GTK_BUTTONS_CLOSE, "%s", safeMsg);
1499 sp_transientize(dlg);
1500 gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE);
1501 gtk_dialog_run(GTK_DIALOG(dlg));
1502 gtk_widget_destroy(dlg);
1503 g_free(safeMsg);
1504 }
1506 bool
1507 sp_ui_overwrite_file(gchar const *filename)
1508 {
1509 bool return_value = FALSE;
1511 if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) {
1512 Gtk::Window *window = SP_ACTIVE_DESKTOP->getToplevel();
1513 gchar* baseName = g_path_get_basename( filename );
1514 gchar* dirName = g_path_get_dirname( filename );
1515 GtkWidget* dialog = gtk_message_dialog_new_with_markup( window->gobj(),
1516 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
1517 GTK_MESSAGE_QUESTION,
1518 GTK_BUTTONS_NONE,
1519 _( "<span weight=\"bold\" size=\"larger\">A file named \"%s\" already exists. Do you want to replace it?</span>\n\n"
1520 "The file already exists in \"%s\". Replacing it will overwrite its contents." ),
1521 baseName,
1522 dirName
1523 );
1524 gtk_dialog_add_buttons( GTK_DIALOG(dialog),
1525 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1526 _("Replace"), GTK_RESPONSE_YES,
1527 NULL );
1528 gtk_dialog_set_default_response( GTK_DIALOG(dialog), GTK_RESPONSE_YES );
1530 if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_YES ) {
1531 return_value = TRUE;
1532 } else {
1533 return_value = FALSE;
1534 }
1535 gtk_widget_destroy(dialog);
1536 g_free( baseName );
1537 g_free( dirName );
1538 } else {
1539 return_value = TRUE;
1540 }
1542 return return_value;
1543 }
1545 static void
1546 sp_ui_menu_item_set_sensitive(SPAction */*action*/, unsigned int sensitive, void *data)
1547 {
1548 return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
1549 }
1551 static void
1552 sp_ui_menu_item_set_name(SPAction */*action*/, Glib::ustring name, void *data)
1553 {
1554 void *child = GTK_BIN (data)->child;
1555 //child is either
1556 //- a GtkHBox, whose first child is a label displaying name if the menu
1557 //item has an accel key
1558 //- a GtkLabel if the menu has no accel key
1559 if (GTK_IS_LABEL(child)) {
1560 gtk_label_set_markup_with_mnemonic(GTK_LABEL (child), name.c_str());
1561 } else if (GTK_IS_HBOX(child)) {
1562 gtk_label_set_markup_with_mnemonic(
1563 GTK_LABEL (gtk_container_get_children(GTK_CONTAINER (child))->data),
1564 name.c_str());
1565 }//else sp_ui_menu_append_item_from_verb has been modified and can set
1566 //a menu item in yet another way...
1567 }
1570 /*
1571 Local Variables:
1572 mode:c++
1573 c-file-style:"stroustrup"
1574 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1575 indent-tabs-mode:nil
1576 fill-column:99
1577 End:
1578 */
1579 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :