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/hex_color_entry.cpp
ui/home_view.cpp
ui/input_chain.cpp
ui/keyboard_shortcuts.cpp
ui/main_menu_bar.cpp
ui/main_window.cpp

View File

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

View File

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

View File

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

View File

@ -10,18 +10,8 @@
#endif
#include "app/app.h"
#include "app/cmd/clear_mask.h"
#include "app/cmd/deselect_mask.h"
#include "app/commands/command.h"
#include "app/commands/commands.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"
#include "app/ui/input_chain.h"
namespace app {
@ -31,8 +21,8 @@ public:
Command* clone() const override { return new ClearCommand(*this); }
protected:
bool onEnabled(Context* context);
void onExecute(Context* context);
bool onEnabled(Context* ctx) override;
void onExecute(Context* ctx) override;
};
ClearCommand::ClearCommand()
@ -42,58 +32,14 @@ ClearCommand::ClearCommand()
{
}
bool ClearCommand::onEnabled(Context* context)
bool ClearCommand::onEnabled(Context* ctx)
{
return context->checkFlags(ContextFlags::ActiveDocumentIsWritable |
ContextFlags::ActiveLayerIsVisible |
ContextFlags::ActiveLayerIsEditable |
ContextFlags::ActiveLayerIsImage);
return App::instance()->inputChain().canClear(ctx);
}
void ClearCommand::onExecute(Context* context)
void ClearCommand::onExecute(Context* ctx)
{
// Clear of several frames is handled with ClearCel command.
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);
App::instance()->inputChain().clear(ctx);
}
Command* CommandFactory::createClearCommand()

View File

@ -11,16 +11,7 @@
#include "app/app.h"
#include "app/commands/command.h"
#include "app/context_access.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"
#include "app/ui/input_chain.h"
namespace app {
@ -30,8 +21,8 @@ public:
Command* clone() const override { return new CopyCommand(*this); }
protected:
bool onEnabled(Context* context);
void onExecute(Context* context);
bool onEnabled(Context* ctx) override;
void onExecute(Context* ctx) override;
};
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);
// 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);
}
App::instance()->inputChain().copy(ctx);
}
Command* CommandFactory::createCopyCommand()

View File

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

View File

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

View File

