Unify Cut/Copy/Paste/Clear handling with InputChain class

This commit is contained in:
David Capello 2015-05-09 19:55:33 -03:00
parent e60236640a
commit 2bbaa45844
24 changed files with 728 additions and 183 deletions

View File

@ -329,6 +329,7 @@ add_library(app-lib
ui/frame_tag_window.cpp ui/frame_tag_window.cpp
ui/hex_color_entry.cpp ui/hex_color_entry.cpp
ui/home_view.cpp ui/home_view.cpp
ui/input_chain.cpp
ui/keyboard_shortcuts.cpp ui/keyboard_shortcuts.cpp
ui/main_menu_bar.cpp ui/main_menu_bar.cpp
ui/main_window.cpp ui/main_window.cpp

View File

@ -47,6 +47,7 @@
#include "app/ui/document_view.h" #include "app/ui/document_view.h"
#include "app/ui/editor/editor.h" #include "app/ui/editor/editor.h"
#include "app/ui/editor/editor_view.h" #include "app/ui/editor/editor_view.h"
#include "app/ui/input_chain.h"
#include "app/ui/keyboard_shortcuts.h" #include "app/ui/keyboard_shortcuts.h"
#include "app/ui/main_window.h" #include "app/ui/main_window.h"
#include "app/ui/status_bar.h" #include "app/ui/status_bar.h"
@ -94,6 +95,7 @@ public:
CommandsModule m_commands_modules; CommandsModule m_commands_modules;
UIContext m_ui_context; UIContext m_ui_context;
RecentFiles m_recent_files; RecentFiles m_recent_files;
InputChain m_inputChain;
// This is a raw pointer because we want to delete this explicitly. // This is a raw pointer because we want to delete this explicitly.
app::crash::DataRecovery* m_recovery; app::crash::DataRecovery* m_recovery;
scripting::Engine m_scriptingEngine; scripting::Engine m_scriptingEngine;
@ -683,6 +685,11 @@ void App::updateDisplayTitleBar()
she::instance()->defaultDisplay()->setTitleBar(title); she::instance()->defaultDisplay()->setTitleBar(title);
} }
InputChain& App::inputChain()
{
return m_modules->m_inputChain;
}
// Updates palette and redraw the screen. // Updates palette and redraw the screen.
void app_refresh_screen() void app_refresh_screen()
{ {

View File

@ -31,6 +31,7 @@ namespace app {
class Document; class Document;
class DocumentExporter; class DocumentExporter;
class INotificationDelegate; class INotificationDelegate;
class InputChain;
class LegacyModules; class LegacyModules;
class LoggerModule; class LoggerModule;
class MainWindow; class MainWindow;
@ -70,6 +71,8 @@ namespace app {
void showNotification(INotificationDelegate* del); void showNotification(INotificationDelegate* del);
void updateDisplayTitleBar(); void updateDisplayTitleBar();
InputChain& inputChain();
// App Signals // App Signals
Signal0<void> Exit; Signal0<void> Exit;
Signal0<void> PaletteChange; Signal0<void> PaletteChange;

View File

@ -11,10 +11,11 @@
#include "app/commands/command.h" #include "app/commands/command.h"
#include "app/app.h"
#include "app/commands/commands.h" #include "app/commands/commands.h"
#include "app/commands/params.h" #include "app/commands/params.h"
#include "app/context.h" #include "app/context.h"
#include "ui/manager.h" #include "app/ui/input_chain.h"
namespace app { namespace app {
@ -60,20 +61,14 @@ void CancelCommand::onExecute(Context* context)
break; break;
case All: case All:
// TODO should the ContextBar be a InputChainElement to intercept onCancel()?
// Discard brush // Discard brush
{ {
Command* discardBrush = CommandsModule::instance()->getCommandByName(CommandId::DiscardBrush); Command* discardBrush = CommandsModule::instance()->getCommandByName(CommandId::DiscardBrush);
context->executeCommand(discardBrush); context->executeCommand(discardBrush);
} }
// Deselect mask App::instance()->inputChain().cancel(context);
if (context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
ContextFlags::HasVisibleMask)) {
Command* deselectMask = CommandsModule::instance()->getCommandByName(CommandId::DeselectMask);
context->executeCommand(deselectMask);
}
ui::Manager::getDefault()->invalidate();
break; break;
} }
} }

View File

@ -10,18 +10,8 @@
#endif #endif
#include "app/app.h" #include "app/app.h"
#include "app/cmd/clear_mask.h"
#include "app/cmd/deselect_mask.h"
#include "app/commands/command.h" #include "app/commands/command.h"
#include "app/commands/commands.h" #include "app/ui/input_chain.h"
#include "app/context_access.h"
#include "app/modules/gui.h"
#include "app/transaction.h"
#include "app/ui/color_bar.h"
#include "app/ui/main_window.h"
#include "app/ui/timeline.h"
#include "doc/layer.h"
#include "doc/mask.h"
namespace app { namespace app {
@ -31,8 +21,8 @@ public:
Command* clone() const override { return new ClearCommand(*this); } Command* clone() const override { return new ClearCommand(*this); }
protected: protected:
bool onEnabled(Context* context); bool onEnabled(Context* ctx) override;
void onExecute(Context* context); void onExecute(Context* ctx) override;
}; };
ClearCommand::ClearCommand() ClearCommand::ClearCommand()
@ -42,58 +32,14 @@ ClearCommand::ClearCommand()
{ {
} }
bool ClearCommand::onEnabled(Context* context) bool ClearCommand::onEnabled(Context* ctx)
{ {
return context->checkFlags(ContextFlags::ActiveDocumentIsWritable | return App::instance()->inputChain().canClear(ctx);
ContextFlags::ActiveLayerIsVisible |
ContextFlags::ActiveLayerIsEditable |
ContextFlags::ActiveLayerIsImage);
} }
void ClearCommand::onExecute(Context* context) void ClearCommand::onExecute(Context* ctx)
{ {
// Clear of several frames is handled with ClearCel command. App::instance()->inputChain().clear(ctx);
DocumentRange range = App::instance()->getMainWindow()->getTimeline()->range();
if (range.enabled()) {
Command* subCommand = NULL;
switch (range.type()) {
case DocumentRange::kCels:
subCommand = CommandsModule::instance()->getCommandByName(CommandId::ClearCel);
break;
case DocumentRange::kFrames:
subCommand = CommandsModule::instance()->getCommandByName(CommandId::RemoveFrame);
break;
case DocumentRange::kLayers:
subCommand = CommandsModule::instance()->getCommandByName(CommandId::RemoveLayer);
break;
}
if (subCommand) {
context->executeCommand(subCommand);
return;
}
}
// TODO add support to clear the mask in the selected range of frames.
ContextWriter writer(context);
Document* document = writer.document();
bool visibleMask = document->isMaskVisible();
if (!writer.cel())
return;
{
Transaction transaction(writer.context(), "Clear");
transaction.execute(new cmd::ClearMask(writer.cel()));
if (visibleMask)
transaction.execute(new cmd::DeselectMask(document));
transaction.commit();
}
if (visibleMask)
document->generateMaskBoundaries();
update_screen_for_document(document);
} }
Command* CommandFactory::createClearCommand() Command* CommandFactory::createClearCommand()

View File

@ -11,16 +11,7 @@
#include "app/app.h" #include "app/app.h"
#include "app/commands/command.h" #include "app/commands/command.h"
#include "app/context_access.h" #include "app/ui/input_chain.h"
#include "app/document.h"
#include "app/ui/main_window.h"
#include "app/ui/timeline.h"
#include "app/util/clipboard.h"
#include "doc/layer.h"
#include "doc/mask.h"
#include "doc/site.h"
#include "doc/sprite.h"
#include "ui/base.h"
namespace app { namespace app {
@ -30,8 +21,8 @@ public:
Command* clone() const override { return new CopyCommand(*this); } Command* clone() const override { return new CopyCommand(*this); }
protected: protected:
bool onEnabled(Context* context); bool onEnabled(Context* ctx) override;
void onExecute(Context* context); void onExecute(Context* ctx) override;
}; };
CopyCommand::CopyCommand() CopyCommand::CopyCommand()
@ -41,25 +32,14 @@ CopyCommand::CopyCommand()
{ {
} }
bool CopyCommand::onEnabled(Context* context) bool CopyCommand::onEnabled(Context* ctx)
{ {
return context->checkFlags(ContextFlags::HasActiveDocument); return App::instance()->inputChain().canCopy(ctx);
} }
void CopyCommand::onExecute(Context* context) void CopyCommand::onExecute(Context* ctx)
{ {
const ContextReader reader(context); App::instance()->inputChain().copy(ctx);
// Copy a range from the timeline.
DocumentRange range = App::instance()->getMainWindow()->getTimeline()->range();
if (range.enabled()) {
clipboard::copy_range(reader, range);
}
else if (reader.site()->document() &&
static_cast<const app::Document*>(reader.site()->document())->isMaskVisible() &&
reader.site()->image()) {
clipboard::copy(reader);
}
} }
Command* CommandFactory::createCopyCommand() Command* CommandFactory::createCopyCommand()

View File

@ -9,13 +9,9 @@
#include "config.h" #include "config.h"
#endif #endif
#include "app/app.h"
#include "app/commands/command.h" #include "app/commands/command.h"
#include "app/context_access.h" #include "app/ui/input_chain.h"
#include "app/util/clipboard.h"
#include "doc/layer.h"
#include "doc/mask.h"
#include "doc/sprite.h"
#include "ui/base.h"
namespace app { namespace app {
@ -25,8 +21,8 @@ public:
Command* clone() const override { return new CutCommand(*this); } Command* clone() const override { return new CutCommand(*this); }
protected: protected:
bool onEnabled(Context* context); bool onEnabled(Context* ctx) override;
void onExecute(Context* context); void onExecute(Context* ctx) override;
}; };
CutCommand::CutCommand() CutCommand::CutCommand()
@ -36,19 +32,14 @@ CutCommand::CutCommand()
{ {
} }
bool CutCommand::onEnabled(Context* context) bool CutCommand::onEnabled(Context* ctx)
{ {
return context->checkFlags(ContextFlags::ActiveDocumentIsWritable | return App::instance()->inputChain().canCut(ctx);
ContextFlags::ActiveLayerIsVisible |
ContextFlags::ActiveLayerIsEditable |
ContextFlags::HasVisibleMask |
ContextFlags::HasActiveImage);
} }
void CutCommand::onExecute(Context* context) void CutCommand::onExecute(Context* ctx)
{ {
ContextWriter writer(context); App::instance()->inputChain().cut(ctx);
clipboard::cut(writer);
} }
Command* CommandFactory::createCutCommand() Command* CommandFactory::createCutCommand()

View File

@ -9,11 +9,9 @@
#include "config.h" #include "config.h"
#endif #endif
#include "app/app.h"
#include "app/commands/command.h" #include "app/commands/command.h"
#include "app/context.h" #include "app/ui/input_chain.h"
#include "app/util/clipboard.h"
#include "doc/layer.h"
#include "doc/sprite.h"
namespace app { namespace app {
@ -23,8 +21,8 @@ public:
Command* clone() const override { return new PasteCommand(*this); } Command* clone() const override { return new PasteCommand(*this); }
protected: protected:
bool onEnabled(Context* context); bool onEnabled(Context* ctx) override;
void onExecute(Context* context); void onExecute(Context* ctx) override;
}; };
PasteCommand::PasteCommand() PasteCommand::PasteCommand()
@ -34,21 +32,14 @@ PasteCommand::PasteCommand()
{ {
} }
bool PasteCommand::onEnabled(Context* context) bool PasteCommand::onEnabled(Context* ctx)
{ {
return return App::instance()->inputChain().canPaste(ctx);
(clipboard::get_current_format() == clipboard::ClipboardImage &&
context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
ContextFlags::ActiveLayerIsVisible |
ContextFlags::ActiveLayerIsEditable |
ContextFlags::ActiveLayerIsImage)) ||
(clipboard::get_current_format() == clipboard::ClipboardDocumentRange &&
context->checkFlags(ContextFlags::ActiveDocumentIsWritable));
} }
void PasteCommand::onExecute(Context* context) void PasteCommand::onExecute(Context* ctx)
{ {
clipboard::paste(); App::instance()->inputChain().paste(ctx);
} }
Command* CommandFactory::createPasteCommand() Command* CommandFactory::createPasteCommand()

