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