Inkscape
Vector Graphics Editor
Loading...
Searching...
No Matches
drag-and-drop.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-2.0-or-later
7/* Authors:
8 *
9 * Copyright (C) Tavmjong Bah 2019
10 *
11 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
12 */
13
14#include "drag-and-drop.h"
15
16#include <span>
17#include <glibmm/i18n.h>
18#include <glibmm/value.h>
19#include <glibmm/miscutils.h>
20#include <giomm/inputstream.h>
21#include <giomm/memoryoutputstream.h>
22#include <gtkmm/droptarget.h>
23
24#include "desktop-style.h"
25#include "desktop.h"
26#include "document.h"
27#include "document-undo.h"
28#include "gradient-drag.h"
29#include "file.h"
30#include "selection.h"
31#include "style.h"
32#include "layer-manager.h"
33
34#include "colors/dragndrop.h"
35#include "extension/input.h"
36
37#include "object/sp-shape.h"
38#include "object/sp-text.h"
39#include "object/sp-flowtext.h"
40
41#include "path/path-util.h"
42
43#include "ui/clipboard.h"
44#include "ui/interface.h"
45#include "ui/tools/tool-base.h"
47#include "ui/widget/canvas.h" // Target, canvas to world transform.
49#include "util/value-utils.h"
50
52using namespace Inkscape::Util;
53
54namespace {
55
56/*
57 * Gtk API wrapping - Todo: Improve gtkmm.
58 */
59
60template <typename T, typename F>
61void foreach(GSList *list, F &&f)
62{
63 g_slist_foreach(list, +[] (void *ptr, void *data) {
64 auto t = reinterpret_cast<T *>(ptr);
65 auto f = reinterpret_cast<F *>(data);
66 f->operator()(t);
67 }, &f);
68}
69
70std::span<char const> get_span(Glib::RefPtr<Glib::Bytes> const &bytes)
71{
72 gsize size{};
73 return {reinterpret_cast<char const *>(bytes->get_data(size)), size};
74}
75
76template <typename T>
77Glib::RefPtr<Glib::Bytes> make_bytes(T &&t)
78{
79 using Td = std::decay_t<T>;
80 auto const p = new Td(std::forward<T>(t));
81 auto const span = std::span<char const>(*p);
82 return Glib::wrap(g_bytes_new_with_free_func(span.data(), span.size_bytes(), +[] (void *p) {
83 delete reinterpret_cast<Td *>(p);
84 }, p));
85}
86
87template <typename T>
88Glib::ValueBase from_bytes(Glib::RefPtr<Glib::Bytes> &&bytes, char const *mime_type) = delete;
89
90template <typename T>
91void deserialize_func(GdkContentDeserializer *deserializer)
92{
93 auto const in = Glib::wrap(gdk_content_deserializer_get_input_stream(deserializer), true);
94 auto const out = Gio::MemoryOutputStream::create();
95 out->splice_async(in, [deserializer, out] (Glib::RefPtr<Gio::AsyncResult> &result) {
96 try {
97 out->splice_finish(result);
98 out->close();
99 *gdk_content_deserializer_get_value(deserializer) = GlibValue::release(from_bytes<T>(out->steal_as_bytes(), gdk_content_deserializer_get_mime_type(deserializer)));
100 gdk_content_deserializer_return_success(deserializer);
101 } catch (Glib::Error const &error) {
102 gdk_content_deserializer_return_error(deserializer, g_error_copy(error.gobj()));
103 }
104 }, Gio::OutputStream::SpliceFlags::CLOSE_SOURCE);
105};
106
107template <typename T>
108void register_deserializer(char const *mime_type)
109{
110 gdk_content_register_deserializer(mime_type, GlibValue::type<T>(), deserialize_func<T>, nullptr, nullptr);
111}
112
113template <typename T>
114Glib::RefPtr<Glib::Bytes> to_bytes(T const &t, char const *mime_type) = delete;
115
116template <typename T>
117void serialize_func(GdkContentSerializer *serializer)
118{
119 auto const out = Glib::wrap(gdk_content_serializer_get_output_stream(serializer), true);
120 auto const bytes = to_bytes(*GlibValue::get<T>(gdk_content_serializer_get_value(serializer)), gdk_content_serializer_get_mime_type(serializer));
121 auto const span = get_span(bytes);
122 out->write_all_async(span.data(), span.size_bytes(), [serializer, out, bytes] (Glib::RefPtr<Gio::AsyncResult> &result) {
123 try {
124 gsize _;
125 out->write_all_finish(result, _);
126 gdk_content_serializer_return_success(serializer);
127 } catch (Glib::Error const &error) {
128 gdk_content_serializer_return_error(serializer, g_error_copy(error.gobj()));
129 }
130 });
131};
132
133template <typename T>
134void register_serializer(char const *mime_type)
135{
136 gdk_content_register_serializer(GlibValue::type<T>(), mime_type, serialize_func<T>, nullptr, nullptr);
137}
138
139/*
140 * Actual code
141 */
142
143struct DnDSvg
144{
145 Glib::RefPtr<Glib::Bytes> bytes;
146};
147
148template <>
149Glib::ValueBase from_bytes<DnDSvg>(Glib::RefPtr<Glib::Bytes> &&bytes, char const *)
150{
151 return GlibValue::create<DnDSvg>(DnDSvg{std::move(bytes)});
152}
153
154template <>
155Glib::ValueBase from_bytes<Colors::Paint>(Glib::RefPtr<Glib::Bytes> &&bytes, char const *mime_type)
156{
157 try {
158 return GlibValue::create<Colors::Paint>(Colors::fromMIMEData(get_span(bytes), mime_type));
159 } catch (Colors::ColorError const &c) {
160 throw Glib::Error(G_FILE_ERROR, 0, c.what());
161 }
162}
163
164template <>
165Glib::RefPtr<Glib::Bytes> to_bytes<Colors::Paint>(Colors::Paint const &paint, char const *mime_type)
166{
167 return make_bytes(getMIMEData(paint, mime_type));
168}
169
170template <>
171Glib::RefPtr<Glib::Bytes> to_bytes<DnDSymbol>(DnDSymbol const &symbol, char const *)
172{
173 return make_bytes(symbol.id.raw());
174}
175
176std::vector<GType> const &get_drop_types()
177{
178 static auto const instance = [] () -> std::vector<GType> {
179 for (auto mime_type : {"image/svg", "image/svg+xml"}) {
180 register_deserializer<DnDSvg>(mime_type);
181 }
182
183 for (auto mime_type : {Colors::mimeOSWB_COLOR, Colors::mimeX_COLOR}) {
184 register_deserializer<Colors::Paint>(mime_type);
185 }
186
187 for (auto mime_type : {Colors::mimeOSWB_COLOR, Colors::mimeX_COLOR, Colors::mimeTEXT}) {
188 register_serializer<Colors::Paint>(mime_type);
189 }
190
191 register_serializer<DnDSymbol>("text/plain;charset=utf-8");
192
193 return {
194 GlibValue::type<Colors::Paint>(),
195 GlibValue::type<DnDSvg>(),
196 GDK_TYPE_FILE_LIST,
197 GlibValue::type<DnDSymbol>(),
198 GDK_TYPE_TEXTURE
199 };
200 }();
201
202 return instance;
203}
204
205bool on_drop(Glib::ValueBase const &value, double x, double y, SPDesktopWidget *dtw, Glib::RefPtr<Gtk::DropTarget> const &drop_target)
206{
207 auto const desktop = dtw->get_desktop();
208 auto const canvas = dtw->get_canvas();
209 auto const doc = desktop->doc();
210 auto const prefs = Inkscape::Preferences::get();
211
212 auto const canvas_pos = Geom::Point{std::round(x), std::round(y)};
213 auto const world_pos = canvas->canvas_to_world(canvas_pos);
214 auto const dt_pos = desktop->w2d(world_pos);
215
216 if (auto const ptr = GlibValue::get<Colors::Paint>(value)) {
217 auto paint = *ptr;
218 auto const item = desktop->getItemAtPoint(world_pos, true);
219 if (!item) {
220 return false;
221 }
222
223 auto find_gradient = [&] (Colors::Color const &color) -> SPGradient * {
224 for (auto obj : doc->getResourceList("gradient")) {
225 auto const grad = cast_unsafe<SPGradient>(obj);
226 if (grad->hasStops() && grad->getId() == color.getName()) {
227 return grad;
228 }
229 }
230 return nullptr;
231 };
232
233 std::string colorspec;
234 if (std::holds_alternative<Colors::NoColor>(paint)) {
235 colorspec = "none";
236 } else {
237 auto &color = std::get<Colors::Color>(paint);
238 if (auto const grad = find_gradient(color)) {
239 colorspec = std::string{"url(#"} + grad->getId() + ")";
240 } else {
241 colorspec = color.toString();
242 }
243 }
244
245 if (desktop->getTool() && desktop->getTool()->get_drag()) {
246 if (desktop->getTool()->get_drag()->dropColor(item, colorspec.c_str(), dt_pos)) {
247 DocumentUndo::done(doc , _("Drop color on gradient"), "");
249 return true;
250 }
251 }
252
253 //if (tools_active(desktop, TOOLS_TEXT)) {
254 // if (sp_text_context_drop_color(c, button_doc)) {
255 // SPDocumentUndo::done(doc , _("Drop color on gradient stop"), "");
256 // }
257 //}
258
259 bool fillnotstroke = drop_target->get_current_drop()->get_actions() != Gdk::DragAction::MOVE;
260 if (fillnotstroke && (is<SPShape>(item) || is<SPText>(item) || is<SPFlowtext>(item))) {
261 if (auto const curve = curve_for_item(item)) {
262 auto const pathv = *curve * (item->i2dt_affine() * desktop->d2w());
263
264 double dist;
265 pathv.nearestTime(world_pos, &dist);
266
267 double const stroke_tolerance =
268 (!item->style->stroke.isNone() ?
270 item->style->stroke_width.computed *
271 item->i2dt_affine().descrim() * 0.5
272 : 0.0)
273 + prefs->getIntLimited("/options/dragtolerance/value", 0, 0, 100);
274
275 if (dist < stroke_tolerance) {
276 fillnotstroke = false;
277 }
278 }
279 }
280
281 auto const css = sp_repr_css_attr_new();
282 sp_repr_css_set_property_string(css, fillnotstroke ? "fill" : "stroke", colorspec);
285
286 item->updateRepr();
287 DocumentUndo::done(doc, _("Drop color"), "");
288 return true;
289 } else if (auto const dndsvg = GlibValue::get<DnDSvg>(value)) {
290 auto const data = get_span(dndsvg->bytes);
291 if (data.empty()) {
292 return false;
293 }
294
295 auto const newdoc = sp_repr_read_mem(data.data(), data.size_bytes(), SP_SVG_NS_URI);
296 if (!newdoc) {
297 sp_ui_error_dialog(_("Could not parse SVG data"));
298 return false;
299 }
300
301 auto const root = newdoc->root();
302 auto const style = root->attribute("style");
303
304 auto const xml_doc = doc->getReprDoc();
305 auto const newgroup = xml_doc->createElement("svg:g");
306 newgroup->setAttribute("style", style);
307 for (auto child = root->firstChild(); child; child = child->next()) {
308 newgroup->appendChild(child->duplicate(xml_doc));
309 }
310
311 Inkscape::GC::release(newdoc);
312
313 // Add it to the current layer
314
315 // Greg's edits to add intelligent positioning of svg drops
316 auto const new_obj = desktop->layerManager().currentLayer()->appendChildRepr(newgroup);
317
318 auto const selection = desktop->getSelection();
319 selection->set(cast<SPItem>(new_obj));
320
321 // move to mouse pointer
323 if (auto const sel_bbox = selection->visualBounds()) {
324 selection->moveRelative(desktop->point() - sel_bbox->midpoint(), false);
325 }
326
327 Inkscape::GC::release(newgroup);
328 DocumentUndo::done(doc, _("Drop SVG"), "");
329 return true;
330 } else if (G_VALUE_HOLDS(value.gobj(), GDK_TYPE_FILE_LIST)) {
331 auto list = reinterpret_cast<GSList *>(g_value_get_boxed(value.gobj()));
332 foreach<GFile>(list, [&] (GFile *f) {
333 auto const path = g_file_get_path(f);
334 if (path && std::strlen(path) > 2) {
335 file_import(doc, path, nullptr);
336 }
337 });
338
339 return true;
340 } else if (GlibValue::holds<DnDSymbol>(value)) {
342 cm->insertSymbol(desktop, dt_pos, false);
343 DocumentUndo::done(doc, _("Drop Symbol"), "");
344 return true;
345 } else if (G_VALUE_HOLDS(value.gobj(), GDK_TYPE_TEXTURE)) {
346 auto const ext = Inkscape::Extension::Input::find_by_mime("image/png");
347 bool const save = std::strcmp(ext->get_param_optiongroup("link"), "embed") == 0;
348 ext->set_param_optiongroup("link", "embed");
349 ext->set_gui(false);
350
351 // Absolutely stupid, and the same for clipboard.cpp.
352 auto const filename = Glib::build_filename(Glib::get_user_cache_dir(), "inkscape-dnd-import");
353 auto const img = Glib::wrap(GDK_TEXTURE(g_value_get_object(value.gobj())), true);
354 img->save_to_png(filename);
355 file_import(doc, filename, ext);
356 unlink(filename.c_str());
357
358 ext->set_param_optiongroup("link", save ? "embed" : "link");
359 ext->set_gui(true);
360 DocumentUndo::done(doc, _("Drop bitmap image"), "");
361 return true;
362 }
363
364 return false;
365}
366
367} // namespace
368
369void ink_drag_setup(SPDesktopWidget *dtw, Gtk::Widget *widget)
370{
371 auto drop_target = Gtk::DropTarget::create(G_TYPE_INVALID, Gdk::DragAction::COPY | Gdk::DragAction::MOVE);
372 drop_target->set_gtypes(get_drop_types());
373 drop_target->signal_drop().connect(sigc::bind(&on_drop, dtw, drop_target), false);
374 widget->add_controller(drop_target);
375}
376
377/*
378 Local Variables:
379 mode:c++
380 c-file-style:"stroustrup"
381 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
382 indent-tabs-mode:nil
383 fill-column:99
384 End:
385*/
386// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :
Rewrite of code originally in desktop-widget.cpp.
Inkscape canvas widget.
Coord descrim() const
Calculate the descriminant.
Definition affine.cpp:434
Two-dimensional point that doubles as a vector.
Definition point.h:66
void updateDraggers()
Regenerates the draggers list from the current selection; is called when selection is changed or modi...
bool dropColor(SPItem *item, gchar const *c, Geom::Point p)
static void done(SPDocument *document, Glib::ustring const &event_description, Glib::ustring const &undo_icon, unsigned int object_modified_tag=0)
static Extension * find_by_mime(char const *mime)
Get an input extension by mime-type matching.
Definition input.cpp:237
SPGroup * currentLayer() const
Returns current top layer.
static Preferences * get()
Access the singleton Preferences object.
void set(XML::Node *repr)
Set the selection to an XML node's SPObject.
Definition selection.h:118
static ClipboardManager * get()
A GtkBox on an SPDesktop.
Inkscape::UI::Widget::Canvas * get_canvas()
SPDesktop * get_desktop()
double current_zoom() const
Definition desktop.h:335
SPDocument * getDocument() const
Definition desktop.h:189
Geom::Affine const & d2w() const
Transformation from desktop to window coordinates.
Definition desktop.h:419
Geom::Point point() const
Returns the mouse point in desktop coordinates; if mouse is outside the canvas, returns the center of...
Definition desktop.cpp:378
SPItem * getItemAtPoint(Geom::Point const &p, bool into_groups, SPItem *upto=nullptr) const
Definition desktop.cpp:352
Inkscape::Selection * getSelection() const
Definition desktop.h:188
Inkscape::UI::Tools::ToolBase * getTool() const
Definition desktop.h:187
SPDocument * doc() const
Definition desktop.h:159
Inkscape::LayerManager & layerManager()
Definition desktop.h:287
Geom::Affine const & w2d() const
Transformation from window to desktop coordinates (zoom/rotate).
Definition desktop.h:416
int ensureUpToDate(unsigned int object_modified_tag=0)
Repeatedly works on getting the document updated, since sometimes it takes more than one pass to get ...
Gradient.
Definition sp-gradient.h:86
Geom::Affine i2dt_affine() const
Returns the transformation from item to desktop coords.
Definition sp-item.cpp:1829
SPStyle * style
Represents the style properties, whether from presentation attributes, the style attribute,...
Definition sp-object.h:248
Inkscape::XML::Node * updateRepr(unsigned int flags=SP_OBJECT_WRITE_EXT)
Updates the object's repr based on the object's state.
SPObject * appendChildRepr(Inkscape::XML::Node *repr)
Append repr as child of this object.
T< SPAttr::STROKE, SPIPaint > stroke
stroke
Definition style.h:247
T< SPAttr::STROKE_WIDTH, SPILength > stroke_width
stroke-width
Definition style.h:249
System-wide clipboard management - class declaration.
RootCluster root
std::shared_ptr< Css const > css
Css & result
Geom::IntPoint size
double c[8][4]
void sp_desktop_apply_css_recursive(SPObject *o, SPCSSAttr *css, bool skip_lines)
Apply style on object and children, recursively.
A class to hold:
Editable view implementation.
TODO: insert short description here.
void ink_drag_setup(SPDesktopWidget *dtw, Gtk::Widget *widget)
Drag and drop of drawings onto canvas.
SPObject * file_import(SPDocument *in_doc, const std::string &path, Inkscape::Extension::Extension *key)
Import a resource.
Definition file.cpp:726
SPItem * item
void sp_ui_error_dialog(char const *message)
Definition interface.cpp:56
double dist(const Point &a, const Point &b)
Definition geometry.cpp:310
std::vector< char > getMIMEData(Paint const &paint, char const *mime_type)
Convert a paint into a draggable object.
Definition dragndrop.cpp:24
std::variant< NoColor, Color > Paint
Definition xml-color.h:32
void save(Extension *key, SPDocument *doc, gchar const *filename, bool check_overwrite, bool official, Inkscape::Extension::FileSaveMethod save_method)
This is a generic function to use the save function of a module (including Autodetect)
Definition system.cpp:162
static R & release(R &r)
Decrements the reference count of a anchored object.
GValue release(Glib::ValueBase &&value)
Release the value from its owning wrapper, leaving the original in an empty state.
Miscellaneous supporting code.
Definition document.h:93
std::optional< Geom::PathVector > curve_for_item(SPItem *item)
Gets an SPCurve from the SPItem.
Definition path-util.cpp:73
Path utilities.
Ocnode * child[8]
Definition quantize.cpp:33
SPCSSAttr * sp_repr_css_attr_new()
Creates an empty SPCSSAttr (a class for manipulating CSS style properties).
Definition repr-css.cpp:67
void sp_repr_css_attr_unref(SPCSSAttr *css)
Unreferences an SPCSSAttr (will be garbage collected if no references remain).
Definition repr-css.cpp:76
void sp_repr_css_set_property_string(SPCSSAttr *css, char const *name, std::string const &value)
Set a style property to a standard string.
Definition repr-css.cpp:234
Document * sp_repr_read_mem(const gchar *buffer, gint length, const gchar *default_ns)
Reads and parses XML from a buffer, returning it as an Document.
Definition repr-io.cpp:324
TODO: insert short description here.
static const Point data[]
Glib::ustring id
Definition curve.h:24
SPStyle - a style object for SPItem objects.
SPDesktop * desktop
Wrapper for the GLib value API.