View File

@ -28,6 +28,7 @@
#include "app/transaction.h" #include "app/transaction.h"
#include "app/ui/color_spectrum.h" #include "app/ui/color_spectrum.h"
#include "app/ui/editor/editor.h" #include "app/ui/editor/editor.h"
#include "app/ui/input_chain.h"
#include "app/ui/skin/skin_theme.h" #include "app/ui/skin/skin_theme.h"
#include "app/ui/status_bar.h" #include "app/ui/status_bar.h"
#include "app/ui_context.h" #include "app/ui_context.h"
@ -41,6 +42,7 @@
#include "she/surface.h" #include "she/surface.h"
#include "ui/graphics.h" #include "ui/graphics.h"
#include "ui/menu.h" #include "ui/menu.h"
#include "ui/message.h"
#include "ui/paint_event.h" #include "ui/paint_event.h"
#include "ui/separator.h" #include "ui/separator.h"
#include "ui/splitter.h" #include "ui/splitter.h"
@ -187,6 +189,7 @@ ColorBar::ColorBar(int align)
UIContext::instance()->addObserver(this); UIContext::instance()->addObserver(this);
m_conn = UIContext::instance()->BeforeCommandExecution.connect(&ColorBar::onBeforeExecuteCommand, this); m_conn = UIContext::instance()->BeforeCommandExecution.connect(&ColorBar::onBeforeExecuteCommand, this);
m_paletteView.FocusEnter.connect(&ColorBar::onFocusPaletteView, this);
} }
ColorBar::~ColorBar() ColorBar::~ColorBar()
@ -243,6 +246,11 @@ void ColorBar::onActiveSiteChange(const doc::Site& site)
} }
} }
void ColorBar::onFocusPaletteView()
{
App::instance()->inputChain().prioritize(this);
}
void ColorBar::onBeforeExecuteCommand(Command* command) void ColorBar::onBeforeExecuteCommand(Command* command)
{ {
if (command->id() == CommandId::Undo || if (command->id() == CommandId::Undo ||
@ -474,7 +482,7 @@ void ColorBar::onBgColorButtonChange(const app::Color& color)
void ColorBar::onColorButtonChange(const app::Color& color) void ColorBar::onColorButtonChange(const app::Color& color)
{ {
if (color.getType() == app::Color::IndexType) if (color.getType() == app::Color::IndexType)
m_paletteView.selectColor(color.getIndex()); m_paletteView.selectColor(color.getIndex(), false);
} }
void ColorBar::onPickSpectrum(const app::Color& color, ui::MouseButtons buttons) void ColorBar::onPickSpectrum(const app::Color& color, ui::MouseButtons buttons)
@ -593,4 +601,58 @@ void ColorBar::destroyRemap()
layout(); layout();
} }
void ColorBar::onNewInputPriority(InputChainElement* element)
{
m_paletteView.clearSelection();
}
bool ColorBar::onCanCut(Context* ctx)
{
return false; // TODO
}
bool ColorBar::onCanCopy(Context* ctx)
{
return (m_paletteView.getSelectedEntriesCount() > 0);
}
bool ColorBar::onCanPaste(Context* ctx)
{
return m_paletteView.areColorsInClipboard();
}
bool ColorBar::onCanClear(Context* ctx)
{
return false; // TODO
}
bool ColorBar::onCut(Context* ctx)
{
return false; // TODO
}
bool ColorBar::onCopy(Context* ctx)
{
m_paletteView.copyToClipboard();
return true;
}
bool ColorBar::onPaste(Context* ctx)
{
m_paletteView.pasteFromClipboard();
return true;
}
bool ColorBar::onClear(Context* ctx)
{
return false; // TODO
}
void ColorBar::onCancel(Context* ctx)
{
m_paletteView.clearSelection();
m_paletteView.discardClipboardSelection();
invalidate();
}
} // namespace app } // namespace app

View File

@ -12,6 +12,7 @@
#include "app/color.h" #include "app/color.h"
#include "app/ui/button_set.h" #include "app/ui/button_set.h"
#include "app/ui/color_button.h" #include "app/ui/color_button.h"
#include "app/ui/input_chain_element.h"
#include "app/ui/palette_popup.h" #include "app/ui/palette_popup.h"
#include "app/ui/palette_view.h" #include "app/ui/palette_view.h"
#include "base/connection.h" #include "base/connection.h"
@ -32,7 +33,8 @@ namespace app {
class ColorBar : public ui::Box class ColorBar : public ui::Box
, public PaletteViewDelegate , public PaletteViewDelegate
, public doc::ContextObserver { , public doc::ContextObserver
, public app::InputChainElement {
static ColorBar* m_instance; static ColorBar* m_instance;
public: public:
static ColorBar* instance() { return m_instance; } static ColorBar* instance() { return m_instance; }
@ -56,11 +58,24 @@ namespace app {
// ContextObserver impl // ContextObserver impl
void onActiveSiteChange(const doc::Site& site) override; void onActiveSiteChange(const doc::Site& site) override;
// InputChainElement impl
void onNewInputPriority(InputChainElement* element) override;
bool onCanCut(Context* ctx) override;
bool onCanCopy(Context* ctx) override;
bool onCanPaste(Context* ctx) override;
bool onCanClear(Context* ctx) override;
bool onCut(Context* ctx) override;
bool onCopy(Context* ctx) override;
bool onPaste(Context* ctx) override;
bool onClear(Context* ctx) override;
void onCancel(Context* ctx) override;
// Signals // Signals
Signal1<void, const app::Color&> FgColorChange; Signal1<void, const app::Color&> FgColorChange;
Signal1<void, const app::Color&> BgColorChange; Signal1<void, const app::Color&> BgColorChange;
protected: protected:
void onFocusPaletteView();
void onBeforeExecuteCommand(Command* command); void onBeforeExecuteCommand(Command* command);
void onPaletteButtonClick(); void onPaletteButtonClick();
void onRemapButtonClick(); void onRemapButtonClick();

View File

@ -133,7 +133,7 @@ void ColorSelector::setColor(const app::Color& color, SetColorOptions options)
if (color.getType() == app::Color::IndexType) { if (color.getType() == app::Color::IndexType) {
m_colorPalette.clearSelection(); m_colorPalette.clearSelection();
m_colorPalette.selectColor(color.getIndex()); m_colorPalette.selectColor(color.getIndex(), false);
} }
m_rgbSliders.setColor(m_color); m_rgbSliders.setColor(m_color);
@ -306,7 +306,7 @@ void ColorSelector::findBestfitIndex(const app::Color& color)
int i = get_current_palette()->findBestfit(r, g, b); int i = get_current_palette()->findBestfit(r, g, b);
if (i >= 0 && i < 256) { if (i >= 0 && i < 256) {
m_colorPalette.clearSelection(); m_colorPalette.clearSelection();
m_colorPalette.selectColor(i); m_colorPalette.selectColor(i, false);
} }
} }

View File

@ -13,11 +13,15 @@
#include "app/app.h" #include "app/app.h"
#include "app/app_menus.h" #include "app/app_menus.h"
#include "app/cmd/clear_mask.h"
#include "app/cmd/deselect_mask.h"
#include "app/commands/commands.h" #include "app/commands/commands.h"
#include "app/console.h" #include "app/console.h"
#include "app/context_access.h"
#include "app/document_access.h" #include "app/document_access.h"
#include "app/modules/editors.h" #include "app/modules/editors.h"
#include "app/modules/palettes.h" #include "app/modules/palettes.h"
#include "app/transaction.h"
#include "app/ui/editor/editor.h" #include "app/ui/editor/editor.h"
#include "app/ui/editor/editor_customization_delegate.h" #include "app/ui/editor/editor_customization_delegate.h"
#include "app/ui/editor/editor_view.h" #include "app/ui/editor/editor_view.h"
@ -27,6 +31,7 @@
#include "app/ui/status_bar.h" #include "app/ui/status_bar.h"
#include "app/ui/workspace.h" #include "app/ui/workspace.h"
#include "app/ui_context.h" #include "app/ui_context.h"
#include "app/util/clipboard.h"
#include "base/path.h" #include "base/path.h"
#include "doc/document_event.h" #include "doc/document_event.h"
#include "doc/layer.h" #include "doc/layer.h"
@ -434,4 +439,109 @@ void DocumentView::onLayerRestacked(doc::DocumentEvent& ev)
m_editor->invalidate(); m_editor->invalidate();
} }
void DocumentView::onNewInputPriority(InputChainElement* element)
{
// Do nothing
}
bool DocumentView::onCanCut(Context* ctx)
{
return ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable |
ContextFlags::ActiveLayerIsVisible |
ContextFlags::ActiveLayerIsEditable |
ContextFlags::HasVisibleMask |
ContextFlags::HasActiveImage);
}
bool DocumentView::onCanCopy(Context* ctx)
{
return ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable |
ContextFlags::ActiveLayerIsVisible |
ContextFlags::ActiveLayerIsEditable |
ContextFlags::HasVisibleMask |
ContextFlags::HasActiveImage);
}
bool DocumentView::onCanPaste(Context* ctx)
{
return
(clipboard::get_current_format() == clipboard::ClipboardImage &&
ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable |
ContextFlags::ActiveLayerIsVisible |
ContextFlags::ActiveLayerIsEditable |
ContextFlags::ActiveLayerIsImage));
}
bool DocumentView::onCanClear(Context* ctx)
{
return ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable |
ContextFlags::ActiveLayerIsVisible |
ContextFlags::ActiveLayerIsEditable |
ContextFlags::ActiveLayerIsImage);
}
bool DocumentView::onCut(Context* ctx)
{
ContextWriter writer(ctx);
clipboard::cut(writer);
return true;
}
bool DocumentView::onCopy(Context* ctx)
{
const ContextReader reader(ctx);
if (reader.site()->document() &&
static_cast<const app::Document*>(reader.site()->document())->isMaskVisible() &&
reader.site()->image()) {
clipboard::copy(reader);
return true;
}
else
return false;
}
bool DocumentView::onPaste(Context* ctx)
{
if (clipboard::get_current_format() == clipboard::ClipboardImage) {
clipboard::paste();
return true;
}
else
return false;
}
bool DocumentView::onClear(Context* ctx)
{
ContextWriter writer(ctx);
Document* document = writer.document();
bool visibleMask = document->isMaskVisible();
if (!writer.cel())
return false;
{
Transaction transaction(writer.context(), "Clear");
transaction.execute(new cmd::ClearMask(writer.cel()));
if (visibleMask)
transaction.execute(new cmd::DeselectMask(document));
transaction.commit();
}
if (visibleMask)
document->generateMaskBoundaries();
document->notifyGeneralUpdate();
return true;
}
void DocumentView::onCancel(Context* ctx)
{
// Deselect mask
if (ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable |
ContextFlags::HasVisibleMask)) {
Command* deselectMask = CommandsModule::instance()->getCommandByName(CommandId::DeselectMask);
ctx->executeCommand(deselectMask);
}
}
} // namespace app } // namespace app

