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"
65 // Include Mac OS X menu synchronization on native OSX build
66 #ifdef GDK_WINDOWING_QUARTZ
67 #include "ige-mac-menu.h"
68 #endif
70 /* Drag and Drop */
71 typedef enum {
72 URI_LIST,
73 SVG_XML_DATA,
74 SVG_DATA,
75 PNG_DATA,
76 JPEG_DATA,
77 IMAGE_DATA,
78 APP_X_INKY_COLOR,
79 APP_X_COLOR,
80 APP_X_NOCOLOR,
81 APP_X_XCOLOR
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-inkscape-nocolor", 0, APP_X_NOCOLOR },
94 {(gchar *)"application/x-inkscape-xcolor", 0, APP_X_XCOLOR },
95 {(gchar *)"application/x-color", 0, APP_X_COLOR }
96 };
98 static GtkTargetEntry *completeDropTargets = 0;
99 static int completeDropTargetsCount = 0;
101 #define ENTRIES_SIZE(n) sizeof(n)/sizeof(n[0])
102 static guint nui_drop_target_entries = ENTRIES_SIZE(ui_drop_target_entries);
103 static void sp_ui_import_files(gchar *buffer);
104 static void sp_ui_import_one_file(char const *filename);
105 static void sp_ui_import_one_file_with_check(gpointer filename, gpointer unused);
106 static void sp_ui_drag_data_received(GtkWidget *widget,
107 GdkDragContext *drag_context,
108 gint x, gint y,
109 GtkSelectionData *data,
110 guint info,
111 guint event_time,
112 gpointer user_data);
113 static void sp_ui_drag_motion( GtkWidget *widget,
114 GdkDragContext *drag_context,
115 gint x, gint y,
116 GtkSelectionData *data,
117 guint info,
118 guint event_time,
119 gpointer user_data );
120 static void sp_ui_drag_leave( GtkWidget *widget,
121 GdkDragContext *drag_context,
122 guint event_time,
123 gpointer user_data );
124 static void sp_ui_menu_item_set_sensitive(SPAction *action,
125 unsigned int sensitive,
126 void *data);
127 static void sp_ui_menu_item_set_name(SPAction *action,
128 Glib::ustring name,
129 void *data);
130 static void sp_recent_open(GtkRecentChooser *, gpointer);
132 SPActionEventVector menu_item_event_vector = {
133 {NULL},
134 NULL,
135 NULL, /* set_active */
136 sp_ui_menu_item_set_sensitive, /* set_sensitive */
137 NULL, /* set_shortcut */
138 sp_ui_menu_item_set_name /* set_name */
139 };
141 static const int MIN_ONSCREEN_DISTANCE = 50;
143 void
144 sp_create_window(SPViewWidget *vw, gboolean editable)
145 {
146 g_return_if_fail(vw != NULL);
147 g_return_if_fail(SP_IS_VIEW_WIDGET(vw));
149 Gtk::Window *win = Inkscape::UI::window_new("", TRUE);
151 gtk_container_add(GTK_CONTAINER(win->gobj()), GTK_WIDGET(vw));
152 gtk_widget_show(GTK_WIDGET(vw));
154 if (editable) {
155 g_object_set_data(G_OBJECT(vw), "window", win);
157 SPDesktopWidget *desktop_widget = reinterpret_cast<SPDesktopWidget*>(vw);
158 SPDesktop* desktop = desktop_widget->desktop;
160 desktop_widget->window = win;
162 win->set_data("desktop", desktop);
163 win->set_data("desktopwidget", desktop_widget);
165 win->signal_delete_event().connect(sigc::mem_fun(*(SPDesktop*)vw->view, &SPDesktop::onDeleteUI));
166 win->signal_window_state_event().connect(sigc::mem_fun(*desktop, &SPDesktop::onWindowStateEvent));
167 win->signal_focus_in_event().connect(sigc::mem_fun(*desktop_widget, &SPDesktopWidget::onFocusInEvent));
169 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
170 gint prefs_geometry =
171 (2==prefs->getInt("/options/savewindowgeometry/value", 0));
172 if (prefs_geometry) {
173 gint pw = prefs->getInt("/desktop/geometry/width", -1);
174 gint ph = prefs->getInt("/desktop/geometry/height", -1);
175 gint px = prefs->getInt("/desktop/geometry/x", -1);
176 gint py = prefs->getInt("/desktop/geometry/y", -1);
177 gint full = prefs->getBool("/desktop/geometry/fullscreen");
178 gint maxed = prefs->getBool("/desktop/geometry/maximized");
179 if (pw>0 && ph>0) {
180 gint w = MIN(gdk_screen_width(), pw);
181 gint h = MIN(gdk_screen_height(), ph);
182 gint x = MIN(gdk_screen_width() - MIN_ONSCREEN_DISTANCE, px);
183 gint y = MIN(gdk_screen_height() - MIN_ONSCREEN_DISTANCE, py);
184 if (w>0 && h>0 && x>0 && y>0) {
185 x = MIN(gdk_screen_width() - w, x);
186 y = MIN(gdk_screen_height() - h, y);
187 }
188 if (w>0 && h>0) {
189 desktop->setWindowSize(w, h);
190 }
192 // Only restore xy for the first window so subsequent windows don't overlap exactly
193 // with first. (Maybe rule should be only restore xy if it's different from xy of
194 // other desktops?)
196 // Empirically it seems that active_desktop==this desktop only the first time a
197 // desktop is created.
198 if (x>0 && y>0) {
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 }
205 if (maxed) {
206 win->maximize();
207 }
208 if (full) {
209 win->fullscreen();
210 }
211 }
213 } else {
214 gtk_window_set_policy(GTK_WINDOW(win->gobj()), TRUE, TRUE, TRUE);
215 }
217 if ( completeDropTargets == 0 || completeDropTargetsCount == 0 )
218 {
219 std::vector<gchar*> types;
221 GSList *list = gdk_pixbuf_get_formats();
222 while ( list ) {
223 int i = 0;
224 GdkPixbufFormat *one = (GdkPixbufFormat*)list->data;
225 gchar** typesXX = gdk_pixbuf_format_get_mime_types(one);
226 for ( i = 0; typesXX[i]; i++ ) {
227 types.push_back(g_strdup(typesXX[i]));
228 }
229 g_strfreev(typesXX);
231 list = g_slist_next(list);
232 }
233 completeDropTargetsCount = nui_drop_target_entries + types.size();
234 completeDropTargets = new GtkTargetEntry[completeDropTargetsCount];
235 for ( int i = 0; i < (int)nui_drop_target_entries; i++ ) {
236 completeDropTargets[i] = ui_drop_target_entries[i];
237 }
238 int pos = nui_drop_target_entries;
240 for (std::vector<gchar*>::iterator it = types.begin() ; it != types.end() ; it++) {
241 completeDropTargets[pos].target = *it;
242 completeDropTargets[pos].flags = 0;
243 completeDropTargets[pos].info = IMAGE_DATA;
244 pos++;
245 }
246 }
248 gtk_drag_dest_set((GtkWidget*)win->gobj(),
249 GTK_DEST_DEFAULT_ALL,
250 completeDropTargets,
251 completeDropTargetsCount,
252 GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE));
255 g_signal_connect(G_OBJECT(win->gobj()),
256 "drag_data_received",
257 G_CALLBACK(sp_ui_drag_data_received),
258 NULL);
259 g_signal_connect(G_OBJECT(win->gobj()),
260 "drag_motion",
261 G_CALLBACK(sp_ui_drag_motion),
262 NULL);
263 g_signal_connect(G_OBJECT(win->gobj()),
264 "drag_leave",
265 G_CALLBACK(sp_ui_drag_leave),
266 NULL);
267 win->show();
269 // needed because the first ACTIVATE_DESKTOP was sent when there was no window yet
270 inkscape_reactivate_desktop(SP_DESKTOP_WIDGET(vw)->desktop);
271 }
273 void
274 sp_ui_new_view()
275 {
276 SPDocument *document;
277 SPViewWidget *dtw;
279 document = SP_ACTIVE_DOCUMENT;
280 if (!document) return;
282 dtw = sp_desktop_widget_new(sp_document_namedview(document, NULL));
283 g_return_if_fail(dtw != NULL);
285 sp_create_window(dtw, TRUE);
286 sp_namedview_window_from_document(static_cast<SPDesktop*>(dtw->view));
287 sp_namedview_update_layers_from_document(static_cast<SPDesktop*>(dtw->view));
288 }
290 /* TODO: not yet working */
291 /* To be re-enabled (by adding to menu) once it works. */
292 void
293 sp_ui_new_view_preview()
294 {
295 SPDocument *document;
296 SPViewWidget *dtw;
298 document = SP_ACTIVE_DOCUMENT;
299 if (!document) return;
301 dtw = (SPViewWidget *) sp_svg_view_widget_new(document);
302 g_return_if_fail(dtw != NULL);
303 sp_svg_view_widget_set_resize(SP_SVG_VIEW_WIDGET(dtw), TRUE, 400.0, 400.0);
305 sp_create_window(dtw, FALSE);
306 }
308 /**
309 * \param widget unused
310 */
311 void
312 sp_ui_close_view(GtkWidget */*widget*/)
313 {
314 if (SP_ACTIVE_DESKTOP == NULL) {
315 return;
316 }
317 if ((SP_ACTIVE_DESKTOP)->shutdown()) {
318 return;
319 }
320 SP_ACTIVE_DESKTOP->destroyWidget();
321 }
324 /**
325 * sp_ui_close_all
326 *
327 * This function is called to exit the program, and iterates through all
328 * open document view windows, attempting to close each in turn. If the
329 * view has unsaved information, the user will be prompted to save,
330 * discard, or cancel.
331 *
332 * Returns FALSE if the user cancels the close_all operation, TRUE
333 * otherwise.
334 */
335 unsigned int
336 sp_ui_close_all(void)
337 {
338 /* Iterate through all the windows, destroying each in the order they
339 become active */
340 while (SP_ACTIVE_DESKTOP) {
341 if ((SP_ACTIVE_DESKTOP)->shutdown()) {
342 /* The user cancelled the operation, so end doing the close */
343 return FALSE;
344 }
345 SP_ACTIVE_DESKTOP->destroyWidget();
346 }
348 return TRUE;
349 }
351 /*
352 * Some day when the right-click menus are ready to start working
353 * smarter with the verbs, we'll need to change this NULL being
354 * sent to sp_action_perform to something useful, or set some kind
355 * of global "right-clicked position" variable for actions to
356 * investigate when they're called.
357 */
358 static void
359 sp_ui_menu_activate(void */*object*/, SPAction *action)
360 {
361 sp_action_perform(action, NULL);
362 }
364 static void
365 sp_ui_menu_select_action(void */*object*/, SPAction *action)
366 {
367 action->view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, action->tip);
368 }
370 static void
371 sp_ui_menu_deselect_action(void */*object*/, SPAction *action)
372 {
373 action->view->tipsMessageContext()->clear();
374 }
376 static void
377 sp_ui_menu_select(gpointer object, gpointer tip)
378 {
379 Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view"));
380 view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, (gchar *)tip);
381 }
383 static void
384 sp_ui_menu_deselect(gpointer object)
385 {
386 Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view"));
387 view->tipsMessageContext()->clear();
388 }
390 /**
391 * sp_ui_menuitem_add_icon
392 *
393 * Creates and attaches a scaled icon to the given menu item.
394 *
395 */
396 void
397 sp_ui_menuitem_add_icon( GtkWidget *item, gchar *icon_name )
398 {
399 GtkWidget *icon;
401 icon = sp_icon_new( Inkscape::ICON_SIZE_MENU, icon_name );
402 gtk_widget_show(icon);
403 gtk_image_menu_item_set_image((GtkImageMenuItem *) item, icon);
404 } // end of sp_ui_menu_add_icon
406 /**
407 * sp_ui_menu_append_item
408 *
409 * Appends a UI item with specific info for Inkscape/Sodipodi.
410 *
411 */
412 static GtkWidget *
413 sp_ui_menu_append_item( GtkMenu *menu, gchar const *stock,
414 gchar const *label, gchar const *tip, Inkscape::UI::View::View *view, GCallback callback,
415 gpointer data, gboolean with_mnemonic = TRUE )
416 {
417 GtkWidget *item;
419 if (stock) {
420 item = gtk_image_menu_item_new_from_stock(stock, NULL);
421 } else if (label) {
422 item = (with_mnemonic)
423 ? gtk_image_menu_item_new_with_mnemonic(label) :
424 gtk_image_menu_item_new_with_label(label);
425 } else {
426 item = gtk_separator_menu_item_new();
427 }
429 gtk_widget_show(item);
431 if (callback) {
432 g_signal_connect(G_OBJECT(item), "activate", callback, data);
433 }
435 if (tip && view) {
436 g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
437 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) tip );
438 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
439 }
441 gtk_menu_append(GTK_MENU(menu), item);
443 return item;
445 } // end of sp_ui_menu_append_item()
447 /**
448 \brief a wrapper around gdk_keyval_name producing (when possible) characters, not names
449 */
450 static gchar const *
451 sp_key_name(guint keyval)
452 {
453 /* TODO: Compare with the definition of gtk_accel_label_refetch in gtk/gtkaccellabel.c (or
454 simply use GtkAccelLabel as the TODO comment in sp_ui_shortcut_string suggests). */
455 gchar const *n = gdk_keyval_name(gdk_keyval_to_upper(keyval));
457 if (!strcmp(n, "asciicircum")) return "^";
458 else if (!strcmp(n, "parenleft" )) return "(";
459 else if (!strcmp(n, "parenright" )) return ")";
460 else if (!strcmp(n, "plus" )) return "+";
461 else if (!strcmp(n, "minus" )) return "-";
462 else if (!strcmp(n, "asterisk" )) return "*";
463 else if (!strcmp(n, "KP_Multiply")) return "*";
464 else if (!strcmp(n, "Delete" )) return "Del";
465 else if (!strcmp(n, "Page_Up" )) return "PgUp";
466 else if (!strcmp(n, "Page_Down" )) return "PgDn";
467 else if (!strcmp(n, "grave" )) return "`";
468 else if (!strcmp(n, "numbersign" )) return "#";
469 else if (!strcmp(n, "bar" )) return "|";
470 else if (!strcmp(n, "slash" )) return "/";
471 else if (!strcmp(n, "exclam" )) return "!";
472 else if (!strcmp(n, "percent" )) return "%";
473 else return n;
474 }
477 /**
478 * \param shortcut A GDK keyval OR'd with SP_SHORTCUT_blah_MASK values.
479 * \param c Points to a buffer at least 256 bytes long.
480 */
481 void
482 sp_ui_shortcut_string(unsigned const shortcut, gchar *const c)
483 {
484 /* TODO: This function shouldn't exist. Our callers should use GtkAccelLabel instead of
485 * a generic GtkLabel containing this string, and should call gtk_widget_add_accelerator.
486 * Will probably need to change sp_shortcut_invoke callers.
487 *
488 * The existing gtk_label_new_with_mnemonic call can be replaced with
489 * g_object_new(GTK_TYPE_ACCEL_LABEL, NULL) followed by
490 * gtk_label_set_text_with_mnemonic(lbl, str).
491 */
492 static GtkAccelLabelClass const &accel_lbl_cls
493 = *(GtkAccelLabelClass const *) g_type_class_peek_static(GTK_TYPE_ACCEL_LABEL);
495 struct { unsigned test; char const *name; } const modifier_tbl[] = {
496 { SP_SHORTCUT_SHIFT_MASK, accel_lbl_cls.mod_name_shift },
497 { SP_SHORTCUT_CONTROL_MASK, accel_lbl_cls.mod_name_control },
498 { SP_SHORTCUT_ALT_MASK, accel_lbl_cls.mod_name_alt }
499 };
501 gchar *p = c;
502 gchar *end = p + 256;
504 for (unsigned i = 0; i < G_N_ELEMENTS(modifier_tbl); ++i) {
505 if ((shortcut & modifier_tbl[i].test)
506 && (p < end))
507 {
508 p += g_snprintf(p, end - p, "%s%s",
509 modifier_tbl[i].name,
510 accel_lbl_cls.mod_separator);
511 }
512 }
513 if (p < end) {
514 p += g_snprintf(p, end - p, "%s", sp_key_name(shortcut & 0xffffff));
515 }
516 end[-1] = '\0'; // snprintf doesn't guarantee to nul-terminate the string.
517 }
519 void
520 sp_ui_dialog_title_string(Inkscape::Verb *verb, gchar *c)
521 {
522 SPAction *action;
523 unsigned int shortcut;
524 gchar *s;
525 gchar key[256];
526 gchar *atitle;
528 action = verb->get_action(NULL);
529 if (!action)
530 return;
532 atitle = sp_action_get_title(action);
534 s = g_stpcpy(c, atitle);
536 g_free(atitle);
538 shortcut = sp_shortcut_get_primary(verb);
539 if (shortcut) {
540 s = g_stpcpy(s, " (");
541 sp_ui_shortcut_string(shortcut, key);
542 s = g_stpcpy(s, key);
543 s = g_stpcpy(s, ")");
544 }
545 }
548 /**
549 * sp_ui_menu_append_item_from_verb
550 *
551 * Appends a custom menu UI from a verb.
552 *
553 */
555 static GtkWidget *
556 sp_ui_menu_append_item_from_verb(GtkMenu *menu, Inkscape::Verb *verb, Inkscape::UI::View::View *view, bool radio = false, GSList *group = NULL)
557 {
558 SPAction *action;
559 GtkWidget *item;
561 if (verb->get_code() == SP_VERB_NONE) {
563 item = gtk_separator_menu_item_new();
565 } else {
566 unsigned int shortcut;
568 action = verb->get_action(view);
570 if (!action) return NULL;
572 shortcut = sp_shortcut_get_primary(verb);
573 if (shortcut) {
574 gchar c[256];
575 sp_ui_shortcut_string(shortcut, c);
576 GtkWidget *const hb = gtk_hbox_new(FALSE, 16);
577 GtkWidget *const name_lbl = gtk_label_new("");
578 gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
579 gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
580 gtk_box_pack_start((GtkBox *) hb, name_lbl, TRUE, TRUE, 0);
581 GtkWidget *const accel_lbl = gtk_label_new(c);
582 gtk_misc_set_alignment((GtkMisc *) accel_lbl, 1.0, 0.5);
583 gtk_box_pack_end((GtkBox *) hb, accel_lbl, FALSE, FALSE, 0);
584 gtk_widget_show_all(hb);
585 if (radio) {
586 item = gtk_radio_menu_item_new (group);
587 } else {
588 item = gtk_image_menu_item_new();
589 }
590 gtk_container_add((GtkContainer *) item, hb);
591 } else {
592 if (radio) {
593 item = gtk_radio_menu_item_new (group);
594 } else {
595 item = gtk_image_menu_item_new ();
596 }
597 GtkWidget *const name_lbl = gtk_label_new("");
598 gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
599 gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
600 gtk_container_add((GtkContainer *) item, name_lbl);
601 }
603 nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
604 if (!action->sensitive) {
605 gtk_widget_set_sensitive(item, FALSE);
606 }
608 if (action->image) {
609 sp_ui_menuitem_add_icon(item, action->image);
610 }
611 gtk_widget_set_events(item, GDK_KEY_PRESS_MASK);
612 g_signal_connect( G_OBJECT(item), "activate",
613 G_CALLBACK(sp_ui_menu_activate), action );
615 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select_action), action );
616 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect_action), action );
617 }
619 gtk_widget_show(item);
620 gtk_menu_append(GTK_MENU(menu), item);
622 return item;
624 } // end of sp_ui_menu_append_item_from_verb
627 static void
628 checkitem_toggled(GtkCheckMenuItem *menuitem, gpointer user_data)
629 {
630 gchar const *pref = (gchar const *) user_data;
631 Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
633 Glib::ustring pref_path;
634 if (reinterpret_cast<SPDesktop*>(view)->is_focusMode()) {
635 pref_path = "/focus/";
636 } else if (reinterpret_cast<SPDesktop*>(view)->is_fullscreen()) {
637 pref_path = "/fullscreen/";
638 } else {
639 pref_path = "/window/";
640 }
641 pref_path += pref;
642 pref_path += "/state";
644 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
645 gboolean checked = gtk_check_menu_item_get_active(menuitem);
646 prefs->setBool(pref_path, checked);
648 reinterpret_cast<SPDesktop*>(view)->layoutWidget();
649 }
651 static gboolean
652 checkitem_update(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_data)
653 {
654 GtkCheckMenuItem *menuitem=GTK_CHECK_MENU_ITEM(widget);
656 gchar const *pref = (gchar const *) user_data;
657 Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
659 Glib::ustring pref_path;
660 if ((static_cast<SPDesktop*>(view))->is_fullscreen()) {
661 pref_path = "/fullscreen/";
662 } else {
663 pref_path = "/window/";
664 }
665 pref_path += pref;
667 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
668 bool ison = prefs->getBool(pref_path + "/state", true);
670 g_signal_handlers_block_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
671 gtk_check_menu_item_set_active(menuitem, ison);
672 g_signal_handlers_unblock_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
674 return FALSE;
675 }
678 void
679 sp_ui_menu_append_check_item_from_verb(GtkMenu *menu, Inkscape::UI::View::View *view, gchar const *label, gchar const *tip, gchar const *pref,
680 void (*callback_toggle)(GtkCheckMenuItem *, gpointer user_data),
681 gboolean (*callback_update)(GtkWidget *widget, GdkEventExpose *event, gpointer user_data),
682 Inkscape::Verb *verb)
683 {
684 GtkWidget *item;
686 unsigned int shortcut = 0;
687 SPAction *action = NULL;
689 if (verb) {
690 shortcut = sp_shortcut_get_primary(verb);
691 action = verb->get_action(view);
692 }
694 if (verb && shortcut) {
695 gchar c[256];
696 sp_ui_shortcut_string(shortcut, c);
698 GtkWidget *hb = gtk_hbox_new(FALSE, 16);
700 {
701 GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
702 gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
703 gtk_box_pack_start((GtkBox *) hb, l, TRUE, TRUE, 0);
704 }
706 {
707 GtkWidget *l = gtk_label_new(c);
708 gtk_misc_set_alignment((GtkMisc *) l, 1.0, 0.5);
709 gtk_box_pack_end((GtkBox *) hb, l, FALSE, FALSE, 0);
710 }
712 gtk_widget_show_all(hb);
714 item = gtk_check_menu_item_new();
715 gtk_container_add((GtkContainer *) item, hb);
716 } else {
717 GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
718 gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
719 item = gtk_check_menu_item_new();
720 gtk_container_add((GtkContainer *) item, l);
721 }
722 #if 0
723 nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
724 if (!action->sensitive) {
725 gtk_widget_set_sensitive(item, FALSE);
726 }
727 #endif
728 gtk_widget_show(item);
730 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
732 g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
734 g_signal_connect( G_OBJECT(item), "toggled", (GCallback) callback_toggle, (void *) pref);
735 g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) callback_update, (void *) pref);
737 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) (action ? action->tip : tip));
738 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
739 }
741 static void
742 sp_recent_open(GtkRecentChooser *recent_menu, gpointer /*user_data*/)
743 {
744 // dealing with the bizarre filename convention in Inkscape for now
745 gchar *uri = gtk_recent_chooser_get_current_uri(GTK_RECENT_CHOOSER(recent_menu));
746 gchar *local_fn = g_filename_from_uri(uri, NULL, NULL);
747 gchar *utf8_fn = g_filename_to_utf8(local_fn, -1, NULL, NULL, NULL);
748 sp_file_open(utf8_fn, NULL);
749 g_free(utf8_fn);
750 g_free(local_fn);
751 g_free(uri);
752 }
754 static void
755 sp_file_new_from_template(GtkWidget */*widget*/, gchar const *uri)
756 {
757 sp_file_new(uri);
758 }
760 void
761 sp_menu_append_new_templates(GtkWidget *menu, Inkscape::UI::View::View *view)
762 {
763 std::list<gchar *> sources;
764 sources.push_back( profile_path("templates") ); // first try user's local dir
765 sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir
767 // Use this loop to iterate through a list of possible document locations.
768 while (!sources.empty()) {
769 gchar *dirname = sources.front();
771 if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) {
772 GError *err = 0;
773 GDir *dir = g_dir_open(dirname, 0, &err);
775 if (dir) {
776 for (gchar const *file = g_dir_read_name(dir); file != NULL; file = g_dir_read_name(dir)) {
777 if (!g_str_has_suffix(file, ".svg") && !g_str_has_suffix(file, ".svgz"))
778 continue; // skip non-svg files
780 gchar *basename = g_path_get_basename(file);
781 if (g_str_has_suffix(basename, ".svg") && g_str_has_prefix(basename, "default."))
782 continue; // skip default.*.svg (i.e. default.svg and translations) - it's in the menu already
784 gchar const *filepath = g_build_filename(dirname, file, NULL);
785 gchar *dupfile = g_strndup(file, strlen(file) - 4);
786 gchar *filename = g_filename_to_utf8(dupfile, -1, NULL, NULL, NULL);
787 g_free(dupfile);
788 GtkWidget *item = gtk_menu_item_new_with_label(filename);
789 g_free(filename);
791 gtk_widget_show(item);
792 // how does "filepath" ever get freed?
793 g_signal_connect(G_OBJECT(item),
794 "activate",
795 G_CALLBACK(sp_file_new_from_template),
796 (gpointer) filepath);
798 if (view) {
799 // set null tip for now; later use a description from the template file
800 g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
801 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) NULL );
802 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
803 }
805 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
806 }
807 g_dir_close(dir);
808 }
809 }
811 // toss the dirname
812 g_free(dirname);
813 sources.pop_front();
814 }
815 }
817 void
818 sp_ui_checkboxes_menus(GtkMenu *m, Inkscape::UI::View::View *view)
819 {
820 //sp_ui_menu_append_check_item_from_verb(m, view, _("_Menu"), _("Show or hide the menu bar"), "menu",
821 // checkitem_toggled, checkitem_update, 0);
822 sp_ui_menu_append_check_item_from_verb(m, view, _("Commands Bar"), _("Show or hide the Commands bar (under the menu)"), "commands",
823 checkitem_toggled, checkitem_update, 0);
824 sp_ui_menu_append_check_item_from_verb(m, view, _("Snap controls Bar"), _("Show or hide the snapping controls"), "snaptoolbox",
825 checkitem_toggled, checkitem_update, 0);
826 sp_ui_menu_append_check_item_from_verb(m, view, _("Tool Controls Bar"), _("Show or hide the Tool Controls bar"), "toppanel",
827 checkitem_toggled, checkitem_update, 0);
828 sp_ui_menu_append_check_item_from_verb(m, view, _("_Toolbox"), _("Show or hide the main toolbox (on the left)"), "toolbox",
829 checkitem_toggled, checkitem_update, 0);
830 sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "rulers",
831 checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_RULERS));
832 sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "scrollbars",
833 checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_SCROLLBARS));
834 sp_ui_menu_append_check_item_from_verb(m, view, _("_Palette"), _("Show or hide the color palette"), "panels",
835 checkitem_toggled, checkitem_update, 0);
836 sp_ui_menu_append_check_item_from_verb(m, view, _("_Statusbar"), _("Show or hide the statusbar (at the bottom of the window)"), "statusbar",
837 checkitem_toggled, checkitem_update, 0);
838 }
840 /** @brief Observer that updates the recent list's max document count */
841 class MaxRecentObserver : public Inkscape::Preferences::Observer {
842 public:
843 MaxRecentObserver(GtkWidget *recent_menu) :
844 Observer("/options/maxrecentdocuments/value"),
845 _rm(recent_menu)
846 {}
847 virtual void notify(Inkscape::Preferences::Entry const &e) {
848 gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(_rm), e.getInt());
849 // hack: the recent menu doesn't repopulate after changing the limit, so we force it
850 g_signal_emit_by_name((gpointer) gtk_recent_manager_get_default(), "changed");
851 }
852 private:
853 GtkWidget *_rm;
854 };
856 /** \brief This function turns XML into a menu
857 \param menus This is the XML that defines the menu
858 \param menu Menu to be added to
859 \param view The View that this menu is being built for
861 This function is realitively simple as it just goes through the XML
862 and parses the individual elements. In the case of a submenu, it
863 just calls itself recursively. Because it is only reasonable to have
864 a couple of submenus, it is unlikely this will go more than two or
865 three times.
867 In the case of an unreconginzed verb, a menu item is made to identify
868 the verb that is missing, and display that. The menu item is also made
869 insensitive.
870 */
871 void
872 sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI::View::View *view)
873 {
874 if (menus == NULL) return;
875 if (menu == NULL) return;
876 GSList *group = NULL;
878 for (Inkscape::XML::Node *menu_pntr = menus;
879 menu_pntr != NULL;
880 menu_pntr = menu_pntr->next()) {
881 if (!strcmp(menu_pntr->name(), "submenu")) {
882 GtkWidget *mitem = gtk_menu_item_new_with_mnemonic(_(menu_pntr->attribute("name")));
883 GtkWidget *submenu = gtk_menu_new();
884 sp_ui_build_dyn_menus(menu_pntr->firstChild(), submenu, view);
885 gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), GTK_WIDGET(submenu));
886 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
887 continue;
888 }
889 if (!strcmp(menu_pntr->name(), "verb")) {
890 gchar const *verb_name = menu_pntr->attribute("verb-id");
891 Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name);
893 if (verb != NULL) {
894 if (menu_pntr->attribute("radio") != NULL) {
895 GtkWidget *item = sp_ui_menu_append_item_from_verb (GTK_MENU(menu), verb, view, true, group);
896 group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item));
897 if (menu_pntr->attribute("default") != NULL) {
898 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
899 }
900 } else {
901 sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view);
902 group = NULL;
903 }
904 } else {
905 gchar string[120];
906 g_snprintf(string, 120, _("Verb \"%s\" Unknown"), verb_name);
907 string[119] = '\0'; /* may not be terminated */
908 GtkWidget *item = gtk_menu_item_new_with_label(string);
909 gtk_widget_set_sensitive(item, false);
910 gtk_widget_show(item);
911 gtk_menu_append(GTK_MENU(menu), item);
912 }
913 continue;
914 }
915 if (!strcmp(menu_pntr->name(), "separator")
916 // This was spelt wrong in the original version
917 // and so this is for backward compatibility. It can
918 // probably be dropped after the 0.44 release.
919 || !strcmp(menu_pntr->name(), "seperator")) {
920 GtkWidget *item = gtk_separator_menu_item_new();
921 gtk_widget_show(item);
922 gtk_menu_append(GTK_MENU(menu), item);
923 continue;
924 }
925 if (!strcmp(menu_pntr->name(), "template-list")) {
926 sp_menu_append_new_templates(menu, view);
927 continue;
928 }
929 if (!strcmp(menu_pntr->name(), "recent-file-list")) {
930 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
932 // create recent files menu
933 int max_recent = prefs->getInt("/options/maxrecentdocuments/value");
934 GtkWidget *recent_menu = gtk_recent_chooser_menu_new_for_manager(gtk_recent_manager_get_default());
935 gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(recent_menu), max_recent);
936 // sort most recently used documents first to preserve previous behavior
937 gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(recent_menu), GTK_RECENT_SORT_MRU);
938 g_signal_connect(G_OBJECT(recent_menu), "item-activated", G_CALLBACK(sp_recent_open), (gpointer) NULL);
940 // add filter to only open files added by Inkscape
941 GtkRecentFilter *inkscape_only_filter = gtk_recent_filter_new();
942 gtk_recent_filter_add_application(inkscape_only_filter, g_get_prgname());
943 gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_menu), inkscape_only_filter);
945 GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic(_("Open _Recent"));
946 gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), recent_menu);
948 gtk_menu_append(GTK_MENU(menu), GTK_WIDGET(recent_item));
949 // this will just sit and update the list's item count
950 static MaxRecentObserver *mro = new MaxRecentObserver(recent_menu);
951 prefs->addObserver(*mro);
952 continue;
953 }
954 if (!strcmp(menu_pntr->name(), "objects-checkboxes")) {
955 sp_ui_checkboxes_menus(GTK_MENU(menu), view);
956 continue;
957 }
958 }
959 }
961 /** \brief Build the main tool bar
962 \param view View to build the bar for
964 Currently the main tool bar is built as a dynamic XML menu using
965 \c sp_ui_build_dyn_menus. This function builds the bar, and then
966 pass it to get items attached to it.
967 */
968 GtkWidget *
969 sp_ui_main_menubar(Inkscape::UI::View::View *view)
970 {
971 GtkWidget *mbar = gtk_menu_bar_new();
973 #ifdef GDK_WINDOWING_QUARTZ
974 ige_mac_menu_set_menu_bar(GTK_MENU_SHELL(mbar));
975 #endif
977 sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view);
979 #ifdef GDK_WINDOWING_QUARTZ
980 return NULL;
981 #else
982 return mbar;
983 #endif
984 }
986 static void leave_group(GtkMenuItem *, SPDesktop *desktop) {
987 desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer()));
988 }
990 static void enter_group(GtkMenuItem *mi, SPDesktop *desktop) {
991 desktop->setCurrentLayer(reinterpret_cast<SPObject *>(g_object_get_data(G_OBJECT(mi), "group")));
992 sp_desktop_selection(desktop)->clear();
993 }
995 GtkWidget *
996 sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item)
997 {
998 GtkWidget *m;
999 SPDesktop *dt;
1001 dt = static_cast<SPDesktop*>(view);
1003 m = gtk_menu_new();
1005 /* Undo and Redo */
1006 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_UNDO), view);
1007 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_REDO), view);
1009 /* Separator */
1010 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1012 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_CUT), view);
1013 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_COPY), view);
1014 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_PASTE), view);
1016 /* Separator */
1017 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1019 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DUPLICATE), view);
1020 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DELETE), view);
1022 /* Item menu */
1023 if (item) {
1024 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1025 sp_object_menu((SPObject *) item, dt, GTK_MENU(m));
1026 }
1028 /* layer menu */
1029 SPGroup *group=NULL;
1030 if (item) {
1031 if (SP_IS_GROUP(item)) {
1032 group = SP_GROUP(item);
1033 } else if ( item != dt->currentRoot() && SP_IS_GROUP(SP_OBJECT_PARENT(item)) ) {
1034 group = SP_GROUP(SP_OBJECT_PARENT(item));
1035 }
1036 }
1038 if (( group && group != dt->currentLayer() ) ||
1039 ( dt->currentLayer() != dt->currentRoot() && SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) ) {
1040 /* Separator */
1041 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
1042 }
1044 if ( group && group != dt->currentLayer() ) {
1045 /* TRANSLATORS: #%s is the id of the group e.g. <g id="#g7">, not a number. */
1046 gchar *label=g_strdup_printf(_("Enter group #%s"), SP_OBJECT_ID(group));
1047 GtkWidget *w = gtk_menu_item_new_with_label(label);
1048 g_free(label);
1049 g_object_set_data(G_OBJECT(w), "group", group);
1050 g_signal_connect(G_OBJECT(w), "activate", GCallback(enter_group), dt);
1051 gtk_widget_show(w);
1052 gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1053 }
1055 if ( dt->currentLayer() != dt->currentRoot() ) {
1056 if ( SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) {
1057 GtkWidget *w = gtk_menu_item_new_with_label(_("Go to parent"));
1058 g_signal_connect(G_OBJECT(w), "activate", GCallback(leave_group), dt);
1059 gtk_widget_show(w);
1060 gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
1062 }
1063 }
1065 return m;
1066 }
1068 /* Drag and Drop */
1069 void
1070 sp_ui_drag_data_received(GtkWidget *widget,
1071 GdkDragContext *drag_context,
1072 gint x, gint y,
1073 GtkSelectionData *data,
1074 guint info,
1075 guint /*event_time*/,
1076 gpointer /*user_data*/)
1077 {
1078 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1079 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1081 switch (info) {
1082 #if ENABLE_MAGIC_COLORS
1083 case APP_X_INKY_COLOR:
1084 {
1085 int destX = 0;
1086 int destY = 0;
1087 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1088 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1090 SPItem *item = desktop->item_at_point( where, true );
1091 if ( item )
1092 {
1093 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1095 if ( data->length >= 8 ) {
1096 cmsHPROFILE srgbProf = cmsCreate_sRGBProfile();
1098 gchar c[64] = {0};
1099 // Careful about endian issues.
1100 guint16* dataVals = (guint16*)data->data;
1101 sp_svg_write_color( c, sizeof(c),
1102 SP_RGBA32_U_COMPOSE(
1103 0x0ff & (dataVals[0] >> 8),
1104 0x0ff & (dataVals[1] >> 8),
1105 0x0ff & (dataVals[2] >> 8),
1106 0xff // can't have transparency in the color itself
1107 //0x0ff & (data->data[3] >> 8),
1108 ));
1109 SPCSSAttr *css = sp_repr_css_attr_new();
1110 bool updatePerformed = false;
1112 if ( data->length > 14 ) {
1113 int flags = dataVals[4];
1115 // piggie-backed palette entry info
1116 int index = dataVals[5];
1117 Glib::ustring palName;
1118 for ( int i = 0; i < dataVals[6]; i++ ) {
1119 palName += (gunichar)dataVals[7+i];
1120 }
1122 // Now hook in a magic tag of some sort.
1123 if ( !palName.empty() && (flags & 1) ) {
1124 gchar* str = g_strdup_printf("%d|", index);
1125 palName.insert( 0, str );
1126 g_free(str);
1127 str = 0;
1129 sp_object_setAttribute( SP_OBJECT(item),
1130 fillnotstroke ? "inkscape:x-fill-tag":"inkscape:x-stroke-tag",
1131 palName.c_str(),
1132 false );
1133 item->updateRepr();
1135 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1136 updatePerformed = true;
1137 }
1138 }
1140 if ( !updatePerformed ) {
1141 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1142 }
1144 sp_desktop_apply_css_recursive( item, css, true );
1145 item->updateRepr();
1147 sp_document_done( doc , SP_VERB_NONE,
1148 _("Drop color"));
1150 if ( srgbProf ) {
1151 cmsCloseProfile( srgbProf );
1152 }
1153 }
1154 }
1155 }
1156 break;
1157 #endif // ENABLE_MAGIC_COLORS
1159 case APP_X_COLOR:
1160 {
1161 int destX = 0;
1162 int destY = 0;
1163 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1164 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1165 Geom::Point const button_dt(desktop->w2d(where));
1166 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1168 if ( data->length == 8 ) {
1169 gchar c[64] = {0};
1170 // Careful about endian issues.
1171 guint16* dataVals = (guint16*)data->data;
1172 sp_svg_write_color( c, 64,
1173 SP_RGBA32_U_COMPOSE(
1174 0x0ff & (dataVals[0] >> 8),
1175 0x0ff & (dataVals[1] >> 8),
1176 0x0ff & (dataVals[2] >> 8),
1177 0xff // can't have transparency in the color itself
1178 //0x0ff & (data->data[3] >> 8),
1179 ));
1181 SPItem *item = desktop->item_at_point( where, true );
1183 bool consumed = false;
1184 if (desktop->event_context && desktop->event_context->get_drag()) {
1185 consumed = desktop->event_context->get_drag()->dropColor(item, c, button_dt);
1186 if (consumed) {
1187 sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1188 desktop->event_context->get_drag()->updateDraggers();
1189 }
1190 }
1192 //if (!consumed && tools_active(desktop, TOOLS_TEXT)) {
1193 // consumed = sp_text_context_drop_color(c, button_doc);
1194 // if (consumed) {
1195 // sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient stop"));
1196 // }
1197 //}
1199 if (!consumed && item) {
1200 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1201 if (fillnotstroke &&
1202 (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1203 Path *livarot_path = Path_for_item(item, true, true);
1204 livarot_path->ConvertWithBackData(0.04);
1206 boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1207 if (position) {
1208 Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1209 Geom::Point delta = nearest - button_doc;
1210 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1211 delta = desktop->d2w(delta);
1212 double stroke_tolerance =
1213 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1214 desktop->current_zoom() *
1215 SP_OBJECT_STYLE (item)->stroke_width.computed *
1216 to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1217 : 0.0)
1218 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1220 if (Geom::L2 (delta) < stroke_tolerance) {
1221 fillnotstroke = false;
1222 }
1223 }
1224 delete livarot_path;
1225 }
1227 SPCSSAttr *css = sp_repr_css_attr_new();
1228 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1230 sp_desktop_apply_css_recursive( item, css, true );
1231 item->updateRepr();
1233 sp_document_done( doc , SP_VERB_NONE,
1234 _("Drop color"));
1235 }
1236 }
1237 }
1238 break;
1240 case APP_X_NOCOLOR:
1241 case APP_X_XCOLOR:
1242 {
1243 gchar* c = g_strdup("none"); // temp
1244 int destX = 0;
1245 int destY = 0;
1246 gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY );
1247 Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) );
1248 Geom::Point const button_dt(desktop->w2d(where));
1249 Geom::Point const button_doc(desktop->dt2doc(button_dt));
1251 SPItem *item = desktop->item_at_point( where, true );
1253 bool consumed = false;
1254 if (desktop->event_context && desktop->event_context->get_drag()) {
1255 consumed = desktop->event_context->get_drag()->dropColor(item, c, button_dt);
1256 if (consumed) {
1257 sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient"));
1258 desktop->event_context->get_drag()->updateDraggers();
1259 }
1260 }
1262 if (!consumed && item) {
1263 bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE);
1264 if (fillnotstroke &&
1265 (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) {
1266 Path *livarot_path = Path_for_item(item, true, true);
1267 livarot_path->ConvertWithBackData(0.04);
1269 boost::optional<Path::cut_position> position = get_nearest_position_on_Path(livarot_path, button_doc);
1270 if (position) {
1271 Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t);
1272 Geom::Point delta = nearest - button_doc;
1273 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1274 delta = desktop->d2w(delta);
1275 double stroke_tolerance =
1276 ( !SP_OBJECT_STYLE(item)->stroke.isNone() ?
1277 desktop->current_zoom() *
1278 SP_OBJECT_STYLE (item)->stroke_width.computed *
1279 to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5
1280 : 0.0)
1281 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
1283 if (Geom::L2 (delta) < stroke_tolerance) {
1284 fillnotstroke = false;
1285 }
1286 }
1287 delete livarot_path;
1288 }
1290 SPCSSAttr *css = sp_repr_css_attr_new();
1291 sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c );
1293 sp_desktop_apply_css_recursive( item, css, true );
1294 item->updateRepr();
1296 sp_document_done( doc , SP_VERB_NONE,
1297 _("Drop color"));
1298 }
1299 g_free(c);
1300 }
1301 break;
1303 case SVG_DATA:
1304 case SVG_XML_DATA: {
1305 gchar *svgdata = (gchar *)data->data;
1307 Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI);
1309 if (rnewdoc == NULL) {
1310 sp_ui_error_dialog(_("Could not parse SVG data"));
1311 return;
1312 }
1314 Inkscape::XML::Node *repr = rnewdoc->root();
1315 gchar const *style = repr->attribute("style");
1317 Inkscape::XML::Node *newgroup = rnewdoc->createElement("svg:g");
1318 newgroup->setAttribute("style", style);
1320 Inkscape::XML::Document * xml_doc = sp_document_repr_doc(doc);
1321 for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) {
1322 Inkscape::XML::Node *newchild = child->duplicate(xml_doc);
1323 newgroup->appendChild(newchild);
1324 }
1326 Inkscape::GC::release(rnewdoc);
1328 // Add it to the current layer
1330 // Greg's edits to add intelligent positioning of svg drops
1331 SPObject *new_obj = NULL;
1332 new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
1334 Inkscape::Selection *selection = sp_desktop_selection(desktop);
1335 selection->set(SP_ITEM(new_obj));
1336 // To move the imported object, we must temporarily set the "transform pattern with
1337 // object" option.
1338 {
1339 Inkscape::Preferences *prefs = Inkscape::Preferences::get();
1340 bool const saved_pref = prefs->getBool("/options/transform/pattern", true);
1341 prefs->setBool("/options/transform/pattern", true);
1342 sp_document_ensure_up_to_date(sp_desktop_document(desktop));
1343 Geom::OptRect sel_bbox = selection->bounds();
1344 if (sel_bbox) {
1345 Geom::Point m( desktop->point() - sel_bbox->midpoint() );
1346 sp_selection_move_relative(selection, m);
1347 }
1348 prefs->setBool("/options/transform/pattern", saved_pref);
1349 }
1351 Inkscape::GC::release(newgroup);
1352 sp_document_done(doc, SP_VERB_NONE,
1353 _("Drop SVG"));
1354 break;
1355 }
1357 case URI_LIST: {
1358 gchar *uri = (gchar *)data->data;
1359 sp_ui_import_files(uri);
1360 break;
1361 }
1363 case PNG_DATA:
1364 case JPEG_DATA:
1365 case IMAGE_DATA: {
1366 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc);
1367 Inkscape::XML::Node *newImage = xml_doc->createElement("svg:image");
1368 gchar *atom_name = gdk_atom_name(data->type);
1370 // this formula taken from Glib docs
1371 guint needed_size = data->length * 4 / 3 + data->length * 4 / (3 * 72) + 7;
1372 needed_size += 5 + 8 + strlen(atom_name); // 5 bytes for data:, 8 for ;base64,
1374 gchar *buffer = (gchar *) g_malloc(needed_size), *buf_work = buffer;
1375 buf_work += g_sprintf(buffer, "data:%s;base64,", atom_name);
1377 gint state = 0, save = 0;
1378 g_base64_encode_step(data->data, data->length, TRUE, buf_work, &state, &save);
1379 g_base64_encode_close(TRUE, buf_work, &state, &save);
1381 newImage->setAttribute("xlink:href", buffer);
1382 g_free(buffer);
1384 GError *error = NULL;
1385 GdkPixbufLoader *loader = gdk_pixbuf_loader_new_with_mime_type( gdk_atom_name(data->type), &error );
1386 if ( loader ) {
1387 error = NULL;
1388 if ( gdk_pixbuf_loader_write( loader, data->data, data->length, &error) ) {
1389 GdkPixbuf *pbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1390 if ( pbuf ) {
1391 char tmp[1024];
1392 int width = gdk_pixbuf_get_width(pbuf);
1393 int height = gdk_pixbuf_get_height(pbuf);
1394 snprintf( tmp, sizeof(tmp), "%d", width );
1395 newImage->setAttribute("width", tmp);
1397 snprintf( tmp, sizeof(tmp), "%d", height );
1398 newImage->setAttribute("height", tmp);
1399 }
1400 }
1401 }
1402 g_free(atom_name);
1404 // Add it to the current layer
1405 desktop->currentLayer()->appendChildRepr(newImage);
1407 Inkscape::GC::release(newImage);
1408 sp_document_done( doc , SP_VERB_NONE,
1409 _("Drop bitmap image"));
1410 break;
1411 }
1412 }
1413 }
1415 #include "gradient-context.h"
1417 void sp_ui_drag_motion( GtkWidget */*widget*/,
1418 GdkDragContext */*drag_context*/,
1419 gint /*x*/, gint /*y*/,
1420 GtkSelectionData */*data*/,
1421 guint /*info*/,
1422 guint /*event_time*/,
1423 gpointer /*user_data*/)
1424 {
1425 // SPDocument *doc = SP_ACTIVE_DOCUMENT;
1426 // SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1429 // g_message("drag-n-drop motion (%4d, %4d) at %d", x, y, event_time);
1430 }
1432 static void sp_ui_drag_leave( GtkWidget */*widget*/,
1433 GdkDragContext */*drag_context*/,
1434 guint /*event_time*/,
1435 gpointer /*user_data*/ )
1436 {
1437 // g_message("drag-n-drop leave at %d", event_time);
1438 }
1440 static void
1441 sp_ui_import_files(gchar *buffer)
1442 {
1443 GList *list = gnome_uri_list_extract_filenames(buffer);
1444 if (!list)
1445 return;
1446 g_list_foreach(list, sp_ui_import_one_file_with_check, NULL);
1447 g_list_foreach(list, (GFunc) g_free, NULL);
1448 g_list_free(list);
1449 }
1451 static void
1452 sp_ui_import_one_file_with_check(gpointer filename, gpointer /*unused*/)
1453 {
1454 if (filename) {
1455 if (strlen((char const *)filename) > 2)
1456 sp_ui_import_one_file((char const *)filename);
1457 }
1458 }
1460 static void
1461 sp_ui_import_one_file(char const *filename)
1462 {
1463 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1464 if (!doc) return;
1466 if (filename == NULL) return;
1468 // Pass off to common implementation
1469 // TODO might need to get the proper type of Inkscape::Extension::Extension
1470 file_import( doc, filename, NULL );
1471 }
1473 void
1474 sp_ui_error_dialog(gchar const *message)
1475 {
1476 GtkWidget *dlg;
1477 gchar *safeMsg = Inkscape::IO::sanitizeString(message);
1479 dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
1480 GTK_BUTTONS_CLOSE, "%s", safeMsg);
1481 sp_transientize(dlg);
1482 gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE);
1483 gtk_dialog_run(GTK_DIALOG(dlg));
1484 gtk_widget_destroy(dlg);
1485 g_free(safeMsg);
1486 }
1488 bool
1489 sp_ui_overwrite_file(gchar const *filename)
1490 {
1491 bool return_value = FALSE;
1493 if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) {
1494 Gtk::Window *window = SP_ACTIVE_DESKTOP->getToplevel();
1495 gchar* baseName = g_path_get_basename( filename );
1496 gchar* dirName = g_path_get_dirname( filename );
1497 GtkWidget* dialog = gtk_message_dialog_new_with_markup( window->gobj(),
1498 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
1499 GTK_MESSAGE_QUESTION,
1500 GTK_BUTTONS_NONE,
1501 _( "<span weight=\"bold\" size=\"larger\">A file named \"%s\" already exists. Do you want to replace it?</span>\n\n"
1502 "The file already exists in \"%s\". Replacing it will overwrite its contents." ),
1503 baseName,
1504 dirName
1505 );
1506 gtk_dialog_add_buttons( GTK_DIALOG(dialog),
1507 GTK_STOCK_CANCEL, GTK_RESPONSE_NO,
1508 _("Replace"), GTK_RESPONSE_YES,
1509 NULL );
1510 gtk_dialog_set_default_response( GTK_DIALOG(dialog), GTK_RESPONSE_YES );
1512 if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_YES ) {
1513 return_value = TRUE;
1514 } else {
1515 return_value = FALSE;
1516 }
1517 gtk_widget_destroy(dialog);
1518 g_free( baseName );
1519 g_free( dirName );
1520 } else {
1521 return_value = TRUE;
1522 }
1524 return return_value;
1525 }
1527 static void
1528 sp_ui_menu_item_set_sensitive(SPAction */*action*/, unsigned int sensitive, void *data)
1529 {
1530 return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
1531 }
1533 static void
1534 sp_ui_menu_item_set_name(SPAction */*action*/, Glib::ustring name, void *data)
1535 {
1536 void *child = GTK_BIN (data)->child;
1537 //child is either
1538 //- a GtkHBox, whose first child is a label displaying name if the menu
1539 //item has an accel key
1540 //- a GtkLabel if the menu has no accel key
1541 if (GTK_IS_LABEL(child)) {
1542 gtk_label_set_markup_with_mnemonic(GTK_LABEL (child), name.c_str());
1543 } else if (GTK_IS_HBOX(child)) {
1544 gtk_label_set_markup_with_mnemonic(
1545 GTK_LABEL (gtk_container_get_children(GTK_CONTAINER (child))->data),
1546 name.c_str());
1547 }//else sp_ui_menu_append_item_from_verb has been modified and can set
1548 //a menu item in yet another way...
1549 }
1552 /*
1553 Local Variables:
1554 mode:c++
1555 c-file-style:"stroustrup"
1556 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1557 indent-tabs-mode:nil
1558 fill-column:99
1559 End:
1560 */
1561 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :