1 | // Aseprite |
2 | // Copyright (C) 2019-2021 Igara Studio S.A. |
3 | // Copyright (C) 2001-2018 David Capello |
4 | // |
5 | // This program is distributed under the terms of |
6 | // the End-User License Agreement for Aseprite. |
7 | |
8 | #ifdef HAVE_CONFIG_H |
9 | #include "config.h" |
10 | #endif |
11 | |
12 | #include "app/app.h" |
13 | #include "app/cmd/clear_mask.h" |
14 | #include "app/cmd/deselect_mask.h" |
15 | #include "app/cmd/set_mask.h" |
16 | #include "app/cmd/trim_cel.h" |
17 | #include "app/console.h" |
18 | #include "app/context_access.h" |
19 | #include "app/doc.h" |
20 | #include "app/doc_api.h" |
21 | #include "app/doc_range.h" |
22 | #include "app/doc_range_ops.h" |
23 | #include "app/modules/editors.h" |
24 | #include "app/modules/gfx.h" |
25 | #include "app/modules/gui.h" |
26 | #include "app/pref/preferences.h" |
27 | #include "app/tx.h" |
28 | #include "app/ui/color_bar.h" |
29 | #include "app/ui/editor/editor.h" |
30 | #include "app/ui/skin/skin_theme.h" |
31 | #include "app/ui/timeline/timeline.h" |
32 | #include "app/ui_context.h" |
33 | #include "app/util/cel_ops.h" |
34 | #include "app/util/clipboard.h" |
35 | #include "app/util/new_image_from_mask.h" |
36 | #include "app/util/range_utils.h" |
37 | #include "clip/clip.h" |
38 | #include "doc/doc.h" |
39 | #include "render/dithering.h" |
40 | #include "render/ordered_dither.h" |
41 | #include "render/quantization.h" |
42 | |
43 | #include <memory> |
44 | #include <stdexcept> |
45 | |
46 | namespace app { |
47 | |
48 | using namespace doc; |
49 | |
50 | namespace { |
51 | |
52 | class ClipboardRange : public DocsObserver { |
53 | public: |
54 | ClipboardRange() : m_doc(nullptr) { |
55 | } |
56 | |
57 | ~ClipboardRange() { |
58 | ASSERT(!m_doc); |
59 | } |
60 | |
61 | void observeUIContext() { |
62 | UIContext::instance()->documents().add_observer(this); |
63 | } |
64 | |
65 | void unobserveUIContext() { |
66 | UIContext::instance()->documents().remove_observer(this); |
67 | } |
68 | |
69 | bool valid() const { |
70 | return (m_doc != nullptr); |
71 | } |
72 | |
73 | void invalidate() { |
74 | m_doc = nullptr; |
75 | } |
76 | |
77 | void setRange(Doc* doc, const DocRange& range) { |
78 | m_doc = doc; |
79 | m_range = range; |
80 | } |
81 | |
82 | Doc* document() const { return m_doc; } |
83 | DocRange range() const { return m_range; } |
84 | |
85 | // DocsObserver impl |
86 | void onRemoveDocument(Doc* doc) override { |
87 | if (doc == m_doc) |
88 | invalidate(); |
89 | } |
90 | |
91 | private: |
92 | Doc* m_doc; |
93 | DocRange m_range; |
94 | }; |
95 | |
96 | } |
97 | |
98 | // Data in the clipboard |
99 | struct Clipboard::Data { |
100 | // Text used when the native clipboard is disabled |
101 | std::string text; |
102 | |
103 | // RGB/Grayscale/Indexed image |
104 | ImageRef image; |
105 | |
106 | // The palette of the image (or tileset) if it's indexed |
107 | std::shared_ptr<Palette> palette; |
108 | |
109 | // In case we copy a tilemap information |
110 | ImageRef tilemap; |
111 | |
112 | // Tileset for the tilemap or a set of tiles if we are copying tiles |
113 | // in the color bar |
114 | std::shared_ptr<Tileset> tileset; |
115 | |
116 | // Selected entries copied from the palette or the tileset |
117 | PalettePicks picks; |
118 | |
119 | // Original selection used to copy the image |
120 | std::shared_ptr<Mask> mask; |
121 | |
122 | // Selected set of layers/layers/cels |
123 | ClipboardRange range; |
124 | |
125 | Data() { |
126 | range.observeUIContext(); |
127 | } |
128 | |
129 | ~Data() { |
130 | clear(); |
131 | range.unobserveUIContext(); |
132 | } |
133 | |
134 | void clear() { |
135 | text.clear(); |
136 | image.reset(); |
137 | palette.reset(); |
138 | tilemap.reset(); |
139 | tileset.reset(); |
140 | picks.clear(); |
141 | mask.reset(); |
142 | range.invalidate(); |
143 | } |
144 | |
145 | ClipboardFormat format() const { |
146 | if (image) |
147 | return ClipboardFormat::Image; |
148 | else if (tilemap) |
149 | return ClipboardFormat::Tilemap; |
150 | else if (range.valid()) |
151 | return ClipboardFormat::DocRange; |
152 | else if (palette && picks.picks()) |
153 | return ClipboardFormat::PaletteEntries; |
154 | else if (tileset && picks.picks()) |
155 | return ClipboardFormat::Tileset; |
156 | else |
157 | return ClipboardFormat::None; |
158 | } |
159 | }; |
160 | |
161 | static bool use_native_clipboard() |
162 | { |
163 | return Preferences::instance().experimental.useNativeClipboard(); |
164 | } |
165 | |
166 | static Clipboard* g_instance = nullptr; |
167 | |
168 | Clipboard* Clipboard::instance() |
169 | { |
170 | return g_instance; |
171 | } |
172 | |
173 | Clipboard::Clipboard() |
174 | : m_data(new Data) |
175 | { |
176 | ASSERT(!g_instance); |
177 | g_instance = this; |
178 | |
179 | registerNativeFormats(); |
180 | } |
181 | |
182 | Clipboard::~Clipboard() |
183 | { |
184 | ASSERT(g_instance == this); |
185 | g_instance = nullptr; |
186 | } |
187 | |
188 | void Clipboard::setClipboardText(const std::string& text) |
189 | { |
190 | if (use_native_clipboard()) { |
191 | clip::set_text(text); |
192 | } |
193 | else { |
194 | m_data->text = text; |
195 | } |
196 | } |
197 | |
198 | bool Clipboard::getClipboardText(std::string& text) |
199 | { |
200 | if (use_native_clipboard()) { |
201 | return clip::get_text(text); |
202 | } |
203 | else { |
204 | text = m_data->text; |
205 | return true; |
206 | } |
207 | } |
208 | |
209 | void Clipboard::setData(Image* image, |
210 | Mask* mask, |
211 | Palette* palette, |
212 | Tileset* tileset, |
213 | bool set_native_clipboard, |
214 | bool image_source_is_transparent) |
215 | { |
216 | const bool isTilemap = (image && image->isTilemap()); |
217 | |
218 | m_data->clear(); |
219 | m_data->palette.reset(palette); |
220 | m_data->tileset.reset(tileset); |
221 | m_data->mask.reset(mask); |
222 | if (isTilemap) |
223 | m_data->tilemap.reset(image); |
224 | else |
225 | m_data->image.reset(image); |
226 | |
227 | if (set_native_clipboard) { |
228 | // Copy tilemap to the native clipboard |
229 | if (isTilemap) { |
230 | ASSERT(tileset); |
231 | setNativeBitmap(image, mask, palette, tileset); |
232 | } |
233 | // Copy non-tilemap images to the native clipboard |
234 | else { |
235 | color_t oldMask = 0; |
236 | if (image) { |
237 | oldMask = image->maskColor(); |
238 | if (!image_source_is_transparent) |
239 | image->setMaskColor(-1); |
240 | } |
241 | |
242 | if (use_native_clipboard()) |
243 | setNativeBitmap(image, mask, palette); |
244 | |
245 | if (image && !image_source_is_transparent) |
246 | image->setMaskColor(oldMask); |
247 | } |
248 | } |
249 | } |
250 | |
251 | bool Clipboard::copyFromDocument(const Site& site, bool merged) |
252 | { |
253 | ASSERT(site.document()); |
254 | const Doc* doc = static_cast<const Doc*>(site.document()); |
255 | const Mask* mask = doc->mask(); |
256 | const Palette* pal = doc->sprite()->palette(site.frame()); |
257 | |
258 | if (!merged && |
259 | site.layer() && |
260 | site.layer()->isTilemap() && |
261 | site.tilemapMode() == TilemapMode::Tiles) { |
262 | const Tileset* ts = static_cast<LayerTilemap*>(site.layer())->tileset(); |
263 | |
264 | Image* image = new_tilemap_from_mask(site, mask); |
265 | if (!image) |
266 | return false; |
267 | |
268 | setData( |
269 | image, |
270 | (mask ? new Mask(*mask): nullptr), |
271 | (pal ? new Palette(*pal): nullptr), |
272 | Tileset::MakeCopyCopyingImages(ts), |
273 | true, // set native clipboard |
274 | site.layer() && !site.layer()->isBackground()); |
275 | |
276 | return true; |
277 | } |
278 | |
279 | Image* image = new_image_from_mask(site, mask, |
280 | Preferences::instance().experimental.newBlend(), |
281 | merged); |
282 | if (!image) |
283 | return false; |
284 | |
285 | setData( |
286 | image, |
287 | (mask ? new Mask(*mask): nullptr), |
288 | (pal ? new Palette(*pal): nullptr), |
289 | nullptr, |
290 | true, // set native clipboard |
291 | site.layer() && !site.layer()->isBackground()); |
292 | |
293 | return true; |
294 | } |
295 | |
296 | ClipboardFormat Clipboard::format() const |
297 | { |
298 | // Check if the native clipboard has an image |
299 | if (use_native_clipboard() && hasNativeBitmap()) { |
300 | return ClipboardFormat::Image; |
301 | } |
302 | else { |
303 | return m_data->format(); |
304 | } |
305 | } |
306 | |
307 | void Clipboard::getDocumentRangeInfo(Doc** document, DocRange* range) |
308 | { |
309 | if (m_data->range.valid()) { |
310 | *document = m_data->range.document(); |
311 | *range = m_data->range.range(); |
312 | } |
313 | else { |
314 | *document = NULL; |
315 | } |
316 | } |
317 | |
318 | void Clipboard::clearMaskFromCels(Tx& tx, |
319 | Doc* doc, |
320 | const Site& site, |
321 | const CelList& cels, |
322 | const bool deselectMask) |
323 | { |
324 | for (Cel* cel : cels) { |
325 | ObjectId celId = cel->id(); |
326 | |
327 | clear_mask_from_cel( |
328 | tx, cel, |
329 | site.tilemapMode(), |
330 | site.tilesetMode()); |
331 | |
332 | // Get cel again just in case the cmd::ClearMask() called cmd::ClearCel() |
333 | cel = doc::get<Cel>(celId); |
334 | if (site.shouldTrimCel(cel)) |
335 | tx(new cmd::TrimCel(cel)); |
336 | } |
337 | |
338 | if (deselectMask) |
339 | tx(new cmd::DeselectMask(doc)); |
340 | } |
341 | |
342 | void Clipboard::clearContent() |
343 | { |
344 | if (use_native_clipboard()) |
345 | clearNativeContent(); |
346 | m_data->clear(); |
347 | } |
348 | |
349 | void Clipboard::cut(ContextWriter& writer) |
350 | { |
351 | ASSERT(writer.document() != NULL); |
352 | ASSERT(writer.sprite() != NULL); |
353 | ASSERT(writer.layer() != NULL); |
354 | |
355 | if (!copyFromDocument(*writer.site())) { |
356 | Console console; |
357 | console.printf("Can't copying an image portion from the current layer\n" ); |
358 | } |
359 | else { |
360 | // TODO This code is similar to DocView::onClear() |
361 | { |
362 | Tx tx(writer.context(), "Cut" ); |
363 | Site site = writer.context()->activeSite(); |
364 | CelList cels; |
365 | if (site.range().enabled()) { |
366 | cels = get_unique_cels_to_edit_pixels(site.sprite(), site.range()); |
367 | } |
368 | else if (site.cel()) { |
369 | cels.push_back(site.cel()); |
370 | } |
371 | clearMaskFromCels(tx, |
372 | writer.document(), |
373 | site, |
374 | cels, |
375 | true); // Deselect mask |
376 | tx.commit(); |
377 | } |
378 | writer.document()->generateMaskBoundaries(); |
379 | update_screen_for_document(writer.document()); |
380 | } |
381 | } |
382 | |
383 | void Clipboard::copy(const ContextReader& reader) |
384 | { |
385 | ASSERT(reader.document() != NULL); |
386 | |
387 | if (!copyFromDocument(*reader.site())) { |
388 | Console console; |
389 | console.printf("Can't copying an image portion from the current layer\n" ); |
390 | return; |
391 | } |
392 | } |
393 | |
394 | void Clipboard::copyMerged(const ContextReader& reader) |
395 | { |
396 | ASSERT(reader.document() != NULL); |
397 | |
398 | copyFromDocument(*reader.site(), true); |
399 | } |
400 | |
401 | void Clipboard::copyRange(const ContextReader& reader, const DocRange& range) |
402 | { |
403 | ASSERT(reader.document() != NULL); |
404 | |
405 | ContextWriter writer(reader); |
406 | |
407 | clearContent(); |
408 | m_data->range.setRange(writer.document(), range); |
409 | |
410 | // TODO Replace this with a signal, because here the timeline |
411 | // depends on the clipboard and the clipboard on the timeline. |
412 | App::instance()->timeline()->activateClipboardRange(); |
413 | } |
414 | |
415 | void Clipboard::copyImage(const Image* image, |
416 | const Mask* mask, |
417 | const Palette* pal) |
418 | { |
419 | ASSERT(image->pixelFormat() != IMAGE_TILEMAP); |
420 | setData( |
421 | Image::createCopy(image), |
422 | (mask ? new Mask(*mask): nullptr), |
423 | (pal ? new Palette(*pal): nullptr), |
424 | nullptr, |
425 | true, false); |
426 | } |
427 | |
428 | void Clipboard::copyTilemap(const Image* image, |
429 | const Mask* mask, |
430 | const Palette* pal, |
431 | const Tileset* tileset) |
432 | { |
433 | ASSERT(image->pixelFormat() == IMAGE_TILEMAP); |
434 | setData( |
435 | Image::createCopy(image), |
436 | (mask ? new Mask(*mask): nullptr), |
437 | (pal ? new Palette(*pal): nullptr), |
438 | Tileset::MakeCopyCopyingImages(tileset), |
439 | true, false); |
440 | } |
441 | |
442 | void Clipboard::copyPalette(const Palette* palette, |
443 | const PalettePicks& picks) |
444 | { |
445 | if (!picks.picks()) |
446 | return; // Do nothing case |
447 | |
448 | setData(nullptr, |
449 | nullptr, |
450 | new Palette(*palette), |
451 | nullptr, |
452 | true, // set native clipboard |
453 | false); |
454 | m_data->picks = picks; |
455 | } |
456 | |
457 | void Clipboard::paste(Context* ctx, |
458 | const bool interactive) |
459 | { |
460 | Site site = ctx->activeSite(); |
461 | Doc* dstDoc = site.document(); |
462 | if (!dstDoc) |
463 | return; |
464 | |
465 | Sprite* dstSpr = site.sprite(); |
466 | if (!dstSpr) |
467 | return; |
468 | |
469 | switch (format()) { |
470 | |
471 | case ClipboardFormat::Image: { |
472 | // Get the image from the native clipboard. |
473 | if (!getImage(nullptr)) |
474 | return; |
475 | |
476 | ASSERT(m_data->image); |
477 | |
478 | Palette* dst_palette = dstSpr->palette(site.frame()); |
479 | |
480 | // Source image (clipboard or a converted copy to the destination 'imgtype') |
481 | ImageRef src_image; |
482 | if (// Copy image of the same pixel format |
483 | (m_data->image->pixelFormat() == dstSpr->pixelFormat() && |
484 | // Indexed images can be copied directly only if both images |
485 | // have the same palette. |
486 | (m_data->image->pixelFormat() != IMAGE_INDEXED || |
487 | m_data->palette->countDiff(dst_palette, NULL, NULL) == 0))) { |
488 | src_image = m_data->image; |
489 | } |
490 | else { |
491 | RgbMap* dst_rgbmap = dstSpr->rgbMap(site.frame()); |
492 | |
493 | src_image.reset( |
494 | render::convert_pixel_format( |
495 | m_data->image.get(), NULL, dstSpr->pixelFormat(), |
496 | render::Dithering(), |
497 | dst_rgbmap, m_data->palette.get(), |
498 | false, |
499 | 0)); |
500 | } |
501 | |
502 | if (current_editor && interactive) { |
503 | // TODO we don't support pasting in multiple cels at the |
504 | // moment, so we clear the range here (same as in |
505 | // PasteTextCommand::onExecute()) |
506 | App::instance()->timeline()->clearAndInvalidateRange(); |
507 | |
508 | // Change to MovingPixelsState |
509 | current_editor->pasteImage(src_image.get(), |
510 | m_data->mask.get()); |
511 | } |
512 | else { |
513 | // Non-interactive version (just copy the image to the cel) |
514 | Layer* dstLayer = site.layer(); |
515 | ASSERT(dstLayer); |
516 | if (!dstLayer || !dstLayer->isImage()) |
517 | return; |
518 | |
519 | Tx tx(ctx, "Paste Image" ); |
520 | DocApi api = dstDoc->getApi(tx); |
521 | Cel* dstCel = api.addCel( |
522 | static_cast<LayerImage*>(dstLayer), site.frame(), |
523 | ImageRef(Image::createCopy(src_image.get()))); |
524 | |
525 | // Adjust bounds |
526 | if (dstCel) { |
527 | if (m_data->mask) { |
528 | if (dstLayer->isReference()) { |
529 | dstCel->setBounds(dstSpr->bounds()); |
530 | |
531 | Mask emptyMask; |
532 | tx(new cmd::SetMask(dstDoc, &emptyMask)); |
533 | } |
534 | else { |
535 | dstCel->setBounds(m_data->mask->bounds()); |
536 | tx(new cmd::SetMask(dstDoc, m_data->mask.get())); |
537 | } |
538 | } |
539 | } |
540 | |
541 | tx.commit(); |
542 | } |
543 | break; |
544 | } |
545 | |
546 | case ClipboardFormat::Tilemap: { |
547 | if (current_editor && interactive) { |
548 | // TODO match both tilesets? |
549 | // TODO add post-command parameters (issue #2324) |
550 | |
551 | // Change to MovingTilemapState |
552 | current_editor->pasteImage(m_data->tilemap.get(), |
553 | m_data->mask.get()); |
554 | } |
555 | else { |
556 | // TODO non-interactive version (for scripts) |
557 | } |
558 | break; |
559 | } |
560 | |
561 | case ClipboardFormat::DocRange: { |
562 | DocRange srcRange = m_data->range.range(); |
563 | Doc* srcDoc = m_data->range.document(); |
564 | Sprite* srcSpr = srcDoc->sprite(); |
565 | |
566 | switch (srcRange.type()) { |
567 | |
568 | case DocRange::kCels: { |
569 | Layer* dstLayer = site.layer(); |
570 | ASSERT(dstLayer); |
571 | if (!dstLayer) |
572 | return; |
573 | |
574 | frame_t dstFrameFirst = site.frame(); |
575 | |
576 | DocRange dstRange; |
577 | dstRange.startRange(dstLayer, dstFrameFirst, DocRange::kCels); |
578 | for (layer_t i=1; i<srcRange.layers(); ++i) { |
579 | dstLayer = dstLayer->getPreviousBrowsable(); |
580 | if (dstLayer == nullptr) |
581 | break; |
582 | } |
583 | dstRange.endRange(dstLayer, dstFrameFirst+srcRange.frames()-1); |
584 | |
585 | // We can use a document range op (copy_range) to copy/paste |
586 | // cels in the same document. |
587 | if (srcDoc == dstDoc) { |
588 | // This is the app::copy_range (not clipboard::copy_range()). |
589 | if (srcRange.layers() == dstRange.layers()) |
590 | app::copy_range(srcDoc, srcRange, dstRange, kDocRangeBefore); |
591 | if (current_editor) |
592 | current_editor->invalidate(); // TODO check if this is necessary |
593 | return; |
594 | } |
595 | |
596 | Tx tx(ctx, "Paste Cels" ); |
597 | DocApi api = dstDoc->getApi(tx); |
598 | |
599 | // Add extra frames if needed |
600 | while (dstFrameFirst+srcRange.frames() > dstSpr->totalFrames()) |
601 | api.addFrame(dstSpr, dstSpr->totalFrames()); |
602 | |
603 | auto srcLayers = srcRange.selectedLayers().toBrowsableLayerList(); |
604 | auto dstLayers = dstRange.selectedLayers().toBrowsableLayerList(); |
605 | |
606 | auto srcIt = srcLayers.begin(); |
607 | auto dstIt = dstLayers.begin(); |
608 | auto srcEnd = srcLayers.end(); |
609 | auto dstEnd = dstLayers.end(); |
610 | |
611 | for (; srcIt != srcEnd && dstIt != dstEnd; ++srcIt, ++dstIt) { |
612 | auto srcLayer = *srcIt; |
613 | auto dstLayer = *dstIt; |
614 | |
615 | if (!srcLayer->isImage() || |
616 | !dstLayer->isImage()) |
617 | continue; |
618 | |
619 | frame_t dstFrame = dstFrameFirst; |
620 | for (frame_t srcFrame : srcRange.selectedFrames()) { |
621 | Cel* srcCel = srcLayer->cel(srcFrame); |
622 | |
623 | if (srcCel && srcCel->image()) { |
624 | api.copyCel( |
625 | static_cast<LayerImage*>(srcLayer), srcFrame, |
626 | static_cast<LayerImage*>(dstLayer), dstFrame); |
627 | } |
628 | else { |
629 | if (Cel* dstCel = dstLayer->cel(dstFrame)) |
630 | api.clearCel(dstCel); |
631 | } |
632 | |
633 | ++dstFrame; |
634 | } |
635 | } |
636 | |
637 | tx.commit(); |
638 | if (current_editor) |
639 | current_editor->invalidate(); // TODO check if this is necessary |
640 | break; |
641 | } |
642 | |
643 | case DocRange::kFrames: { |
644 | frame_t dstFrame = site.frame(); |
645 | |
646 | // We use a DocRange operation to copy frames inside |
647 | // the same sprite. |
648 | if (srcSpr == dstSpr) { |
649 | DocRange dstRange; |
650 | dstRange.startRange(nullptr, dstFrame, DocRange::kFrames); |
651 | dstRange.endRange(nullptr, dstFrame); |
652 | app::copy_range(srcDoc, srcRange, dstRange, kDocRangeBefore); |
653 | break; |
654 | } |
655 | |
656 | Tx tx(ctx, "Paste Frames" ); |
657 | DocApi api = dstDoc->getApi(tx); |
658 | |
659 | auto srcLayers = srcSpr->allBrowsableLayers(); |
660 | auto dstLayers = dstSpr->allBrowsableLayers(); |
661 | |
662 | for (frame_t srcFrame : srcRange.selectedFrames()) { |
663 | api.addEmptyFrame(dstSpr, dstFrame); |
664 | api.setFrameDuration(dstSpr, dstFrame, srcSpr->frameDuration(srcFrame)); |
665 | |
666 | auto srcIt = srcLayers.begin(); |
667 | auto dstIt = dstLayers.begin(); |
668 | auto srcEnd = srcLayers.end(); |
669 | auto dstEnd = dstLayers.end(); |
670 | |
671 | for (; srcIt != srcEnd && dstIt != dstEnd; ++srcIt, ++dstIt) { |
672 | auto srcLayer = *srcIt; |
673 | auto dstLayer = *dstIt; |
674 | |
675 | if (!srcLayer->isImage() || |
676 | !dstLayer->isImage()) |
677 | continue; |
678 | |
679 | Cel* cel = static_cast<LayerImage*>(srcLayer)->cel(srcFrame); |
680 | if (cel && cel->image()) { |
681 | api.copyCel( |
682 | static_cast<LayerImage*>(srcLayer), srcFrame, |
683 | static_cast<LayerImage*>(dstLayer), dstFrame); |
684 | } |
685 | } |
686 | |
687 | ++dstFrame; |
688 | } |
689 | |
690 | tx.commit(); |
691 | if (current_editor) |
692 | current_editor->invalidate(); // TODO check if this is necessary |
693 | break; |
694 | } |
695 | |
696 | case DocRange::kLayers: { |
697 | if (srcDoc->colorMode() != dstDoc->colorMode()) |
698 | throw std::runtime_error("You cannot copy layers of document with different color modes" ); |
699 | |
700 | Tx tx(ctx, "Paste Layers" ); |
701 | DocApi api = dstDoc->getApi(tx); |
702 | |
703 | // Remove children if their parent is selected so we only |
704 | // copy the parent. |
705 | SelectedLayers = srcRange.selectedLayers(); |
706 | srcLayersSet.removeChildrenIfParentIsSelected(); |
707 | LayerList srcLayers = srcLayersSet.toBrowsableLayerList(); |
708 | |
709 | // Expand frames of dstDoc if it's needed. |
710 | frame_t maxFrame = 0; |
711 | for (Layer* srcLayer : srcLayers) { |
712 | if (!srcLayer->isImage()) |
713 | continue; |
714 | |
715 | Cel* lastCel = static_cast<LayerImage*>(srcLayer)->getLastCel(); |
716 | if (lastCel && maxFrame < lastCel->frame()) |
717 | maxFrame = lastCel->frame(); |
718 | } |
719 | while (dstSpr->totalFrames() < maxFrame+1) |
720 | api.addEmptyFrame(dstSpr, dstSpr->totalFrames()); |
721 | |
722 | for (Layer* srcLayer : srcLayers) { |
723 | Layer* afterThis; |
724 | if (srcLayer->isBackground() && !dstDoc->sprite()->backgroundLayer()) |
725 | afterThis = nullptr; |
726 | else |
727 | afterThis = dstSpr->root()->lastLayer(); |
728 | |
729 | Layer* newLayer = nullptr; |
730 | if (srcLayer->isImage()) |
731 | newLayer = new LayerImage(dstSpr); |
732 | else if (srcLayer->isGroup()) |
733 | newLayer = new LayerGroup(dstSpr); |
734 | else |
735 | continue; |
736 | |
737 | api.addLayer(dstSpr->root(), newLayer, afterThis); |
738 | |
739 | srcDoc->copyLayerContent(srcLayer, dstDoc, newLayer); |
740 | } |
741 | |
742 | tx.commit(); |
743 | if (current_editor) |
744 | current_editor->invalidate(); // TODO check if this is necessary |
745 | break; |
746 | } |
747 | } |
748 | break; |
749 | } |
750 | |
751 | } |
752 | } |
753 | |
754 | ImageRef Clipboard::getImage(Palette* palette) |
755 | { |
756 | // Get the image from the native clipboard. |
757 | if (use_native_clipboard()) { |
758 | Image* native_image = nullptr; |
759 | Mask* native_mask = nullptr; |
760 | Palette* native_palette = nullptr; |
761 | Tileset* native_tileset = nullptr; |
762 | getNativeBitmap(&native_image, |
763 | &native_mask, |
764 | &native_palette, |
765 | &native_tileset); |
766 | if (native_image) { |
767 | setData(native_image, |
768 | native_mask, |
769 | native_palette, |
770 | native_tileset, |
771 | false, false); |
772 | } |
773 | } |
774 | if (m_data->palette && palette) |
775 | m_data->palette->copyColorsTo(palette); |
776 | return m_data->image; |
777 | } |
778 | |
779 | bool Clipboard::getImageSize(gfx::Size& size) |
780 | { |
781 | if (use_native_clipboard() && getNativeBitmapSize(&size)) |
782 | return true; |
783 | |
784 | if (m_data->image) { |
785 | size.w = m_data->image->width(); |
786 | size.h = m_data->image->height(); |
787 | return true; |
788 | } |
789 | |
790 | return false; |
791 | } |
792 | |
793 | Palette* Clipboard::getPalette() |
794 | { |
795 | if (format() == ClipboardFormat::PaletteEntries) { |
796 | ASSERT(m_data->palette); |
797 | return m_data->palette.get(); |
798 | } |
799 | else |
800 | return nullptr; |
801 | } |
802 | |
803 | const PalettePicks& Clipboard::getPalettePicks() |
804 | { |
805 | return m_data->picks; |
806 | } |
807 | |
808 | } // namespace app |
809 | |