Code

moving trunk for module inkscape
[inkscape.git] / src / interface.cpp
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)
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);
117     
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);
190 void
191 sp_ui_new_view()
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));
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()
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);
224 /**
225  * \param widget unused
226  */
227 void
228 sp_ui_close_view(GtkWidget *widget)
230     if (SP_ACTIVE_DESKTOP == NULL) {
231         return;
232     }
233     if ((SP_ACTIVE_DESKTOP)->shutdown()) {
234         return;
235     }
236     SP_ACTIVE_DESKTOP->destroyWidget();
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)
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;
267 static gint
268 sp_ui_delete(GtkWidget *widget, GdkEvent *event, Inkscape::UI::View::View *view)
270     return view->shutdown();
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)
283     sp_action_perform(action, NULL);
286 static void
287 sp_ui_menu_select_action(void *object, SPAction *action)
289     action->view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, action->tip);
292 static void
293 sp_ui_menu_deselect_action(void *object, SPAction *action)
295     action->view->tipsMessageContext()->clear();
298 static void
299 sp_ui_menu_select(gpointer object, gpointer tip)
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);
305 static void
306 sp_ui_menu_deselect(gpointer object)
308     Inkscape::UI::View::View *view = static_cast<Inkscape::UI::View::View*>  (g_object_get_data(G_OBJECT(object), "view"));
309     view->tipsMessageContext()->clear();
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 )
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 )
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)
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;
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)
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.
440 void
441 sp_ui_dialog_title_string(Inkscape::Verb *verb, gchar *c)
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     }
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)
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)
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();
566 static gboolean
567 checkitem_update(GtkWidget *widget, GdkEventExpose *event, gpointer user_data)
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;
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)
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);
653 static void
654 sp_recent_open(GtkWidget *widget, gchar const *uri)
656     sp_file_open(uri, NULL);
659 static void
660 sp_file_new_from_template(GtkWidget *widget, gchar const *uri)
662     sp_file_new(uri);
665 void
666 sp_menu_append_new_templates(GtkWidget *menu, Inkscape::UI::View::View *view)
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     }
722 void
723 sp_menu_append_recent_documents(GtkWidget *menu, Inkscape::UI::View::View* /* view */)
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     }
751 void
752 sp_ui_checkboxes_menus(GtkMenu *m, Inkscape::UI::View::View *view)
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);
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)
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     }
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)
869     GtkWidget *mbar = gtk_menu_bar_new();
871     sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view);
873     return mbar;
876 static void leave_group(GtkMenuItem *, SPDesktop *desktop) {
877     desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer()));
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();
885 GtkWidget *
886 sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item)
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;
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)
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
996            
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     }
1080 static void
1081 sp_ui_import_files(gchar *buffer)
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);
1091 static void
1092 sp_ui_import_one_file_with_check(gpointer filename, gpointer unused)
1094     if (filename) {
1095         if (strlen((char const *)filename) > 2)
1096             sp_ui_import_one_file((char const *)filename);
1097     }
1100 static void
1101 sp_ui_import_one_file(char const *filename)
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 );
1113 void
1114 sp_ui_error_dialog(gchar const *message)
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);
1128 bool
1129 sp_ui_overwrite_file(gchar const *filename)
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;
1182 static void
1183 sp_ui_menu_item_set_sensitive(SPAction *action, unsigned int sensitive, void *data)
1185     return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive);
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 :