1 #define __SP_INTERFACE_C__
3 /**
4 * 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 "inkscape-private.h"
24 #include "extension/effect.h"
25 #include "widgets/icon.h"
26 #include "prefs-utils.h"
27 #include "path-prefix.h"
29 #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 "object-ui.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-namedview.h"
44 #include "helper/action.h"
45 #include "helper/gnome-utils.h"
46 #include "helper/window.h"
48 #include "io/sys.h"
49 #include "io/stringstream.h"
50 #include "io/base64stream.h"
52 #include "dialogs/dialog-events.h"
54 #include "message-context.h"
57 using Inkscape::IO::StringOutputStream;
58 using Inkscape::IO::Base64OutputStream;
60 /* forward declaration */
61 static gint sp_ui_delete(GtkWidget *widget, GdkEvent *event, Inkscape::UI::View::View *view);
63 /* Drag and Drop */
64 typedef enum {
65 URI_LIST,
66 SVG_XML_DATA,
67 SVG_DATA,
68 PNG_DATA,
69 JPEG_DATA,
70 IMAGE_DATA
71 } ui_drop_target_info;
73 static GtkTargetEntry ui_drop_target_entries [] = {
74 {"text/uri-list", 0, URI_LIST},
75 {"image/svg+xml", 0, SVG_XML_DATA},
76 {"image/svg", 0, SVG_DATA},
77 {"image/png", 0, PNG_DATA},
78 {"image/jpeg", 0, JPEG_DATA},
79 };
81 static GtkTargetEntry *completeDropTargets = 0;
82 static int completeDropTargetsCount = 0;
84 #define ENTRIES_SIZE(n) sizeof(n)/sizeof(n[0])
85 static guint nui_drop_target_entries = ENTRIES_SIZE(ui_drop_target_entries);
86 static void sp_ui_import_files(gchar *buffer);
87 static void sp_ui_import_one_file(char const *filename);
88 static void sp_ui_import_one_file_with_check(gpointer filename, gpointer unused);
89 static void sp_ui_drag_data_received(GtkWidget *widget,
90 GdkDragContext *drag_context,
91 gint x, gint y,
92 GtkSelectionData *data,
93 guint info,
94 guint event_time,
95 gpointer user_data);
96 static void sp_ui_menu_item_set_sensitive(SPAction *action,
97 unsigned int sensitive,
98 void *data);
100 SPActionEventVector menu_item_event_vector = {
101 {NULL},
102 NULL,
103 NULL, /* set_active */
104 sp_ui_menu_item_set_sensitive, /* set_sensitive */
105 NULL /* set_shortcut */
106 };
108 void
109 sp_create_window(SPViewWidget *vw, gboolean editable)
110 {
111 GtkWidget *w, *hb;
113 g_return_if_fail(vw != NULL);
114 g_return_if_fail(SP_IS_VIEW_WIDGET(vw));
116 w = sp_window_new("", TRUE);
118 if (editable) {
119 g_object_set_data(G_OBJECT(vw), "window", w);
120 reinterpret_cast<SPDesktopWidget*>(vw)->window =
121 static_cast<GtkWindow*>((void*)w);
122 }
124 hb = gtk_hbox_new(FALSE, 0);
125 gtk_widget_show(hb);
126 gtk_container_add(GTK_CONTAINER(w), hb);
127 g_object_set_data(G_OBJECT(w), "hbox", hb);
129 /* fixme: */
130 if (editable) {
131 gtk_window_set_default_size((GtkWindow *) w, 640, 480);
132 g_object_set_data(G_OBJECT(w), "desktop", SP_DESKTOP_WIDGET(vw)->desktop);
133 g_object_set_data(G_OBJECT(w), "desktopwidget", vw);
134 g_signal_connect(G_OBJECT(w), "delete_event", G_CALLBACK(sp_ui_delete), vw->view);
135 g_signal_connect(G_OBJECT(w), "focus_in_event", G_CALLBACK(sp_desktop_widget_set_focus), vw);
136 } else {
137 gtk_window_set_policy(GTK_WINDOW(w), TRUE, TRUE, TRUE);
138 }
140 gtk_box_pack_end(GTK_BOX(hb), GTK_WIDGET(vw), TRUE, TRUE, 0);
141 gtk_widget_show(GTK_WIDGET(vw));
144 if ( completeDropTargets == 0 || completeDropTargetsCount == 0 )
145 {
146 std::vector<gchar*> types;
148 GSList *list = gdk_pixbuf_get_formats();
149 while ( list ) {
150 int i = 0;
151 GdkPixbufFormat *one = (GdkPixbufFormat*)list->data;
152 gchar** typesXX = gdk_pixbuf_format_get_mime_types(one);
153 for ( i = 0; typesXX[i]; i++ ) {
154 types.push_back(g_strdup(typesXX[i]));
155 }
156 g_strfreev(typesXX);
158 list = g_slist_next(list);
159 }
160 completeDropTargetsCount = nui_drop_target_entries + types.size();
161 completeDropTargets = new GtkTargetEntry[completeDropTargetsCount];
162 for ( int i = 0; i < (int)nui_drop_target_entries; i++ ) {
163 completeDropTargets[i] = ui_drop_target_entries[i];
164 }
165 int pos = nui_drop_target_entries;
167 for (std::vector<gchar*>::iterator it = types.begin() ; it != types.end() ; it++) {
168 completeDropTargets[pos].target = *it;
169 completeDropTargets[pos].flags = 0;
170 completeDropTargets[pos].info = IMAGE_DATA;
171 pos++;
172 }
173 }
175 gtk_drag_dest_set(w,
176 GTK_DEST_DEFAULT_ALL,
177 completeDropTargets,
178 completeDropTargetsCount,
179 GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE));
180 g_signal_connect(G_OBJECT(w),
181 "drag_data_received",
182 G_CALLBACK(sp_ui_drag_data_received),
183 NULL);
184 gtk_widget_show(w);
186 // needed because the first ACTIVATE_DESKTOP was sent when there was no window yet
187 inkscape_reactivate_desktop(SP_DESKTOP_WIDGET(vw)->desktop);
188 }
190 void
191 sp_ui_new_view()
192 {
193 SPDocument *document;
194 SPViewWidget *dtw;
196 document = SP_ACTIVE_DOCUMENT;
197 if (!document) return;
199 dtw = sp_desktop_widget_new(sp_document_namedview(document, NULL));
200 g_return_if_fail(dtw != NULL);
202 sp_create_window(dtw, TRUE);
203 sp_namedview_window_from_document(static_cast<SPDesktop*>(dtw->view));
204 }
206 /* TODO: not yet working */
207 /* To be re-enabled (by adding to menu) once it works. */
208 void
209 sp_ui_new_view_preview()
210 {
211 SPDocument *document;
212 SPViewWidget *dtw;
214 document = SP_ACTIVE_DOCUMENT;
215 if (!document) return;
217 dtw = (SPViewWidget *) sp_svg_view_widget_new(document);
218 g_return_if_fail(dtw != NULL);
219 sp_svg_view_widget_set_resize(SP_SVG_VIEW_WIDGET(dtw), TRUE, 400.0, 400.0);
221 sp_create_window(dtw, FALSE);
222 }
224 /**
225 * \param widget unused
226 */
227 void
228 sp_ui_close_view(GtkWidget *widget)
229 {
230 if (SP_ACTIVE_DESKTOP == NULL) {
231 return;
232 }
233 if ((SP_ACTIVE_DESKTOP)->shutdown()) {
234 return;
235 }
236 SP_ACTIVE_DESKTOP->destroyWidget();
237 }
240 /**
241 * sp_ui_close_all
242 *
243 * This function is called to exit the program, and iterates through all
244 * open document view windows, attempting to close each in turn. If the
245 * view has unsaved information, the user will be prompted to save,
246 * discard, or cancel.
247 *
248 * Returns FALSE if the user cancels the close_all operation, TRUE
249 * otherwise.
250 */
251 unsigned int
252 sp_ui_close_all(void)
253 {
254 /* Iterate through all the windows, destroying each in the order they
255 become active */
256 while (SP_ACTIVE_DESKTOP) {
257 if ((SP_ACTIVE_DESKTOP)->shutdown()) {
258 /* The user cancelled the operation, so end doing the close */
259 return FALSE;
260 }
261 SP_ACTIVE_DESKTOP->destroyWidget();
262 }
264 return TRUE;
265 }
267 static gint
268 sp_ui_delete(GtkWidget *widget, GdkEvent *event, Inkscape::UI::View::View *view)
269 {
270 return view->shutdown();
271 }
273 /*
274 * Some day when the right-click menus are ready to start working
275 * smarter with the verbs, we'll need to change this NULL being
276 * sent to sp_action_perform to something useful, or set some kind
277 * of global "right-clicked position" variable for actions to
278 * investigate when they're called.
279 */
280 static void
281 sp_ui_menu_activate(void *object, SPAction *action)
282 {
283 sp_action_perform(action, NULL);
284 }
286 static void
287 sp_ui_menu_select_action(void *object, SPAction *action)
288 {
289 action->view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, action->tip);
290 }
292 static void
293 sp_ui_menu_deselect_action(void *object, SPAction *action)
294 {
295 action->view->tipsMessageContext()->clear();
296 }
298 static void
299 sp_ui_menu_select(gpointer object, gpointer tip)
300 {
301 Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view"));
302 view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, (gchar *)tip);
303 }
305 static void
306 sp_ui_menu_deselect(gpointer object)
307 {
308 Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*> (g_object_get_data(G_OBJECT(object), "view"));
309 view->tipsMessageContext()->clear();
310 }
312 /**
313 * sp_ui_menuitem_add_icon
314 *
315 * Creates and attaches a scaled icon to the given menu item.
316 *
317 */
318 void
319 sp_ui_menuitem_add_icon( GtkWidget *item, gchar *icon_name )
320 {
321 GtkWidget *icon;
323 icon = sp_icon_new( GTK_ICON_SIZE_MENU, icon_name );
324 gtk_widget_show(icon);
325 gtk_image_menu_item_set_image((GtkImageMenuItem *) item, icon);
326 } // end of sp_ui_menu_add_icon
328 /**
329 * sp_ui_menu_append_item
330 *
331 * Appends a UI item with specific info for Inkscape/Sodipodi.
332 *
333 */
334 static GtkWidget *
335 sp_ui_menu_append_item( GtkMenu *menu, gchar const *stock,
336 gchar const *label, gchar const *tip, Inkscape::UI::View::View *view, GCallback callback,
337 gpointer data, gboolean with_mnemonic = TRUE )
338 {
339 GtkWidget *item;
341 if (stock) {
342 item = gtk_image_menu_item_new_from_stock(stock, NULL);
343 } else if (label) {
344 item = (with_mnemonic)
345 ? gtk_image_menu_item_new_with_mnemonic(label) :
346 gtk_image_menu_item_new_with_label(label);
347 } else {
348 item = gtk_separator_menu_item_new();
349 }
351 gtk_widget_show(item);
353 if (callback) {
354 g_signal_connect(G_OBJECT(item), "activate", callback, data);
355 }
357 if (tip && view) {
358 g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
359 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) tip );
360 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
361 }
363 gtk_menu_append(GTK_MENU(menu), item);
365 return item;
367 } // end of sp_ui_menu_append_item()
369 /**
370 \brief a wrapper around gdk_keyval_name producing (when possible) characters, not names
371 */
372 static gchar const *
373 sp_key_name(guint keyval)
374 {
375 /* TODO: Compare with the definition of gtk_accel_label_refetch in gtk/gtkaccellabel.c (or
376 simply use GtkAccelLabel as the TODO comment in sp_ui_shortcut_string suggests). */
377 gchar const *n = gdk_keyval_name(gdk_keyval_to_upper(keyval));
379 if (!strcmp(n, "asciicircum")) return "^";
380 else if (!strcmp(n, "parenleft" )) return "(";
381 else if (!strcmp(n, "parenright" )) return ")";
382 else if (!strcmp(n, "plus" )) return "+";
383 else if (!strcmp(n, "minus" )) return "-";
384 else if (!strcmp(n, "asterisk" )) return "*";
385 else if (!strcmp(n, "KP_Multiply")) return "*";
386 else if (!strcmp(n, "Delete" )) return "Del";
387 else if (!strcmp(n, "Page_Up" )) return "PgUp";
388 else if (!strcmp(n, "Page_Down" )) return "PgDn";
389 else if (!strcmp(n, "grave" )) return "`";
390 else if (!strcmp(n, "numbersign" )) return "#";
391 else if (!strcmp(n, "bar" )) return "|";
392 else if (!strcmp(n, "slash" )) return "/";
393 else if (!strcmp(n, "exclam" )) return "!";
394 else return n;
395 }
398 /**
399 * \param shortcut A GDK keyval OR'd with SP_SHORTCUT_blah_MASK values.
400 * \param c Points to a buffer at least 256 bytes long.
401 */
402 void
403 sp_ui_shortcut_string(unsigned const shortcut, gchar *const c)
404 {
405 /* TODO: This function shouldn't exist. Our callers should use GtkAccelLabel instead of
406 * a generic GtkLabel containing this string, and should call gtk_widget_add_accelerator.
407 * Will probably need to change sp_shortcut_invoke callers.
408 *
409 * The existing gtk_label_new_with_mnemonic call can be replaced with
410 * g_object_new(GTK_TYPE_ACCEL_LABEL, NULL) followed by
411 * gtk_label_set_text_with_mnemonic(lbl, str).
412 */
413 static GtkAccelLabelClass const &accel_lbl_cls
414 = *(GtkAccelLabelClass const *) g_type_class_peek_static(GTK_TYPE_ACCEL_LABEL);
416 struct { unsigned test; char const *name; } const modifier_tbl[] = {
417 { SP_SHORTCUT_SHIFT_MASK, accel_lbl_cls.mod_name_shift },
418 { SP_SHORTCUT_CONTROL_MASK, accel_lbl_cls.mod_name_control },
419 { SP_SHORTCUT_ALT_MASK, accel_lbl_cls.mod_name_alt }
420 };
422 gchar *p = c;
423 gchar *end = p + 256;
425 for (unsigned i = 0; i < G_N_ELEMENTS(modifier_tbl); ++i) {
426 if ((shortcut & modifier_tbl[i].test)
427 && (p < end))
428 {
429 p += g_snprintf(p, end - p, "%s%s",
430 modifier_tbl[i].name,
431 accel_lbl_cls.mod_separator);
432 }
433 }
434 if (p < end) {
435 p += g_snprintf(p, end - p, "%s", sp_key_name(shortcut & 0xffffff));
436 }
437 end[-1] = '\0'; // snprintf doesn't guarantee to nul-terminate the string.
438 }
440 void
441 sp_ui_dialog_title_string(Inkscape::Verb *verb, gchar *c)
442 {
443 SPAction *action;
444 unsigned int shortcut;
445 gchar *s;
446 gchar key[256];
447 gchar *atitle;
449 action = verb->get_action(NULL);
450 if (!action)
451 return;
453 atitle = sp_action_get_title(action);
455 s = g_stpcpy(c, atitle);
457 g_free(atitle);
459 shortcut = sp_shortcut_get_primary(verb);
460 if (shortcut) {
461 s = g_stpcpy(s, " (");
462 sp_ui_shortcut_string(shortcut, key);
463 s = g_stpcpy(s, key);
464 s = g_stpcpy(s, ")");
465 }
466 }
469 /**
470 * sp_ui_menu_append_item_from_verb
471 *
472 * Appends a custom menu UI from a verb.
473 *
474 */
476 static GtkWidget *
477 sp_ui_menu_append_item_from_verb(GtkMenu *menu, Inkscape::Verb *verb, Inkscape::UI::View::View *view, bool radio = false, GSList *group = NULL)
478 {
479 SPAction *action;
480 GtkWidget *item;
482 if (verb->get_code() == SP_VERB_NONE) {
484 item = gtk_separator_menu_item_new();
486 } else {
487 unsigned int shortcut;
489 action = verb->get_action(view);
491 if (!action) return NULL;
493 shortcut = sp_shortcut_get_primary(verb);
494 if (shortcut) {
495 gchar c[256];
496 sp_ui_shortcut_string(shortcut, c);
497 GtkWidget *const hb = gtk_hbox_new(FALSE, 16);
498 GtkWidget *const name_lbl = gtk_label_new("");
499 gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
500 gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
501 gtk_box_pack_start((GtkBox *) hb, name_lbl, TRUE, TRUE, 0);
502 GtkWidget *const accel_lbl = gtk_label_new(c);
503 gtk_misc_set_alignment((GtkMisc *) accel_lbl, 1.0, 0.5);
504 gtk_box_pack_end((GtkBox *) hb, accel_lbl, FALSE, FALSE, 0);
505 gtk_widget_show_all(hb);
506 if (radio) {
507 item = gtk_radio_menu_item_new (group);
508 } else {
509 item = gtk_image_menu_item_new();
510 }
511 gtk_container_add((GtkContainer *) item, hb);
512 } else {
513 if (radio) {
514 item = gtk_radio_menu_item_new (group);
515 } else {
516 item = gtk_image_menu_item_new ();
517 }
518 GtkWidget *const name_lbl = gtk_label_new("");
519 gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name);
520 gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5);
521 gtk_container_add((GtkContainer *) item, name_lbl);
522 }
524 nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
525 if (!action->sensitive) {
526 gtk_widget_set_sensitive(item, FALSE);
527 }
529 if (action->image) {
530 sp_ui_menuitem_add_icon(item, action->image);
531 }
532 gtk_widget_set_events(item, GDK_KEY_PRESS_MASK);
533 g_signal_connect( G_OBJECT(item), "activate",
534 G_CALLBACK(sp_ui_menu_activate), action );
536 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select_action), action );
537 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect_action), action );
538 }
540 gtk_widget_show(item);
541 gtk_menu_append(GTK_MENU(menu), item);
543 return item;
545 } // end of sp_ui_menu_append_item_from_verb
548 static void
549 checkitem_toggled(GtkCheckMenuItem *menuitem, gpointer user_data)
550 {
551 gchar const *pref = (gchar const *) user_data;
552 Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
554 gchar const *pref_path;
555 if (reinterpret_cast<SPDesktop*>(view)->is_fullscreen)
556 pref_path = g_strconcat("fullscreen.", pref, NULL);
557 else
558 pref_path = g_strconcat("window.", pref, NULL);
560 gboolean checked = gtk_check_menu_item_get_active(menuitem);
561 prefs_set_int_attribute(pref_path, "state", checked);
563 reinterpret_cast<SPDesktop*>(view)->layoutWidget();
564 }
566 static gboolean
567 checkitem_update(GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
568 {
569 GtkCheckMenuItem *menuitem=GTK_CHECK_MENU_ITEM(widget);
571 gchar const *pref = (gchar const *) user_data;
572 Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view");
574 gchar const *pref_path;
575 if (static_cast<SPDesktop*>(view)->is_fullscreen)
576 pref_path = g_strconcat("fullscreen.", pref, NULL);
577 else
578 pref_path = g_strconcat("window.", pref, NULL);
580 gint ison = prefs_get_int_attribute_limited(pref_path, "state", 1, 0, 1);
582 g_signal_handlers_block_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
583 gtk_check_menu_item_set_active(menuitem, ison);
584 g_signal_handlers_unblock_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data);
586 return FALSE;
587 }
590 void
591 sp_ui_menu_append_check_item_from_verb(GtkMenu *menu, Inkscape::UI::View::View *view, gchar const *label, gchar const *tip, gchar const *pref,
592 void (*callback_toggle)(GtkCheckMenuItem *, gpointer user_data),
593 gboolean (*callback_update)(GtkWidget *widget, GdkEventExpose *event, gpointer user_data),
594 Inkscape::Verb *verb)
595 {
596 GtkWidget *item;
598 unsigned int shortcut = 0;
599 SPAction *action = NULL;
601 if (verb) {
602 shortcut = sp_shortcut_get_primary(verb);
603 action = verb->get_action(view);
604 }
606 if (verb && shortcut) {
607 gchar c[256];
608 sp_ui_shortcut_string(shortcut, c);
610 GtkWidget *hb = gtk_hbox_new(FALSE, 16);
612 {
613 GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
614 gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
615 gtk_box_pack_start((GtkBox *) hb, l, TRUE, TRUE, 0);
616 }
618 {
619 GtkWidget *l = gtk_label_new(c);
620 gtk_misc_set_alignment((GtkMisc *) l, 1.0, 0.5);
621 gtk_box_pack_end((GtkBox *) hb, l, FALSE, FALSE, 0);
622 }
624 gtk_widget_show_all(hb);
626 item = gtk_check_menu_item_new();
627 gtk_container_add((GtkContainer *) item, hb);
628 } else {
629 GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label);
630 gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5);
631 item = gtk_check_menu_item_new();
632 gtk_container_add((GtkContainer *) item, l);
633 }
634 #if 0
635 nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item);
636 if (!action->sensitive) {
637 gtk_widget_set_sensitive(item, FALSE);
638 }
639 #endif
640 gtk_widget_show(item);
642 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
644 g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
646 g_signal_connect( G_OBJECT(item), "toggled", (GCallback) callback_toggle, (void *) pref);
647 g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) callback_update, (void *) pref);
649 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) (action ? action->tip : tip));
650 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
651 }
653 static void
654 sp_recent_open(GtkWidget *widget, gchar const *uri)
655 {
656 sp_file_open(uri, NULL);
657 }
659 static void
660 sp_file_new_from_template(GtkWidget *widget, gchar const *uri)
661 {
662 sp_file_new(uri);
663 }
665 void
666 sp_menu_append_new_templates(GtkWidget *menu, Inkscape::UI::View::View *view)
667 {
668 std::list<gchar *> sources;
669 sources.push_back( profile_path("templates") ); // first try user's local dir
670 sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir
672 // Use this loop to iterate through a list of possible document locations.
673 while (!sources.empty()) {
674 gchar *dirname = sources.front();
676 if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) {
677 GError *err = 0;
678 GDir *dir = g_dir_open(dirname, 0, &err);
680 if (dir) {
681 for (gchar const *file = g_dir_read_name(dir); file != NULL; file = g_dir_read_name(dir)) {
682 if (!g_str_has_suffix(file, ".svg"))
683 continue; // skip non-svg files
685 gchar *basename = g_path_get_basename(file);
686 if (g_str_has_suffix(basename, ".svg") && g_str_has_prefix(basename, "default."))
687 continue; // skip default.*.svg (i.e. default.svg and translations) - it's in the menu already
689 gchar const *filepath = g_build_filename(dirname, file, NULL);
690 gchar *dupfile = g_strndup(file, strlen(file) - 4);
691 gchar *filename = g_filename_to_utf8(dupfile, -1, NULL, NULL, NULL);
692 g_free(dupfile);
693 GtkWidget *item = gtk_menu_item_new_with_label(filename);
694 g_free(filename);
696 gtk_widget_show(item);
697 // how does "filepath" ever get freed?
698 g_signal_connect(G_OBJECT(item),
699 "activate",
700 G_CALLBACK(sp_file_new_from_template),
701 (gpointer) filepath);
703 if (view) {
704 // set null tip for now; later use a description from the template file
705 g_object_set_data(G_OBJECT(item), "view", (gpointer) view);
706 g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) NULL );
707 g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL);
708 }
710 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
711 }
712 g_dir_close(dir);
713 }
714 }
716 // toss the dirname
717 g_free(dirname);
718 sources.pop_front();
719 }
720 }
722 void
723 sp_menu_append_recent_documents(GtkWidget *menu, Inkscape::UI::View::View* /* view */)
724 {
725 gchar const **recent = prefs_get_recent_files();
726 if (recent) {
727 int i;
729 for (i = 0; recent[i] != NULL; i += 2) {
730 gchar const *uri = recent[i];
731 gchar const *name = recent[i + 1];
733 GtkWidget *item = gtk_menu_item_new_with_label(name);
734 gtk_widget_show(item);
735 g_signal_connect(G_OBJECT(item),
736 "activate",
737 G_CALLBACK(sp_recent_open),
738 (gpointer)uri);
739 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
740 }
742 g_free(recent);
743 } else {
744 GtkWidget *item = gtk_menu_item_new_with_label(_("None"));
745 gtk_widget_show(item);
746 gtk_widget_set_sensitive(item, FALSE);
747 gtk_menu_shell_append(GTK_MENU_SHELL(menu), item);
748 }
749 }
751 void
752 sp_ui_checkboxes_menus(GtkMenu *m, Inkscape::UI::View::View *view)
753 {
754 //sp_ui_menu_append_check_item_from_verb(m, view, _("_Menu"), _("Show or hide the menu bar"), "menu",
755 // checkitem_toggled, checkitem_update, 0);
756 sp_ui_menu_append_check_item_from_verb(m, view, _("Commands Bar"), _("Show or hide the Commands bar (under the menu)"), "commands",
757 checkitem_toggled, checkitem_update, 0);
758 sp_ui_menu_append_check_item_from_verb(m, view, _("Tool Controls"), _("Show or hide the Tool Controls panel"), "toppanel",
759 checkitem_toggled, checkitem_update, 0);
760 sp_ui_menu_append_check_item_from_verb(m, view, _("_Toolbox"), _("Show or hide the main toolbox (on the left)"), "toolbox",
761 checkitem_toggled, checkitem_update, 0);
762 sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "rulers",
763 checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_RULERS));
764 sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "scrollbars",
765 checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_SCROLLBARS));
766 sp_ui_menu_append_check_item_from_verb(m, view, _("_Statusbar"), _("Show or hide the statusbar (at the bottom of the window)"), "statusbar",
767 checkitem_toggled, checkitem_update, 0);
768 sp_ui_menu_append_check_item_from_verb(m, view, _("_Panels"), _("Show or hide the panels"), "panels",
769 checkitem_toggled, checkitem_update, 0);
770 }
772 /** \brief This function turns XML into a menu
773 \param menus This is the XML that defines the menu
774 \param menu Menu to be added to
775 \param view The View that this menu is being built for
777 This function is realitively simple as it just goes through the XML
778 and parses the individual elements. In the case of a submenu, it
779 just calls itself recursively. Because it is only reasonable to have
780 a couple of submenus, it is unlikely this will go more than two or
781 three times.
783 In the case of an unreconginzed verb, a menu item is made to identify
784 the verb that is missing, and display that. The menu item is also made
785 insensitive.
786 */
787 void
788 sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI::View::View *view)
789 {
790 if (menus == NULL) return;
791 if (menu == NULL) return;
792 GSList *group = NULL;
794 for (Inkscape::XML::Node *menu_pntr = menus;
795 menu_pntr != NULL;
796 menu_pntr = menu_pntr->next()) {
797 if (!strcmp(menu_pntr->name(), "submenu")) {
798 if (!strcmp(menu_pntr->attribute("name"), "Effects") && !prefs_get_int_attribute("extensions", "show-effects-menu", 0)) {
799 continue;
800 }
801 GtkWidget *mitem = gtk_menu_item_new_with_mnemonic(_(menu_pntr->attribute("name")));
802 GtkWidget *submenu = gtk_menu_new();
803 sp_ui_build_dyn_menus(menu_pntr->firstChild(), submenu, view);
804 gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), GTK_WIDGET(submenu));
805 gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem);
806 continue;
807 }
808 if (!strcmp(menu_pntr->name(), "verb")) {
809 gchar const *verb_name = menu_pntr->attribute("verb-id");
810 Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name);
812 if (verb != NULL) {
813 if (menu_pntr->attribute("radio") != NULL) {
814 GtkWidget *item = sp_ui_menu_append_item_from_verb (GTK_MENU(menu), verb, view, true, group);
815 group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item));
816 if (menu_pntr->attribute("default") != NULL) {
817 gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE);
818 }
819 } else {
820 sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view);
821 group = NULL;
822 }
823 } else {
824 gchar string[120];
825 g_snprintf(string, 120, _("Verb \"%s\" Unknown"), verb_name);
826 string[119] = '\0'; /* may not be terminated */
827 GtkWidget *item = gtk_menu_item_new_with_label(string);
828 gtk_widget_set_sensitive(item, false);
829 gtk_widget_show(item);
830 gtk_menu_append(GTK_MENU(menu), item);
831 }
832 continue;
833 }
834 if (!strcmp(menu_pntr->name(), "separator")
835 // This was spelt wrong in the original version
836 // and so this is for backward compatibility. It can
837 // probably be dropped after the 0.44 release.
838 || !strcmp(menu_pntr->name(), "seperator")) {
839 GtkWidget *item = gtk_separator_menu_item_new();
840 gtk_widget_show(item);
841 gtk_menu_append(GTK_MENU(menu), item);
842 continue;
843 }
844 if (!strcmp(menu_pntr->name(), "template-list")) {
845 sp_menu_append_new_templates(menu, view);
846 continue;
847 }
848 if (!strcmp(menu_pntr->name(), "recent-file-list")) {
849 sp_menu_append_recent_documents(menu, view);
850 continue;
851 }
852 if (!strcmp(menu_pntr->name(), "objects-checkboxes")) {
853 sp_ui_checkboxes_menus(GTK_MENU(menu), view);
854 continue;
855 }
856 }
857 }
859 /** \brief Build the main tool bar
860 \param view View to build the bar for
862 Currently the main tool bar is built as a dynamic XML menu using
863 \c sp_ui_build_dyn_menus. This function builds the bar, and then
864 pass it to get items attached to it.
865 */
866 GtkWidget *
867 sp_ui_main_menubar(Inkscape::UI::View::View *view)
868 {
869 GtkWidget *mbar = gtk_menu_bar_new();
871 sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view);
873 return mbar;
874 }
876 static void leave_group(GtkMenuItem *, SPDesktop *desktop) {
877 desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer()));
878 }
880 static void enter_group(GtkMenuItem *mi, SPDesktop *desktop) {
881 desktop->setCurrentLayer(reinterpret_cast<SPObject *>(g_object_get_data(G_OBJECT(mi), "group")));
882 SP_DT_SELECTION(desktop)->clear();
883 }
885 GtkWidget *
886 sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item)
887 {
888 GtkWidget *m;
889 SPDesktop *dt;
891 dt = static_cast<SPDesktop*>(view);
893 m = gtk_menu_new();
895 /* Undo and Redo */
896 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_UNDO), view);
897 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_REDO), view);
899 /* Separator */
900 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
902 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_CUT), view);
903 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_COPY), view);
904 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_PASTE), view);
906 /* Separator */
907 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
909 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DUPLICATE), view);
910 sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DELETE), view);
912 /* Item menu */
913 if (item) {
914 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
915 sp_object_menu((SPObject *) item, dt, GTK_MENU(m));
916 }
918 /* layer menu */
919 SPGroup *group=NULL;
920 if (item) {
921 if (SP_IS_GROUP(item)) {
922 group = SP_GROUP(item);
923 } else if ( item != dt->currentRoot() && SP_IS_GROUP(SP_OBJECT_PARENT(item)) ) {
924 group = SP_GROUP(SP_OBJECT_PARENT(item));
925 }
926 }
928 if (( group && group != dt->currentLayer() ) ||
929 ( dt->currentLayer() != dt->currentRoot() ) ) {
930 sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL);
931 }
933 if ( group && group != dt->currentLayer() ) {
934 /* TRANSLATORS: #%s is the id of the group e.g. <g id="#g7">, not a number. */
935 gchar *label=g_strdup_printf(_("Enter group #%s"), SP_OBJECT_ID(group));
936 GtkWidget *w = gtk_menu_item_new_with_label(label);
937 g_free(label);
938 g_object_set_data(G_OBJECT(w), "group", group);
939 g_signal_connect(G_OBJECT(w), "activate", GCallback(enter_group), dt);
940 gtk_widget_show(w);
941 gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
942 }
944 if ( dt->currentLayer() != dt->currentRoot() ) {
945 if ( SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) {
946 GtkWidget *w = gtk_menu_item_new_with_label(_("Go to parent"));
947 g_signal_connect(G_OBJECT(w), "activate", GCallback(leave_group), dt);
948 gtk_widget_show(w);
949 gtk_menu_shell_append(GTK_MENU_SHELL(m), w);
951 }
952 }
954 return m;
955 }
957 /* Drag and Drop */
958 void
959 sp_ui_drag_data_received(GtkWidget *widget,
960 GdkDragContext *drag_context,
961 gint x, gint y,
962 GtkSelectionData *data,
963 guint info,
964 guint event_time,
965 gpointer user_data)
966 {
967 switch (info) {
968 case SVG_DATA:
969 case SVG_XML_DATA: {
970 gchar *svgdata = (gchar *)data->data;
972 SPDocument *doc = SP_ACTIVE_DOCUMENT;
974 Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI);
976 if (rnewdoc == NULL) {
977 sp_ui_error_dialog(_("Could not parse SVG data"));
978 return;
979 }
981 Inkscape::XML::Node *repr = sp_repr_document_root(rnewdoc);
982 gchar const *style = repr->attribute("style");
984 Inkscape::XML::Node *newgroup = sp_repr_new("svg:g");
985 newgroup->setAttribute("style", style);
987 for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) {
988 Inkscape::XML::Node *newchild = child->duplicate();
989 newgroup->appendChild(newchild);
990 }
992 Inkscape::GC::release(rnewdoc);
994 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
995 // Add it to the current layer
997 // Greg's edits to add intelligent positioning of svg drops
998 SPObject *new_obj = NULL;
999 new_obj = desktop->currentLayer()->appendChildRepr(newgroup);
1001 Inkscape::Selection *selection = SP_DT_SELECTION(desktop);
1002 selection->set(SP_ITEM(new_obj));
1003 // To move the imported object, we must temporarily set the "transform pattern with
1004 // object" option.
1005 {
1006 int const saved_pref = prefs_get_int_attribute("options.transform", "pattern", 1);
1007 prefs_set_int_attribute("options.transform", "pattern", 1);
1008 sp_document_ensure_up_to_date(SP_DT_DOCUMENT(desktop));
1009 NR::Point m( desktop->point() - selection->bounds().midpoint() );
1010 sp_selection_move_relative(selection, m);
1011 prefs_set_int_attribute("options.transform", "pattern", saved_pref);
1012 }
1014 Inkscape::GC::release(newgroup);
1015 sp_document_done(doc);
1016 break;
1017 }
1019 case URI_LIST: {
1020 gchar *uri = (gchar *)data->data;
1021 sp_ui_import_files(uri);
1022 break;
1023 }
1025 case PNG_DATA:
1026 case JPEG_DATA:
1027 case IMAGE_DATA: {
1028 char tmp[1024];
1030 StringOutputStream outs;
1031 Base64OutputStream b64out(outs);
1032 b64out.setColumnWidth(0);
1034 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1036 Inkscape::XML::Node *newImage = sp_repr_new("svg:image");
1038 for ( int i = 0; i < data->length; i++ ) {
1039 b64out.put( data->data[i] );
1040 }
1041 b64out.close();
1044 Glib::ustring str = outs.getString();
1046 snprintf( tmp, sizeof(tmp), "data:%s;base64,", gdk_atom_name(data->type) );
1047 str.insert( 0, tmp );
1048 newImage->setAttribute("xlink:href", str.c_str());
1050 GError *error = NULL;
1051 GdkPixbufLoader *loader = gdk_pixbuf_loader_new_with_mime_type( gdk_atom_name(data->type), &error );
1052 if ( loader ) {
1053 error = NULL;
1054 if ( gdk_pixbuf_loader_write( loader, data->data, data->length, &error) ) {
1055 GdkPixbuf *pbuf = gdk_pixbuf_loader_get_pixbuf(loader);
1056 if ( pbuf ) {
1057 int width = gdk_pixbuf_get_width(pbuf);
1058 int height = gdk_pixbuf_get_height(pbuf);
1059 snprintf( tmp, sizeof(tmp), "%d", width );
1060 newImage->setAttribute("width", tmp);
1062 snprintf( tmp, sizeof(tmp), "%d", height );
1063 newImage->setAttribute("height", tmp);
1064 }
1065 }
1066 }
1068 SPDesktop *desktop = SP_ACTIVE_DESKTOP;
1070 // Add it to the current layer
1071 desktop->currentLayer()->appendChildRepr(newImage);
1073 Inkscape::GC::release(newImage);
1074 sp_document_done( doc );
1075 break;
1076 }
1077 }
1078 }
1080 static void
1081 sp_ui_import_files(gchar *buffer)
1082 {
1083 GList *list = gnome_uri_list_extract_filenames(buffer);
1084 if (!list)
1085 return;
1086 g_list_foreach(list, sp_ui_import_one_file_with_check, NULL);
1087 g_list_foreach(list, (GFunc) g_free, NULL);
1088 g_list_free(list);
1089 }
1091 static void
1092 sp_ui_import_one_file_with_check(gpointer filename, gpointer unused)
1093 {
1094 if (filename) {
1095 if (strlen((char const *)filename) > 2)
1096 sp_ui_import_one_file((char const *)filename);
1097 }
1098 }
1100 static void
1101 sp_ui_import_one_file(char const *filename)
1102 {
1103 SPDocument *doc = SP_ACTIVE_DOCUMENT;
1104 if (!doc) return;
1106 if (filename == NULL) return;
1108 // Pass off to common implementation
1109 // TODO might need to get the proper type of Inkscape::Extension::Extension
1110 file_import( doc, filename, NULL );
1111 }
1113 void
1114 sp_ui_error_dialog(gchar const *message)
1115 {
1116 GtkWidget *dlg;
1117 gchar *safeMsg = Inkscape::IO::sanitizeString(message);
1119 dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR,
1120 GTK_BUTTONS_CLOSE, safeMsg);
1121 sp_transientize(dlg);
1122 gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE);
1123 gtk_dialog_run(GTK_DIALOG(dlg));
1124 gtk_widget_destroy(dlg);
1125 g_free(safeMsg);
1126 }
1128 bool
1129 sp_ui_overwrite_file(gchar const *filename)
1130 {
1131 bool return_value = FALSE;
1132 GtkWidget *dialog;
1133 GtkWidget *hbox;
1134 GtkWidget *boxdata;
1135 gchar *title;
1136 gchar *text;
1138 if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) {
1140 title = g_strdup_printf(_("Overwrite %s"), filename);
1141 dialog = gtk_dialog_new_with_buttons(title,
1142 NULL,
1143 (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
1144 GTK_STOCK_NO,
1145 GTK_RESPONSE_NO,
1146 GTK_STOCK_YES,
1147 GTK_RESPONSE_YES,
1148 NULL);
1149 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_YES);
1151 sp_transientize(dialog);
1152 gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
1154 hbox = gtk_hbox_new(FALSE, 5);
1155 boxdata = gtk_image_new_from_stock(GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG);
1156 gtk_widget_show(boxdata);
1157 gtk_box_pack_start(GTK_BOX(hbox), boxdata, TRUE, TRUE, 5);
1158 text = g_strdup_printf(_("The file %s already exists. Do you want to overwrite that file with the current document?"), filename);
1159 boxdata = gtk_label_new(text);
1160 gtk_label_set_line_wrap(GTK_LABEL(boxdata), TRUE);
1161 gtk_widget_show(boxdata);
1162 gtk_box_pack_start(GTK_BOX(hbox), boxdata, FALSE, FALSE, 5);
1163 gtk_widget_show(hbox);
1164 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), hbox, TRUE, TRUE, 5);
1166 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_YES) {
1167 return_value = TRUE;
1168 } else {
1169 return_value = FALSE;
1170 }
1172 gtk_widget_destroy(dialog);
1173 g_free(title);
1174 g_free(text);
1175 } else {
1176 return_value = TRUE;
1177 }
1179 return return_value;
1180 }
1182 static void
1183 sp_ui_menu_item_set_sensitive(SPAction *action, unsigned int sensitive, void *data)
1184 {
1185 return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
1186 }
1188 /*
1189 Local Variables:
1190 mode:c++
1191 c-file-style:"stroustrup"
1192 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1193 indent-tabs-mode:nil
1194 fill-column:99
1195 End:
1196 */
1197 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :