diff -u inkscape-0.47.0/debian/changelog inkscape-0.47.0/debian/changelog --- inkscape-0.47.0/debian/changelog +++ inkscape-0.47.0/debian/changelog @@ -1,3 +1,10 @@ +inkscape (0.47.0-2ubuntu3) lucid; urgency=low + + * 55-paste-in-unwritable-directory.dpatch, debian/control: Allow paste + from clipboard when in unwritable working directory (LP: #211607) + + -- Alexander Valavanis Tue, 06 Apr 2010 00:29:36 +0100 + inkscape (0.47.0-2ubuntu2) lucid; urgency=low * debian/control: Replaced ttf-bitstream-vera suggestion with ttf-dejavu diff -u inkscape-0.47.0/debian/patches/00list inkscape-0.47.0/debian/patches/00list --- inkscape-0.47.0/debian/patches/00list +++ inkscape-0.47.0/debian/patches/00list @@ -3,0 +4 @@ +55-paste-in-unwritable-directory only in patch2: unchanged: --- inkscape-0.47.0.orig/debian/patches/55-paste-in-unwritable-directory.dpatch +++ inkscape-0.47.0/debian/patches/55-paste-in-unwritable-directory.dpatch @@ -0,0 +1,3452 @@ +#! /bin/sh /usr/share/dpatch/dpatch-run +## 55-paste-in-unwritable-directory.dpatch by Alexander Valavanis +## +## All lines beginning with `## DP:' are a description of the patch. +## DP: Allows paste from clipboard when in unwritable working directory +## DP: (LP: #211607) +## DP: From upstream revision 9143 by Krzysztof Kosinski + +@DPATCH@ +diff -urNad inkscape-0.47.0~/src/extension/extension.cpp inkscape-0.47.0/src/extension/extension.cpp +--- inkscape-0.47.0~/src/extension/extension.cpp 2009-11-16 17:29:19.000000000 +0000 ++++ inkscape-0.47.0/src/extension/extension.cpp 2010-04-06 00:26:58.396488845 +0100 +@@ -57,7 +57,9 @@ + not related to the module directly. If the Repr does not include + a name and an ID the module will be left in an errored state. + */ +-Extension::Extension (Inkscape::XML::Node * in_repr, Implementation::Implementation * in_imp) : _help(NULL) ++Extension::Extension (Inkscape::XML::Node * in_repr, Implementation::Implementation * in_imp) ++ : _help(NULL) ++ , _gui(true) + { + repr = in_repr; + Inkscape::GC::anchor(in_repr); +@@ -673,7 +675,7 @@ + Gtk::Widget * + Extension::autogui (SPDocument * doc, Inkscape::XML::Node * node, sigc::signal * changeSignal) + { +- if (param_visible_count() == 0) return NULL; ++ if (!_gui || param_visible_count() == 0) return NULL; + + AutoGUI * agui = Gtk::manage(new AutoGUI()); + +diff -urNad inkscape-0.47.0~/src/extension/extension.h inkscape-0.47.0/src/extension/extension.h +--- inkscape-0.47.0~/src/extension/extension.h 2009-11-16 17:29:19.000000000 +0000 ++++ inkscape-0.47.0/src/extension/extension.h 2010-04-06 00:26:58.396488845 +0100 +@@ -99,6 +99,7 @@ + state_t _state; /**< Which state the Extension is currently in */ + std::vector _deps; /**< Dependencies for this extension */ + static std::ofstream error_file; /**< This is the place where errors get reported */ ++ bool _gui; + + protected: + Inkscape::XML::Node *repr; /**< The XML description of the Extension */ +@@ -219,6 +220,7 @@ + public: + Gtk::Widget * autogui (SPDocument * doc, Inkscape::XML::Node * node, sigc::signal * changeSignal = NULL); + void paramListString (std::list & retlist); ++ void set_gui(bool s) { _gui = s; } + + /* Extension editor dialog stuff */ + public: +diff -urNad inkscape-0.47.0~/src/extension/input.h inkscape-0.47.0/src/extension/input.h +--- inkscape-0.47.0~/src/extension/input.h 2009-11-16 17:29:19.000000000 +0000 ++++ inkscape-0.47.0/src/extension/input.h 2010-04-06 00:26:58.406488685 +0100 +@@ -11,6 +11,7 @@ + #ifndef INKSCAPE_EXTENSION_INPUT_H__ + #define INKSCAPE_EXTENSION_INPUT_H__ + ++#include + #include + #include "extension.h" + #include "xml/repr.h" +@@ -30,8 +31,14 @@ + gchar *output_extension; /**< Setting of what output extension should be used */ + + public: +- class open_failed {}; /**< Generic failure for an undescribed reason */ +- class no_extension_found {}; /**< Failed because we couldn't find an extension to match the filename */ ++ struct open_failed : public std::exception { ++ virtual ~open_failed() throw() {} ++ const char *what() const throw() { return "Open failed"; } ++ }; ++ struct no_extension_found : public std::exception { ++ virtual ~no_extension_found() throw() {} ++ const char *what() const throw() { return "No suitable input extension found"; } ++ }; + + Input (Inkscape::XML::Node * in_repr, + Implementation::Implementation * in_imp); +diff -urNad inkscape-0.47.0~/src/extension/internal/gdkpixbuf-input.cpp inkscape-0.47.0/src/extension/internal/gdkpixbuf-input.cpp +--- inkscape-0.47.0~/src/extension/internal/gdkpixbuf-input.cpp 2009-11-16 17:29:18.000000000 +0000 ++++ inkscape-0.47.0/src/extension/internal/gdkpixbuf-input.cpp 2010-04-06 00:26:58.406488685 +0100 +@@ -1,8 +1,10 @@ + #ifdef HAVE_CONFIG_H + # include + #endif ++#include + #include "document-private.h" + #include ++#include "extension/input.h" + #include "extension/system.h" + #include "gdkpixbuf-input.h" + #include "selection-chemistry.h" +@@ -16,69 +18,107 @@ + namespace Extension { + namespace Internal { + ++static std::set create_lossy_set() ++{ ++ std::set lossy; ++ lossy.insert(".jpg"); ++ lossy.insert(".jpeg"); ++ return lossy; ++} ++ + SPDocument * +-GdkpixbufInput::open(Inkscape::Extension::Input */*mod*/, char const *uri) ++GdkpixbufInput::open(Inkscape::Extension::Input *mod, char const *uri) + { ++ bool embed = !mod->get_param_bool("link"); + SPDocument *doc = NULL; + GdkPixbuf *pb = Inkscape::IO::pixbuf_new_from_file( uri, NULL ); ++ static std::set lossy = create_lossy_set(); + + if (pb) { /* We are readable */ ++ bool is_lossy; ++ Glib::ustring mime_type, ext; ++ Glib::ustring u = uri; ++ std::size_t dotpos = u.rfind('.'); ++ if (dotpos != Glib::ustring::npos) { ++ ext = u.substr(dotpos, Glib::ustring::npos); ++ } ++ ++ // HACK: replace with something better based on GIO ++ if (!ext.empty() && lossy.find(ext) != lossy.end()) { ++ is_lossy = true; ++ mime_type = "image/jpeg"; ++ } else { ++ is_lossy = false; ++ mime_type = "image/png"; ++ } ++ + doc = sp_document_new(NULL, TRUE, TRUE); + bool saved = sp_document_get_undo_sensitive(doc); + sp_document_set_undo_sensitive(doc, false); // no need to undo in this temporary document + +- Inkscape::XML::Node *repr = NULL; +- + double width = gdk_pixbuf_get_width(pb); + double height = gdk_pixbuf_get_height(pb); + gchar const *str = gdk_pixbuf_get_option( pb, "Inkscape::DpiX" ); +- if ( str ) +- { ++ if ( str ) { + gint dpi = atoi(str); +- if ( dpi > 0 && dpi != 72 ) +- { ++ if ( dpi > 0 && dpi != 72 ) { + double scale = 72.0 / (double)dpi; + width *= scale; + } + } + str = gdk_pixbuf_get_option( pb, "Inkscape::DpiY" ); +- if ( str ) +- { ++ if ( str ) { + gint dpi = atoi(str); +- if ( dpi > 0 && dpi != 72 ) +- { ++ if ( dpi > 0 && dpi != 72 ) { + double scale = 72.0 / (double)dpi; + height *= scale; + } + } + ++ // Create image node + Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc); +- // import as +- repr = xml_doc->createElement("svg:image"); ++ Inkscape::XML::Node *image_node = xml_doc->createElement("svg:image"); ++ sp_repr_set_svg_double(image_node, "width", width); ++ sp_repr_set_svg_double(image_node, "height", height); + +- // convert filename to uri +- gchar* _uri = g_filename_to_uri(uri, NULL, NULL); +- if(_uri) { +- repr->setAttribute("xlink:href", _uri); +- g_free(_uri); ++ if (embed) { ++ // Save pixbuf as JPEG or PNG for embedding ++ gchar *data; ++ gsize length; ++ gdk_pixbuf_save_to_buffer(pb, &data, &length, is_lossy ? "jpeg" : "png", NULL, NULL); ++ ++ // Save base64 encoded data in image node ++ // this formula taken from Glib docs ++ guint needed_size = length * 4 / 3 + length * 4 / (3 * 72) + 7; ++ needed_size += 5 + 8 + mime_type.size(); // 5 bytes for data:, 8 for ;base64, ++ ++ gchar *buffer = (gchar *) g_malloc(needed_size), *buf_work = buffer; ++ buf_work += g_sprintf(buffer, "data:%s;base64,", mime_type.data()); ++ ++ gint state = 0, save = 0; ++ gsize written = 0; ++ written += g_base64_encode_step((guchar*) data, length, TRUE, buf_work, &state, &save); ++ written += g_base64_encode_close(TRUE, buf_work + written, &state, &save); ++ buf_work[written] = 0; // null terminate ++ ++ image_node->setAttribute("xlink:href", buffer); ++ g_free(buffer); + } else { +- repr->setAttribute("xlink:href", uri); ++ // convert filename to uri ++ gchar* _uri = g_filename_to_uri(uri, NULL, NULL); ++ if(_uri) { ++ image_node->setAttribute("xlink:href", _uri); ++ g_free(_uri); ++ } else { ++ image_node->setAttribute("xlink:href", uri); ++ } + } +- /* impl: doc->base is currently NULL, so we can use uri for href whether it's absolute +- * or relative. The href will get rewritten by rebase_hrefs if by chance uri is relative +- * and doc gets saved to a different directory. +- * +- * We don't bother setting sodipodi:absref, as we assume it's never useful to have +- * sodipodi:absref with the same value as xlink:href, and rebase_hrefs will provide +- * sodipodi:absref values where necessary. */ + +- sp_repr_set_svg_double(repr, "width", width); +- sp_repr_set_svg_double(repr, "height", height); ++ g_object_unref(pb); + +- SP_DOCUMENT_ROOT(doc)->appendChildRepr(repr); +- Inkscape::GC::release(repr); +- gdk_pixbuf_unref(pb); +- //alter the canvas size to fit the image size ++ // Add it to the current layer ++ SP_DOCUMENT_ROOT(doc)->appendChildRepr(image_node); ++ Inkscape::GC::release(image_node); + fit_canvas_to_drawing(doc); + // restore undo, as now this document may be shown to the user if a bitmap was opened + sp_document_set_undo_sensitive(doc, saved); +@@ -126,6 +166,9 @@ + "\n" + "" N_("%s GDK pixbuf Input") "\n" + "org.inkscape.input.gdkpixbuf.%s\n" ++ "false" + "\n" + ".%s\n" + "%s\n" +diff -urNad inkscape-0.47.0~/src/interface.cpp inkscape-0.47.0/src/interface.cpp +--- inkscape-0.47.0~/src/interface.cpp 2009-11-16 17:29:46.000000000 +0000 ++++ inkscape-0.47.0/src/interface.cpp 2010-04-06 00:26:58.406488685 +0100 +@@ -23,7 +23,9 @@ + #include + + #include "inkscape-private.h" ++#include "extension/db.h" + #include "extension/effect.h" ++#include "extension/input.h" + #include "widgets/icon.h" + #include "preferences.h" + #include "path-prefix.h" +@@ -1449,48 +1451,26 @@ + case PNG_DATA: + case JPEG_DATA: + case IMAGE_DATA: { +- Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc); +- Inkscape::XML::Node *newImage = xml_doc->createElement("svg:image"); +- gchar *atom_name = gdk_atom_name(data->type); +- +- // this formula taken from Glib docs +- guint needed_size = data->length * 4 / 3 + data->length * 4 / (3 * 72) + 7; +- needed_size += 5 + 8 + strlen(atom_name); // 5 bytes for data:, 8 for ;base64, +- +- gchar *buffer = (gchar *) g_malloc(needed_size), *buf_work = buffer; +- buf_work += g_sprintf(buffer, "data:%s;base64,", atom_name); +- +- gint state = 0, save = 0; +- g_base64_encode_step(data->data, data->length, TRUE, buf_work, &state, &save); +- g_base64_encode_close(TRUE, buf_work, &state, &save); +- +- newImage->setAttribute("xlink:href", buffer); +- g_free(buffer); +- +- GError *error = NULL; +- GdkPixbufLoader *loader = gdk_pixbuf_loader_new_with_mime_type( gdk_atom_name(data->type), &error ); +- if ( loader ) { +- error = NULL; +- if ( gdk_pixbuf_loader_write( loader, data->data, data->length, &error) ) { +- GdkPixbuf *pbuf = gdk_pixbuf_loader_get_pixbuf(loader); +- if ( pbuf ) { +- char tmp[1024]; +- int width = gdk_pixbuf_get_width(pbuf); +- int height = gdk_pixbuf_get_height(pbuf); +- snprintf( tmp, sizeof(tmp), "%d", width ); +- newImage->setAttribute("width", tmp); ++ const char *mime = (info == JPEG_DATA ? "image/jpeg" : "image/png"); + +- snprintf( tmp, sizeof(tmp), "%d", height ); +- newImage->setAttribute("height", tmp); +- } +- } ++ Inkscape::Extension::DB::InputList o; ++ Inkscape::Extension::db.get_input_list(o); ++ Inkscape::Extension::DB::InputList::const_iterator i = o.begin(); ++ while (i != o.end() && strcmp( (*i)->get_mimetype(), mime ) != 0) { ++ ++i; + } +- g_free(atom_name); ++ Inkscape::Extension::Extension *ext = *i; ++ bool save = ext->get_param_bool("link"); ++ ext->set_param_bool("link", false); ++ ext->set_gui(false); + +- // Add it to the current layer +- desktop->currentLayer()->appendChildRepr(newImage); ++ gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-dnd-import", NULL ); ++ g_file_set_contents(filename, reinterpret_cast(data->data), data->length, NULL); ++ file_import(doc, filename, ext); ++ g_free(filename); + +- Inkscape::GC::release(newImage); ++ ext->set_param_bool("link", save); ++ ext->set_gui(true); + sp_document_done( doc , SP_VERB_NONE, + _("Drop bitmap image")); + break; +diff -urNad inkscape-0.47.0~/src/interface.cpp.orig inkscape-0.47.0/src/interface.cpp.orig +--- inkscape-0.47.0~/src/interface.cpp.orig 1970-01-01 01:00:00.000000000 +0100 ++++ inkscape-0.47.0/src/interface.cpp.orig 2009-11-16 17:29:46.000000000 +0000 +@@ -0,0 +1,1647 @@ ++#define __SP_INTERFACE_C__ ++ ++/** @file ++ * @brief Main UI stuff ++ */ ++/* Authors: ++ * Lauris Kaplinski ++ * Frank Felfe ++ * bulia byak ++ * ++ * Copyright (C) 1999-2005 authors ++ * Copyright (C) 2001-2002 Ximian, Inc. ++ * Copyright (C) 2004 David Turner ++ * ++ * Released under GNU GPL, read the file 'COPYING' for more information ++ */ ++ ++#ifdef HAVE_CONFIG_H ++# include "config.h" ++#endif ++ ++#include ++#include ++ ++#include "inkscape-private.h" ++#include "extension/effect.h" ++#include "widgets/icon.h" ++#include "preferences.h" ++#include "path-prefix.h" ++#include "shortcuts.h" ++#include "document.h" ++#include "desktop-handles.h" ++#include "file.h" ++#include "interface.h" ++#include "desktop.h" ++#include "ui/context-menu.h" ++#include "selection.h" ++#include "selection-chemistry.h" ++#include "svg-view-widget.h" ++#include "widgets/desktop-widget.h" ++#include "sp-item-group.h" ++#include "sp-text.h" ++#include "sp-gradient-fns.h" ++#include "sp-gradient.h" ++#include "sp-flowtext.h" ++#include "sp-namedview.h" ++#include "ui/view/view.h" ++#include "helper/action.h" ++#include "helper/gnome-utils.h" ++#include "helper/window.h" ++#include "io/sys.h" ++#include "dialogs/dialog-events.h" ++#include "message-context.h" ++ ++// Added for color drag-n-drop ++#if ENABLE_LCMS ++#include "lcms.h" ++#endif // ENABLE_LCMS ++#include "display/sp-canvas.h" ++#include "color.h" ++#include "svg/svg-color.h" ++#include "desktop-style.h" ++#include "style.h" ++#include "event-context.h" ++#include "gradient-drag.h" ++#include "widgets/ege-paint-def.h" ++ ++// Include Mac OS X menu synchronization on native OSX build ++#ifdef GDK_WINDOWING_QUARTZ ++#include "ige-mac-menu.h" ++#endif ++ ++/* Drag and Drop */ ++typedef enum { ++ URI_LIST, ++ SVG_XML_DATA, ++ SVG_DATA, ++ PNG_DATA, ++ JPEG_DATA, ++ IMAGE_DATA, ++ APP_X_INKY_COLOR, ++ APP_X_COLOR, ++ APP_OSWB_COLOR, ++} ui_drop_target_info; ++ ++static GtkTargetEntry ui_drop_target_entries [] = { ++ {(gchar *)"text/uri-list", 0, URI_LIST }, ++ {(gchar *)"image/svg+xml", 0, SVG_XML_DATA }, ++ {(gchar *)"image/svg", 0, SVG_DATA }, ++ {(gchar *)"image/png", 0, PNG_DATA }, ++ {(gchar *)"image/jpeg", 0, JPEG_DATA }, ++#if ENABLE_MAGIC_COLORS ++ {(gchar *)"application/x-inkscape-color", 0, APP_X_INKY_COLOR}, ++#endif // ENABLE_MAGIC_COLORS ++ {(gchar *)"application/x-oswb-color", 0, APP_OSWB_COLOR }, ++ {(gchar *)"application/x-color", 0, APP_X_COLOR } ++}; ++ ++static GtkTargetEntry *completeDropTargets = 0; ++static int completeDropTargetsCount = 0; ++static bool temporarily_block_actions = false; ++ ++#define ENTRIES_SIZE(n) sizeof(n)/sizeof(n[0]) ++static guint nui_drop_target_entries = ENTRIES_SIZE(ui_drop_target_entries); ++static void sp_ui_import_files(gchar *buffer); ++static void sp_ui_import_one_file(char const *filename); ++static void sp_ui_import_one_file_with_check(gpointer filename, gpointer unused); ++static void sp_ui_drag_data_received(GtkWidget *widget, ++ GdkDragContext *drag_context, ++ gint x, gint y, ++ GtkSelectionData *data, ++ guint info, ++ guint event_time, ++ gpointer user_data); ++static void sp_ui_drag_motion( GtkWidget *widget, ++ GdkDragContext *drag_context, ++ gint x, gint y, ++ GtkSelectionData *data, ++ guint info, ++ guint event_time, ++ gpointer user_data ); ++static void sp_ui_drag_leave( GtkWidget *widget, ++ GdkDragContext *drag_context, ++ guint event_time, ++ gpointer user_data ); ++static void sp_ui_menu_item_set_sensitive(SPAction *action, ++ unsigned int sensitive, ++ void *data); ++static void sp_ui_menu_item_set_name(SPAction *action, ++ Glib::ustring name, ++ void *data); ++static void sp_recent_open(GtkRecentChooser *, gpointer); ++ ++SPActionEventVector menu_item_event_vector = { ++ {NULL}, ++ NULL, ++ NULL, /* set_active */ ++ sp_ui_menu_item_set_sensitive, /* set_sensitive */ ++ NULL, /* set_shortcut */ ++ sp_ui_menu_item_set_name /* set_name */ ++}; ++ ++static const int MIN_ONSCREEN_DISTANCE = 50; ++ ++void ++sp_create_window(SPViewWidget *vw, gboolean editable) ++{ ++ g_return_if_fail(vw != NULL); ++ g_return_if_fail(SP_IS_VIEW_WIDGET(vw)); ++ ++ Gtk::Window *win = Inkscape::UI::window_new("", TRUE); ++ ++ gtk_container_add(GTK_CONTAINER(win->gobj()), GTK_WIDGET(vw)); ++ gtk_widget_show(GTK_WIDGET(vw)); ++ ++ if (editable) { ++ g_object_set_data(G_OBJECT(vw), "window", win); ++ ++ SPDesktopWidget *desktop_widget = reinterpret_cast(vw); ++ SPDesktop* desktop = desktop_widget->desktop; ++ ++ desktop_widget->window = win; ++ ++ win->set_data("desktop", desktop); ++ win->set_data("desktopwidget", desktop_widget); ++ ++ win->signal_delete_event().connect(sigc::mem_fun(*(SPDesktop*)vw->view, &SPDesktop::onDeleteUI)); ++ win->signal_window_state_event().connect(sigc::mem_fun(*desktop, &SPDesktop::onWindowStateEvent)); ++ win->signal_focus_in_event().connect(sigc::mem_fun(*desktop_widget, &SPDesktopWidget::onFocusInEvent)); ++ ++ Inkscape::Preferences *prefs = Inkscape::Preferences::get(); ++ gint prefs_geometry = ++ (2==prefs->getInt("/options/savewindowgeometry/value", 0)); ++ if (prefs_geometry) { ++ gint pw = prefs->getInt("/desktop/geometry/width", -1); ++ gint ph = prefs->getInt("/desktop/geometry/height", -1); ++ gint px = prefs->getInt("/desktop/geometry/x", -1); ++ gint py = prefs->getInt("/desktop/geometry/y", -1); ++ gint full = prefs->getBool("/desktop/geometry/fullscreen"); ++ gint maxed = prefs->getBool("/desktop/geometry/maximized"); ++ if (pw>0 && ph>0) { ++ gint w = MIN(gdk_screen_width(), pw); ++ gint h = MIN(gdk_screen_height(), ph); ++ gint x = MIN(gdk_screen_width() - MIN_ONSCREEN_DISTANCE, px); ++ gint y = MIN(gdk_screen_height() - MIN_ONSCREEN_DISTANCE, py); ++ if (w>0 && h>0) { ++ x = MIN(gdk_screen_width() - w, x); ++ y = MIN(gdk_screen_height() - h, y); ++ desktop->setWindowSize(w, h); ++ } ++ ++ // Only restore xy for the first window so subsequent windows don't overlap exactly ++ // with first. (Maybe rule should be only restore xy if it's different from xy of ++ // other desktops?) ++ ++ // Empirically it seems that active_desktop==this desktop only the first time a ++ // desktop is created. ++ SPDesktop *active_desktop = SP_ACTIVE_DESKTOP; ++ if (active_desktop == desktop || active_desktop==NULL) { ++ desktop->setWindowPosition(Geom::Point(x, y)); ++ } ++ } ++ if (maxed) { ++ win->maximize(); ++ } ++ if (full) { ++ win->fullscreen(); ++ } ++ } ++ ++ } else { ++ gtk_window_set_policy(GTK_WINDOW(win->gobj()), TRUE, TRUE, TRUE); ++ } ++ ++ if ( completeDropTargets == 0 || completeDropTargetsCount == 0 ) ++ { ++ std::vector types; ++ ++ GSList *list = gdk_pixbuf_get_formats(); ++ while ( list ) { ++ int i = 0; ++ GdkPixbufFormat *one = (GdkPixbufFormat*)list->data; ++ gchar** typesXX = gdk_pixbuf_format_get_mime_types(one); ++ for ( i = 0; typesXX[i]; i++ ) { ++ types.push_back(g_strdup(typesXX[i])); ++ } ++ g_strfreev(typesXX); ++ ++ list = g_slist_next(list); ++ } ++ completeDropTargetsCount = nui_drop_target_entries + types.size(); ++ completeDropTargets = new GtkTargetEntry[completeDropTargetsCount]; ++ for ( int i = 0; i < (int)nui_drop_target_entries; i++ ) { ++ completeDropTargets[i] = ui_drop_target_entries[i]; ++ } ++ int pos = nui_drop_target_entries; ++ ++ for (std::vector::iterator it = types.begin() ; it != types.end() ; it++) { ++ completeDropTargets[pos].target = *it; ++ completeDropTargets[pos].flags = 0; ++ completeDropTargets[pos].info = IMAGE_DATA; ++ pos++; ++ } ++ } ++ ++ gtk_drag_dest_set((GtkWidget*)win->gobj(), ++ GTK_DEST_DEFAULT_ALL, ++ completeDropTargets, ++ completeDropTargetsCount, ++ GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE)); ++ ++ ++ g_signal_connect(G_OBJECT(win->gobj()), ++ "drag_data_received", ++ G_CALLBACK(sp_ui_drag_data_received), ++ NULL); ++ g_signal_connect(G_OBJECT(win->gobj()), ++ "drag_motion", ++ G_CALLBACK(sp_ui_drag_motion), ++ NULL); ++ g_signal_connect(G_OBJECT(win->gobj()), ++ "drag_leave", ++ G_CALLBACK(sp_ui_drag_leave), ++ NULL); ++ win->show(); ++ ++ // needed because the first ACTIVATE_DESKTOP was sent when there was no window yet ++ inkscape_reactivate_desktop(SP_DESKTOP_WIDGET(vw)->desktop); ++} ++ ++void ++sp_ui_new_view() ++{ ++ SPDocument *document; ++ SPViewWidget *dtw; ++ ++ document = SP_ACTIVE_DOCUMENT; ++ if (!document) return; ++ ++ dtw = sp_desktop_widget_new(sp_document_namedview(document, NULL)); ++ g_return_if_fail(dtw != NULL); ++ ++ sp_create_window(dtw, TRUE); ++ sp_namedview_window_from_document(static_cast(dtw->view)); ++ sp_namedview_update_layers_from_document(static_cast(dtw->view)); ++} ++ ++/* TODO: not yet working */ ++/* To be re-enabled (by adding to menu) once it works. */ ++void ++sp_ui_new_view_preview() ++{ ++ SPDocument *document; ++ SPViewWidget *dtw; ++ ++ document = SP_ACTIVE_DOCUMENT; ++ if (!document) return; ++ ++ dtw = (SPViewWidget *) sp_svg_view_widget_new(document); ++ g_return_if_fail(dtw != NULL); ++ sp_svg_view_widget_set_resize(SP_SVG_VIEW_WIDGET(dtw), TRUE, 400.0, 400.0); ++ ++ sp_create_window(dtw, FALSE); ++} ++ ++/** ++ * \param widget unused ++ */ ++void ++sp_ui_close_view(GtkWidget */*widget*/) ++{ ++ SPDesktop *dt = SP_ACTIVE_DESKTOP; ++ ++ if (dt == NULL) { ++ return; ++ } ++ ++ if (dt->shutdown()) { ++ return; // Shutdown operation has been canceled, so do nothing ++ } ++ ++ // Shutdown can proceed; use the stored reference to the desktop here instead of the current SP_ACTIVE_DESKTOP, ++ // because the user might have changed the focus in the meantime (see bug #381357 on Launchpad) ++ dt->destroyWidget(); ++} ++ ++ ++/** ++ * sp_ui_close_all ++ * ++ * This function is called to exit the program, and iterates through all ++ * open document view windows, attempting to close each in turn. If the ++ * view has unsaved information, the user will be prompted to save, ++ * discard, or cancel. ++ * ++ * Returns FALSE if the user cancels the close_all operation, TRUE ++ * otherwise. ++ */ ++unsigned int ++sp_ui_close_all(void) ++{ ++ /* Iterate through all the windows, destroying each in the order they ++ become active */ ++ while (SP_ACTIVE_DESKTOP) { ++ SPDesktop *dt = SP_ACTIVE_DESKTOP; ++ if (dt->shutdown()) { ++ /* The user canceled the operation, so end doing the close */ ++ return FALSE; ++ } ++ // Shutdown can proceed; use the stored reference to the desktop here instead of the current SP_ACTIVE_DESKTOP, ++ // because the user might have changed the focus in the meantime (see bug #381357 on Launchpad) ++ dt->destroyWidget(); ++ } ++ ++ return TRUE; ++} ++ ++/* ++ * Some day when the right-click menus are ready to start working ++ * smarter with the verbs, we'll need to change this NULL being ++ * sent to sp_action_perform to something useful, or set some kind ++ * of global "right-clicked position" variable for actions to ++ * investigate when they're called. ++ */ ++static void ++sp_ui_menu_activate(void */*object*/, SPAction *action) ++{ ++ if (!temporarily_block_actions) { ++ sp_action_perform(action, NULL); ++ } ++} ++ ++static void ++sp_ui_menu_select_action(void */*object*/, SPAction *action) ++{ ++ action->view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, action->tip); ++} ++ ++static void ++sp_ui_menu_deselect_action(void */*object*/, SPAction *action) ++{ ++ action->view->tipsMessageContext()->clear(); ++} ++ ++static void ++sp_ui_menu_select(gpointer object, gpointer tip) ++{ ++ Inkscape::UI::View::View *view = static_cast (g_object_get_data(G_OBJECT(object), "view")); ++ view->tipsMessageContext()->set(Inkscape::NORMAL_MESSAGE, (gchar *)tip); ++} ++ ++static void ++sp_ui_menu_deselect(gpointer object) ++{ ++ Inkscape::UI::View::View *view = static_cast (g_object_get_data(G_OBJECT(object), "view")); ++ view->tipsMessageContext()->clear(); ++} ++ ++/** ++ * sp_ui_menuitem_add_icon ++ * ++ * Creates and attaches a scaled icon to the given menu item. ++ * ++ */ ++void ++sp_ui_menuitem_add_icon( GtkWidget *item, gchar *icon_name ) ++{ ++ GtkWidget *icon; ++ ++ icon = sp_icon_new( Inkscape::ICON_SIZE_MENU, icon_name ); ++ gtk_widget_show(icon); ++ gtk_image_menu_item_set_image((GtkImageMenuItem *) item, icon); ++} // end of sp_ui_menu_add_icon ++ ++/** ++ * sp_ui_menu_append_item ++ * ++ * Appends a UI item with specific info for Inkscape/Sodipodi. ++ * ++ */ ++static GtkWidget * ++sp_ui_menu_append_item( GtkMenu *menu, gchar const *stock, ++ gchar const *label, gchar const *tip, Inkscape::UI::View::View *view, GCallback callback, ++ gpointer data, gboolean with_mnemonic = TRUE ) ++{ ++ GtkWidget *item; ++ ++ if (stock) { ++ item = gtk_image_menu_item_new_from_stock(stock, NULL); ++ } else if (label) { ++ item = (with_mnemonic) ++ ? gtk_image_menu_item_new_with_mnemonic(label) : ++ gtk_image_menu_item_new_with_label(label); ++ } else { ++ item = gtk_separator_menu_item_new(); ++ } ++ ++ gtk_widget_show(item); ++ ++ if (callback) { ++ g_signal_connect(G_OBJECT(item), "activate", callback, data); ++ } ++ ++ if (tip && view) { ++ g_object_set_data(G_OBJECT(item), "view", (gpointer) view); ++ g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) tip ); ++ g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL); ++ } ++ ++ gtk_menu_append(GTK_MENU(menu), item); ++ ++ return item; ++ ++} // end of sp_ui_menu_append_item() ++ ++/** ++\brief a wrapper around gdk_keyval_name producing (when possible) characters, not names ++ */ ++static gchar const * ++sp_key_name(guint keyval) ++{ ++ /* TODO: Compare with the definition of gtk_accel_label_refetch in gtk/gtkaccellabel.c (or ++ simply use GtkAccelLabel as the TODO comment in sp_ui_shortcut_string suggests). */ ++ gchar const *n = gdk_keyval_name(gdk_keyval_to_upper(keyval)); ++ ++ if (!strcmp(n, "asciicircum")) return "^"; ++ else if (!strcmp(n, "parenleft" )) return "("; ++ else if (!strcmp(n, "parenright" )) return ")"; ++ else if (!strcmp(n, "plus" )) return "+"; ++ else if (!strcmp(n, "minus" )) return "-"; ++ else if (!strcmp(n, "asterisk" )) return "*"; ++ else if (!strcmp(n, "KP_Multiply")) return "*"; ++ else if (!strcmp(n, "Delete" )) return "Del"; ++ else if (!strcmp(n, "Page_Up" )) return "PgUp"; ++ else if (!strcmp(n, "Page_Down" )) return "PgDn"; ++ else if (!strcmp(n, "grave" )) return "`"; ++ else if (!strcmp(n, "numbersign" )) return "#"; ++ else if (!strcmp(n, "bar" )) return "|"; ++ else if (!strcmp(n, "slash" )) return "/"; ++ else if (!strcmp(n, "exclam" )) return "!"; ++ else if (!strcmp(n, "percent" )) return "%"; ++ else return n; ++} ++ ++ ++/** ++ * \param shortcut A GDK keyval OR'd with SP_SHORTCUT_blah_MASK values. ++ * \param c Points to a buffer at least 256 bytes long. ++ */ ++void ++sp_ui_shortcut_string(unsigned const shortcut, gchar *const c) ++{ ++ /* TODO: This function shouldn't exist. Our callers should use GtkAccelLabel instead of ++ * a generic GtkLabel containing this string, and should call gtk_widget_add_accelerator. ++ * Will probably need to change sp_shortcut_invoke callers. ++ * ++ * The existing gtk_label_new_with_mnemonic call can be replaced with ++ * g_object_new(GTK_TYPE_ACCEL_LABEL, NULL) followed by ++ * gtk_label_set_text_with_mnemonic(lbl, str). ++ */ ++ static GtkAccelLabelClass const &accel_lbl_cls ++ = *(GtkAccelLabelClass const *) g_type_class_peek_static(GTK_TYPE_ACCEL_LABEL); ++ ++ struct { unsigned test; char const *name; } const modifier_tbl[] = { ++ { SP_SHORTCUT_SHIFT_MASK, accel_lbl_cls.mod_name_shift }, ++ { SP_SHORTCUT_CONTROL_MASK, accel_lbl_cls.mod_name_control }, ++ { SP_SHORTCUT_ALT_MASK, accel_lbl_cls.mod_name_alt } ++ }; ++ ++ gchar *p = c; ++ gchar *end = p + 256; ++ ++ for (unsigned i = 0; i < G_N_ELEMENTS(modifier_tbl); ++i) { ++ if ((shortcut & modifier_tbl[i].test) ++ && (p < end)) ++ { ++ p += g_snprintf(p, end - p, "%s%s", ++ modifier_tbl[i].name, ++ accel_lbl_cls.mod_separator); ++ } ++ } ++ if (p < end) { ++ p += g_snprintf(p, end - p, "%s", sp_key_name(shortcut & 0xffffff)); ++ } ++ end[-1] = '\0'; // snprintf doesn't guarantee to nul-terminate the string. ++} ++ ++void ++sp_ui_dialog_title_string(Inkscape::Verb *verb, gchar *c) ++{ ++ SPAction *action; ++ unsigned int shortcut; ++ gchar *s; ++ gchar key[256]; ++ gchar *atitle; ++ ++ action = verb->get_action(NULL); ++ if (!action) ++ return; ++ ++ atitle = sp_action_get_title(action); ++ ++ s = g_stpcpy(c, atitle); ++ ++ g_free(atitle); ++ ++ shortcut = sp_shortcut_get_primary(verb); ++ if (shortcut) { ++ s = g_stpcpy(s, " ("); ++ sp_ui_shortcut_string(shortcut, key); ++ s = g_stpcpy(s, key); ++ s = g_stpcpy(s, ")"); ++ } ++} ++ ++ ++/** ++ * sp_ui_menu_append_item_from_verb ++ * ++ * Appends a custom menu UI from a verb. ++ * ++ */ ++ ++static GtkWidget * ++sp_ui_menu_append_item_from_verb(GtkMenu *menu, Inkscape::Verb *verb, Inkscape::UI::View::View *view, bool radio = false, GSList *group = NULL) ++{ ++ SPAction *action; ++ GtkWidget *item; ++ ++ if (verb->get_code() == SP_VERB_NONE) { ++ ++ item = gtk_separator_menu_item_new(); ++ ++ } else { ++ unsigned int shortcut; ++ ++ action = verb->get_action(view); ++ ++ if (!action) return NULL; ++ ++ shortcut = sp_shortcut_get_primary(verb); ++ if (shortcut) { ++ gchar c[256]; ++ sp_ui_shortcut_string(shortcut, c); ++ GtkWidget *const hb = gtk_hbox_new(FALSE, 16); ++ GtkWidget *const name_lbl = gtk_label_new(""); ++ gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name); ++ gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5); ++ gtk_box_pack_start((GtkBox *) hb, name_lbl, TRUE, TRUE, 0); ++ GtkWidget *const accel_lbl = gtk_label_new(c); ++ gtk_misc_set_alignment((GtkMisc *) accel_lbl, 1.0, 0.5); ++ gtk_box_pack_end((GtkBox *) hb, accel_lbl, FALSE, FALSE, 0); ++ gtk_widget_show_all(hb); ++ if (radio) { ++ item = gtk_radio_menu_item_new (group); ++ } else { ++ item = gtk_image_menu_item_new(); ++ } ++ gtk_container_add((GtkContainer *) item, hb); ++ } else { ++ if (radio) { ++ item = gtk_radio_menu_item_new (group); ++ } else { ++ item = gtk_image_menu_item_new (); ++ } ++ GtkWidget *const name_lbl = gtk_label_new(""); ++ gtk_label_set_markup_with_mnemonic(GTK_LABEL(name_lbl), action->name); ++ gtk_misc_set_alignment((GtkMisc *) name_lbl, 0.0, 0.5); ++ gtk_container_add((GtkContainer *) item, name_lbl); ++ } ++ ++ nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item); ++ if (!action->sensitive) { ++ gtk_widget_set_sensitive(item, FALSE); ++ } ++ ++ if (action->image) { ++ sp_ui_menuitem_add_icon(item, action->image); ++ } ++ gtk_widget_set_events(item, GDK_KEY_PRESS_MASK); ++ g_object_set_data(G_OBJECT(item), "view", (gpointer) view); ++ g_signal_connect( G_OBJECT(item), "activate", G_CALLBACK(sp_ui_menu_activate), action ); ++ g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select_action), action ); ++ g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect_action), action ); ++ } ++ ++ gtk_widget_show(item); ++ gtk_menu_append(GTK_MENU(menu), item); ++ ++ return item; ++ ++} // end of sp_ui_menu_append_item_from_verb ++ ++ ++static void ++checkitem_toggled(GtkCheckMenuItem *menuitem, gpointer user_data) ++{ ++ gchar const *pref = (gchar const *) user_data; ++ Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view"); ++ ++ Glib::ustring pref_path; ++ if (reinterpret_cast(view)->is_focusMode()) { ++ pref_path = "/focus/"; ++ } else if (reinterpret_cast(view)->is_fullscreen()) { ++ pref_path = "/fullscreen/"; ++ } else { ++ pref_path = "/window/"; ++ } ++ pref_path += pref; ++ pref_path += "/state"; ++ ++ Inkscape::Preferences *prefs = Inkscape::Preferences::get(); ++ gboolean checked = gtk_check_menu_item_get_active(menuitem); ++ prefs->setBool(pref_path, checked); ++ ++ reinterpret_cast(view)->layoutWidget(); ++} ++ ++static gboolean ++checkitem_update(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_data) ++{ ++ GtkCheckMenuItem *menuitem=GTK_CHECK_MENU_ITEM(widget); ++ ++ gchar const *pref = (gchar const *) user_data; ++ Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(menuitem), "view"); ++ ++ Glib::ustring pref_path; ++ if ((static_cast(view))->is_fullscreen()) { ++ pref_path = "/fullscreen/"; ++ } else { ++ pref_path = "/window/"; ++ } ++ pref_path += pref; ++ ++ Inkscape::Preferences *prefs = Inkscape::Preferences::get(); ++ bool ison = prefs->getBool(pref_path + "/state", true); ++ ++ g_signal_handlers_block_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data); ++ gtk_check_menu_item_set_active(menuitem, ison); ++ g_signal_handlers_unblock_by_func(G_OBJECT(menuitem), (gpointer)(GCallback)checkitem_toggled, user_data); ++ ++ return FALSE; ++} ++ ++/** ++ * \brief Callback function to update the status of the radio buttons in the View -> Display mode menu (Normal, No Filters, Outline) ++ */ ++ ++static gboolean ++update_view_menu(GtkWidget *widget, GdkEventExpose */*event*/, gpointer user_data) ++{ ++ SPAction *action = (SPAction *) user_data; ++ g_assert(action->id != NULL); ++ ++ Inkscape::UI::View::View *view = (Inkscape::UI::View::View *) g_object_get_data(G_OBJECT(widget), "view"); ++ SPDesktop *dt = static_cast(view); ++ Inkscape::RenderMode mode = dt->getMode(); ++ ++ bool new_state = false; ++ if (!strcmp(action->id, "ViewModeNormal")) { ++ new_state = mode == Inkscape::RENDERMODE_NORMAL; ++ } else if (!strcmp(action->id, "ViewModeNoFilters")) { ++ new_state = mode == Inkscape::RENDERMODE_NO_FILTERS; ++ } else if (!strcmp(action->id, "ViewModeOutline")) { ++ new_state = mode == Inkscape::RENDERMODE_OUTLINE; ++ } else { ++ g_warning("update_view_menu does not handle this verb"); ++ } ++ ++ if (new_state) { //only one of the radio buttons has to be activated; the others will automatically be deactivated ++ if (!gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget))) { ++ // When the GtkMenuItem version of the "activate" signal has been emitted by a GtkRadioMenuItem, there is a second ++ // emission as the most recently active item is toggled to inactive. This is dealt with before the original signal is handled. ++ // This emission however should not invoke any actions, hence we block it here: ++ temporarily_block_actions = true; ++ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (widget), TRUE); ++ temporarily_block_actions = false; ++ } ++ } ++ ++ return FALSE; ++} ++ ++void ++sp_ui_menu_append_check_item_from_verb(GtkMenu *menu, Inkscape::UI::View::View *view, gchar const *label, gchar const *tip, gchar const *pref, ++ void (*callback_toggle)(GtkCheckMenuItem *, gpointer user_data), ++ gboolean (*callback_update)(GtkWidget *widget, GdkEventExpose *event, gpointer user_data), ++ Inkscape::Verb *verb) ++{ ++ GtkWidget *item; ++ ++ unsigned int shortcut = 0; ++ SPAction *action = NULL; ++ ++ if (verb) { ++ shortcut = sp_shortcut_get_primary(verb); ++ action = verb->get_action(view); ++ } ++ ++ if (verb && shortcut) { ++ gchar c[256]; ++ sp_ui_shortcut_string(shortcut, c); ++ ++ GtkWidget *hb = gtk_hbox_new(FALSE, 16); ++ ++ { ++ GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label); ++ gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5); ++ gtk_box_pack_start((GtkBox *) hb, l, TRUE, TRUE, 0); ++ } ++ ++ { ++ GtkWidget *l = gtk_label_new(c); ++ gtk_misc_set_alignment((GtkMisc *) l, 1.0, 0.5); ++ gtk_box_pack_end((GtkBox *) hb, l, FALSE, FALSE, 0); ++ } ++ ++ gtk_widget_show_all(hb); ++ ++ item = gtk_check_menu_item_new(); ++ gtk_container_add((GtkContainer *) item, hb); ++ } else { ++ GtkWidget *l = gtk_label_new_with_mnemonic(action ? action->name : label); ++ gtk_misc_set_alignment((GtkMisc *) l, 0.0, 0.5); ++ item = gtk_check_menu_item_new(); ++ gtk_container_add((GtkContainer *) item, l); ++ } ++#if 0 ++ nr_active_object_add_listener((NRActiveObject *)action, (NRObjectEventVector *)&menu_item_event_vector, sizeof(SPActionEventVector), item); ++ if (!action->sensitive) { ++ gtk_widget_set_sensitive(item, FALSE); ++ } ++#endif ++ gtk_widget_show(item); ++ ++ gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); ++ ++ g_object_set_data(G_OBJECT(item), "view", (gpointer) view); ++ ++ g_signal_connect( G_OBJECT(item), "toggled", (GCallback) callback_toggle, (void *) pref); ++ g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) callback_update, (void *) pref); ++ ++ g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) (action ? action->tip : tip)); ++ g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL); ++} ++ ++static void ++sp_recent_open(GtkRecentChooser *recent_menu, gpointer /*user_data*/) ++{ ++ // dealing with the bizarre filename convention in Inkscape for now ++ gchar *uri = gtk_recent_chooser_get_current_uri(GTK_RECENT_CHOOSER(recent_menu)); ++ gchar *local_fn = g_filename_from_uri(uri, NULL, NULL); ++ gchar *utf8_fn = g_filename_to_utf8(local_fn, -1, NULL, NULL, NULL); ++ sp_file_open(utf8_fn, NULL); ++ g_free(utf8_fn); ++ g_free(local_fn); ++ g_free(uri); ++} ++ ++static void ++sp_file_new_from_template(GtkWidget */*widget*/, gchar const *uri) ++{ ++ sp_file_new(uri); ++} ++ ++void ++sp_menu_append_new_templates(GtkWidget *menu, Inkscape::UI::View::View *view) ++{ ++ std::list sources; ++ sources.push_back( profile_path("templates") ); // first try user's local dir ++ sources.push_back( g_strdup(INKSCAPE_TEMPLATESDIR) ); // then the system templates dir ++ ++ // Use this loop to iterate through a list of possible document locations. ++ while (!sources.empty()) { ++ gchar *dirname = sources.front(); ++ ++ if ( Inkscape::IO::file_test( dirname, (GFileTest)(G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR) ) ) { ++ GError *err = 0; ++ GDir *dir = g_dir_open(dirname, 0, &err); ++ ++ if (dir) { ++ for (gchar const *file = g_dir_read_name(dir); file != NULL; file = g_dir_read_name(dir)) { ++ if (!g_str_has_suffix(file, ".svg") && !g_str_has_suffix(file, ".svgz")) ++ continue; // skip non-svg files ++ ++ gchar *basename = g_path_get_basename(file); ++ if (g_str_has_suffix(basename, ".svg") && g_str_has_prefix(basename, "default.")) ++ continue; // skip default.*.svg (i.e. default.svg and translations) - it's in the menu already ++ ++ gchar const *filepath = g_build_filename(dirname, file, NULL); ++ gchar *dupfile = g_strndup(file, strlen(file) - 4); ++ gchar *filename = g_filename_to_utf8(dupfile, -1, NULL, NULL, NULL); ++ g_free(dupfile); ++ GtkWidget *item = gtk_menu_item_new_with_label(filename); ++ g_free(filename); ++ ++ gtk_widget_show(item); ++ // how does "filepath" ever get freed? ++ g_signal_connect(G_OBJECT(item), ++ "activate", ++ G_CALLBACK(sp_file_new_from_template), ++ (gpointer) filepath); ++ ++ if (view) { ++ // set null tip for now; later use a description from the template file ++ g_object_set_data(G_OBJECT(item), "view", (gpointer) view); ++ g_signal_connect( G_OBJECT(item), "select", G_CALLBACK(sp_ui_menu_select), (gpointer) NULL ); ++ g_signal_connect( G_OBJECT(item), "deselect", G_CALLBACK(sp_ui_menu_deselect), NULL); ++ } ++ ++ gtk_menu_shell_append(GTK_MENU_SHELL(menu), item); ++ } ++ g_dir_close(dir); ++ } ++ } ++ ++ // toss the dirname ++ g_free(dirname); ++ sources.pop_front(); ++ } ++} ++ ++void ++sp_ui_checkboxes_menus(GtkMenu *m, Inkscape::UI::View::View *view) ++{ ++ //sp_ui_menu_append_check_item_from_verb(m, view, _("_Menu"), _("Show or hide the menu bar"), "menu", ++ // checkitem_toggled, checkitem_update, 0); ++ sp_ui_menu_append_check_item_from_verb(m, view, _("Commands Bar"), _("Show or hide the Commands bar (under the menu)"), "commands", ++ checkitem_toggled, checkitem_update, 0); ++ sp_ui_menu_append_check_item_from_verb(m, view, _("Snap Controls Bar"), _("Show or hide the snapping controls"), "snaptoolbox", ++ checkitem_toggled, checkitem_update, 0); ++ sp_ui_menu_append_check_item_from_verb(m, view, _("Tool Controls Bar"), _("Show or hide the Tool Controls bar"), "toppanel", ++ checkitem_toggled, checkitem_update, 0); ++ sp_ui_menu_append_check_item_from_verb(m, view, _("_Toolbox"), _("Show or hide the main toolbox (on the left)"), "toolbox", ++ checkitem_toggled, checkitem_update, 0); ++ sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "rulers", ++ checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_RULERS)); ++ sp_ui_menu_append_check_item_from_verb(m, view, NULL, NULL, "scrollbars", ++ checkitem_toggled, checkitem_update, Inkscape::Verb::get(SP_VERB_TOGGLE_SCROLLBARS)); ++ sp_ui_menu_append_check_item_from_verb(m, view, _("_Palette"), _("Show or hide the color palette"), "panels", ++ checkitem_toggled, checkitem_update, 0); ++ sp_ui_menu_append_check_item_from_verb(m, view, _("_Statusbar"), _("Show or hide the statusbar (at the bottom of the window)"), "statusbar", ++ checkitem_toggled, checkitem_update, 0); ++} ++ ++/** @brief Observer that updates the recent list's max document count */ ++class MaxRecentObserver : public Inkscape::Preferences::Observer { ++public: ++ MaxRecentObserver(GtkWidget *recent_menu) : ++ Observer("/options/maxrecentdocuments/value"), ++ _rm(recent_menu) ++ {} ++ virtual void notify(Inkscape::Preferences::Entry const &e) { ++ gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(_rm), e.getInt()); ++ // hack: the recent menu doesn't repopulate after changing the limit, so we force it ++ g_signal_emit_by_name((gpointer) gtk_recent_manager_get_default(), "changed"); ++ } ++private: ++ GtkWidget *_rm; ++}; ++ ++/** \brief This function turns XML into a menu ++ \param menus This is the XML that defines the menu ++ \param menu Menu to be added to ++ \param view The View that this menu is being built for ++ ++ This function is realitively simple as it just goes through the XML ++ and parses the individual elements. In the case of a submenu, it ++ just calls itself recursively. Because it is only reasonable to have ++ a couple of submenus, it is unlikely this will go more than two or ++ three times. ++ ++ In the case of an unrecognized verb, a menu item is made to identify ++ the verb that is missing, and display that. The menu item is also made ++ insensitive. ++*/ ++void ++sp_ui_build_dyn_menus(Inkscape::XML::Node *menus, GtkWidget *menu, Inkscape::UI::View::View *view) ++{ ++ if (menus == NULL) return; ++ if (menu == NULL) return; ++ GSList *group = NULL; ++ ++ for (Inkscape::XML::Node *menu_pntr = menus; ++ menu_pntr != NULL; ++ menu_pntr = menu_pntr->next()) { ++ if (!strcmp(menu_pntr->name(), "submenu")) { ++ GtkWidget *mitem = gtk_menu_item_new_with_mnemonic(_(menu_pntr->attribute("name"))); ++ GtkWidget *submenu = gtk_menu_new(); ++ sp_ui_build_dyn_menus(menu_pntr->firstChild(), submenu, view); ++ gtk_menu_item_set_submenu(GTK_MENU_ITEM(mitem), GTK_WIDGET(submenu)); ++ gtk_menu_shell_append(GTK_MENU_SHELL(menu), mitem); ++ continue; ++ } ++ if (!strcmp(menu_pntr->name(), "verb")) { ++ gchar const *verb_name = menu_pntr->attribute("verb-id"); ++ Inkscape::Verb *verb = Inkscape::Verb::getbyid(verb_name); ++ ++ if (verb != NULL) { ++ if (menu_pntr->attribute("radio") != NULL) { ++ GtkWidget *item = sp_ui_menu_append_item_from_verb (GTK_MENU(menu), verb, view, true, group); ++ group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM(item)); ++ if (menu_pntr->attribute("default") != NULL) { ++ gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), TRUE); ++ } ++ if (verb->get_code() != SP_VERB_NONE) { ++ SPAction *action = verb->get_action(view); ++ g_signal_connect( G_OBJECT(item), "expose_event", (GCallback) update_view_menu, (void *) action); ++ } ++ } else { ++ sp_ui_menu_append_item_from_verb(GTK_MENU(menu), verb, view); ++ group = NULL; ++ } ++ } else { ++ gchar string[120]; ++ g_snprintf(string, 120, _("Verb \"%s\" Unknown"), verb_name); ++ string[119] = '\0'; /* may not be terminated */ ++ GtkWidget *item = gtk_menu_item_new_with_label(string); ++ gtk_widget_set_sensitive(item, false); ++ gtk_widget_show(item); ++ gtk_menu_append(GTK_MENU(menu), item); ++ } ++ continue; ++ } ++ if (!strcmp(menu_pntr->name(), "separator") ++ // This was spelt wrong in the original version ++ // and so this is for backward compatibility. It can ++ // probably be dropped after the 0.44 release. ++ || !strcmp(menu_pntr->name(), "seperator")) { ++ GtkWidget *item = gtk_separator_menu_item_new(); ++ gtk_widget_show(item); ++ gtk_menu_append(GTK_MENU(menu), item); ++ continue; ++ } ++ if (!strcmp(menu_pntr->name(), "template-list")) { ++ sp_menu_append_new_templates(menu, view); ++ continue; ++ } ++ if (!strcmp(menu_pntr->name(), "recent-file-list")) { ++ Inkscape::Preferences *prefs = Inkscape::Preferences::get(); ++ ++ // create recent files menu ++ int max_recent = prefs->getInt("/options/maxrecentdocuments/value"); ++ GtkWidget *recent_menu = gtk_recent_chooser_menu_new_for_manager(gtk_recent_manager_get_default()); ++ gtk_recent_chooser_set_limit(GTK_RECENT_CHOOSER(recent_menu), max_recent); ++ // sort most recently used documents first to preserve previous behavior ++ gtk_recent_chooser_set_sort_type(GTK_RECENT_CHOOSER(recent_menu), GTK_RECENT_SORT_MRU); ++ g_signal_connect(G_OBJECT(recent_menu), "item-activated", G_CALLBACK(sp_recent_open), (gpointer) NULL); ++ ++ // add filter to only open files added by Inkscape ++ GtkRecentFilter *inkscape_only_filter = gtk_recent_filter_new(); ++ gtk_recent_filter_add_application(inkscape_only_filter, g_get_prgname()); ++ gtk_recent_chooser_add_filter(GTK_RECENT_CHOOSER(recent_menu), inkscape_only_filter); ++ ++ GtkWidget *recent_item = gtk_menu_item_new_with_mnemonic(_("Open _Recent")); ++ gtk_menu_item_set_submenu(GTK_MENU_ITEM(recent_item), recent_menu); ++ ++ gtk_menu_append(GTK_MENU(menu), GTK_WIDGET(recent_item)); ++ // this will just sit and update the list's item count ++ static MaxRecentObserver *mro = new MaxRecentObserver(recent_menu); ++ prefs->addObserver(*mro); ++ continue; ++ } ++ if (!strcmp(menu_pntr->name(), "objects-checkboxes")) { ++ sp_ui_checkboxes_menus(GTK_MENU(menu), view); ++ continue; ++ } ++ } ++} ++ ++/** \brief Build the main tool bar ++ \param view View to build the bar for ++ ++ Currently the main tool bar is built as a dynamic XML menu using ++ \c sp_ui_build_dyn_menus. This function builds the bar, and then ++ pass it to get items attached to it. ++*/ ++GtkWidget * ++sp_ui_main_menubar(Inkscape::UI::View::View *view) ++{ ++ GtkWidget *mbar = gtk_menu_bar_new(); ++ ++#ifdef GDK_WINDOWING_QUARTZ ++ ige_mac_menu_set_menu_bar(GTK_MENU_SHELL(mbar)); ++#endif ++ ++ sp_ui_build_dyn_menus(inkscape_get_menus(INKSCAPE), mbar, view); ++ ++#ifdef GDK_WINDOWING_QUARTZ ++ return NULL; ++#else ++ return mbar; ++#endif ++} ++ ++static void leave_group(GtkMenuItem *, SPDesktop *desktop) { ++ desktop->setCurrentLayer(SP_OBJECT_PARENT(desktop->currentLayer())); ++} ++ ++static void enter_group(GtkMenuItem *mi, SPDesktop *desktop) { ++ desktop->setCurrentLayer(reinterpret_cast(g_object_get_data(G_OBJECT(mi), "group"))); ++ sp_desktop_selection(desktop)->clear(); ++} ++ ++GtkWidget * ++sp_ui_context_menu(Inkscape::UI::View::View *view, SPItem *item) ++{ ++ GtkWidget *m; ++ SPDesktop *dt; ++ ++ dt = static_cast(view); ++ ++ m = gtk_menu_new(); ++ ++ /* Undo and Redo */ ++ sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_UNDO), view); ++ sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_REDO), view); ++ ++ /* Separator */ ++ sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL); ++ ++ sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_CUT), view); ++ sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_COPY), view); ++ sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_PASTE), view); ++ ++ /* Separator */ ++ sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL); ++ ++ sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DUPLICATE), view); ++ sp_ui_menu_append_item_from_verb(GTK_MENU(m), Inkscape::Verb::get(SP_VERB_EDIT_DELETE), view); ++ ++ /* Item menu */ ++ if (item) { ++ sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL); ++ sp_object_menu((SPObject *) item, dt, GTK_MENU(m)); ++ } ++ ++ /* layer menu */ ++ SPGroup *group=NULL; ++ if (item) { ++ if (SP_IS_GROUP(item)) { ++ group = SP_GROUP(item); ++ } else if ( item != dt->currentRoot() && SP_IS_GROUP(SP_OBJECT_PARENT(item)) ) { ++ group = SP_GROUP(SP_OBJECT_PARENT(item)); ++ } ++ } ++ ++ if (( group && group != dt->currentLayer() ) || ++ ( dt->currentLayer() != dt->currentRoot() && SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) ) { ++ /* Separator */ ++ sp_ui_menu_append_item(GTK_MENU(m), NULL, NULL, NULL, NULL, NULL, NULL); ++ } ++ ++ if ( group && group != dt->currentLayer() ) { ++ /* TRANSLATORS: #%s is the id of the group e.g. , not a number. */ ++ gchar *label=g_strdup_printf(_("Enter group #%s"), SP_OBJECT_ID(group)); ++ GtkWidget *w = gtk_menu_item_new_with_label(label); ++ g_free(label); ++ g_object_set_data(G_OBJECT(w), "group", group); ++ g_signal_connect(G_OBJECT(w), "activate", GCallback(enter_group), dt); ++ gtk_widget_show(w); ++ gtk_menu_shell_append(GTK_MENU_SHELL(m), w); ++ } ++ ++ if ( dt->currentLayer() != dt->currentRoot() ) { ++ if ( SP_OBJECT_PARENT(dt->currentLayer()) != dt->currentRoot() ) { ++ GtkWidget *w = gtk_menu_item_new_with_label(_("Go to parent")); ++ g_signal_connect(G_OBJECT(w), "activate", GCallback(leave_group), dt); ++ gtk_widget_show(w); ++ gtk_menu_shell_append(GTK_MENU_SHELL(m), w); ++ ++ } ++ } ++ ++ return m; ++} ++ ++/* Drag and Drop */ ++void ++sp_ui_drag_data_received(GtkWidget *widget, ++ GdkDragContext *drag_context, ++ gint x, gint y, ++ GtkSelectionData *data, ++ guint info, ++ guint /*event_time*/, ++ gpointer /*user_data*/) ++{ ++ SPDocument *doc = SP_ACTIVE_DOCUMENT; ++ SPDesktop *desktop = SP_ACTIVE_DESKTOP; ++ ++ switch (info) { ++#if ENABLE_MAGIC_COLORS ++ case APP_X_INKY_COLOR: ++ { ++ int destX = 0; ++ int destY = 0; ++ gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY ); ++ Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) ); ++ ++ SPItem *item = desktop->item_at_point( where, true ); ++ if ( item ) ++ { ++ bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE); ++ ++ if ( data->length >= 8 ) { ++ cmsHPROFILE srgbProf = cmsCreate_sRGBProfile(); ++ ++ gchar c[64] = {0}; ++ // Careful about endian issues. ++ guint16* dataVals = (guint16*)data->data; ++ sp_svg_write_color( c, sizeof(c), ++ SP_RGBA32_U_COMPOSE( ++ 0x0ff & (dataVals[0] >> 8), ++ 0x0ff & (dataVals[1] >> 8), ++ 0x0ff & (dataVals[2] >> 8), ++ 0xff // can't have transparency in the color itself ++ //0x0ff & (data->data[3] >> 8), ++ )); ++ SPCSSAttr *css = sp_repr_css_attr_new(); ++ bool updatePerformed = false; ++ ++ if ( data->length > 14 ) { ++ int flags = dataVals[4]; ++ ++ // piggie-backed palette entry info ++ int index = dataVals[5]; ++ Glib::ustring palName; ++ for ( int i = 0; i < dataVals[6]; i++ ) { ++ palName += (gunichar)dataVals[7+i]; ++ } ++ ++ // Now hook in a magic tag of some sort. ++ if ( !palName.empty() && (flags & 1) ) { ++ gchar* str = g_strdup_printf("%d|", index); ++ palName.insert( 0, str ); ++ g_free(str); ++ str = 0; ++ ++ sp_object_setAttribute( SP_OBJECT(item), ++ fillnotstroke ? "inkscape:x-fill-tag":"inkscape:x-stroke-tag", ++ palName.c_str(), ++ false ); ++ item->updateRepr(); ++ ++ sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c ); ++ updatePerformed = true; ++ } ++ } ++ ++ if ( !updatePerformed ) { ++ sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", c ); ++ } ++ ++ sp_desktop_apply_css_recursive( item, css, true ); ++ item->updateRepr(); ++ ++ sp_document_done( doc , SP_VERB_NONE, ++ _("Drop color")); ++ ++ if ( srgbProf ) { ++ cmsCloseProfile( srgbProf ); ++ } ++ } ++ } ++ } ++ break; ++#endif // ENABLE_MAGIC_COLORS ++ ++ case APP_X_COLOR: ++ { ++ int destX = 0; ++ int destY = 0; ++ gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY ); ++ Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) ); ++ Geom::Point const button_dt(desktop->w2d(where)); ++ Geom::Point const button_doc(desktop->dt2doc(button_dt)); ++ ++ if ( data->length == 8 ) { ++ gchar colorspec[64] = {0}; ++ // Careful about endian issues. ++ guint16* dataVals = (guint16*)data->data; ++ sp_svg_write_color( colorspec, sizeof(colorspec), ++ SP_RGBA32_U_COMPOSE( ++ 0x0ff & (dataVals[0] >> 8), ++ 0x0ff & (dataVals[1] >> 8), ++ 0x0ff & (dataVals[2] >> 8), ++ 0xff // can't have transparency in the color itself ++ //0x0ff & (data->data[3] >> 8), ++ )); ++ ++ SPItem *item = desktop->item_at_point( where, true ); ++ ++ bool consumed = false; ++ if (desktop->event_context && desktop->event_context->get_drag()) { ++ consumed = desktop->event_context->get_drag()->dropColor(item, colorspec, button_dt); ++ if (consumed) { ++ sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient")); ++ desktop->event_context->get_drag()->updateDraggers(); ++ } ++ } ++ ++ //if (!consumed && tools_active(desktop, TOOLS_TEXT)) { ++ // consumed = sp_text_context_drop_color(c, button_doc); ++ // if (consumed) { ++ // sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient stop")); ++ // } ++ //} ++ ++ if (!consumed && item) { ++ bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE); ++ if (fillnotstroke && ++ (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) { ++ Path *livarot_path = Path_for_item(item, true, true); ++ livarot_path->ConvertWithBackData(0.04); ++ ++ boost::optional position = get_nearest_position_on_Path(livarot_path, button_doc); ++ if (position) { ++ Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t); ++ Geom::Point delta = nearest - button_doc; ++ Inkscape::Preferences *prefs = Inkscape::Preferences::get(); ++ delta = desktop->d2w(delta); ++ double stroke_tolerance = ++ ( !SP_OBJECT_STYLE(item)->stroke.isNone() ? ++ desktop->current_zoom() * ++ SP_OBJECT_STYLE (item)->stroke_width.computed * ++ to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5 ++ : 0.0) ++ + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); ++ ++ if (Geom::L2 (delta) < stroke_tolerance) { ++ fillnotstroke = false; ++ } ++ } ++ delete livarot_path; ++ } ++ ++ SPCSSAttr *css = sp_repr_css_attr_new(); ++ sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec ); ++ ++ sp_desktop_apply_css_recursive( item, css, true ); ++ item->updateRepr(); ++ ++ sp_document_done( doc , SP_VERB_NONE, ++ _("Drop color")); ++ } ++ } ++ } ++ break; ++ ++ case APP_OSWB_COLOR: ++ { ++ bool worked = false; ++ Glib::ustring colorspec; ++ if ( data->format == 8 ) { ++ ege::PaintDef color; ++ worked = color.fromMIMEData("application/x-oswb-color", ++ reinterpret_cast(data->data), ++ data->length, ++ data->format); ++ if ( worked ) { ++ if ( color.getType() == ege::PaintDef::CLEAR ) { ++ colorspec = ""; // TODO check if this is sufficient ++ } else if ( color.getType() == ege::PaintDef::NONE ) { ++ colorspec = "none"; ++ } else { ++ unsigned int r = color.getR(); ++ unsigned int g = color.getG(); ++ unsigned int b = color.getB(); ++ ++ SPGradient* matches = 0; ++ const GSList *gradients = sp_document_get_resource_list(doc, "gradient"); ++ for (const GSList *item = gradients; item; item = item->next) { ++ SPGradient* grad = SP_GRADIENT(item->data); ++ if ( color.descr == grad->id ) { ++ if ( grad->has_stops ) { ++ matches = grad; ++ break; ++ } ++ } ++ } ++ if (matches) { ++ colorspec = "url(#"; ++ colorspec += matches->id; ++ colorspec += ")"; ++ } else { ++ gchar* tmp = g_strdup_printf("#%02x%02x%02x", r, g, b); ++ colorspec = tmp; ++ g_free(tmp); ++ } ++ } ++ } ++ } ++ if ( worked ) { ++ int destX = 0; ++ int destY = 0; ++ gtk_widget_translate_coordinates( widget, &(desktop->canvas->widget), x, y, &destX, &destY ); ++ Geom::Point where( sp_canvas_window_to_world( desktop->canvas, Geom::Point( destX, destY ) ) ); ++ Geom::Point const button_dt(desktop->w2d(where)); ++ Geom::Point const button_doc(desktop->dt2doc(button_dt)); ++ ++ SPItem *item = desktop->item_at_point( where, true ); ++ ++ bool consumed = false; ++ if (desktop->event_context && desktop->event_context->get_drag()) { ++ consumed = desktop->event_context->get_drag()->dropColor(item, colorspec.c_str(), button_dt); ++ if (consumed) { ++ sp_document_done( doc , SP_VERB_NONE, _("Drop color on gradient")); ++ desktop->event_context->get_drag()->updateDraggers(); ++ } ++ } ++ ++ if (!consumed && item) { ++ bool fillnotstroke = (drag_context->action != GDK_ACTION_MOVE); ++ if (fillnotstroke && ++ (SP_IS_SHAPE(item) || SP_IS_TEXT(item) || SP_IS_FLOWTEXT(item))) { ++ Path *livarot_path = Path_for_item(item, true, true); ++ livarot_path->ConvertWithBackData(0.04); ++ ++ boost::optional position = get_nearest_position_on_Path(livarot_path, button_doc); ++ if (position) { ++ Geom::Point nearest = get_point_on_Path(livarot_path, position->piece, position->t); ++ Geom::Point delta = nearest - button_doc; ++ Inkscape::Preferences *prefs = Inkscape::Preferences::get(); ++ delta = desktop->d2w(delta); ++ double stroke_tolerance = ++ ( !SP_OBJECT_STYLE(item)->stroke.isNone() ? ++ desktop->current_zoom() * ++ SP_OBJECT_STYLE (item)->stroke_width.computed * ++ to_2geom(sp_item_i2d_affine(item)).descrim() * 0.5 ++ : 0.0) ++ + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100); ++ ++ if (Geom::L2 (delta) < stroke_tolerance) { ++ fillnotstroke = false; ++ } ++ } ++ delete livarot_path; ++ } ++ ++ SPCSSAttr *css = sp_repr_css_attr_new(); ++ sp_repr_css_set_property( css, fillnotstroke ? "fill":"stroke", colorspec.c_str() ); ++ ++ sp_desktop_apply_css_recursive( item, css, true ); ++ item->updateRepr(); ++ ++ sp_document_done( doc , SP_VERB_NONE, ++ _("Drop color")); ++ } ++ } ++ } ++ break; ++ ++ case SVG_DATA: ++ case SVG_XML_DATA: { ++ gchar *svgdata = (gchar *)data->data; ++ ++ Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, data->length, SP_SVG_NS_URI); ++ ++ if (rnewdoc == NULL) { ++ sp_ui_error_dialog(_("Could not parse SVG data")); ++ return; ++ } ++ ++ Inkscape::XML::Node *repr = rnewdoc->root(); ++ gchar const *style = repr->attribute("style"); ++ ++ Inkscape::XML::Node *newgroup = rnewdoc->createElement("svg:g"); ++ newgroup->setAttribute("style", style); ++ ++ Inkscape::XML::Document * xml_doc = sp_document_repr_doc(doc); ++ for (Inkscape::XML::Node *child = repr->firstChild(); child != NULL; child = child->next()) { ++ Inkscape::XML::Node *newchild = child->duplicate(xml_doc); ++ newgroup->appendChild(newchild); ++ } ++ ++ Inkscape::GC::release(rnewdoc); ++ ++ // Add it to the current layer ++ ++ // Greg's edits to add intelligent positioning of svg drops ++ SPObject *new_obj = NULL; ++ new_obj = desktop->currentLayer()->appendChildRepr(newgroup); ++ ++ Inkscape::Selection *selection = sp_desktop_selection(desktop); ++ selection->set(SP_ITEM(new_obj)); ++ ++ // move to mouse pointer ++ { ++ sp_document_ensure_up_to_date(sp_desktop_document(desktop)); ++ Geom::OptRect sel_bbox = selection->bounds(); ++ if (sel_bbox) { ++ Geom::Point m( desktop->point() - sel_bbox->midpoint() ); ++ sp_selection_move_relative(selection, m, false); ++ } ++ } ++ ++ Inkscape::GC::release(newgroup); ++ sp_document_done(doc, SP_VERB_NONE, ++ _("Drop SVG")); ++ break; ++ } ++ ++ case URI_LIST: { ++ gchar *uri = (gchar *)data->data; ++ sp_ui_import_files(uri); ++ break; ++ } ++ ++ case PNG_DATA: ++ case JPEG_DATA: ++ case IMAGE_DATA: { ++ Inkscape::XML::Document *xml_doc = sp_document_repr_doc(doc); ++ Inkscape::XML::Node *newImage = xml_doc->createElement("svg:image"); ++ gchar *atom_name = gdk_atom_name(data->type); ++ ++ // this formula taken from Glib docs ++ guint needed_size = data->length * 4 / 3 + data->length * 4 / (3 * 72) + 7; ++ needed_size += 5 + 8 + strlen(atom_name); // 5 bytes for data:, 8 for ;base64, ++ ++ gchar *buffer = (gchar *) g_malloc(needed_size), *buf_work = buffer; ++ buf_work += g_sprintf(buffer, "data:%s;base64,", atom_name); ++ ++ gint state = 0, save = 0; ++ g_base64_encode_step(data->data, data->length, TRUE, buf_work, &state, &save); ++ g_base64_encode_close(TRUE, buf_work, &state, &save); ++ ++ newImage->setAttribute("xlink:href", buffer); ++ g_free(buffer); ++ ++ GError *error = NULL; ++ GdkPixbufLoader *loader = gdk_pixbuf_loader_new_with_mime_type( gdk_atom_name(data->type), &error ); ++ if ( loader ) { ++ error = NULL; ++ if ( gdk_pixbuf_loader_write( loader, data->data, data->length, &error) ) { ++ GdkPixbuf *pbuf = gdk_pixbuf_loader_get_pixbuf(loader); ++ if ( pbuf ) { ++ char tmp[1024]; ++ int width = gdk_pixbuf_get_width(pbuf); ++ int height = gdk_pixbuf_get_height(pbuf); ++ snprintf( tmp, sizeof(tmp), "%d", width ); ++ newImage->setAttribute("width", tmp); ++ ++ snprintf( tmp, sizeof(tmp), "%d", height ); ++ newImage->setAttribute("height", tmp); ++ } ++ } ++ } ++ g_free(atom_name); ++ ++ // Add it to the current layer ++ desktop->currentLayer()->appendChildRepr(newImage); ++ ++ Inkscape::GC::release(newImage); ++ sp_document_done( doc , SP_VERB_NONE, ++ _("Drop bitmap image")); ++ break; ++ } ++ } ++} ++ ++#include "gradient-context.h" ++ ++void sp_ui_drag_motion( GtkWidget */*widget*/, ++ GdkDragContext */*drag_context*/, ++ gint /*x*/, gint /*y*/, ++ GtkSelectionData */*data*/, ++ guint /*info*/, ++ guint /*event_time*/, ++ gpointer /*user_data*/) ++{ ++// SPDocument *doc = SP_ACTIVE_DOCUMENT; ++// SPDesktop *desktop = SP_ACTIVE_DESKTOP; ++ ++ ++// g_message("drag-n-drop motion (%4d, %4d) at %d", x, y, event_time); ++} ++ ++static void sp_ui_drag_leave( GtkWidget */*widget*/, ++ GdkDragContext */*drag_context*/, ++ guint /*event_time*/, ++ gpointer /*user_data*/ ) ++{ ++// g_message("drag-n-drop leave at %d", event_time); ++} ++ ++static void ++sp_ui_import_files(gchar *buffer) ++{ ++ GList *list = gnome_uri_list_extract_filenames(buffer); ++ if (!list) ++ return; ++ g_list_foreach(list, sp_ui_import_one_file_with_check, NULL); ++ g_list_foreach(list, (GFunc) g_free, NULL); ++ g_list_free(list); ++} ++ ++static void ++sp_ui_import_one_file_with_check(gpointer filename, gpointer /*unused*/) ++{ ++ if (filename) { ++ if (strlen((char const *)filename) > 2) ++ sp_ui_import_one_file((char const *)filename); ++ } ++} ++ ++static void ++sp_ui_import_one_file(char const *filename) ++{ ++ SPDocument *doc = SP_ACTIVE_DOCUMENT; ++ if (!doc) return; ++ ++ if (filename == NULL) return; ++ ++ // Pass off to common implementation ++ // TODO might need to get the proper type of Inkscape::Extension::Extension ++ file_import( doc, filename, NULL ); ++} ++ ++void ++sp_ui_error_dialog(gchar const *message) ++{ ++ GtkWidget *dlg; ++ gchar *safeMsg = Inkscape::IO::sanitizeString(message); ++ ++ dlg = gtk_message_dialog_new(NULL, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_ERROR, ++ GTK_BUTTONS_CLOSE, "%s", safeMsg); ++ sp_transientize(dlg); ++ gtk_window_set_resizable(GTK_WINDOW(dlg), FALSE); ++ gtk_dialog_run(GTK_DIALOG(dlg)); ++ gtk_widget_destroy(dlg); ++ g_free(safeMsg); ++} ++ ++bool ++sp_ui_overwrite_file(gchar const *filename) ++{ ++ bool return_value = FALSE; ++ ++ if (Inkscape::IO::file_test(filename, G_FILE_TEST_EXISTS)) { ++ Gtk::Window *window = SP_ACTIVE_DESKTOP->getToplevel(); ++ gchar* baseName = g_path_get_basename( filename ); ++ gchar* dirName = g_path_get_dirname( filename ); ++ GtkWidget* dialog = gtk_message_dialog_new_with_markup( window->gobj(), ++ (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT), ++ GTK_MESSAGE_QUESTION, ++ GTK_BUTTONS_NONE, ++ _( "A file named \"%s\" already exists. Do you want to replace it?\n\n" ++ "The file already exists in \"%s\". Replacing it will overwrite its contents." ), ++ baseName, ++ dirName ++ ); ++ gtk_dialog_add_buttons( GTK_DIALOG(dialog), ++ GTK_STOCK_CANCEL, GTK_RESPONSE_NO, ++ _("Replace"), GTK_RESPONSE_YES, ++ NULL ); ++ gtk_dialog_set_default_response( GTK_DIALOG(dialog), GTK_RESPONSE_YES ); ++ ++ if ( gtk_dialog_run( GTK_DIALOG(dialog) ) == GTK_RESPONSE_YES ) { ++ return_value = TRUE; ++ } else { ++ return_value = FALSE; ++ } ++ gtk_widget_destroy(dialog); ++ g_free( baseName ); ++ g_free( dirName ); ++ } else { ++ return_value = TRUE; ++ } ++ ++ return return_value; ++} ++ ++static void ++sp_ui_menu_item_set_sensitive(SPAction */*action*/, unsigned int sensitive, void *data) ++{ ++ return gtk_widget_set_sensitive(GTK_WIDGET(data), sensitive); ++} ++ ++static void ++sp_ui_menu_item_set_name(SPAction */*action*/, Glib::ustring name, void *data) ++{ ++ void *child = GTK_BIN (data)->child; ++ //child is either ++ //- a GtkHBox, whose first child is a label displaying name if the menu ++ //item has an accel key ++ //- a GtkLabel if the menu has no accel key ++ if (GTK_IS_LABEL(child)) { ++ gtk_label_set_markup_with_mnemonic(GTK_LABEL (child), name.c_str()); ++ } else if (GTK_IS_HBOX(child)) { ++ gtk_label_set_markup_with_mnemonic( ++ GTK_LABEL (gtk_container_get_children(GTK_CONTAINER (child))->data), ++ name.c_str()); ++ }//else sp_ui_menu_append_item_from_verb has been modified and can set ++ //a menu item in yet another way... ++} ++ ++ ++/* ++ Local Variables: ++ mode:c++ ++ c-file-style:"stroustrup" ++ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) ++ indent-tabs-mode:nil ++ fill-column:99 ++ End: ++*/ ++// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : +diff -urNad inkscape-0.47.0~/src/ui/clipboard.cpp inkscape-0.47.0/src/ui/clipboard.cpp +--- inkscape-0.47.0~/src/ui/clipboard.cpp 2009-11-16 17:29:40.000000000 +0000 ++++ inkscape-0.47.0/src/ui/clipboard.cpp 2010-04-06 00:26:58.413992406 +0100 +@@ -872,26 +872,25 @@ + Glib::RefPtr img = _clipboard->wait_for_image(); + if (!img) return false; + +- // Very stupid hack: Write into a file, then import the file into the document. +- // To avoid using tmpfile and POSIX file handles, make the filename based on current time. +- // This wasn't my idea, I just copied this from selection-chemistry.cpp +- // and just can't think of something saner at the moment. Pasting more than +- // one image per second will overwrite the image. +- // However, I don't think anyone is able to copy a _different_ image into inkscape +- // in 1 second. +- time_t rawtime; +- char image_filename[128]; ++ // AARGH stupid ++ Inkscape::Extension::DB::InputList o; ++ Inkscape::Extension::db.get_input_list(o); ++ Inkscape::Extension::DB::InputList::const_iterator i = o.begin(); ++ while (i != o.end() && strcmp( (*i)->get_mimetype(), "image/png" ) != 0) { ++ ++i; ++ } ++ Inkscape::Extension::Extension *png = *i; ++ bool save = png->get_param_bool("link"); ++ png->set_param_bool("link", false); ++ png->set_gui(false); + +- time(&rawtime); +- strftime(image_filename, 128, "inkscape_pasted_image_%Y%m%d_%H%M%S.png", localtime( &rawtime )); +- /// @todo Check whether the encoding is correct here +- Inkscape::Preferences *prefs = Inkscape::Preferences::get(); +- std::string save_folder = Glib::filename_from_utf8(prefs->getString("/dialogs/save_as/path")); ++ gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-clipboard-import", NULL ); ++ img->save(filename, "png"); ++ file_import(doc, filename, png); ++ g_free(filename); + +- gchar *image_path = g_build_filename(save_folder.data(), image_filename, NULL); +- img->save(image_path, "png"); +- file_import(doc, image_path, NULL); +- g_free(image_path); ++ png->set_param_bool("link", save); ++ png->set_gui(true); + + return true; + } +diff -urNad inkscape-0.47.0~/src/ui/clipboard.cpp.orig inkscape-0.47.0/src/ui/clipboard.cpp.orig +--- inkscape-0.47.0~/src/ui/clipboard.cpp.orig 1970-01-01 01:00:00.000000000 +0100 ++++ inkscape-0.47.0/src/ui/clipboard.cpp.orig 2009-11-16 17:29:40.000000000 +0000 +@@ -0,0 +1,1429 @@ ++/** @file ++ * @brief System-wide clipboard management - implementation ++ */ ++/* Authors: ++ * Krzysztof KosiƄski ++ * Incorporates some code from selection-chemistry.cpp, see that file for more credits. ++ * ++ * Copyright (C) 2008 authors ++ * ++ * This program is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU General Public License ++ * as published by the Free Software Foundation; either version 2 ++ * of the License, or (at your option) any later version. ++ * ++ * See the file COPYING for details. ++ */ ++ ++#include "ui/clipboard.h" ++ ++// TODO: reduce header bloat if possible ++ ++#include ++#include ++#include ++#include ++#include ++#include // for g_file_set_contents etc., used in _onGet and paste ++#include "gc-core.h" ++#include "xml/repr.h" ++#include "inkscape.h" ++#include "io/stringstream.h" ++#include "desktop.h" ++#include "desktop-handles.h" ++#include "desktop-style.h" // for sp_desktop_set_style, used in _pasteStyle ++#include "document.h" ++#include "document-private.h" ++#include "selection.h" ++#include "message-stack.h" ++#include "context-fns.h" ++#include "dropper-context.h" // used in copy() ++#include "style.h" ++#include "extension/db.h" // extension database ++#include "extension/input.h" ++#include "extension/output.h" ++#include "selection-chemistry.h" ++#include "libnr/nr-rect.h" ++#include "libnr/nr-convert2geom.h" ++#include <2geom/rect.h> ++#include <2geom/transforms.h> ++#include "box3d.h" ++#include "gradient-drag.h" ++#include "sp-item.h" ++#include "sp-item-transform.h" // for sp_item_scale_rel, used in _pasteSize ++#include "sp-path.h" ++#include "sp-pattern.h" ++#include "sp-shape.h" ++#include "sp-gradient.h" ++#include "sp-gradient-reference.h" ++#include "sp-gradient-fns.h" ++#include "sp-linear-gradient-fns.h" ++#include "sp-radial-gradient-fns.h" ++#include "sp-clippath.h" ++#include "sp-mask.h" ++#include "sp-textpath.h" ++#include "sp-rect.h" ++#include "live_effects/lpeobject.h" ++#include "live_effects/lpeobject-reference.h" ++#include "live_effects/parameter/path.h" ++#include "svg/svg.h" // for sp_svg_transform_write, used in _copySelection ++#include "svg/css-ostringstream.h" // used in _parseColor ++#include "file.h" // for file_import, used in _pasteImage ++#include "preferences.h" // for used in _pasteImage ++#include "text-context.h" ++#include "text-editing.h" ++#include "tools-switch.h" ++#include "path-chemistry.h" ++#include "id-clash.h" ++#include "unit-constants.h" ++#include "helper/png-write.h" ++#include "svg/svg-color.h" ++#include "sp-namedview.h" ++#include "snap.h" ++ ++/// @brief Made up mimetype to represent Gdk::Pixbuf clipboard contents ++#define CLIPBOARD_GDK_PIXBUF_TARGET "image/x-gdk-pixbuf" ++ ++#define CLIPBOARD_TEXT_TARGET "text/plain" ++ ++#ifdef WIN32 ++#include ++// Clipboard Formats: http://msdn.microsoft.com/en-us/library/ms649013(VS.85).aspx ++// On Windows, most graphical applications can handle CF_DIB/CF_BITMAP and/or CF_ENHMETAFILE ++// GTK automatically presents an "image/bmp" target as CF_DIB/CF_BITMAP ++// Presenting "image/x-emf" as CF_ENHMETAFILE must be done by Inkscape ? ++#define CLIPBOARD_WIN32_EMF_TARGET "CF_ENHMETAFILE" ++#define CLIPBOARD_WIN32_EMF_MIME "image/x-emf" ++#endif ++ ++namespace Inkscape { ++namespace UI { ++ ++ ++/** ++ * @brief Default implementation of the clipboard manager ++ */ ++class ClipboardManagerImpl : public ClipboardManager { ++public: ++ virtual void copy(); ++ virtual void copyPathParameter(Inkscape::LivePathEffect::PathParam *); ++ virtual bool paste(bool in_place); ++ virtual bool pasteStyle(); ++ virtual bool pasteSize(bool, bool, bool); ++ virtual bool pastePathEffect(); ++ virtual Glib::ustring getPathParameter(); ++ virtual Glib::ustring getShapeOrTextObjectId(); ++ virtual const gchar *getFirstObjectID(); ++ ++ ClipboardManagerImpl(); ++ ~ClipboardManagerImpl(); ++ ++private: ++ void _copySelection(Inkscape::Selection *); ++ void _copyUsedDefs(SPItem *); ++ void _copyGradient(SPGradient *); ++ void _copyPattern(SPPattern *); ++ void _copyTextPath(SPTextPath *); ++ Inkscape::XML::Node *_copyNode(Inkscape::XML::Node *, Inkscape::XML::Document *, Inkscape::XML::Node *); ++ ++ void _pasteDocument(SPDocument *, bool in_place); ++ void _pasteDefs(SPDocument *); ++ bool _pasteImage(); ++ bool _pasteText(); ++ SPCSSAttr *_parseColor(const Glib::ustring &); ++ void _applyPathEffect(SPItem *, gchar const *); ++ SPDocument *_retrieveClipboard(Glib::ustring = ""); ++ ++ // clipboard callbacks ++ void _onGet(Gtk::SelectionData &, guint); ++ void _onClear(); ++ ++ // various helpers ++ void _createInternalClipboard(); ++ void _discardInternalClipboard(); ++ Inkscape::XML::Node *_createClipNode(); ++ Geom::Scale _getScale(Geom::Point const &, Geom::Point const &, Geom::Rect const &, bool, bool); ++ Glib::ustring _getBestTarget(); ++ void _setClipboardTargets(); ++ void _setClipboardColor(guint32); ++ void _userWarn(SPDesktop *, char const *); ++ ++ void _inkscape_wait_for_targets(std::list &); ++ ++ // private properites ++ SPDocument *_clipboardSPDoc; ///< Document that stores the clipboard until someone requests it ++ Inkscape::XML::Node *_defs; ///< Reference to the clipboard document's defs node ++ Inkscape::XML::Node *_root; ///< Reference to the clipboard's root node ++ Inkscape::XML::Node *_clipnode; ///< The node that holds extra information ++ Inkscape::XML::Document *_doc; ///< Reference to the clipboard's Inkscape::XML::Document ++ ++ // we need a way to copy plain text AND remember its style; ++ // the standard _clipnode is only available in an SVG tree, hence this special storage ++ SPCSSAttr *_text_style; ///< Style copied along with plain text fragment ++ ++ Glib::RefPtr _clipboard; ///< Handle to the system wide clipboard - for convenience ++ std::list _preferred_targets; ///< List of supported clipboard targets ++}; ++ ++ ++ClipboardManagerImpl::ClipboardManagerImpl() ++ : _clipboardSPDoc(NULL), ++ _defs(NULL), ++ _root(NULL), ++ _clipnode(NULL), ++ _doc(NULL), ++ _text_style(NULL), ++ _clipboard( Gtk::Clipboard::get() ) ++{ ++ // push supported clipboard targets, in order of preference ++ _preferred_targets.push_back("image/x-inkscape-svg"); ++ _preferred_targets.push_back("image/svg+xml"); ++ _preferred_targets.push_back("image/svg+xml-compressed"); ++#ifdef WIN32 ++ _preferred_targets.push_back(CLIPBOARD_WIN32_EMF_MIME); ++#endif ++ _preferred_targets.push_back("application/pdf"); ++ _preferred_targets.push_back("image/x-adobe-illustrator"); ++} ++ ++ ++ClipboardManagerImpl::~ClipboardManagerImpl() {} ++ ++ ++/** ++ * @brief Copy selection contents to the clipboard ++ */ ++void ClipboardManagerImpl::copy() ++{ ++ SPDesktop *desktop = SP_ACTIVE_DESKTOP; ++ if ( desktop == NULL ) return; ++ Inkscape::Selection *selection = sp_desktop_selection(desktop); ++ ++ // Special case for when the gradient dragger is active - copies gradient color ++ if (desktop->event_context->get_drag()) { ++ GrDrag *drag = desktop->event_context->get_drag(); ++ if (drag->hasSelection()) { ++ guint32 col = drag->getColor(); ++ ++ // set the color as clipboard content (text in RRGGBBAA format) ++ _setClipboardColor(col); ++ ++ // create a style with this color on fill and opacity in master opacity, so it can be ++ // pasted on other stops or objects ++ if (_text_style) { ++ sp_repr_css_attr_unref(_text_style); ++ _text_style = NULL; ++ } ++ _text_style = sp_repr_css_attr_new(); ++ // print and set properties ++ gchar color_str[16]; ++ g_snprintf(color_str, 16, "#%06x", col >> 8); ++ sp_repr_css_set_property(_text_style, "fill", color_str); ++ float opacity = SP_RGBA32_A_F(col); ++ if (opacity > 1.0) opacity = 1.0; // safeguard ++ Inkscape::CSSOStringStream opcss; ++ opcss << opacity; ++ sp_repr_css_set_property(_text_style, "opacity", opcss.str().data()); ++ ++ _discardInternalClipboard(); ++ return; ++ } ++ } ++ ++ // Special case for when the color picker ("dropper") is active - copies color under cursor ++ if (tools_isactive(desktop, TOOLS_DROPPER)) { ++ _setClipboardColor(sp_dropper_context_get_color(desktop->event_context)); ++ _discardInternalClipboard(); ++ return; ++ } ++ ++ // Special case for when the text tool is active - if some text is selected, copy plain text, ++ // not the object that holds it; also copy the style at cursor into ++ if (tools_isactive(desktop, TOOLS_TEXT)) { ++ _discardInternalClipboard(); ++ Glib::ustring selected_text = sp_text_get_selected_text(desktop->event_context); ++ if (!selected_text.empty()) { ++ _clipboard->set_text(selected_text); ++ } ++ if (_text_style) { ++ sp_repr_css_attr_unref(_text_style); ++ _text_style = NULL; ++ } ++ _text_style = sp_text_get_style_at_cursor(desktop->event_context); ++ return; ++ } ++ ++ if (selection->isEmpty()) { // check whether something is selected ++ _userWarn(desktop, _("Nothing was copied.")); ++ return; ++ } ++ _discardInternalClipboard(); ++ ++ _createInternalClipboard(); // construct a new clipboard document ++ _copySelection(selection); // copy all items in the selection to the internal clipboard ++ fit_canvas_to_drawing(_clipboardSPDoc); ++ ++ _setClipboardTargets(); ++} ++ ++ ++/** ++ * @brief Copy a Live Path Effect path parameter to the clipboard ++ * @param pp The path parameter to store in the clipboard ++ */ ++void ClipboardManagerImpl::copyPathParameter(Inkscape::LivePathEffect::PathParam *pp) ++{ ++ if ( pp == NULL ) return; ++ gchar *svgd = sp_svg_write_path( pp->get_pathvector() ); ++ if ( svgd == NULL || *svgd == '\0' ) return; ++ ++ _discardInternalClipboard(); ++ _createInternalClipboard(); ++ ++ Inkscape::XML::Node *pathnode = _doc->createElement("svg:path"); ++ pathnode->setAttribute("d", svgd); ++ g_free(svgd); ++ _root->appendChild(pathnode); ++ Inkscape::GC::release(pathnode); ++ ++ fit_canvas_to_drawing(_clipboardSPDoc); ++ _setClipboardTargets(); ++} ++ ++/** ++ * @brief Paste from the system clipboard into the active desktop ++ * @param in_place Whether to put the contents where they were when copied ++ */ ++bool ClipboardManagerImpl::paste(bool in_place) ++{ ++ // do any checking whether we really are able to paste before requesting the contents ++ SPDesktop *desktop = SP_ACTIVE_DESKTOP; ++ if ( desktop == NULL ) return false; ++ if ( Inkscape::have_viable_layer(desktop, desktop->messageStack()) == false ) return false; ++ ++ Glib::ustring target = _getBestTarget(); ++ ++ // Special cases of clipboard content handling go here ++ // Note that target priority is determined in _getBestTarget. ++ // TODO: Handle x-special/gnome-copied-files and text/uri-list to support pasting files ++ ++ // if there is an image on the clipboard, paste it ++ if ( target == CLIPBOARD_GDK_PIXBUF_TARGET ) return _pasteImage(); ++ // if there's only text, paste it into a selected text object or create a new one ++ if ( target == CLIPBOARD_TEXT_TARGET ) return _pasteText(); ++ ++ // otherwise, use the import extensions ++ SPDocument *tempdoc = _retrieveClipboard(target); ++ if ( tempdoc == NULL ) { ++ _userWarn(desktop, _("Nothing on the clipboard.")); ++ return false; ++ } ++ ++ _pasteDocument(tempdoc, in_place); ++ sp_document_unref(tempdoc); ++ ++ return true; ++} ++ ++/** ++ * @brief Returns the id of the first visible copied object ++ */ ++const gchar *ClipboardManagerImpl::getFirstObjectID() ++{ ++ SPDocument *tempdoc = _retrieveClipboard("image/x-inkscape-svg"); ++ if ( tempdoc == NULL ) { ++ return NULL; ++ } ++ ++ Inkscape::XML::Node ++ *root = sp_document_repr_root(tempdoc); ++ ++ if (!root) ++ return NULL; ++ ++ Inkscape::XML::Node *ch = sp_repr_children(root); ++ while (ch != NULL && ++ strcmp(ch->name(), "svg:g") && ++ strcmp(ch->name(), "svg:path") && ++ strcmp(ch->name(), "svg:use") && ++ strcmp(ch->name(), "svg:text") && ++ strcmp(ch->name(), "svg:image") && ++ strcmp(ch->name(), "svg:rect") ++ ) ++ ch = ch->next(); ++ ++ if (ch) { ++ return ch->attribute("id"); ++ } ++ ++ return NULL; ++} ++ ++ ++/** ++ * @brief Implements the Paste Style action ++ */ ++bool ClipboardManagerImpl::pasteStyle() ++{ ++ SPDesktop *desktop = SP_ACTIVE_DESKTOP; ++ if (desktop == NULL) return false; ++ ++ // check whether something is selected ++ Inkscape::Selection *selection = sp_desktop_selection(desktop); ++ if (selection->isEmpty()) { ++ _userWarn(desktop, _("Select object(s) to paste style to.")); ++ return false; ++ } ++ ++ SPDocument *tempdoc = _retrieveClipboard("image/x-inkscape-svg"); ++ if ( tempdoc == NULL ) { ++ // no document, but we can try _text_style ++ if (_text_style) { ++ sp_desktop_set_style(desktop, _text_style); ++ return true; ++ } else { ++ _userWarn(desktop, _("No style on the clipboard.")); ++ return false; ++ } ++ } ++ ++ Inkscape::XML::Node ++ *root = sp_document_repr_root(tempdoc), ++ *clipnode = sp_repr_lookup_name(root, "inkscape:clipboard", 1); ++ ++ bool pasted = false; ++ ++ if (clipnode) { ++ _pasteDefs(tempdoc); ++ SPCSSAttr *style = sp_repr_css_attr(clipnode, "style"); ++ sp_desktop_set_style(desktop, style); ++ pasted = true; ++ } ++ else { ++ _userWarn(desktop, _("No style on the clipboard.")); ++ } ++ ++ sp_document_unref(tempdoc); ++ return pasted; ++} ++ ++ ++/** ++ * @brief Resize the selection or each object in the selection to match the clipboard's size ++ * @param separately Whether to scale each object in the selection separately ++ * @param apply_x Whether to scale the width of objects / selection ++ * @param apply_y Whether to scale the height of objects / selection ++ */ ++bool ClipboardManagerImpl::pasteSize(bool separately, bool apply_x, bool apply_y) ++{ ++ if(!apply_x && !apply_y) return false; // pointless parameters ++ ++ SPDesktop *desktop = SP_ACTIVE_DESKTOP; ++ if ( desktop == NULL ) return false; ++ Inkscape::Selection *selection = sp_desktop_selection(desktop); ++ if (selection->isEmpty()) { ++ _userWarn(desktop, _("Select object(s) to paste size to.")); ++ return false; ++ } ++ ++ // FIXME: actually, this should accept arbitrary documents ++ SPDocument *tempdoc = _retrieveClipboard("image/x-inkscape-svg"); ++ if ( tempdoc == NULL ) { ++ _userWarn(desktop, _("No size on the clipboard.")); ++ return false; ++ } ++ ++ // retrieve size ifomration from the clipboard ++ Inkscape::XML::Node *root = sp_document_repr_root(tempdoc); ++ Inkscape::XML::Node *clipnode = sp_repr_lookup_name(root, "inkscape:clipboard", 1); ++ bool pasted = false; ++ if (clipnode) { ++ Geom::Point min, max; ++ sp_repr_get_point(clipnode, "min", &min); ++ sp_repr_get_point(clipnode, "max", &max); ++ ++ // resize each object in the selection ++ if (separately) { ++ for (GSList *i = const_cast(selection->itemList()) ; i ; i = i->next) { ++ SPItem *item = SP_ITEM(i->data); ++ Geom::OptRect obj_size = sp_item_bbox_desktop(item); ++ if ( !obj_size ) continue; ++ sp_item_scale_rel(item, _getScale(min, max, *obj_size, apply_x, apply_y)); ++ } ++ } ++ // resize the selection as a whole ++ else { ++ Geom::OptRect sel_size = selection->bounds(); ++ if ( sel_size ) { ++ sp_selection_scale_relative(selection, sel_size->midpoint(), ++ _getScale(min, max, *sel_size, apply_x, apply_y)); ++ } ++ } ++ pasted = true; ++ } ++ sp_document_unref(tempdoc); ++ return pasted; ++} ++ ++ ++/** ++ * @brief Applies a path effect from the clipboard to the selected path ++ */ ++bool ClipboardManagerImpl::pastePathEffect() ++{ ++ /** @todo FIXME: pastePathEffect crashes when moving the path with the applied effect, ++ segfaulting in fork_private_if_necessary(). */ ++ ++ SPDesktop *desktop = SP_ACTIVE_DESKTOP; ++ if ( desktop == NULL ) ++ return false; ++ ++ Inkscape::Selection *selection = sp_desktop_selection(desktop); ++ if (selection && selection->isEmpty()) { ++ _userWarn(desktop, _("Select object(s) to paste live path effect to.")); ++ return false; ++ } ++ ++ SPDocument *tempdoc = _retrieveClipboard("image/x-inkscape-svg"); ++ if ( tempdoc ) { ++ Inkscape::XML::Node *root = sp_document_repr_root(tempdoc); ++ Inkscape::XML::Node *clipnode = sp_repr_lookup_name(root, "inkscape:clipboard", 1); ++ if ( clipnode ) { ++ gchar const *effect = clipnode->attribute("inkscape:path-effect"); ++ if ( effect ) { ++ _pasteDefs(tempdoc); ++ // make sure all selected items are converted to paths first (i.e. rectangles) ++ sp_selected_path_to_curves(desktop, false); ++ for (GSList *item = const_cast(selection->itemList()) ; item ; item = item->next) { ++ _applyPathEffect(reinterpret_cast(item->data), effect); ++ } ++ ++ return true; ++ } ++ } ++ } ++ ++ // no_effect: ++ _userWarn(desktop, _("No effect on the clipboard.")); ++ return false; ++} ++ ++ ++/** ++ * @brief Get LPE path data from the clipboard ++ * @return The retrieved path data (contents of the d attribute), or "" if no path was found ++ */ ++Glib::ustring ClipboardManagerImpl::getPathParameter() ++{ ++ SPDocument *tempdoc = _retrieveClipboard(); // any target will do here ++ if ( tempdoc == NULL ) { ++ _userWarn(SP_ACTIVE_DESKTOP, _("Nothing on the clipboard.")); ++ return ""; ++ } ++ Inkscape::XML::Node ++ *root = sp_document_repr_root(tempdoc), ++ *path = sp_repr_lookup_name(root, "svg:path", -1); // unlimited search depth ++ if ( path == NULL ) { ++ _userWarn(SP_ACTIVE_DESKTOP, _("Clipboard does not contain a path.")); ++ sp_document_unref(tempdoc); ++ return ""; ++ } ++ gchar const *svgd = path->attribute("d"); ++ return svgd; ++} ++ ++ ++/** ++ * @brief Get object id of a shape or text item from the clipboard ++ * @return The retrieved id string (contents of the id attribute), or "" if no shape or text item was found ++ */ ++Glib::ustring ClipboardManagerImpl::getShapeOrTextObjectId() ++{ ++ SPDocument *tempdoc = _retrieveClipboard(); // any target will do here ++ if ( tempdoc == NULL ) { ++ _userWarn(SP_ACTIVE_DESKTOP, _("Nothing on the clipboard.")); ++ return ""; ++ } ++ Inkscape::XML::Node *root = sp_document_repr_root(tempdoc); ++ ++ Inkscape::XML::Node *repr = sp_repr_lookup_name(root, "svg:path", -1); // unlimited search depth ++ if ( repr == NULL ) ++ repr = sp_repr_lookup_name(root, "svg:text", -1); ++ ++ if ( repr == NULL ) { ++ _userWarn(SP_ACTIVE_DESKTOP, _("Clipboard does not contain a path.")); ++ sp_document_unref(tempdoc); ++ return ""; ++ } ++ gchar const *svgd = repr->attribute("id"); ++ return svgd; ++} ++ ++ ++/** ++ * @brief Iterate over a list of items and copy them to the clipboard. ++ */ ++void ClipboardManagerImpl::_copySelection(Inkscape::Selection *selection) ++{ ++ GSList const *items = selection->itemList(); ++ // copy the defs used by all items ++ for (GSList *i = const_cast(items) ; i != NULL ; i = i->next) { ++ _copyUsedDefs(SP_ITEM (i->data)); ++ } ++ ++ // copy the representation of the items ++ GSList *sorted_items = g_slist_copy(const_cast(items)); ++ sorted_items = g_slist_sort(sorted_items, (GCompareFunc) sp_object_compare_position); ++ ++ for (GSList *i = sorted_items ; i ; i = i->next) { ++ if (!SP_IS_ITEM(i->data)) continue; ++ Inkscape::XML::Node *obj = SP_OBJECT_REPR(i->data); ++ Inkscape::XML::Node *obj_copy = _copyNode(obj, _doc, _root); ++ ++ // copy complete inherited style ++ SPCSSAttr *css = sp_repr_css_attr_inherited(obj, "style"); ++ sp_repr_css_set(obj_copy, css, "style"); ++ sp_repr_css_attr_unref(css); ++ ++ // write the complete accumulated transform passed to us ++ // (we're dealing with unattached representations, so we write to their attributes ++ // instead of using sp_item_set_transform) ++ gchar *transform_str = sp_svg_transform_write(sp_item_i2doc_affine(SP_ITEM(i->data))); ++ obj_copy->setAttribute("transform", transform_str); ++ g_free(transform_str); ++ } ++ ++ // copy style for Paste Style action ++ if (sorted_items) { ++ if(SP_IS_ITEM(sorted_items->data)) { ++ SPCSSAttr *style = take_style_from_item((SPItem *) sorted_items->data); ++ sp_repr_css_set(_clipnode, style, "style"); ++ sp_repr_css_attr_unref(style); ++ } ++ ++ // copy path effect from the first path ++ if (SP_IS_OBJECT(sorted_items->data)) { ++ gchar const *effect = SP_OBJECT_REPR(sorted_items->data)->attribute("inkscape:path-effect"); ++ if (effect) { ++ _clipnode->setAttribute("inkscape:path-effect", effect); ++ } ++ } ++ } ++ ++ Geom::OptRect size = selection->bounds(); ++ if (size) { ++ sp_repr_set_point(_clipnode, "min", size->min()); ++ sp_repr_set_point(_clipnode, "max", size->max()); ++ } ++ ++ g_slist_free(sorted_items); ++} ++ ++ ++/** ++ * @brief Recursively copy all the definitions used by a given item to the clipboard defs ++ */ ++void ClipboardManagerImpl::_copyUsedDefs(SPItem *item) ++{ ++ // copy fill and stroke styles (patterns and gradients) ++ SPStyle *style = SP_OBJECT_STYLE(item); ++ ++ if (style && (style->fill.isPaintserver())) { ++ SPObject *server = SP_OBJECT_STYLE_FILL_SERVER(item); ++ if (SP_IS_LINEARGRADIENT(server) || SP_IS_RADIALGRADIENT(server)) ++ _copyGradient(SP_GRADIENT(server)); ++ if (SP_IS_PATTERN(server)) ++ _copyPattern(SP_PATTERN(server)); ++ } ++ if (style && (style->stroke.isPaintserver())) { ++ SPObject *server = SP_OBJECT_STYLE_STROKE_SERVER(item); ++ if (SP_IS_LINEARGRADIENT(server) || SP_IS_RADIALGRADIENT(server)) ++ _copyGradient(SP_GRADIENT(server)); ++ if (SP_IS_PATTERN(server)) ++ _copyPattern(SP_PATTERN(server)); ++ } ++ ++ // For shapes, copy all of the shape's markers ++ if (SP_IS_SHAPE(item)) { ++ SPShape *shape = SP_SHAPE (item); ++ for (int i = 0 ; i < SP_MARKER_LOC_QTY ; i++) { ++ if (shape->marker[i]) { ++ _copyNode(SP_OBJECT_REPR(SP_OBJECT(shape->marker[i])), _doc, _defs); ++ } ++ } ++ } ++ // For lpe items, copy lpe stack if applicable ++ if (SP_IS_LPE_ITEM(item)) { ++ SPLPEItem *lpeitem = SP_LPE_ITEM (item); ++ if (sp_lpe_item_has_path_effect(lpeitem)) { ++ for (PathEffectList::iterator it = lpeitem->path_effect_list->begin(); it != lpeitem->path_effect_list->end(); ++it) ++ { ++ LivePathEffectObject *lpeobj = (*it)->lpeobject; ++ if (lpeobj) ++ _copyNode(SP_OBJECT_REPR(SP_OBJECT(lpeobj)), _doc, _defs); ++ } ++ } ++ } ++ // For 3D boxes, copy perspectives ++ if (SP_IS_BOX3D(item)) { ++ _copyNode(SP_OBJECT_REPR(SP_OBJECT(box3d_get_perspective(SP_BOX3D(item)))), _doc, _defs); ++ } ++ // Copy text paths ++ if (SP_IS_TEXT_TEXTPATH(item)) { ++ _copyTextPath(SP_TEXTPATH(sp_object_first_child(SP_OBJECT(item)))); ++ } ++ // Copy clipping objects ++ if (item->clip_ref->getObject()) { ++ _copyNode(SP_OBJECT_REPR(item->clip_ref->getObject()), _doc, _defs); ++ } ++ // Copy mask objects ++ if (item->mask_ref->getObject()) { ++ SPObject *mask = item->mask_ref->getObject(); ++ _copyNode(SP_OBJECT_REPR(mask), _doc, _defs); ++ // recurse into the mask for its gradients etc. ++ for (SPObject *o = SP_OBJECT(mask)->children ; o != NULL ; o = o->next) { ++ if (SP_IS_ITEM(o)) ++ _copyUsedDefs(SP_ITEM(o)); ++ } ++ } ++ // Copy filters ++ if (style->getFilter()) { ++ SPObject *filter = style->getFilter(); ++ if (SP_IS_FILTER(filter)) { ++ _copyNode(SP_OBJECT_REPR(filter), _doc, _defs); ++ } ++ } ++ ++ // recurse ++ for (SPObject *o = SP_OBJECT(item)->children ; o != NULL ; o = o->next) { ++ if (SP_IS_ITEM(o)) ++ _copyUsedDefs(SP_ITEM(o)); ++ } ++} ++ ++ ++/** ++ * @brief Copy a single gradient to the clipboard's defs element ++ */ ++void ClipboardManagerImpl::_copyGradient(SPGradient *gradient) ++{ ++ while (gradient) { ++ // climb up the refs, copying each one in the chain ++ _copyNode(SP_OBJECT_REPR(gradient), _doc, _defs); ++ gradient = gradient->ref->getObject(); ++ } ++} ++ ++ ++/** ++ * @brief Copy a single pattern to the clipboard document's defs element ++ */ ++void ClipboardManagerImpl::_copyPattern(SPPattern *pattern) ++{ ++ // climb up the references, copying each one in the chain ++ while (pattern) { ++ _copyNode(SP_OBJECT_REPR(pattern), _doc, _defs); ++ ++ // items in the pattern may also use gradients and other patterns, so recurse ++ for (SPObject *child = sp_object_first_child(SP_OBJECT(pattern)) ; child != NULL ; child = SP_OBJECT_NEXT(child) ) { ++ if (!SP_IS_ITEM (child)) continue; ++ _copyUsedDefs(SP_ITEM(child)); ++ } ++ pattern = pattern->ref->getObject(); ++ } ++} ++ ++ ++/** ++ * @brief Copy a text path to the clipboard's defs element ++ */ ++void ClipboardManagerImpl::_copyTextPath(SPTextPath *tp) ++{ ++ SPItem *path = sp_textpath_get_path_item(tp); ++ if(!path) return; ++ Inkscape::XML::Node *path_node = SP_OBJECT_REPR(path); ++ ++ // Do not copy the text path to defs if it's already copied ++ if(sp_repr_lookup_child(_root, "id", path_node->attribute("id"))) return; ++ _copyNode(path_node, _doc, _defs); ++} ++ ++ ++/** ++ * @brief Copy a single XML node from one document to another ++ * @param node The node to be copied ++ * @param target_doc The document to which the node is to be copied ++ * @param parent The node in the target document which will become the parent of the copied node ++ * @return Pointer to the copied node ++ */ ++Inkscape::XML::Node *ClipboardManagerImpl::_copyNode(Inkscape::XML::Node *node, Inkscape::XML::Document *target_doc, Inkscape::XML::Node *parent) ++{ ++ Inkscape::XML::Node *dup = node->duplicate(target_doc); ++ parent->appendChild(dup); ++ Inkscape::GC::release(dup); ++ return dup; ++} ++ ++ ++/** ++ * @brief Paste the contents of a document into the active desktop ++ * @param clipdoc The document to paste ++ * @param in_place Whether to paste the selection where it was when copied ++ * @pre @c clipdoc is not empty and items can be added to the current layer ++ */ ++void ClipboardManagerImpl::_pasteDocument(SPDocument *clipdoc, bool in_place) ++{ ++ SPDesktop *desktop = SP_ACTIVE_DESKTOP; ++ SPDocument *target_document = sp_desktop_document(desktop); ++ Inkscape::XML::Node ++ *root = sp_document_repr_root(clipdoc), ++ *target_parent = SP_OBJECT_REPR(desktop->currentLayer()); ++ Inkscape::XML::Document *target_xmldoc = sp_document_repr_doc(target_document); ++ ++ // copy definitions ++ _pasteDefs(clipdoc); ++ ++ // copy objects ++ GSList *pasted_objects = NULL; ++ for (Inkscape::XML::Node *obj = root->firstChild() ; obj ; obj = obj->next()) { ++ // Don't copy metadata, defs, named views and internal clipboard contents to the document ++ if (!strcmp(obj->name(), "svg:defs")) continue; ++ if (!strcmp(obj->name(), "svg:metadata")) continue; ++ if (!strcmp(obj->name(), "sodipodi:namedview")) continue; ++ if (!strcmp(obj->name(), "inkscape:clipboard")) continue; ++ Inkscape::XML::Node *obj_copy = _copyNode(obj, target_xmldoc, target_parent); ++ pasted_objects = g_slist_prepend(pasted_objects, (gpointer) obj_copy); ++ } ++ ++ // Change the selection to the freshly pasted objects ++ Inkscape::Selection *selection = sp_desktop_selection(desktop); ++ selection->setReprList(pasted_objects); ++ ++ // invers apply parent transform ++ Geom::Matrix doc2parent = sp_item_i2doc_affine(SP_ITEM(desktop->currentLayer())).inverse(); ++ sp_selection_apply_affine(selection, desktop->dt2doc() * doc2parent * desktop->doc2dt(), true, false); ++ ++ // Update (among other things) all curves in paths, for bounds() to work ++ sp_document_ensure_up_to_date(target_document); ++ ++ // move selection either to original position (in_place) or to mouse pointer ++ Geom::OptRect sel_bbox = selection->bounds(); ++ if (sel_bbox) { ++ // get offset of selection to original position of copied elements ++ Geom::Point pos_original; ++ Inkscape::XML::Node *clipnode = sp_repr_lookup_name(root, "inkscape:clipboard", 1); ++ if (clipnode) { ++ Geom::Point min, max; ++ sp_repr_get_point(clipnode, "min", &min); ++ sp_repr_get_point(clipnode, "max", &max); ++ pos_original = Geom::Point(min[Geom::X], max[Geom::Y]); ++ } ++ Geom::Point offset = pos_original - sel_bbox->corner(3); ++ ++ if (!in_place) { ++ SnapManager &m = desktop->namedview->snap_manager; ++ m.setup(desktop, false); // Don't display the snapindicator ++ ++ // get offset from mouse pointer to bbox center, snap to grid if enabled ++ Geom::Point mouse_offset = desktop->point() - sel_bbox->midpoint(); ++ offset = m.multipleOfGridPitch(mouse_offset - offset) + offset; ++ } ++ ++ sp_selection_move_relative(selection, offset); ++ } ++ ++ g_slist_free(pasted_objects); ++} ++ ++ ++/** ++ * @brief Paste SVG defs from the document retrieved from the clipboard into the active document ++ * @param clipdoc The document to paste ++ * @pre @c clipdoc != NULL and pasting into the active document is possible ++ */ ++void ClipboardManagerImpl::_pasteDefs(SPDocument *clipdoc) ++{ ++ // boilerplate vars copied from _pasteDocument ++ SPDesktop *desktop = SP_ACTIVE_DESKTOP; ++ SPDocument *target_document = sp_desktop_document(desktop); ++ Inkscape::XML::Node ++ *root = sp_document_repr_root(clipdoc), ++ *defs = sp_repr_lookup_name(root, "svg:defs", 1), ++ *target_defs = SP_OBJECT_REPR(SP_DOCUMENT_DEFS(target_document)); ++ Inkscape::XML::Document *target_xmldoc = sp_document_repr_doc(target_document); ++ ++ prevent_id_clashes(clipdoc, target_document); ++ ++ for (Inkscape::XML::Node *def = defs->firstChild() ; def ; def = def->next()) { ++ _copyNode(def, target_xmldoc, target_defs); ++ } ++} ++ ++ ++/** ++ * @brief Retrieve a bitmap image from the clipboard and paste it into the active document ++ */ ++bool ClipboardManagerImpl::_pasteImage() ++{ ++ SPDocument *doc = SP_ACTIVE_DOCUMENT; ++ if ( doc == NULL ) return false; ++ ++ // retrieve image data ++ Glib::RefPtr img = _clipboard->wait_for_image(); ++ if (!img) return false; ++ ++ // Very stupid hack: Write into a file, then import the file into the document. ++ // To avoid using tmpfile and POSIX file handles, make the filename based on current time. ++ // This wasn't my idea, I just copied this from selection-chemistry.cpp ++ // and just can't think of something saner at the moment. Pasting more than ++ // one image per second will overwrite the image. ++ // However, I don't think anyone is able to copy a _different_ image into inkscape ++ // in 1 second. ++ time_t rawtime; ++ char image_filename[128]; ++ ++ time(&rawtime); ++ strftime(image_filename, 128, "inkscape_pasted_image_%Y%m%d_%H%M%S.png", localtime( &rawtime )); ++ /// @todo Check whether the encoding is correct here ++ Inkscape::Preferences *prefs = Inkscape::Preferences::get(); ++ std::string save_folder = Glib::filename_from_utf8(prefs->getString("/dialogs/save_as/path")); ++ ++ gchar *image_path = g_build_filename(save_folder.data(), image_filename, NULL); ++ img->save(image_path, "png"); ++ file_import(doc, image_path, NULL); ++ g_free(image_path); ++ ++ return true; ++} ++ ++/** ++ * @brief Paste text into the selected text object or create a new one to hold it ++ */ ++bool ClipboardManagerImpl::_pasteText() ++{ ++ SPDesktop *desktop = SP_ACTIVE_DESKTOP; ++ if ( desktop == NULL ) return false; ++ ++ // if the text editing tool is active, paste the text into the active text object ++ if (tools_isactive(desktop, TOOLS_TEXT)) ++ return sp_text_paste_inline(desktop->event_context); ++ ++ // try to parse the text as a color and, if successful, apply it as the current style ++ SPCSSAttr *css = _parseColor(_clipboard->wait_for_text()); ++ if (css) { ++ sp_desktop_set_style(desktop, css); ++ return true; ++ } ++ ++ return false; ++} ++ ++ ++/** ++ * @brief Attempt to parse the passed string as a hexadecimal RGB or RGBA color ++ * @param text The Glib::ustring to parse ++ * @return New CSS style representation if the parsing was successful, NULL otherwise ++ */ ++SPCSSAttr *ClipboardManagerImpl::_parseColor(const Glib::ustring &text) ++{ ++// TODO reuse existing code instead of replicating here. ++ Glib::ustring::size_type len = text.bytes(); ++ char *str = const_cast(text.data()); ++ bool attempt_alpha = false; ++ if ( !str || ( *str == '\0' ) ) return NULL; // this is OK due to boolean short-circuit ++ ++ // those conditionals guard against parsing e.g. the string "fab" as "fab000" ++ // (incomplete color) and "45fab71" as "45fab710" (incomplete alpha) ++ if ( *str == '#' ) { ++ if ( len < 7 ) return NULL; ++ if ( len >= 9 ) attempt_alpha = true; ++ } else { ++ if ( len < 6 ) return NULL; ++ if ( len >= 8 ) attempt_alpha = true; ++ } ++ ++ unsigned int color = 0, alpha = 0xff; ++ ++ // skip a leading #, if present ++ if ( *str == '#' ) ++str; ++ ++ // try to parse first 6 digits ++ int res = sscanf(str, "%6x", &color); ++ if ( res && ( res != EOF ) ) { ++ if (attempt_alpha) {// try to parse alpha if there's enough characters ++ sscanf(str + 6, "%2x", &alpha); ++ if ( !res || res == EOF ) alpha = 0xff; ++ } ++ ++ SPCSSAttr *color_css = sp_repr_css_attr_new(); ++ ++ // print and set properties ++ gchar color_str[16]; ++ g_snprintf(color_str, 16, "#%06x", color); ++ sp_repr_css_set_property(color_css, "fill", color_str); ++ ++ float opacity = static_cast(alpha)/static_cast(0xff); ++ if (opacity > 1.0) opacity = 1.0; // safeguard ++ Inkscape::CSSOStringStream opcss; ++ opcss << opacity; ++ sp_repr_css_set_property(color_css, "fill-opacity", opcss.str().data()); ++ return color_css; ++ } ++ return NULL; ++} ++ ++ ++/** ++ * @brief Applies a pasted path effect to a given item ++ */ ++void ClipboardManagerImpl::_applyPathEffect(SPItem *item, gchar const *effect) ++{ ++ if ( item == NULL ) return; ++ if ( SP_IS_RECT(item) ) return; ++ ++ if (SP_IS_LPE_ITEM(item)) ++ { ++ SPLPEItem *lpeitem = SP_LPE_ITEM(item); ++ SPObject *obj = sp_uri_reference_resolve(_clipboardSPDoc, effect); ++ if (!obj) return; ++ // if the effect is not used by anyone, we might as well take it ++ LivePathEffectObject *lpeobj = LIVEPATHEFFECT(obj)->fork_private_if_necessary(1); ++ sp_lpe_item_add_path_effect(lpeitem, lpeobj); ++ } ++} ++ ++ ++/** ++ * @brief Retrieve the clipboard contents as a document ++ * @return Clipboard contents converted to SPDocument, or NULL if no suitable content was present ++ */ ++SPDocument *ClipboardManagerImpl::_retrieveClipboard(Glib::ustring required_target) ++{ ++ Glib::ustring best_target; ++ if ( required_target == "" ) ++ best_target = _getBestTarget(); ++ else ++ best_target = required_target; ++ ++ if ( best_target == "" ) { ++ return NULL; ++ } ++ ++ // FIXME: Temporary hack until we add memory input. ++ // Save the clipboard contents to some file, then read it ++ gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-clipboard-import", NULL ); ++ ++ bool file_saved = false; ++ Glib::ustring target = best_target; ++ ++#ifdef WIN32 ++ if (best_target == CLIPBOARD_WIN32_EMF_TARGET) ++ { // Try to save clipboard data as en emf file (using win32 api) ++ if (OpenClipboard(NULL)) { ++ HGLOBAL hglb = GetClipboardData(CF_ENHMETAFILE); ++ if (hglb) { ++ HENHMETAFILE hemf = CopyEnhMetaFile((HENHMETAFILE) hglb, filename); ++ if (hemf) { ++ file_saved = true; ++ target = CLIPBOARD_WIN32_EMF_MIME; ++ DeleteEnhMetaFile(hemf); ++ } ++ } ++ CloseClipboard(); ++ } ++ } ++#endif ++ ++ if (!file_saved) { ++ if ( !_clipboard->wait_is_target_available(best_target) ) { ++ return NULL; ++ } ++ ++ // doing this synchronously makes better sense ++ // TODO: use another method because this one is badly broken imo. ++ // from documentation: "Returns: A SelectionData object, which will be invalid if retrieving the given target failed." ++ // I don't know how to check whether an object is 'valid' or not, unusable if that's not possible... ++ Gtk::SelectionData sel = _clipboard->wait_for_contents(best_target); ++ target = sel.get_target(); // this can crash if the result was invalid of last function. No way to check for this :( ++ ++ // FIXME: Temporary hack until we add memory input. ++ // Save the clipboard contents to some file, then read it ++ g_file_set_contents(filename, (const gchar *) sel.get_data(), sel.get_length(), NULL); ++ } ++ ++ // there is no specific plain SVG input extension, so if we can paste the Inkscape SVG format, ++ // we use the image/svg+xml mimetype to look up the input extension ++ if(target == "image/x-inkscape-svg") ++ target = "image/svg+xml"; ++ ++ Inkscape::Extension::DB::InputList inlist; ++ Inkscape::Extension::db.get_input_list(inlist); ++ Inkscape::Extension::DB::InputList::const_iterator in = inlist.begin(); ++ for (; in != inlist.end() && target != (*in)->get_mimetype() ; ++in){}; ++ if ( in == inlist.end() ) ++ return NULL; // this shouldn't happen unless _getBestTarget returns something bogus ++ ++ SPDocument *tempdoc = NULL; ++ try { ++ tempdoc = (*in)->open(filename); ++ } catch (...) { ++ } ++ g_unlink(filename); ++ g_free(filename); ++ ++ return tempdoc; ++} ++ ++ ++/** ++ * @brief Callback called when some other application requests data from Inkscape ++ * ++ * Finds a suitable output extension to save the internal clipboard document, ++ * then saves it to memory and sets the clipboard contents. ++ */ ++void ClipboardManagerImpl::_onGet(Gtk::SelectionData &sel, guint /*info*/) ++{ ++ g_assert( _clipboardSPDoc != NULL ); ++ ++ Glib::ustring target = sel.get_target(); ++ if(target == "") return; // this shouldn't happen ++ ++ if (target == CLIPBOARD_TEXT_TARGET) { ++ target = "image/x-inkscape-svg"; ++ } ++ ++ Inkscape::Extension::DB::OutputList outlist; ++ Inkscape::Extension::db.get_output_list(outlist); ++ Inkscape::Extension::DB::OutputList::const_iterator out = outlist.begin(); ++ for ( ; out != outlist.end() && target != (*out)->get_mimetype() ; ++out){}; ++ if ( out == outlist.end() && target != "image/png") return; // this also shouldn't happen ++ ++ // FIXME: Temporary hack until we add support for memory output. ++ // Save to a temporary file, read it back and then set the clipboard contents ++ gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-clipboard-export", NULL ); ++ gsize len; gchar *data; ++ ++ try { ++ if (out == outlist.end() && target == "image/png") ++ { ++ gdouble dpi = PX_PER_IN; ++ guint32 bgcolor = 0x00000000; ++ ++ Geom::Point origin (SP_ROOT(_clipboardSPDoc->root)->x.computed, SP_ROOT(_clipboardSPDoc->root)->y.computed); ++ Geom::Rect area = Geom::Rect(origin, origin + sp_document_dimensions(_clipboardSPDoc)); ++ ++ unsigned long int width = (unsigned long int) (area.width() * dpi / PX_PER_IN + 0.5); ++ unsigned long int height = (unsigned long int) (area.height() * dpi / PX_PER_IN + 0.5); ++ ++ // read from namedview ++ Inkscape::XML::Node *nv = sp_repr_lookup_name (_clipboardSPDoc->rroot, "sodipodi:namedview"); ++ if (nv && nv->attribute("pagecolor")) ++ bgcolor = sp_svg_read_color(nv->attribute("pagecolor"), 0xffffff00); ++ if (nv && nv->attribute("inkscape:pageopacity")) ++ bgcolor |= SP_COLOR_F_TO_U(sp_repr_get_double_attribute (nv, "inkscape:pageopacity", 1.0)); ++ ++ sp_export_png_file(_clipboardSPDoc, filename, area, width, height, dpi, dpi, bgcolor, NULL, NULL, true, NULL); ++ } ++ else ++ { ++ if (!(*out)->loaded()) { ++ // Need to load the extension. ++ (*out)->set_state(Inkscape::Extension::Extension::STATE_LOADED); ++ } ++ (*out)->save(_clipboardSPDoc, filename); ++ } ++ g_file_get_contents(filename, &data, &len, NULL); ++ ++ sel.set(8, (guint8 const *) data, len); ++ } catch (...) { ++ } ++ ++ g_unlink(filename); // delete the temporary file ++ g_free(filename); ++} ++ ++ ++/** ++ * @brief Callback when someone else takes the clipboard ++ * ++ * When the clipboard owner changes, this callback clears the internal clipboard document ++ * to reduce memory usage. ++ */ ++void ClipboardManagerImpl::_onClear() ++{ ++ // why is this called before _onGet??? ++ //_discardInternalClipboard(); ++} ++ ++ ++/** ++ * @brief Creates an internal clipboard document from scratch ++ */ ++void ClipboardManagerImpl::_createInternalClipboard() ++{ ++ if ( _clipboardSPDoc == NULL ) { ++ _clipboardSPDoc = sp_document_new(NULL, false, true); ++ //g_assert( _clipboardSPDoc != NULL ); ++ _defs = SP_OBJECT_REPR(SP_DOCUMENT_DEFS(_clipboardSPDoc)); ++ _doc = sp_document_repr_doc(_clipboardSPDoc); ++ _root = sp_document_repr_root(_clipboardSPDoc); ++ ++ _clipnode = _doc->createElement("inkscape:clipboard"); ++ _root->appendChild(_clipnode); ++ Inkscape::GC::release(_clipnode); ++ ++ // once we create a SVG document, style will be stored in it, so flush _text_style ++ if (_text_style) { ++ sp_repr_css_attr_unref(_text_style); ++ _text_style = NULL; ++ } ++ } ++} ++ ++ ++/** ++ * @brief Deletes the internal clipboard document ++ */ ++void ClipboardManagerImpl::_discardInternalClipboard() ++{ ++ if ( _clipboardSPDoc != NULL ) { ++ sp_document_unref(_clipboardSPDoc); ++ _clipboardSPDoc = NULL; ++ _defs = NULL; ++ _doc = NULL; ++ _root = NULL; ++ _clipnode = NULL; ++ } ++} ++ ++ ++/** ++ * @brief Get the scale to resize an item, based on the command and desktop state ++ */ ++Geom::Scale ClipboardManagerImpl::_getScale(Geom::Point const &min, Geom::Point const &max, Geom::Rect const &obj_rect, bool apply_x, bool apply_y) ++{ ++ SPDesktop *desktop = SP_ACTIVE_DESKTOP; ++ double scale_x = 1.0; ++ double scale_y = 1.0; ++ ++ if (apply_x) { ++ scale_x = (max[Geom::X] - min[Geom::X]) / obj_rect[Geom::X].extent(); ++ } ++ if (apply_y) { ++ scale_y = (max[Geom::Y] - min[Geom::Y]) / obj_rect[Geom::Y].extent(); ++ } ++ // If the "lock aspect ratio" button is pressed and we paste only a single coordinate, ++ // resize the second one by the same ratio too ++ if (desktop->isToolboxButtonActive("lock")) { ++ if (apply_x && !apply_y) scale_y = scale_x; ++ if (apply_y && !apply_x) scale_x = scale_y; ++ } ++ ++ return Geom::Scale(scale_x, scale_y); ++} ++ ++ ++/** ++ * @brief Find the most suitable clipboard target ++ */ ++Glib::ustring ClipboardManagerImpl::_getBestTarget() ++{ ++ // GTKmm's wait_for_targets() is broken, see the comment in _inkscape_wait_for_targets() ++ std::list targets; // = _clipboard->wait_for_targets(); ++ _inkscape_wait_for_targets(targets); ++ ++ // clipboard target debugging snippet ++ /* ++ g_debug("Begin clipboard targets"); ++ for ( std::list::iterator x = targets.begin() ; x != targets.end(); ++x ) ++ g_debug("Clipboard target: %s", (*x).data()); ++ g_debug("End clipboard targets\n"); ++ //*/ ++ ++ for(std::list::iterator i = _preferred_targets.begin() ; ++ i != _preferred_targets.end() ; ++i) ++ { ++ if ( std::find(targets.begin(), targets.end(), *i) != targets.end() ) ++ return *i; ++ } ++#ifdef WIN32 ++ if (OpenClipboard(NULL)) ++ { // If both bitmap and metafile are present, pick the one that was exported first. ++ UINT format = EnumClipboardFormats(0); ++ while (format) { ++ if (format == CF_ENHMETAFILE || format == CF_DIB || format == CF_BITMAP) ++ break; ++ format = EnumClipboardFormats(format); ++ } ++ CloseClipboard(); ++ ++ if (format == CF_ENHMETAFILE) ++ return CLIPBOARD_WIN32_EMF_TARGET; ++ if (format == CF_DIB || format == CF_BITMAP) ++ return CLIPBOARD_GDK_PIXBUF_TARGET; ++ } ++ ++ if (IsClipboardFormatAvailable(CF_ENHMETAFILE)) ++ return CLIPBOARD_WIN32_EMF_TARGET; ++#endif ++ if (_clipboard->wait_is_image_available()) ++ return CLIPBOARD_GDK_PIXBUF_TARGET; ++ if (_clipboard->wait_is_text_available()) ++ return CLIPBOARD_TEXT_TARGET; ++ ++ return ""; ++} ++ ++ ++/** ++ * @brief Set the clipboard targets to reflect the mimetypes Inkscape can output ++ */ ++void ClipboardManagerImpl::_setClipboardTargets() ++{ ++ Inkscape::Extension::DB::OutputList outlist; ++ Inkscape::Extension::db.get_output_list(outlist); ++ std::list target_list; ++ bool plaintextSet = false; ++ for (Inkscape::Extension::DB::OutputList::const_iterator out = outlist.begin() ; out != outlist.end() ; ++out) { ++ if ( !(*out)->deactivated() ) { ++ Glib::ustring mime = (*out)->get_mimetype(); ++ if (mime != CLIPBOARD_TEXT_TARGET) { ++ if ( !plaintextSet && (mime.find("svg") == Glib::ustring::npos) ) { ++ target_list.push_back(Gtk::TargetEntry(CLIPBOARD_TEXT_TARGET)); ++ plaintextSet = true; ++ } ++ target_list.push_back(Gtk::TargetEntry(mime)); ++ } ++ } ++ } ++ ++ // Add PNG export explicitly since there is no extension for this... ++ // On Windows, GTK will also present this as a CF_DIB/CF_BITMAP ++ target_list.push_back(Gtk::TargetEntry( "image/png" )); ++ ++ _clipboard->set(target_list, ++ sigc::mem_fun(*this, &ClipboardManagerImpl::_onGet), ++ sigc::mem_fun(*this, &ClipboardManagerImpl::_onClear)); ++ ++#ifdef WIN32 ++ // If the "image/x-emf" target handled by the emf extension would be ++ // presented as a CF_ENHMETAFILE automatically (just like an "image/bmp" ++ // is presented as a CF_BITMAP) this code would not be needed.. ??? ++ // Or maybe there is some other way to achieve the same? ++ ++ // Note: Metafile is the only format that is rendered and stored in clipboard ++ // on Copy, all other formats are rendered only when needed by a Paste command. ++ ++ // FIXME: This should at least be rewritten to use "delayed rendering". ++ // If possible make it delayed rendering by using GTK API only. ++ ++ if (OpenClipboard(NULL)) { ++ if ( _clipboardSPDoc != NULL ) { ++ const Glib::ustring target = CLIPBOARD_WIN32_EMF_MIME; ++ ++ Inkscape::Extension::DB::OutputList outlist; ++ Inkscape::Extension::db.get_output_list(outlist); ++ Inkscape::Extension::DB::OutputList::const_iterator out = outlist.begin(); ++ for ( ; out != outlist.end() && target != (*out)->get_mimetype() ; ++out); ++ if ( out != outlist.end() ) { ++ // FIXME: Temporary hack until we add support for memory output. ++ // Save to a temporary file, read it back and then set the clipboard contents ++ gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-clipboard-export.emf", NULL ); ++ ++ try { ++ (*out)->save(_clipboardSPDoc, filename); ++ HENHMETAFILE hemf = GetEnhMetaFileA(filename); ++ if (hemf) { ++ SetClipboardData(CF_ENHMETAFILE, hemf); ++ DeleteEnhMetaFile(hemf); ++ } ++ } catch (...) { ++ } ++ g_unlink(filename); // delete the temporary file ++ g_free(filename); ++ } ++ } ++ CloseClipboard(); ++ } ++#endif ++} ++ ++ ++/** ++ * @brief Set the string representation of a 32-bit RGBA color as the clipboard contents ++ */ ++void ClipboardManagerImpl::_setClipboardColor(guint32 color) ++{ ++ gchar colorstr[16]; ++ g_snprintf(colorstr, 16, "%08x", color); ++ _clipboard->set_text(colorstr); ++} ++ ++ ++/** ++ * @brief Put a notification on the mesage stack ++ */ ++void ClipboardManagerImpl::_userWarn(SPDesktop *desktop, char const *msg) ++{ ++ desktop->messageStack()->flash(Inkscape::WARNING_MESSAGE, msg); ++} ++ ++ ++// GTKMM's clipboard::wait_for_targets is buggy and might return bogus, see ++// ++// https://bugs.launchpad.net/inkscape/+bug/296778 ++// http://mail.gnome.org/archives/gtk-devel-list/2009-June/msg00062.html ++// ++// for details. Until this has been fixed upstream we will use our own implementation ++// of this method, as copied from /gtkmm-2.16.0/gtk/gtkmm/clipboard.cc. ++void ClipboardManagerImpl::_inkscape_wait_for_targets(std::list &listTargets) ++{ ++ //Get a newly-allocated array of atoms: ++ GdkAtom* targets = 0; ++ gint n_targets = 0; ++ gboolean test = gtk_clipboard_wait_for_targets( gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), &targets, &n_targets ); ++ if(!test) ++ n_targets = 0; //otherwise it will be -1. ++ ++ //Add the targets to the C++ container: ++ for(int i = 0; i < n_targets; i++) ++ { ++ //Convert the atom to a string: ++ gchar* const atom_name = gdk_atom_name(targets[i]); ++ ++ Glib::ustring target; ++ if(atom_name) ++ target = Glib::ScopedPtr(atom_name).get(); //This frees the gchar*. ++ ++ listTargets.push_back(target); ++ } ++} ++ ++/* ####################################### ++ ClipboardManager class ++ ####################################### */ ++ ++ClipboardManager *ClipboardManager::_instance = NULL; ++ ++ClipboardManager::ClipboardManager() {} ++ClipboardManager::~ClipboardManager() {} ++ClipboardManager *ClipboardManager::get() ++{ ++ if ( _instance == NULL ) ++ _instance = new ClipboardManagerImpl; ++ return _instance; ++} ++ ++} // namespace Inkscape ++} // namespace IO ++ ++/* ++ Local Variables: ++ mode:c++ ++ c-file-style:"stroustrup" ++ c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) ++ indent-tabs-mode:nil ++ fill-column:99 ++ End: ++*/ ++// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 :