2015-02-12 23:16:25 +08:00
|
|
|
// Aseprite
|
2019-09-06 02:03:13 +08:00
|
|
|
// Copyright (C) 2019 Igara Studio S.A.
|
2018-03-30 03:45:57 +08:00
|
|
|
// Copyright (C) 2001-2018 David Capello
|
2015-02-12 23:16:25 +08:00
|
|
|
//
|
2016-08-27 04:02:58 +08:00
|
|
|
// This program is distributed under the terms of
|
|
|
|
// the End-User License Agreement for Aseprite.
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
#ifdef HAVE_CONFIG_H
|
2007-09-19 07:57:02 +08:00
|
|
|
#include "config.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#endif
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/app.h"
|
2015-01-19 09:05:33 +08:00
|
|
|
#include "app/cmd/clear_mask.h"
|
2015-05-11 08:36:46 +08:00
|
|
|
#include "app/cmd/deselect_mask.h"
|
2019-06-28 02:34:56 +08:00
|
|
|
#include "app/cmd/set_mask.h"
|
2016-05-04 23:32:39 +08:00
|
|
|
#include "app/cmd/trim_cel.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/console.h"
|
|
|
|
#include "app/context_access.h"
|
2018-07-07 22:54:44 +08:00
|
|
|
#include "app/doc.h"
|
2018-07-07 14:07:16 +08:00
|
|
|
#include "app/doc_api.h"
|
2018-07-07 21:07:21 +08:00
|
|
|
#include "app/doc_range.h"
|
|
|
|
#include "app/doc_range_ops.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/modules/editors.h"
|
|
|
|
#include "app/modules/gfx.h"
|
|
|
|
#include "app/modules/gui.h"
|
2018-05-24 23:56:07 +08:00
|
|
|
#include "app/pref/preferences.h"
|
2018-08-21 03:00:59 +08:00
|
|
|
#include "app/tx.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/ui/color_bar.h"
|
|
|
|
#include "app/ui/editor/editor.h"
|
|
|
|
#include "app/ui/skin/skin_theme.h"
|
2017-03-27 00:33:12 +08:00
|
|
|
#include "app/ui/timeline/timeline.h"
|
2013-08-06 08:20:19 +08:00
|
|
|
#include "app/ui_context.h"
|
|
|
|
#include "app/util/clipboard.h"
|
2016-04-30 07:42:05 +08:00
|
|
|
#include "app/util/clipboard_native.h"
|
2015-04-02 20:55:18 +08:00
|
|
|
#include "app/util/new_image_from_mask.h"
|
2019-09-06 02:03:13 +08:00
|
|
|
#include "app/util/range_utils.h"
|
2018-05-24 23:56:07 +08:00
|
|
|
#include "clip/clip.h"
|
2014-10-21 09:21:31 +08:00
|
|
|
#include "doc/doc.h"
|
2019-04-04 06:32:24 +08:00
|
|
|
#include "render/dithering.h"
|
2017-05-20 02:49:31 +08:00
|
|
|
#include "render/ordered_dither.h"
|
2014-12-28 22:06:11 +08:00
|
|
|
#include "render/quantization.h"
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2019-08-02 06:14:46 +08:00
|
|
|
#include <memory>
|
2014-08-08 21:52:21 +08:00
|
|
|
#include <stdexcept>
|
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
namespace app {
|
|
|
|
|
2015-02-12 23:16:25 +08:00
|
|
|
namespace {
|
2014-08-08 21:33:45 +08:00
|
|
|
|
2018-07-07 19:38:04 +08:00
|
|
|
class ClipboardRange : public DocsObserver {
|
2014-08-08 21:33:45 +08:00
|
|
|
public:
|
2016-01-30 20:13:46 +08:00
|
|
|
ClipboardRange() : m_doc(nullptr) {
|
|
|
|
}
|
|
|
|
|
|
|
|
~ClipboardRange() {
|
|
|
|
ASSERT(!m_doc);
|
|
|
|
}
|
|
|
|
|
|
|
|
void observeUIContext() {
|
2016-09-14 02:02:00 +08:00
|
|
|
UIContext::instance()->documents().add_observer(this);
|
2016-01-30 20:13:46 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void unobserveUIContext() {
|
2016-09-14 02:02:00 +08:00
|
|
|
UIContext::instance()->documents().remove_observer(this);
|
2014-08-08 21:33:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
bool valid() {
|
2016-01-30 20:13:46 +08:00
|
|
|
return (m_doc != nullptr);
|
2014-08-08 21:33:45 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void invalidate() {
|
2016-01-30 20:13:46 +08:00
|
|
|
m_doc = nullptr;
|
2014-08-08 21:33:45 +08:00
|
|
|
}
|
|
|
|
|
2018-07-07 22:54:44 +08:00
|
|
|
void setRange(Doc* doc, const DocRange& range) {
|
2014-08-08 21:33:45 +08:00
|
|
|
m_doc = doc;
|
|
|
|
m_range = range;
|
|
|
|
}
|
|
|
|
|
2018-07-07 22:54:44 +08:00
|
|
|
Doc* document() const { return m_doc; }
|
2018-07-07 21:07:21 +08:00
|
|
|
DocRange range() const { return m_range; }
|
2014-08-08 21:33:45 +08:00
|
|
|
|
2018-07-07 19:38:04 +08:00
|
|
|
// DocsObserver impl
|
2018-07-07 22:54:44 +08:00
|
|
|
void onRemoveDocument(Doc* doc) override {
|
2016-01-30 20:13:46 +08:00
|
|
|
if (doc == m_doc)
|
|
|
|
invalidate();
|
|
|
|
}
|
|
|
|
|
2014-08-08 21:33:45 +08:00
|
|
|
private:
|
2018-07-07 22:54:44 +08:00
|
|
|
Doc* m_doc;
|
2018-07-07 21:07:21 +08:00
|
|
|
DocRange m_range;
|
2014-08-08 21:33:45 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2016-01-30 20:13:46 +08:00
|
|
|
namespace clipboard {
|
|
|
|
|
2014-10-21 09:21:31 +08:00
|
|
|
using namespace doc;
|
2013-08-06 08:20:19 +08:00
|
|
|
|
2019-08-02 06:14:46 +08:00
|
|
|
static std::shared_ptr<Palette> clipboard_palette;
|
2015-06-18 23:50:33 +08:00
|
|
|
static PalettePicks clipboard_picks;
|
2015-05-11 08:36:46 +08:00
|
|
|
static ImageRef clipboard_image;
|
2019-08-02 06:14:46 +08:00
|
|
|
static std::shared_ptr<Mask> clipboard_mask;
|
2014-08-08 21:33:45 +08:00
|
|
|
static ClipboardRange clipboard_range;
|
2009-03-08 03:14:40 +08:00
|
|
|
|
2016-01-30 20:13:46 +08:00
|
|
|
static ClipboardManager* g_instance = nullptr;
|
|
|
|
|
2018-05-24 23:56:07 +08:00
|
|
|
static bool use_native_clipboard()
|
|
|
|
{
|
|
|
|
return Preferences::instance().experimental.useNativeClipboard();
|
|
|
|
}
|
|
|
|
|
|
|
|
ClipboardManager* ClipboardManager::instance()
|
|
|
|
{
|
|
|
|
return g_instance;
|
|
|
|
}
|
|
|
|
|
2016-01-30 20:13:46 +08:00
|
|
|
ClipboardManager::ClipboardManager()
|
2007-09-19 07:57:02 +08:00
|
|
|
{
|
2016-01-30 20:13:46 +08:00
|
|
|
ASSERT(!g_instance);
|
|
|
|
g_instance = this;
|
|
|
|
|
2016-04-30 07:42:05 +08:00
|
|
|
register_native_clipboard_formats();
|
|
|
|
|
2016-01-30 20:13:46 +08:00
|
|
|
clipboard_range.observeUIContext();
|
|
|
|
}
|
|
|
|
|
|
|
|
ClipboardManager::~ClipboardManager()
|
|
|
|
{
|
|
|
|
clipboard_range.invalidate();
|
|
|
|
clipboard_range.unobserveUIContext();
|
|
|
|
|
|
|
|
// Clean the whole clipboard
|
2015-05-11 08:36:46 +08:00
|
|
|
clipboard_palette.reset();
|
|
|
|
clipboard_image.reset();
|
2015-07-24 09:42:14 +08:00
|
|
|
clipboard_mask.reset();
|
2016-01-30 20:13:46 +08:00
|
|
|
|
2016-01-30 20:16:46 +08:00
|
|
|
ASSERT(g_instance == this);
|
2016-01-30 20:13:46 +08:00
|
|
|
g_instance = nullptr;
|
2010-03-01 10:36:05 +08:00
|
|
|
}
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2018-05-24 23:56:07 +08:00
|
|
|
void ClipboardManager::setClipboardText(const std::string& text)
|
2009-03-08 03:14:40 +08:00
|
|
|
{
|
2018-05-24 23:56:07 +08:00
|
|
|
if (use_native_clipboard()) {
|
|
|
|
clip::set_text(text);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
m_text = text;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ClipboardManager::getClipboardText(std::string& text)
|
|
|
|
{
|
|
|
|
if (use_native_clipboard()) {
|
|
|
|
return clip::get_text(text);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
text = m_text;
|
|
|
|
return true;
|
|
|
|
}
|
2016-01-30 20:13:46 +08:00
|
|
|
}
|
2009-03-08 03:14:40 +08:00
|
|
|
|
2016-04-30 07:42:05 +08:00
|
|
|
static void set_clipboard_image(Image* image,
|
|
|
|
Mask* mask,
|
|
|
|
Palette* palette,
|
|
|
|
bool set_system_clipboard,
|
|
|
|
bool image_source_is_transparent)
|
2016-01-30 20:13:46 +08:00
|
|
|
{
|
2015-05-11 08:36:46 +08:00
|
|
|
clipboard_palette.reset(palette);
|
|
|
|
clipboard_picks.clear();
|
|
|
|
clipboard_image.reset(image);
|
2015-07-24 09:42:14 +08:00
|
|
|
clipboard_mask.reset(mask);
|
2009-03-08 03:14:40 +08:00
|
|
|
|
2016-04-30 07:42:05 +08:00
|
|
|
// Copy image to the native clipboard
|
|
|
|
if (set_system_clipboard) {
|
|
|
|
color_t oldMask;
|
|
|
|
if (image) {
|
|
|
|
oldMask = image->maskColor();
|
|
|
|
if (!image_source_is_transparent)
|
|
|
|
image->setMaskColor(-1);
|
|
|
|
}
|
|
|
|
|
2018-05-24 23:56:07 +08:00
|
|
|
if (use_native_clipboard())
|
|
|
|
set_native_clipboard_bitmap(image, mask, palette);
|
2016-04-30 07:42:05 +08:00
|
|
|
|
|
|
|
if (image && !image_source_is_transparent)
|
|
|
|
image->setMaskColor(oldMask);
|
|
|
|
}
|
2014-08-08 21:33:45 +08:00
|
|
|
|
|
|
|
clipboard_range.invalidate();
|
2007-09-19 07:57:02 +08:00
|
|
|
}
|
|
|
|
|
2016-05-17 23:59:48 +08:00
|
|
|
static bool copy_from_document(const Site& site, bool merged = false)
|
2007-09-19 07:57:02 +08:00
|
|
|
{
|
2018-07-07 22:54:44 +08:00
|
|
|
const Doc* document = static_cast<const Doc*>(site.document());
|
2016-05-17 23:59:48 +08:00
|
|
|
ASSERT(document);
|
2013-03-12 07:29:45 +08:00
|
|
|
|
2016-05-17 23:59:48 +08:00
|
|
|
const Mask* mask = document->mask();
|
2019-02-26 05:02:58 +08:00
|
|
|
Image* image = new_image_from_mask(site, mask,
|
|
|
|
Preferences::instance().experimental.newBlend(),
|
|
|
|
merged);
|
2009-03-08 03:14:40 +08:00
|
|
|
if (!image)
|
|
|
|
return false;
|
|
|
|
|
2015-04-21 03:27:09 +08:00
|
|
|
const Palette* pal = document->sprite()->palette(site.frame());
|
2016-04-30 07:42:05 +08:00
|
|
|
set_clipboard_image(
|
|
|
|
image,
|
|
|
|
(mask ? new Mask(*mask): nullptr),
|
|
|
|
(pal ? new Palette(*pal): nullptr),
|
|
|
|
true,
|
|
|
|
site.layer() && !site.layer()->isBackground());
|
|
|
|
|
2009-03-08 03:14:40 +08:00
|
|
|
return true;
|
|
|
|
}
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2016-01-30 20:13:46 +08:00
|
|
|
ClipboardFormat get_current_format()
|
2009-03-08 03:14:40 +08:00
|
|
|
{
|
2016-04-30 07:42:05 +08:00
|
|
|
// Check if the native clipboard has an image
|
2018-05-24 23:56:07 +08:00
|
|
|
if (use_native_clipboard() &&
|
|
|
|
has_native_clipboard_bitmap())
|
2014-08-08 21:33:45 +08:00
|
|
|
return ClipboardImage;
|
2016-04-30 07:42:05 +08:00
|
|
|
else if (clipboard_image)
|
2014-08-08 21:33:45 +08:00
|
|
|
return ClipboardImage;
|
|
|
|
else if (clipboard_range.valid())
|
2018-07-07 21:07:21 +08:00
|
|
|
return ClipboardDocRange;
|
2015-05-11 08:36:46 +08:00
|
|
|
else if (clipboard_palette && clipboard_picks.picks())
|
|
|
|
return ClipboardPaletteEntries;
|
2014-08-08 21:33:45 +08:00
|
|
|
else
|
|
|
|
return ClipboardNone;
|
|
|
|
}
|
|
|
|
|
2018-07-07 22:54:44 +08:00
|
|
|
void get_document_range_info(Doc** document, DocRange* range)
|
2014-08-08 21:33:45 +08:00
|
|
|
{
|
|
|
|
if (clipboard_range.valid()) {
|
|
|
|
*document = clipboard_range.document();
|
|
|
|
*range = clipboard_range.range();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
*document = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-09-06 02:03:13 +08:00
|
|
|
void clear_mask_from_cels(Tx& tx,
|
|
|
|
Doc* doc,
|
|
|
|
const CelList& cels,
|
|
|
|
const bool deselectMask)
|
|
|
|
{
|
|
|
|
for (Cel* cel : cels) {
|
|
|
|
ObjectId celId = cel->id();
|
|
|
|
|
|
|
|
tx(new cmd::ClearMask(cel));
|
|
|
|
|
|
|
|
// Get cel again just in case the cmd::ClearMask() called cmd::ClearCel()
|
|
|
|
cel = doc::get<Cel>(celId);
|
|
|
|
if (cel &&
|
|
|
|
cel->layer()->isTransparent()) {
|
|
|
|
tx(new cmd::TrimCel(cel));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (deselectMask)
|
|
|
|
tx(new cmd::DeselectMask(doc));
|
|
|
|
}
|
|
|
|
|
2016-01-30 20:13:46 +08:00
|
|
|
void clear_content()
|
2014-08-08 21:33:45 +08:00
|
|
|
{
|
2016-04-30 07:42:05 +08:00
|
|
|
set_clipboard_image(nullptr, nullptr, nullptr, true, false);
|
2007-09-19 07:57:02 +08:00
|
|
|
}
|
|
|
|
|
2016-01-30 20:13:46 +08:00
|
|
|
void cut(ContextWriter& writer)
|
2007-09-19 07:57:02 +08:00
|
|
|
{
|
2013-03-12 07:29:45 +08:00
|
|
|
ASSERT(writer.document() != NULL);
|
|
|
|
ASSERT(writer.sprite() != NULL);
|
|
|
|
ASSERT(writer.layer() != NULL);
|
2011-03-23 08:11:25 +08:00
|
|
|
|
2015-04-21 03:27:09 +08:00
|
|
|
if (!copy_from_document(*writer.site())) {
|
2009-06-11 23:11:11 +08:00
|
|
|
Console console;
|
|
|
|
console.printf("Can't copying an image portion from the current layer\n");
|
|
|
|
}
|
2007-09-19 07:57:02 +08:00
|
|
|
else {
|
2008-10-11 23:59:13 +08:00
|
|
|
{
|
2018-08-21 03:00:59 +08:00
|
|
|
Tx tx(writer.context(), "Cut");
|
2019-09-06 02:03:13 +08:00
|
|
|
Site site = writer.context()->activeSite();
|
|
|
|
CelList cels;
|
|
|
|
if (site.range().enabled()) {
|
|
|
|
cels = get_unlocked_unique_cels(site.sprite(), site.range());
|
|
|
|
}
|
|
|
|
else if (site.cel()) {
|
|
|
|
cels.push_back(site.cel());
|
|
|
|
}
|
|
|
|
clear_mask_from_cels(tx,
|
|
|
|
writer.document(),
|
|
|
|
cels,
|
|
|
|
true); // Deselect mask
|
2018-08-21 03:00:59 +08:00
|
|
|
tx.commit();
|
2008-10-11 23:59:13 +08:00
|
|
|
}
|
2013-03-12 07:29:45 +08:00
|
|
|
writer.document()->generateMaskBoundaries();
|
|
|
|
update_screen_for_document(writer.document());
|
2007-09-19 07:57:02 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-01-30 20:13:46 +08:00
|
|
|
void copy(const ContextReader& reader)
|
2007-09-19 07:57:02 +08:00
|
|
|
{
|
2013-03-12 07:29:45 +08:00
|
|
|
ASSERT(reader.document() != NULL);
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2015-04-21 03:27:09 +08:00
|
|
|
if (!copy_from_document(*reader.site())) {
|
2009-06-11 23:11:11 +08:00
|
|
|
Console console;
|
2010-09-19 10:54:56 +08:00
|
|
|
console.printf("Can't copying an image portion from the current layer\n");
|
2014-08-08 21:33:45 +08:00
|
|
|
return;
|
2009-06-11 23:11:11 +08:00
|
|
|
}
|
2007-09-19 07:57:02 +08:00
|
|
|
}
|
|
|
|
|
2016-05-17 23:59:48 +08:00
|
|
|
void copy_merged(const ContextReader& reader)
|
|
|
|
{
|
|
|
|
ASSERT(reader.document() != NULL);
|
|
|
|
|
|
|
|
copy_from_document(*reader.site(), true);
|
|
|
|
}
|
|
|
|
|
2018-07-07 21:07:21 +08:00
|
|
|
void copy_range(const ContextReader& reader, const DocRange& range)
|
2014-08-08 21:33:45 +08:00
|
|
|
{
|
|
|
|
ASSERT(reader.document() != NULL);
|
|
|
|
|
|
|
|
ContextWriter writer(reader);
|
|
|
|
|
2015-07-24 09:42:14 +08:00
|
|
|
clear_content();
|
2014-08-08 21:33:45 +08:00
|
|
|
clipboard_range.setRange(writer.document(), range);
|
|
|
|
|
|
|
|
// TODO Replace this with a signal, because here the timeline
|
2015-03-17 06:53:20 +08:00
|
|
|
// depends on the clipboard and the clipboard on the timeline.
|
2016-04-23 00:19:06 +08:00
|
|
|
App::instance()->timeline()->activateClipboardRange();
|
2014-08-08 21:33:45 +08:00
|
|
|
}
|
|
|
|
|
2016-01-30 20:13:46 +08:00
|
|
|
void copy_image(const Image* image, const Mask* mask, const Palette* pal)
|
2009-03-08 03:14:40 +08:00
|
|
|
{
|
2015-07-24 09:42:14 +08:00
|
|
|
set_clipboard_image(
|
|
|
|
Image::createCopy(image),
|
|
|
|
(mask ? new Mask(*mask): nullptr),
|
2016-04-30 07:42:05 +08:00
|
|
|
(pal ? new Palette(*pal): nullptr),
|
|
|
|
true, false);
|
2009-03-08 03:14:40 +08:00
|
|
|
}
|
|
|
|
|
2016-01-30 20:13:46 +08:00
|
|
|
void copy_palette(const Palette* palette, const doc::PalettePicks& picks)
|
2015-05-11 08:36:46 +08:00
|
|
|
{
|
|
|
|
if (!picks.picks())
|
|
|
|
return; // Do nothing case
|
|
|
|
|
2015-07-24 09:42:14 +08:00
|
|
|
set_clipboard_image(nullptr,
|
|
|
|
nullptr,
|
2016-04-30 07:42:05 +08:00
|
|
|
new Palette(*palette),
|
|
|
|
true, false);
|
2015-05-11 08:36:46 +08:00
|
|
|
clipboard_picks = picks;
|
|
|
|
}
|
|
|
|
|
2019-06-28 02:34:56 +08:00
|
|
|
void paste(Context* ctx, const bool interactive)
|
2007-09-19 07:57:02 +08:00
|
|
|
{
|
2019-06-28 02:34:56 +08:00
|
|
|
Site site = ctx->activeSite();
|
|
|
|
Doc* dstDoc = site.document();
|
|
|
|
if (!dstDoc)
|
2013-01-21 05:40:37 +08:00
|
|
|
return;
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2019-06-28 02:34:56 +08:00
|
|
|
Sprite* dstSpr = site.sprite();
|
|
|
|
if (!dstSpr)
|
|
|
|
return;
|
2014-08-08 21:33:45 +08:00
|
|
|
|
|
|
|
switch (get_current_format()) {
|
|
|
|
|
|
|
|
case clipboard::ClipboardImage: {
|
2018-05-24 23:56:07 +08:00
|
|
|
// Get the image from the native clipboard.
|
2019-06-29 05:50:13 +08:00
|
|
|
if (!get_image(nullptr))
|
2014-08-08 21:33:45 +08:00
|
|
|
return;
|
|
|
|
|
2019-06-29 05:50:13 +08:00
|
|
|
ASSERT(clipboard_image);
|
|
|
|
|
2019-06-28 02:34:56 +08:00
|
|
|
Palette* dst_palette = dstSpr->palette(site.frame());
|
2014-08-08 21:33:45 +08:00
|
|
|
|
|
|
|
// Source image (clipboard or a converted copy to the destination 'imgtype')
|
2015-05-11 08:36:46 +08:00
|
|
|
ImageRef src_image;
|
2014-08-18 11:21:03 +08:00
|
|
|
if (clipboard_image->pixelFormat() == dstSpr->pixelFormat() &&
|
2014-08-08 21:33:45 +08:00
|
|
|
// Indexed images can be copied directly only if both images
|
|
|
|
// have the same palette.
|
|
|
|
(clipboard_image->pixelFormat() != IMAGE_INDEXED ||
|
|
|
|
clipboard_palette->countDiff(dst_palette, NULL, NULL) == 0)) {
|
|
|
|
src_image = clipboard_image;
|
|
|
|
}
|
|
|
|
else {
|
2019-06-28 02:34:56 +08:00
|
|
|
RgbMap* dst_rgbmap = dstSpr->rgbMap(site.frame());
|
2014-08-08 21:33:45 +08:00
|
|
|
|
2015-05-11 08:36:46 +08:00
|
|
|
src_image.reset(
|
|
|
|
render::convert_pixel_format(
|
|
|
|
clipboard_image.get(), NULL, dstSpr->pixelFormat(),
|
2019-04-04 06:32:24 +08:00
|
|
|
render::Dithering(),
|
2017-05-16 03:25:09 +08:00
|
|
|
dst_rgbmap, clipboard_palette.get(),
|
2015-07-14 20:44:31 +08:00
|
|
|
false,
|
|
|
|
0));
|
2014-08-08 21:33:45 +08:00
|
|
|
}
|
|
|
|
|
2019-06-28 02:34:56 +08:00
|
|
|
if (current_editor && interactive) {
|
2019-09-06 02:03:13 +08:00
|
|
|
// TODO we don't support pasting in multiple cels at the moment,
|
|
|
|
// so we clear the range here.
|
|
|
|
App::instance()->timeline()->clearAndInvalidateRange();
|
|
|
|
|
2019-06-28 02:34:56 +08:00
|
|
|
// Change to MovingPixelsState
|
|
|
|
current_editor->pasteImage(src_image.get(),
|
|
|
|
clipboard_mask.get());
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Non-interactive version (just copy the image to the cel)
|
|
|
|
Layer* dstLayer = site.layer();
|
|
|
|
ASSERT(dstLayer);
|
|
|
|
if (!dstLayer || !dstLayer->isImage())
|
|
|
|
return;
|
|
|
|
|
|
|
|
Tx tx(ctx, "Paste Image");
|
|
|
|
DocApi api = dstDoc->getApi(tx);
|
|
|
|
Cel* dstCel = api.addCel(
|
|
|
|
static_cast<LayerImage*>(dstLayer), site.frame(),
|
|
|
|
ImageRef(Image::createCopy(src_image.get())));
|
|
|
|
|
|
|
|
// Adjust bounds
|
|
|
|
if (dstCel) {
|
|
|
|
if (clipboard_mask) {
|
|
|
|
if (dstLayer->isReference()) {
|
|
|
|
dstCel->setBounds(dstSpr->bounds());
|
|
|
|
|
|
|
|
Mask emptyMask;
|
|
|
|
tx(new cmd::SetMask(dstDoc, &emptyMask));
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
dstCel->setBounds(clipboard_mask->bounds());
|
|
|
|
tx(new cmd::SetMask(dstDoc, clipboard_mask.get()));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
tx.commit();
|
|
|
|
}
|
2014-08-08 21:33:45 +08:00
|
|
|
break;
|
|
|
|
}
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2018-07-07 21:07:21 +08:00
|
|
|
case clipboard::ClipboardDocRange: {
|
|
|
|
DocRange srcRange = clipboard_range.range();
|
2018-07-07 22:54:44 +08:00
|
|
|
Doc* srcDoc = clipboard_range.document();
|
2014-08-18 11:21:03 +08:00
|
|
|
Sprite* srcSpr = srcDoc->sprite();
|
|
|
|
|
|
|
|
switch (srcRange.type()) {
|
|
|
|
|
2018-07-07 21:07:21 +08:00
|
|
|
case DocRange::kCels: {
|
2019-06-28 02:34:56 +08:00
|
|
|
Layer* dstLayer = site.layer();
|
|
|
|
ASSERT(dstLayer);
|
|
|
|
if (!dstLayer)
|
|
|
|
return;
|
|
|
|
|
|
|
|
frame_t dstFrameFirst = site.frame();
|
2016-08-25 23:31:00 +08:00
|
|
|
|
2018-07-07 21:07:21 +08:00
|
|
|
DocRange dstRange;
|
|
|
|
dstRange.startRange(dstLayer, dstFrameFirst, DocRange::kCels);
|
2016-08-25 23:31:00 +08:00
|
|
|
for (layer_t i=1; i<srcRange.layers(); ++i) {
|
2016-12-08 20:11:54 +08:00
|
|
|
dstLayer = dstLayer->getPreviousBrowsable();
|
2016-08-25 23:31:00 +08:00
|
|
|
if (dstLayer == nullptr)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
dstRange.endRange(dstLayer, dstFrameFirst+srcRange.frames()-1);
|
|
|
|
|
2015-08-08 05:27:48 +08:00
|
|
|
// We can use a document range op (copy_range) to copy/paste
|
|
|
|
// cels in the same document.
|
|
|
|
if (srcDoc == dstDoc) {
|
2015-08-20 03:43:13 +08:00
|
|
|
// This is the app::copy_range (not clipboard::copy_range()).
|
2016-08-25 23:31:00 +08:00
|
|
|
if (srcRange.layers() == dstRange.layers())
|
2018-07-07 21:07:21 +08:00
|
|
|
app::copy_range(srcDoc, srcRange, dstRange, kDocRangeBefore);
|
2019-06-28 02:34:56 +08:00
|
|
|
if (current_editor)
|
|
|
|
current_editor->invalidate(); // TODO check if this is necessary
|
2015-08-08 05:27:48 +08:00
|
|
|
return;
|
|
|
|
}
|
2014-09-01 01:24:29 +08:00
|
|
|
|
2019-06-28 02:34:56 +08:00
|
|
|
Tx tx(ctx, "Paste Cels");
|
2018-08-21 03:00:59 +08:00
|
|
|
DocApi api = dstDoc->getApi(tx);
|
2014-08-18 11:21:03 +08:00
|
|
|
|
2015-08-08 04:09:03 +08:00
|
|
|
// Add extra frames if needed
|
2016-08-25 23:31:00 +08:00
|
|
|
while (dstFrameFirst+srcRange.frames() > dstSpr->totalFrames())
|
2015-08-08 04:09:03 +08:00
|
|
|
api.addFrame(dstSpr, dstSpr->totalFrames());
|
|
|
|
|
2018-03-30 03:45:57 +08:00
|
|
|
auto srcLayers = srcRange.selectedLayers().toLayerList();
|
|
|
|
auto dstLayers = dstRange.selectedLayers().toLayerList();
|
|
|
|
|
|
|
|
auto srcIt = srcLayers.begin();
|
|
|
|
auto dstIt = dstLayers.begin();
|
|
|
|
auto srcEnd = srcLayers.end();
|
|
|
|
auto dstEnd = dstLayers.end();
|
2016-08-25 23:31:00 +08:00
|
|
|
|
|
|
|
for (; srcIt != srcEnd && dstIt != dstEnd; ++srcIt, ++dstIt) {
|
|
|
|
auto srcLayer = *srcIt;
|
|
|
|
auto dstLayer = *dstIt;
|
|
|
|
|
|
|
|
if (!srcLayer->isImage() ||
|
|
|
|
!dstLayer->isImage())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
frame_t dstFrame = dstFrameFirst;
|
|
|
|
for (frame_t srcFrame : srcRange.selectedFrames()) {
|
|
|
|
Cel* srcCel = srcLayer->cel(srcFrame);
|
2015-08-08 04:09:03 +08:00
|
|
|
|
|
|
|
if (srcCel && srcCel->image()) {
|
2019-09-27 06:09:23 +08:00
|
|
|
api.copyCel(
|
|
|
|
static_cast<LayerImage*>(srcLayer), srcFrame,
|
|
|
|
static_cast<LayerImage*>(dstLayer), dstFrame);
|
2014-08-18 11:21:03 +08:00
|
|
|
}
|
|
|
|
else {
|
2019-09-27 06:09:23 +08:00
|
|
|
if (Cel* dstCel = dstLayer->cel(dstFrame))
|
2014-09-17 20:53:25 +08:00
|
|
|
api.clearCel(dstCel);
|
2014-08-18 11:21:03 +08:00
|
|
|
}
|
|
|
|
|
2016-08-25 23:31:00 +08:00
|
|
|
++dstFrame;
|
|
|
|
}
|
2014-08-18 11:21:03 +08:00
|
|
|
}
|
|
|
|
|
2018-08-21 03:00:59 +08:00
|
|
|
tx.commit();
|
2019-06-28 02:34:56 +08:00
|
|
|
if (current_editor)
|
|
|
|
current_editor->invalidate(); // TODO check if this is necessary
|
2014-08-18 11:21:03 +08:00
|
|
|
break;
|
|
|
|
}
|
2014-03-30 08:31:27 +08:00
|
|
|
|
2018-07-07 21:07:21 +08:00
|
|
|
case DocRange::kFrames: {
|
2019-06-28 02:34:56 +08:00
|
|
|
frame_t dstFrame = site.frame();
|
2014-08-18 11:21:03 +08:00
|
|
|
|
2018-07-07 21:07:21 +08:00
|
|
|
// We use a DocRange operation to copy frames inside
|
2016-08-25 23:31:00 +08:00
|
|
|
// the same sprite.
|
2016-05-10 02:47:09 +08:00
|
|
|
if (srcSpr == dstSpr) {
|
2018-07-07 21:07:21 +08:00
|
|
|
DocRange dstRange;
|
|
|
|
dstRange.startRange(nullptr, dstFrame, DocRange::kFrames);
|
2016-08-25 23:31:00 +08:00
|
|
|
dstRange.endRange(nullptr, dstFrame);
|
2018-07-07 21:07:21 +08:00
|
|
|
app::copy_range(srcDoc, srcRange, dstRange, kDocRangeBefore);
|
2016-08-25 23:31:00 +08:00
|
|
|
break;
|
2016-05-10 02:47:09 +08:00
|
|
|
}
|
|
|
|
|
2019-06-28 02:34:56 +08:00
|
|
|
Tx tx(ctx, "Paste Frames");
|
2018-08-21 03:00:59 +08:00
|
|
|
DocApi api = dstDoc->getApi(tx);
|
2016-05-10 02:47:09 +08:00
|
|
|
|
2016-08-25 23:31:00 +08:00
|
|
|
auto srcLayers = srcSpr->allBrowsableLayers();
|
|
|
|
auto dstLayers = dstSpr->allBrowsableLayers();
|
2016-05-10 02:47:09 +08:00
|
|
|
|
2016-08-25 23:31:00 +08:00
|
|
|
for (frame_t srcFrame : srcRange.selectedFrames()) {
|
|
|
|
api.addEmptyFrame(dstSpr, dstFrame);
|
2016-05-10 02:47:09 +08:00
|
|
|
api.setFrameDuration(dstSpr, dstFrame, srcSpr->frameDuration(srcFrame));
|
2014-08-18 11:21:03 +08:00
|
|
|
|
2016-08-25 23:31:00 +08:00
|
|
|
auto srcIt = srcLayers.begin();
|
|
|
|
auto dstIt = dstLayers.begin();
|
|
|
|
auto srcEnd = srcLayers.end();
|
|
|
|
auto dstEnd = dstLayers.end();
|
|
|
|
|
|
|
|
for (; srcIt != srcEnd && dstIt != dstEnd; ++srcIt, ++dstIt) {
|
|
|
|
auto srcLayer = *srcIt;
|
|
|
|
auto dstLayer = *dstIt;
|
|
|
|
|
|
|
|
if (!srcLayer->isImage() ||
|
|
|
|
!dstLayer->isImage())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
Cel* cel = static_cast<LayerImage*>(srcLayer)->cel(srcFrame);
|
2014-08-18 11:21:03 +08:00
|
|
|
if (cel && cel->image()) {
|
|
|
|
api.copyCel(
|
2016-08-25 23:31:00 +08:00
|
|
|
static_cast<LayerImage*>(srcLayer), srcFrame,
|
|
|
|
static_cast<LayerImage*>(dstLayer), dstFrame);
|
2014-08-18 11:21:03 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-29 07:39:11 +08:00
|
|
|
++dstFrame;
|
2014-08-18 11:21:03 +08:00
|
|
|
}
|
2014-03-30 08:31:27 +08:00
|
|
|
|
2018-08-21 03:00:59 +08:00
|
|
|
tx.commit();
|
2019-06-28 02:34:56 +08:00
|
|
|
if (current_editor)
|
|
|
|
current_editor->invalidate(); // TODO check if this is necessary
|
2014-08-18 11:21:03 +08:00
|
|
|
break;
|
|
|
|
}
|
2015-02-12 23:16:25 +08:00
|
|
|
|
2018-07-07 21:07:21 +08:00
|
|
|
case DocRange::kLayers: {
|
2014-08-18 11:21:03 +08:00
|
|
|
if (srcDoc->colorMode() != dstDoc->colorMode())
|
2014-08-08 21:33:45 +08:00
|
|
|
throw std::runtime_error("You cannot copy layers of document with different color modes");
|
|
|
|
|
2019-06-28 02:34:56 +08:00
|
|
|
Tx tx(ctx, "Paste Layers");
|
2018-08-21 03:00:59 +08:00
|
|
|
DocApi api = dstDoc->getApi(tx);
|
2014-08-08 21:33:45 +08:00
|
|
|
|
2016-08-25 23:31:00 +08:00
|
|
|
// Remove children if their parent is selected so we only
|
|
|
|
// copy the parent.
|
|
|
|
SelectedLayers srcLayersSet = srcRange.selectedLayers();
|
|
|
|
srcLayersSet.removeChildrenIfParentIsSelected();
|
|
|
|
LayerList srcLayers = srcLayersSet.toLayerList();
|
|
|
|
|
2014-08-18 11:21:03 +08:00
|
|
|
// Expand frames of dstDoc if it's needed.
|
2016-08-25 23:31:00 +08:00
|
|
|
frame_t maxFrame = 0;
|
|
|
|
for (Layer* srcLayer : srcLayers) {
|
|
|
|
if (!srcLayer->isImage())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
Cel* lastCel = static_cast<LayerImage*>(srcLayer)->getLastCel();
|
2014-08-20 19:29:21 +08:00
|
|
|
if (lastCel && maxFrame < lastCel->frame())
|
2014-08-12 18:57:40 +08:00
|
|
|
maxFrame = lastCel->frame();
|
|
|
|
}
|
2015-03-17 20:29:24 +08:00
|
|
|
while (dstSpr->totalFrames() < maxFrame+1)
|
|
|
|
api.addEmptyFrame(dstSpr, dstSpr->totalFrames());
|
2014-08-12 18:57:40 +08:00
|
|
|
|
2016-08-25 23:31:00 +08:00
|
|
|
for (Layer* srcLayer : srcLayers) {
|
2015-03-17 21:06:37 +08:00
|
|
|
Layer* afterThis;
|
2016-08-25 23:31:00 +08:00
|
|
|
if (srcLayer->isBackground() && !dstDoc->sprite()->backgroundLayer())
|
2015-03-17 21:06:37 +08:00
|
|
|
afterThis = nullptr;
|
|
|
|
else
|
2016-06-08 06:38:56 +08:00
|
|
|
afterThis = dstSpr->root()->lastLayer();
|
2015-03-17 21:06:37 +08:00
|
|
|
|
2016-08-25 23:31:00 +08:00
|
|
|
Layer* newLayer = nullptr;
|
|
|
|
if (srcLayer->isImage())
|
|
|
|
newLayer = new LayerImage(dstSpr);
|
|
|
|
else if (srcLayer->isGroup())
|
|
|
|
newLayer = new LayerGroup(dstSpr);
|
|
|
|
else
|
|
|
|
continue;
|
|
|
|
|
2016-06-08 06:38:56 +08:00
|
|
|
api.addLayer(dstSpr->root(), newLayer, afterThis);
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2016-08-25 23:31:00 +08:00
|
|
|
srcDoc->copyLayerContent(srcLayer, dstDoc, newLayer);
|
2014-08-08 21:33:45 +08:00
|
|
|
}
|
2007-09-19 07:57:02 +08:00
|
|
|
|
2018-08-21 03:00:59 +08:00
|
|
|
tx.commit();
|
2019-06-28 02:34:56 +08:00
|
|
|
if (current_editor)
|
|
|
|
current_editor->invalidate(); // TODO check if this is necessary
|
2014-08-08 21:33:45 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
2014-08-18 11:21:03 +08:00
|
|
|
|
2014-08-08 21:33:45 +08:00
|
|
|
}
|
2007-09-19 07:57:02 +08:00
|
|
|
}
|
2012-02-12 04:06:35 +08:00
|
|
|
|
2019-06-29 05:50:13 +08:00
|
|
|
ImageRef get_image(Palette* palette)
|
|
|
|
{
|
|
|
|
// Get the image from the native clipboard.
|
|
|
|
if (use_native_clipboard()) {
|
|
|
|
Image* native_image = nullptr;
|
|
|
|
Mask* native_mask = nullptr;
|
|
|
|
Palette* native_palette = nullptr;
|
|
|
|
get_native_clipboard_bitmap(&native_image, &native_mask, &native_palette);
|
|
|
|
if (native_image)
|
|
|
|
set_clipboard_image(native_image, native_mask, native_palette,
|
|
|
|
false, false);
|
|
|
|
}
|
|
|
|
if (clipboard_palette && palette)
|
|
|
|
clipboard_palette->copyColorsTo(palette);
|
|
|
|
return clipboard_image;
|
|
|
|
}
|
|
|
|
|
2016-01-30 20:13:46 +08:00
|
|
|
bool get_image_size(gfx::Size& size)
|
2012-02-12 04:06:35 +08:00
|
|
|
{
|
2018-05-24 23:56:07 +08:00
|
|
|
if (use_native_clipboard() &&
|
|
|
|
get_native_clipboard_bitmap_size(&size))
|
2016-04-30 07:42:05 +08:00
|
|
|
return true;
|
2018-05-24 23:56:07 +08:00
|
|
|
|
2015-05-11 21:41:11 +08:00
|
|
|
if (clipboard_image) {
|
2014-07-30 12:28:15 +08:00
|
|
|
size.w = clipboard_image->width();
|
|
|
|
size.h = clipboard_image->height();
|
2012-02-12 04:06:35 +08:00
|
|
|
return true;
|
|
|
|
}
|
2016-04-30 07:42:05 +08:00
|
|
|
|
|
|
|
return false;
|
2012-02-12 04:06:35 +08:00
|
|
|
}
|
2013-08-06 08:20:19 +08:00
|
|
|
|
2016-01-30 20:13:46 +08:00
|
|
|
Palette* get_palette()
|
2015-05-11 08:36:46 +08:00
|
|
|
{
|
|
|
|
if (clipboard::get_current_format() == ClipboardPaletteEntries) {
|
|
|
|
ASSERT(clipboard_palette);
|
|
|
|
return clipboard_palette.get();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return nullptr;
|
|
|
|
}
|
|
|
|
|
2016-01-30 20:13:46 +08:00
|
|
|
const PalettePicks& get_palette_picks()
|
2015-05-11 08:36:46 +08:00
|
|
|
{
|
|
|
|
return clipboard_picks;
|
|
|
|
}
|
|
|
|
|
2016-01-30 20:13:46 +08:00
|
|
|
} // namespace clipboard
|
|
|
|
|
2013-08-06 08:20:19 +08:00
|
|
|
} // namespace app
|