// SPDX-License-Identifier: GPL-2.0-or-later /** * @file * Drag and drop of drawings onto canvas. */ /* Authors: * * Copyright (C) Tavmjong Bah 2019 * * Released under GNU GPL v2+, read the file 'COPYING' for more information. */ #include "drag-and-drop.h" #include // Internationalization #include "desktop-style.h" #include "document.h" #include "document-undo.h" #include "gradient-drag.h" #include "file.h" #include "selection.h" #include "style.h" #include "layer-manager.h" #include "extension/db.h" #include "extension/find_extension_by_mime.h" #include "object/sp-shape.h" #include "object/sp-text.h" #include "object/sp-flowtext.h" #include "path/path-util.h" #include "svg/svg-color.h" // write color #include "ui/clipboard.h" #include "ui/interface.h" #include "ui/tools/tool-base.h" #include "ui/widget/canvas.h" // Target, canvas to world transform. #include "widgets/desktop-widget.h" #include "widgets/ege-paint-def.h" using Inkscape::DocumentUndo; /* Drag and Drop */ enum ui_drop_target_info { URI_LIST, SVG_XML_DATA, SVG_DATA, PNG_DATA, JPEG_DATA, IMAGE_DATA, APP_X_INKY_COLOR, APP_X_COLOR, APP_OSWB_COLOR, APP_X_INK_PASTE }; static GtkTargetEntry ui_drop_target_entries [] = { // clang-format off {(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 }, {(gchar *)"application/x-oswb-color", 0, APP_OSWB_COLOR }, {(gchar *)"application/x-color", 0, APP_X_COLOR }, {(gchar *)"application/x-inkscape-paste", 0, APP_X_INK_PASTE } // clang-format on }; static GtkTargetEntry *completeDropTargets = nullptr; static int completeDropTargetsCount = 0; static guint nui_drop_target_entries = G_N_ELEMENTS(ui_drop_target_entries); /* Drag and Drop */ static void ink_drag_data_received(GtkWidget *widget, GdkDragContext *drag_context, gint x, gint y, GtkSelectionData *data, guint info, guint /*event_time*/, gpointer user_data) { auto dtw = static_cast(user_data); SPDesktop *desktop = dtw->desktop; SPDocument *doc = desktop->doc(); switch (info) { case APP_X_COLOR: { int destX = 0; int destY = 0; auto canvas = dtw->get_canvas(); gtk_widget_translate_coordinates( widget, GTK_WIDGET(canvas->gobj()), x, y, &destX, &destY ); Geom::Point where( canvas->canvas_to_world(Geom::Point(destX, destY))); Geom::Point const button_dt(desktop->w2d(where)); Geom::Point const button_doc(desktop->dt2doc(button_dt)); if ( gtk_selection_data_get_length (data) == 8 ) { gchar colorspec[64] = {0}; // Careful about endian issues. guint16* dataVals = (guint16*)gtk_selection_data_get_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->getItemAtPoint( 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) { DocumentUndo::done( doc , _("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) { // SPDocumentUndo::done( doc , _("Drop color on gradient stop"), ""); // } //} if (!consumed && item) { bool fillnotstroke = (gdk_drag_context_get_actions (drag_context) != 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); std::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 = ( !item->style->stroke.isNone() ? desktop->current_zoom() * item->style->stroke_width.computed * item->i2dt_affine().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(); DocumentUndo::done( doc , _("Drop color"), "" ); } } } break; case APP_OSWB_COLOR: { bool worked = false; Glib::ustring colorspec; if ( gtk_selection_data_get_format (data) == 8 ) { ege::PaintDef color; worked = color.fromMIMEData("application/x-oswb-color", reinterpret_cast(gtk_selection_data_get_data (data)), gtk_selection_data_get_length (data), gtk_selection_data_get_format (data)); 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 = nullptr; std::vector gradients = doc->getResourceList("gradient"); for (auto gradient : gradients) { SPGradient* grad = SP_GRADIENT(gradient); if ( color.descr == grad->getId() ) { if ( grad->hasStops() ) { matches = grad; break; } } } if (matches) { colorspec = "url(#"; colorspec += matches->getId(); 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; auto canvas = dtw->get_canvas(); gtk_widget_translate_coordinates( widget, GTK_WIDGET(canvas->gobj()), x, y, &destX, &destY ); Geom::Point where( canvas->canvas_to_world(Geom::Point(destX, destY))); Geom::Point const button_dt(desktop->w2d(where)); Geom::Point const button_doc(desktop->dt2doc(button_dt)); SPItem *item = desktop->getItemAtPoint( 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) { DocumentUndo::done( doc, _("Drop color on gradient"), "" ); desktop->event_context->get_drag()->updateDraggers(); } } if (!consumed && item) { bool fillnotstroke = (gdk_drag_context_get_actions (drag_context) != 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); std::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 = ( !item->style->stroke.isNone() ? desktop->current_zoom() * item->style->stroke_width.computed * item->i2dt_affine().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(); DocumentUndo::done( doc, _("Drop color"), "" ); } } } break; case SVG_DATA: case SVG_XML_DATA: { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); prefs->setBool("/options/onimport", true); gchar *svgdata = (gchar *)gtk_selection_data_get_data (data); Inkscape::XML::Document *rnewdoc = sp_repr_read_mem(svgdata, gtk_selection_data_get_length (data), SP_SVG_NS_URI); if (rnewdoc == nullptr) { sp_ui_error_dialog(_("Could not parse SVG data")); return; } Inkscape::XML::Node *repr = rnewdoc->root(); gchar const *style = repr->attribute("style"); Inkscape::XML::Document * xml_doc = doc->getReprDoc(); Inkscape::XML::Node *newgroup = xml_doc->createElement("svg:g"); newgroup->setAttribute("style", style); for (Inkscape::XML::Node *child = repr->firstChild(); child != nullptr; 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 = nullptr; new_obj = desktop->layerManager().currentLayer()->appendChildRepr(newgroup); Inkscape::Selection *selection = desktop->getSelection(); selection->set(SP_ITEM(new_obj)); // move to mouse pointer { desktop->getDocument()->ensureUpToDate(); Geom::OptRect sel_bbox = selection->visualBounds(); if (sel_bbox) { Geom::Point m( desktop->point() - sel_bbox->midpoint() ); selection->moveRelative(m, false); } } Inkscape::GC::release(newgroup); DocumentUndo::done( doc, _("Drop SVG"), "" ); prefs->setBool("/options/onimport", false); break; } case URI_LIST: { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); prefs->setBool("/options/onimport", true); gchar *uri = (gchar *)gtk_selection_data_get_data (data); sp_ui_import_files(uri); prefs->setBool("/options/onimport", false); break; } case APP_X_INK_PASTE: { Inkscape::UI::ClipboardManager *cm = Inkscape::UI::ClipboardManager::get(); cm->paste(desktop); DocumentUndo::done( doc, _("Drop Symbol"), "" ); break; } case PNG_DATA: case JPEG_DATA: case IMAGE_DATA: { Inkscape::Extension::Extension *ext = Inkscape::Extension::find_by_mime((info == JPEG_DATA ? "image/jpeg" : "image/png")); bool save = (strcmp(ext->get_param_optiongroup("link"), "embed") == 0); ext->set_param_optiongroup("link", "embed"); ext->set_gui(false); gchar *filename = g_build_filename( g_get_tmp_dir(), "inkscape-dnd-import", nullptr ); g_file_set_contents(filename, reinterpret_cast(gtk_selection_data_get_data (data)), gtk_selection_data_get_length (data), nullptr); file_import(doc, filename, ext); g_free(filename); ext->set_param_optiongroup("link", save ? "embed" : "link"); ext->set_gui(true); DocumentUndo::done( doc, _("Drop bitmap image"), "" ); break; } } } #if 0 static void ink_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 ink_drag_leave( GtkWidget */*widget*/, GdkDragContext */*drag_context*/, guint /*event_time*/, gpointer /*user_data*/ ) { // g_message("drag-n-drop leave at %d", event_time); } #endif void ink_drag_setup(SPDesktopWidget* dtw) { if ( completeDropTargets == nullptr || completeDropTargetsCount == 0 ) { std::vector types; std::vector list = Gdk::Pixbuf::get_formats(); for (auto one:list) { std::vector typesXX = one.get_mime_types(); for (auto i:typesXX) { types.push_back(i); } } 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 (auto & type : types) { completeDropTargets[pos].target = g_strdup(type.c_str()); completeDropTargets[pos].flags = 0; completeDropTargets[pos].info = IMAGE_DATA; pos++; } } auto canvas = dtw->get_canvas(); gtk_drag_dest_set(GTK_WIDGET(canvas->gobj()), GTK_DEST_DEFAULT_ALL, completeDropTargets, completeDropTargetsCount, GdkDragAction(GDK_ACTION_COPY | GDK_ACTION_MOVE)); g_signal_connect(G_OBJECT(canvas->gobj()), "drag_data_received", G_CALLBACK(ink_drag_data_received), dtw); #if 0 g_signal_connect(G_OBJECT(win->gobj()), "drag_motion", G_CALLBACK(ink_drag_motion), NULL); g_signal_connect(G_OBJECT(win->gobj()), "drag_leave", G_CALLBACK(ink_drag_leave), NULL); #endif } /* 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:fileencoding=utf-8:textwidth=99 :