@ -28,6 +28,7 @@
#include "app/transaction.h"
#include "app/ui/color_spectrum.h"
#include "app/ui/editor/editor.h"
#include "app/ui/input_chain.h"
#include "app/ui/skin/skin_theme.h"
#include "app/ui/status_bar.h"
#include "app/ui_context.h"
@ -41,6 +42,7 @@
#include "she/surface.h"
#include "ui/graphics.h"
#include "ui/menu.h"
#include "ui/message.h"
#include "ui/paint_event.h"
#include "ui/separator.h"
#include "ui/splitter.h"
@ -187,6 +189,7 @@ ColorBar::ColorBar(int align)
UIContext::instance()->addObserver(this);
m_conn = UIContext::instance()->BeforeCommandExecution.connect(&ColorBar::onBeforeExecuteCommand, this);
m_paletteView.FocusEnter.connect(&ColorBar::onFocusPaletteView, this);
}
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)
{
if (command->id() == CommandId::Undo ||
@ -474,7 +482,7 @@ void ColorBar::onBgColorButtonChange(const app::Color& color)
void ColorBar::onColorButtonChange(const app::Color& color)
{
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)
@ -593,4 +601,58 @@ void ColorBar::destroyRemap()
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

View File

@ -12,6 +12,7 @@
#include "app/color.h"
#include "app/ui/button_set.h"
#include "app/ui/color_button.h"
#include "app/ui/input_chain_element.h"
#include "app/ui/palette_popup.h"
#include "app/ui/palette_view.h"
#include "base/connection.h"
@ -32,7 +33,8 @@ namespace app {
class ColorBar : public ui::Box
, public PaletteViewDelegate
, public doc::ContextObserver {
, public doc::ContextObserver
, public app::InputChainElement {
static ColorBar* m_instance;
public:
static ColorBar* instance() { return m_instance; }
@ -56,11 +58,24 @@ namespace app {
// ContextObserver impl
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
Signal1<void, const app::Color&> FgColorChange;
Signal1<void, const app::Color&> BgColorChange;
protected:
void onFocusPaletteView();
void onBeforeExecuteCommand(Command* command);
void onPaletteButtonClick();
void onRemapButtonClick();

View File

@ -133,7 +133,7 @@ void ColorSelector::setColor(const app::Color& color, SetColorOptions options)
if (color.getType() == app::Color::IndexType) {
m_colorPalette.clearSelection();
m_colorPalette.selectColor(color.getIndex());
m_colorPalette.selectColor(color.getIndex(), false);
}
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);
if (i >= 0 && i < 256) {
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_menus.h"
#include "app/cmd/clear_mask.h"
#include "app/cmd/deselect_mask.h"
#include "app/commands/commands.h"
#include "app/console.h"
#include "app/context_access.h"
#include "app/document_access.h"
#include "app/modules/editors.h"
#include "app/modules/palettes.h"
#include "app/transaction.h"
#include "app/ui/editor/editor.h"
#include "app/ui/editor/editor_customization_delegate.h"
#include "app/ui/editor/editor_view.h"
@ -27,6 +31,7 @@
#include "app/ui/status_bar.h"
#include "app/ui/workspace.h"
#include "app/ui_context.h"
#include "app/util/clipboard.h"
#include "base/path.h"
#include "doc/document_event.h"
#include "doc/layer.h"
@ -434,4 +439,109 @@ void DocumentView::onLayerRestacked(doc::DocumentEvent& ev)
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

View File

@ -9,6 +9,7 @@
#define APP_UI_DOCUMENT_VIEW_H_INCLUDED
#pragma once
#include "app/ui/input_chain_element.h"
#include "app/ui/tabs.h"
#include "app/ui/workspace_view.h"
#include "doc/document_observer.h"
@ -29,7 +30,8 @@ namespace app {
class DocumentView : public ui::Box
, public TabView
, public doc::DocumentObserver
, public WorkspaceView {
, public WorkspaceView
, public app::InputChainElement {
public:
enum Type {
Normal,
@ -57,6 +59,7 @@ namespace app {
void onClonedFrom(WorkspaceView* from) override;
bool onCloseView(Workspace* workspace) override;
void onTabPopup(Workspace* workspace) override;
InputChainElement* onGetInputChainElement() override { return this; }
// DocumentObserver implementation
void onGeneralUpdate(doc::DocumentEvent& ev) override;
@ -69,6 +72,18 @@ namespace app {
void onTotalFramesChanged(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:
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/palettes.h"
#include "app/ui/editor/editor.h"
#include "app/ui/keyboard_shortcuts.h"
#include "app/ui/palette_view.h"
#include "app/ui/skin/skin_theme.h"
#include "app/ui/skin/style.h"
@ -104,18 +103,25 @@ void PaletteView::setColumns(int columns)
void PaletteView::clearSelection()
{
bool invalidate = (m_selectedEntries.picks() > 0);
std::fill(m_selectedEntries.begin(),
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);
if (m_currentEntry != index || !m_selectedEntries[index]) {
m_currentEntry = index;
m_rangeAnchor = index;
m_selectedEntries[index] = true;
if (startRange)
m_selectedEntries[index] = true;
update_scroll(m_currentEntry);
invalidate();
@ -172,6 +178,11 @@ void PaletteView::getSelectedEntries(PalettePicks& entries) const
entries = m_selectedEntries;
}
int PaletteView::getSelectedEntriesCount() const
{
return m_selectedEntries.picks();
}
app::Color PaletteView::getColorByPosition(const gfx::Point& pos)
{
gfx::Point relPos = pos - getBounds().getOrigin();
@ -218,10 +229,31 @@ void PaletteView::pasteFromClipboard()
m_delegate->onPaletteViewPasteColors(
m_clipboardEditor, m_clipboardEntries, m_selectedEntries);
// We just hide the marching ants, the user can paste multiple
// times.
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
{
return isMarchingAntsRunning();
@ -231,36 +263,13 @@ bool PaletteView::onProcessMessage(Message* msg)
{
switch (msg->type()) {
case kKeyDownMessage: {
Key* key = KeyboardShortcuts::instance()->command(CommandId::Copy);
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);
case kFocusEnterMessage:
FocusEnter();
break;
}
case kMouseEnterMessage:
case kKeyDownMessage:
case kKeyUpMessage:
case kMouseEnterMessage:
updateCopyFlag(msg);
break;
@ -297,7 +306,7 @@ bool PaletteView::onProcessMessage(Message* msg)
if (msg->type() == kMouseMoveMessage)
selectRange(m_rangeAnchor, idx);
else
selectColor(idx);
selectColor(idx, true);
// Emit signal
if (m_delegate)
@ -478,10 +487,8 @@ void PaletteView::onDrawMarchingAnts()
void PaletteView::onDestroyEditor(Editor* editor)
{
if (m_clipboardEditor == editor) {
setClipboardEditor(nullptr);
stopMarchingAnts();
}
if (m_clipboardEditor == editor)
discardClipboardSelection();
}
void PaletteView::request_size(int* w, int* h)

View File

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

View File

@ -32,6 +32,7 @@
#include "app/ui/configure_timeline_popup.h"
#include "app/ui/document_view.h"
#include "app/ui/editor/editor.h"
#include "app/ui/input_chain.h"
#include "app/ui/skin/skin_theme.h"
#include "app/ui/skin/style.h"
#include "app/ui/status_bar.h"
@ -138,14 +139,6 @@ void Timeline::updateUsingEditor(Editor* 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();
// We always update the editor. In this way the timeline keeps in
@ -246,6 +239,10 @@ bool Timeline::onProcessMessage(Message* msg)
{
switch (msg->type()) {
case kFocusEnterMessage:
App::instance()->inputChain().prioritize(this);
break;
case kTimerMessage:
if (static_cast<TimerMessage*>(msg)->timer() == &m_clipboard_timer) {
Document* clipboard_document;
@ -2333,4 +2330,101 @@ FrameTag* Timeline::Hit::getFrameTag() const
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

View File

@ -13,6 +13,7 @@
#include "app/pref/preferences.h"
#include "app/ui/ani_controls.h"
#include "app/ui/editor/editor_observer.h"
#include "app/ui/input_chain_element.h"
#include "base/connection.h"
#include "doc/document_observer.h"
#include "doc/documents_observer.h"
@ -53,7 +54,8 @@ namespace app {
class Timeline : public ui::Widget
, public doc::DocumentsObserver
, public doc::DocumentObserver
, public app::EditorObserver {
, public app::EditorObserver
, public app::InputChainElement {
public:
typedef DocumentRange Range;
@ -119,6 +121,18 @@ namespace app {
void onAfterLayerChanged(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:
struct Hit {
int part;

View File

@ -11,6 +11,8 @@
#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/workspace_tabs.h"
#include "app/ui/workspace_view.h"
@ -298,4 +300,100 @@ WorkspaceTabs* Workspace::getTabsAt(const gfx::Point& pos)
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

View File

@ -9,6 +9,7 @@
#define APP_UI_WORKSPACE_H_INCLUDED
#pragma once
#include "app/ui/input_chain_element.h"
#include "app/ui/tabs.h"
#include "app/ui/workspace_panel.h"
#include "base/signal.h"
@ -17,7 +18,8 @@
namespace app {
class WorkspaceTabs;
class Workspace : public ui::Widget {
class Workspace : public ui::Widget
, public app::InputChainElement {
public:
typedef WorkspaceViews::iterator iterator;
@ -58,6 +60,18 @@ namespace app {
// Returns true if the view was docked inside the workspace.
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;
protected:

View File

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

View File

@ -17,6 +17,7 @@
#include "app/ui/color_bar.h"
#include "app/ui/document_view.h"
#include "app/ui/editor/editor.h"
#include "app/ui/input_chain.h"
#include "app/ui/main_window.h"
#include "app/ui/preview_editor.h"
#include "app/ui/status_bar.h"
@ -76,13 +77,17 @@ DocumentView* UIContext::activeView() const
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
// is the a preview.
if (m_lastSelectedView == docView ||
(docView && docView->isPreview()))
return;
MainWindow* mainWin = App::instance()->getMainWindow();
if (docView) {
mainWin->getTabsBar()->selectTab(docView);