View File

@ -9,6 +9,7 @@
#define APP_UI_DOCUMENT_VIEW_H_INCLUDED #define APP_UI_DOCUMENT_VIEW_H_INCLUDED
#pragma once #pragma once
#include "app/ui/input_chain_element.h"
#include "app/ui/tabs.h" #include "app/ui/tabs.h"
#include "app/ui/workspace_view.h" #include "app/ui/workspace_view.h"
#include "doc/document_observer.h" #include "doc/document_observer.h"
@ -29,7 +30,8 @@ namespace app {
class DocumentView : public ui::Box class DocumentView : public ui::Box
, public TabView , public TabView
, public doc::DocumentObserver , public doc::DocumentObserver
, public WorkspaceView { , public WorkspaceView
, public app::InputChainElement {
public: public:
enum Type { enum Type {
Normal, Normal,
@ -57,6 +59,7 @@ namespace app {
void onClonedFrom(WorkspaceView* from) override; void onClonedFrom(WorkspaceView* from) override;
bool onCloseView(Workspace* workspace) override; bool onCloseView(Workspace* workspace) override;
void onTabPopup(Workspace* workspace) override; void onTabPopup(Workspace* workspace) override;
InputChainElement* onGetInputChainElement() override { return this; }
// DocumentObserver implementation // DocumentObserver implementation
void onGeneralUpdate(doc::DocumentEvent& ev) override; void onGeneralUpdate(doc::DocumentEvent& ev) override;
@ -69,6 +72,18 @@ namespace app {
void onTotalFramesChanged(doc::DocumentEvent& ev) override; void onTotalFramesChanged(doc::DocumentEvent& ev) override;
void onLayerRestacked(doc::DocumentEvent& ev) override; void onLayerRestacked(doc::DocumentEvent& ev) override;
// InputChainElement impl
void onNewInputPriority(InputChainElement* element) override;
bool onCanCut(Context* ctx) override;
bool onCanCopy(Context* ctx) override;
bool onCanPaste(Context* ctx) override;
bool onCanClear(Context* ctx) override;
bool onCut(Context* ctx) override;
bool onCopy(Context* ctx) override;
bool onPaste(Context* ctx) override;
bool onClear(Context* ctx) override;
void onCancel(Context* ctx) override;
protected: protected:
bool onProcessMessage(ui::Message* msg) override; bool onProcessMessage(ui::Message* msg) override;

109
src/app/ui/input_chain.cpp Normal file
View File

@ -0,0 +1,109 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/ui/input_chain.h"
#include "app/ui/input_chain_element.h"
#include <algorithm>
namespace app {
void InputChain::prioritize(InputChainElement* element)
{
if (!m_elements.empty() && m_elements.front() == element)
return;
auto it = std::find(m_elements.begin(), m_elements.end(), element);
if (it != m_elements.end())
m_elements.erase(it);
for (auto e : m_elements)
e->onNewInputPriority(element);
m_elements.insert(m_elements.begin(), element);
}
bool InputChain::canCut(Context* ctx)
{
for (auto e : m_elements) {
if (e->onCanCut(ctx))
return true;
}
return false;
}
bool InputChain::canCopy(Context* ctx)
{
for (auto e : m_elements) {
if (e->onCanCopy(ctx))
return true;
}
return false;
}
bool InputChain::canPaste(Context* ctx)
{
for (auto e : m_elements) {
if (e->onCanPaste(ctx))
return true;
}
return false;
}
bool InputChain::canClear(Context* ctx)
{
for (auto e : m_elements) {
if (e->onCanClear(ctx))
return true;
}
return false;
}
void InputChain::cut(Context* ctx)
{
for (auto e : m_elements) {
if (e->onCut(ctx))
break;
}
}
void InputChain::copy(Context* ctx)
{
for (auto e : m_elements) {
if (e->onCopy(ctx))
break;
}
}
void InputChain::paste(Context* ctx)
{
for (auto e : m_elements) {
if (e->onPaste(ctx))
break;
}
}
void InputChain::clear(Context* ctx)
{
for (auto e : m_elements) {
if (e->onClear(ctx))
break;
}
}
void InputChain::cancel(Context* ctx)
{
for (auto e : m_elements)
e->onCancel(ctx);
}
} // namespace app

44
src/app/ui/input_chain.h Normal file
View File

@ -0,0 +1,44 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifndef APP_INPUT_CHAIN_H_INCLUDED
#define APP_INPUT_CHAIN_H_INCLUDED
#pragma once
#include "base/observable.h"
namespace app {
class Context;
class InputChainElement;
// The chain of objects (in order) that want to receive
// input/commands from the user (e.g. ColorBar, Timeline, and
// Workspace/DocumentView). When each of these elements receive the
// user focus, they call InputChain::prioritize().
class InputChain {
public:
void prioritize(InputChainElement* element);
bool canCut(Context* ctx);
bool canCopy(Context* ctx);
bool canPaste(Context* ctx);
bool canClear(Context* ctx);
void cut(Context* ctx);
void copy(Context* ctx);
void paste(Context* ctx);
void clear(Context* ctx);
void cancel(Context* ctx);
private:
std::vector<InputChainElement*> m_elements;
};
} // namespace app
#endif

View File

@ -0,0 +1,37 @@
// Aseprite
// Copyright (C) 2001-2015 David Capello
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License version 2 as
// published by the Free Software Foundation.
#ifndef APP_INPUT_CHAIN_ELEMENT_H_INCLUDED
#define APP_INPUT_CHAIN_ELEMENT_H_INCLUDED
#pragma once
namespace app {
class Context;
class InputChainElement {
public:
virtual ~InputChainElement() { }
// Called when a new element has priorty in the chain.
virtual void onNewInputPriority(InputChainElement* element) = 0;
virtual bool onCanCut(Context* ctx) = 0;
virtual bool onCanCopy(Context* ctx) = 0;
virtual bool onCanPaste(Context* ctx) = 0;
virtual bool onCanClear(Context* ctx) = 0;
virtual bool onCut(Context* ctx) = 0;
virtual bool onCopy(Context* ctx) = 0;
virtual bool onPaste(Context* ctx) = 0;
virtual bool onClear(Context* ctx) = 0;
virtual void onCancel(Context* ctx) = 0;
};
} // namespace app
#endif

View File

@ -17,7 +17,6 @@
#include "app/modules/gui.h" #include "app/modules/gui.h"
#include "app/modules/palettes.h" #include "app/modules/palettes.h"
#include "app/ui/editor/editor.h" #include "app/ui/editor/editor.h"
#include "app/ui/keyboard_shortcuts.h"
#include "app/ui/palette_view.h" #include "app/ui/palette_view.h"
#include "app/ui/skin/skin_theme.h" #include "app/ui/skin/skin_theme.h"
#include "app/ui/skin/style.h" #include "app/ui/skin/style.h"
@ -104,18 +103,25 @@ void PaletteView::setColumns(int columns)
void PaletteView::clearSelection() void PaletteView::clearSelection()
{ {
bool invalidate = (m_selectedEntries.picks() > 0);
std::fill(m_selectedEntries.begin(), std::fill(m_selectedEntries.begin(),
m_selectedEntries.end(), false); m_selectedEntries.end(), false);
if (invalidate)
this->invalidate();
} }
void PaletteView::selectColor(int index) void PaletteView::selectColor(int index, bool startRange)
{ {
ASSERT(index >= 0 && index < Palette::MaxColors); ASSERT(index >= 0 && index < Palette::MaxColors);
if (m_currentEntry != index || !m_selectedEntries[index]) { if (m_currentEntry != index || !m_selectedEntries[index]) {
m_currentEntry = index; m_currentEntry = index;
m_rangeAnchor = index; m_rangeAnchor = index;
m_selectedEntries[index] = true;
if (startRange)
m_selectedEntries[index] = true;
update_scroll(m_currentEntry); update_scroll(m_currentEntry);
invalidate(); invalidate();
@ -172,6 +178,11 @@ void PaletteView::getSelectedEntries(PalettePicks& entries) const
entries = m_selectedEntries; entries = m_selectedEntries;
} }
int PaletteView::getSelectedEntriesCount() const
{
return m_selectedEntries.picks();
}
app::Color PaletteView::getColorByPosition(const gfx::Point& pos) app::Color PaletteView::getColorByPosition(const gfx::Point& pos)
{ {
gfx::Point relPos = pos - getBounds().getOrigin(); gfx::Point relPos = pos - getBounds().getOrigin();
@ -218,10 +229,31 @@ void PaletteView::pasteFromClipboard()
m_delegate->onPaletteViewPasteColors( m_delegate->onPaletteViewPasteColors(
m_clipboardEditor, m_clipboardEntries, m_selectedEntries); m_clipboardEditor, m_clipboardEntries, m_selectedEntries);
// We just hide the marching ants, the user can paste multiple
// times.
stopMarchingAnts(); stopMarchingAnts();
invalidate();
} }
} }
void PaletteView::discardClipboardSelection()
{
bool redraw = false;
if (m_clipboardEditor) {
setClipboardEditor(nullptr);
redraw = true;
}
if (areColorsInClipboard()) {
stopMarchingAnts();
redraw = true;
}
if (redraw)
invalidate();
}
bool PaletteView::areColorsInClipboard() const bool PaletteView::areColorsInClipboard() const
{ {
return isMarchingAntsRunning(); return isMarchingAntsRunning();
@ -231,36 +263,13 @@ bool PaletteView::onProcessMessage(Message* msg)
{ {
switch (msg->type()) { switch (msg->type()) {
case kKeyDownMessage: { case kFocusEnterMessage:
Key* key = KeyboardShortcuts::instance()->command(CommandId::Copy); FocusEnter();
if (key && key->isPressed(msg)) {
copyToClipboard();
return true;
}
key = KeyboardShortcuts::instance()->command(CommandId::Paste);
if (key && key->isPressed(msg)) {
pasteFromClipboard();
return true;
}
if (isMarchingAntsRunning()) {
key = KeyboardShortcuts::instance()->command(CommandId::DeselectMask);
Key* esc = KeyboardShortcuts::instance()->command(CommandId::Cancel);
if ((key && key->isPressed(msg)) ||
(esc && esc->isPressed(msg))) {
stopMarchingAnts();
invalidate();
return true;
}
}
updateCopyFlag(msg);
break; break;
}
case kMouseEnterMessage: case kKeyDownMessage:
case kKeyUpMessage: case kKeyUpMessage:
case kMouseEnterMessage:
updateCopyFlag(msg); updateCopyFlag(msg);
break; break;
@ -297,7 +306,7 @@ bool PaletteView::onProcessMessage(Message* msg)
if (msg->type() == kMouseMoveMessage) if (msg->type() == kMouseMoveMessage)
selectRange(m_rangeAnchor, idx); selectRange(m_rangeAnchor, idx);
else else
selectColor(idx); selectColor(idx, true);
// Emit signal // Emit signal
if (m_delegate) if (m_delegate)
@ -478,10 +487,8 @@ void PaletteView::onDrawMarchingAnts()
void PaletteView::onDestroyEditor(Editor* editor) void PaletteView::onDestroyEditor(Editor* editor)
{ {
if (m_clipboardEditor == editor) { if (m_clipboardEditor == editor)
setClipboardEditor(nullptr); discardClipboardSelection();
stopMarchingAnts();
}
} }
void PaletteView::request_size(int* w, int* h) void PaletteView::request_size(int* w, int* h)

View File

@ -52,12 +52,13 @@ namespace app {
void setColumns(int columns); void setColumns(int columns);
void clearSelection(); void clearSelection();
void selectColor(int index); void selectColor(int index, bool startRange);
void selectRange(int index1, int index2); void selectRange(int index1, int index2);
int getSelectedEntry() const; int getSelectedEntry() const;
bool getSelectedRange(int& index1, int& index2) const; bool getSelectedRange(int& index1, int& index2) const;
void getSelectedEntries(doc::PalettePicks& entries) const; void getSelectedEntries(doc::PalettePicks& entries) const;
int getSelectedEntriesCount() const;
app::Color getColorByPosition(const gfx::Point& pos); app::Color getColorByPosition(const gfx::Point& pos);
@ -66,8 +67,11 @@ namespace app {
void copyToClipboard(); void copyToClipboard();
void pasteFromClipboard(); void pasteFromClipboard();
void discardClipboardSelection();
bool areColorsInClipboard() const; bool areColorsInClipboard() const;
Signal0<void> FocusEnter;
protected: protected:
bool onProcessMessage(ui::Message* msg) override; bool onProcessMessage(ui::Message* msg) override;
void onPaint(ui::PaintEvent& ev) override; void onPaint(ui::PaintEvent& ev) override;

View File

@ -32,6 +32,7 @@
#include "app/ui/configure_timeline_popup.h" #include "app/ui/configure_timeline_popup.h"
#include "app/ui/document_view.h" #include "app/ui/document_view.h"
#include "app/ui/editor/editor.h" #include "app/ui/editor/editor.h"
#include "app/ui/input_chain.h"
#include "app/ui/skin/skin_theme.h" #include "app/ui/skin/skin_theme.h"
#include "app/ui/skin/style.h" #include "app/ui/skin/style.h"
#include "app/ui/status_bar.h" #include "app/ui/status_bar.h"
@ -138,14 +139,6 @@ void Timeline::updateUsingEditor(Editor* editor)
{ {
m_aniControls.updateUsingEditor(editor); m_aniControls.updateUsingEditor(editor);
// As a sprite editor was selected, it looks like the user wants to
// execute commands targetting the editor instead of the
// timeline. Here we disable the selected range, so commands like
// Clear, Copy, Cut, etc. don't target the Timeline and they are
// sent to the active sprite editor.
m_range.disableRange();
invalidate();
detachDocument(); detachDocument();
// We always update the editor. In this way the timeline keeps in // We always update the editor. In this way the timeline keeps in
@ -246,6 +239,10 @@ bool Timeline::onProcessMessage(Message* msg)
{ {
switch (msg->type()) { switch (msg->type()) {
case kFocusEnterMessage:
App::instance()->inputChain().prioritize(this);
break;
case kTimerMessage: case kTimerMessage:
if (static_cast<TimerMessage*>(msg)->timer() == &m_clipboard_timer) { if (static_cast<TimerMessage*>(msg)->timer() == &m_clipboard_timer) {
Document* clipboard_document; Document* clipboard_document;
@ -2333,4 +2330,101 @@ FrameTag* Timeline::Hit::getFrameTag() const
return get<FrameTag>(frameTag); return get<FrameTag>(frameTag);
} }
void Timeline::onNewInputPriority(InputChainElement* element)
{
// As a another input element has priority (e.g. Timeline or
// Editor), it looks like the user wants to execute commands
// targetting the editor instead of the timeline. Here we disable
// the selected range, so commands like Clear, Copy, Cut, etc. don't
// target the Timeline and they are sent to the active sprite
// editor.
m_range.disableRange();
invalidate();
}
bool Timeline::onCanCut(Context* ctx)
{
return false; // TODO
}
bool Timeline::onCanCopy(Context* ctx)
{
return
m_range.enabled() &&
ctx->checkFlags(ContextFlags::HasActiveDocument);
}
bool Timeline::onCanPaste(Context* ctx)
{
return
(clipboard::get_current_format() == clipboard::ClipboardDocumentRange &&
ctx->checkFlags(ContextFlags::ActiveDocumentIsWritable));
}
bool Timeline::onCanClear(Context* ctx)
{
return (m_document && m_sprite && m_range.enabled());
}
bool Timeline::onCut(Context* ctx)
{
return false; // TODO
}
bool Timeline::onCopy(Context* ctx)
{
if (m_range.enabled()) {
const ContextReader reader(ctx);
if (reader.document()) {
clipboard::copy_range(reader, m_range);
return true;
}
}
return false;
}
bool Timeline::onPaste(Context* ctx)
{
if (clipboard::get_current_format() == clipboard::ClipboardDocumentRange) {
clipboard::paste();
return true;
}
else
return false;
}
bool Timeline::onClear(Context* ctx)
{
if (!m_document || !m_sprite || !m_range.enabled())
return false;
Command* cmd = nullptr;
switch (m_range.type()) {
case DocumentRange::kCels:
cmd = CommandsModule::instance()->getCommandByName(CommandId::ClearCel);
break;
case DocumentRange::kFrames:
cmd = CommandsModule::instance()->getCommandByName(CommandId::RemoveFrame);
break;
case DocumentRange::kLayers:
cmd = CommandsModule::instance()->getCommandByName(CommandId::RemoveLayer);
break;
}
if (cmd) {
ctx->executeCommand(cmd);
return true;
}
else
return false;
}
void Timeline::onCancel(Context* ctx)
{
m_range.disableRange();
clearClipboardRange();
invalidate();
}
} // namespace app } // namespace app

View File

@ -13,6 +13,7 @@
#include "app/pref/preferences.h" #include "app/pref/preferences.h"
#include "app/ui/ani_controls.h" #include "app/ui/ani_controls.h"
#include "app/ui/editor/editor_observer.h" #include "app/ui/editor/editor_observer.h"
#include "app/ui/input_chain_element.h"
#include "base/connection.h" #include "base/connection.h"
#include "doc/document_observer.h" #include "doc/document_observer.h"
#include "doc/documents_observer.h" #include "doc/documents_observer.h"
@ -53,7 +54,8 @@ namespace app {
class Timeline : public ui::Widget class Timeline : public ui::Widget
, public doc::DocumentsObserver , public doc::DocumentsObserver
, public doc::DocumentObserver , public doc::DocumentObserver
, public app::EditorObserver { , public app::EditorObserver
, public app::InputChainElement {
public: public:
typedef DocumentRange Range; typedef DocumentRange Range;
@ -119,6 +121,18 @@ namespace app {
void onAfterLayerChanged(Editor* editor) override; void onAfterLayerChanged(Editor* editor) override;
void onDestroyEditor(Editor* editor) override; void onDestroyEditor(Editor* editor) override;
// InputChainElement impl
void onNewInputPriority(InputChainElement* element) override;
bool onCanCut(Context* ctx) override;
bool onCanCopy(Context* ctx) override;
bool onCanPaste(Context* ctx) override;
bool onCanClear(Context* ctx) override;
bool onCut(Context* ctx) override;
bool onCopy(Context* ctx) override;
bool onPaste(Context* ctx) override;
bool onClear(Context* ctx) override;
void onCancel(Context* ctx) override;
private: private:
struct Hit { struct Hit {
int part; int part;

View File

@ -11,6 +11,8 @@
#include "app/ui/workspace.h" #include "app/ui/workspace.h"
#include "app/app.h"
#include "app/ui/input_chain.h"
#include "app/ui/skin/skin_theme.h" #include "app/ui/skin/skin_theme.h"
#include "app/ui/workspace_tabs.h" #include "app/ui/workspace_tabs.h"
#include "app/ui/workspace_view.h" #include "app/ui/workspace_view.h"
@ -298,4 +300,100 @@ WorkspaceTabs* Workspace::getTabsAt(const gfx::Point& pos)
return nullptr; return nullptr;
} }
void Workspace::onNewInputPriority(InputChainElement* newElement)
{
WorkspaceView* view = activeView();
InputChainElement* activeElement = (view ? view->onGetInputChainElement(): nullptr);
if (activeElement)
activeElement->onNewInputPriority(newElement);
}
bool Workspace::onCanCut(Context* ctx)
{
WorkspaceView* view = activeView();
InputChainElement* activeElement = (view ? view->onGetInputChainElement(): nullptr);
if (activeElement)
return activeElement->onCanCut(ctx);
else
return false;
}
bool Workspace::onCanCopy(Context* ctx)
{
WorkspaceView* view = activeView();
InputChainElement* activeElement = (view ? view->onGetInputChainElement(): nullptr);
if (activeElement)
return activeElement->onCanCopy(ctx);
else
return false;
}
bool Workspace::onCanPaste(Context* ctx)
{
WorkspaceView* view = activeView();
InputChainElement* activeElement = (view ? view->onGetInputChainElement(): nullptr);
if (activeElement)
return activeElement->onCanPaste(ctx);
else
return false;
}
bool Workspace::onCanClear(Context* ctx)
{
WorkspaceView* view = activeView();
InputChainElement* activeElement = (view ? view->onGetInputChainElement(): nullptr);
if (activeElement)
return activeElement->onCanClear(ctx);
else
return false;
}
bool Workspace::onCut(Context* ctx)
{
WorkspaceView* view = activeView();
InputChainElement* activeElement = (view ? view->onGetInputChainElement(): nullptr);
if (activeElement)
return activeElement->onCut(ctx);
else
return false;
}
bool Workspace::onCopy(Context* ctx)
{
WorkspaceView* view = activeView();
InputChainElement* activeElement = (view ? view->onGetInputChainElement(): nullptr);
if (activeElement)
return activeElement->onCopy(ctx);
else
return false;
}
bool Workspace::onPaste(Context* ctx)
{
WorkspaceView* view = activeView();
InputChainElement* activeElement = (view ? view->onGetInputChainElement(): nullptr);
if (activeElement)
return activeElement->onPaste(ctx);
else
return false;
}
bool Workspace::onClear(Context* ctx)
{
WorkspaceView* view = activeView();
InputChainElement* activeElement = (view ? view->onGetInputChainElement(): nullptr);
if (activeElement)
return activeElement->onClear(ctx);
else
return false;
}
void Workspace::onCancel(Context* ctx)
{
WorkspaceView* view = activeView();
InputChainElement* activeElement = (view ? view->onGetInputChainElement(): nullptr);
if (activeElement)
activeElement->onCancel(ctx);
}
} // namespace app } // namespace app

View File

@ -9,6 +9,7 @@
#define APP_UI_WORKSPACE_H_INCLUDED #define APP_UI_WORKSPACE_H_INCLUDED
#pragma once #pragma once
#include "app/ui/input_chain_element.h"
#include "app/ui/tabs.h" #include "app/ui/tabs.h"
#include "app/ui/workspace_panel.h" #include "app/ui/workspace_panel.h"
#include "base/signal.h" #include "base/signal.h"
@ -17,7 +18,8 @@
namespace app { namespace app {
class WorkspaceTabs; class WorkspaceTabs;
class Workspace : public ui::Widget { class Workspace : public ui::Widget
, public app::InputChainElement {
public: public:
typedef WorkspaceViews::iterator iterator; typedef WorkspaceViews::iterator iterator;
@ -58,6 +60,18 @@ namespace app {
// Returns true if the view was docked inside the workspace. // Returns true if the view was docked inside the workspace.
DropViewAtResult dropViewAt(const gfx::Point& pos, WorkspaceView* view, bool clone); DropViewAtResult dropViewAt(const gfx::Point& pos, WorkspaceView* view, bool clone);
// InputChainElement impl
void onNewInputPriority(InputChainElement* element) override;
bool onCanCut(Context* ctx) override;
bool onCanCopy(Context* ctx) override;
bool onCanPaste(Context* ctx) override;
bool onCanClear(Context* ctx) override;
bool onCut(Context* ctx) override;
bool onCopy(Context* ctx) override;
bool onPaste(Context* ctx) override;
bool onClear(Context* ctx) override;
void onCancel(Context* ctx) override;
Signal0<void> ActiveViewChanged; Signal0<void> ActiveViewChanged;
protected: protected:

View File

@ -14,6 +14,7 @@ namespace ui {
} }
namespace app { namespace app {
class InputChainElement;
class Workspace; class Workspace;
class WorkspaceView { class WorkspaceView {
@ -39,6 +40,8 @@ namespace app {
virtual bool onCloseView(Workspace* workspace) = 0; virtual bool onCloseView(Workspace* workspace) = 0;
virtual void onTabPopup(Workspace* workspace) = 0; virtual void onTabPopup(Workspace* workspace) = 0;
virtual InputChainElement* onGetInputChainElement() { return nullptr; }
}; };
} // namespace app } // namespace app

View File

@ -17,6 +17,7 @@
#include "app/ui/color_bar.h" #include "app/ui/color_bar.h"
#include "app/ui/document_view.h" #include "app/ui/document_view.h"
#include "app/ui/editor/editor.h" #include "app/ui/editor/editor.h"
#include "app/ui/input_chain.h"
#include "app/ui/main_window.h" #include "app/ui/main_window.h"
#include "app/ui/preview_editor.h" #include "app/ui/preview_editor.h"
#include "app/ui/status_bar.h" #include "app/ui/status_bar.h"
@ -76,13 +77,17 @@ DocumentView* UIContext::activeView() const
void UIContext::setActiveView(DocumentView* docView) void UIContext::setActiveView(DocumentView* docView)
{ {
MainWindow* mainWin = App::instance()->getMainWindow();
// Prioritize workspace for user input.
App::instance()->inputChain().prioritize(mainWin->getWorkspace());
// Do nothing cases: 1) the view is already selected, or 2) the view // Do nothing cases: 1) the view is already selected, or 2) the view
// is the a preview. // is the a preview.
if (m_lastSelectedView == docView || if (m_lastSelectedView == docView ||
(docView && docView->isPreview())) (docView && docView->isPreview()))
return; return;
MainWindow* mainWin = App::instance()->getMainWindow();
if (docView) { if (docView) {
mainWin->getTabsBar()->selectTab(docView); mainWin->getTabsBar()->selectTab(docView);