aseprite/src/app/context.cpp
David Capello cd342f5630 [lua] Add events handling with Sprite.events & App.events
Added a new Events object with :on() and :off() methods to start or
stop listening to a specific event respectively. This also allows to
add several callbacks for the same event.

Replaced the temporal Site.onChange & Sprite.onChange implementations.

Related to several issues (enable more possibilities for): #138, #1403, #1949, #2965, #2980
2021-10-07 18:56:39 -03:00

287 lines
6.6 KiB
C++

// Aseprite
// Copyright (C) 2018-2021 Igara Studio S.A.
// Copyright (C) 2001-2018 David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "app/context.h"
#include "app/active_site_handler.h"
#include "app/app.h"
#include "app/commands/command.h"
#include "app/commands/commands.h"
#include "app/console.h"
#include "app/doc.h"
#include "app/pref/preferences.h"
#include "app/site.h"
#include "base/scoped_value.h"
#include "doc/layer.h"
#include "ui/system.h"
#include <algorithm>
#include <stdexcept>
namespace app {
Context::Context()
: m_docs(this)
, m_lastSelectedDoc(nullptr)
, m_preferences(nullptr)
{
m_docs.add_observer(this);
}
Context::~Context()
{
if (m_preferences)
m_docs.remove_observer(m_preferences.get());
m_docs.remove_observer(this);
}
Preferences& Context::preferences() const
{
if (!m_preferences) {
m_preferences.reset(new Preferences);
m_docs.add_observer(m_preferences.get());
}
return *m_preferences;
}
void Context::sendDocumentToTop(Doc* document)
{
ASSERT(document != NULL);
documents().move(document, 0);
}
void Context::closeDocument(Doc* doc)
{
onCloseDocument(doc);
}
Site Context::activeSite() const
{
Site site;
onGetActiveSite(&site);
return site;
}
Doc* Context::activeDocument() const
{
Site site;
onGetActiveSite(&site);
return site.document();
}
void Context::setActiveDocument(Doc* document)
{
onSetActiveDocument(document, true);
}
void Context::setActiveLayer(doc::Layer* layer)
{
onSetActiveLayer(layer);
}
void Context::setActiveFrame(const doc::frame_t frame)
{
onSetActiveFrame(frame);
}
void Context::setRange(const DocRange& range)
{
onSetRange(range);
}
void Context::setSelectedColors(const doc::PalettePicks& picks)
{
onSetSelectedColors(picks);
}
bool Context::hasModifiedDocuments() const
{
for (auto doc : documents())
if (doc->isModified())
return true;
return false;
}
void Context::notifyActiveSiteChanged()
{
Site site = activeSite();
notify_observers<const Site&>(&ContextObserver::onActiveSiteChange, site);
}
void Context::executeCommandFromMenuOrShortcut(Command* command, const Params& params)
{
ui::assert_ui_thread();
// With this we avoid executing a command when we are inside another
// command (e.g. if we press Cmd-S quickly the program can enter two
// times in the File > Save command and hang).
static Command* executingCommand = nullptr;
if (executingCommand) { // Ignore command execution
LOG(VERBOSE, "CTXT: Ignoring command %s because we are inside %s\n",
command->id().c_str(), executingCommand->id().c_str());
return;
}
base::ScopedValue<Command*> commandGuard(executingCommand,
command, nullptr);
executeCommand(command, params);
}
void Context::executeCommand(Command* command, const Params& params)
{
ASSERT(command);
if (!command)
return;
Console console;
LOG(VERBOSE, "CTXT: Executing command %s\n", command->id().c_str());
try {
m_flags.update(this);
#if 0
// params.empty() can be empty when we call the command from Lua
// with a table.
ASSERT(!command->needsParams() || !params.empty());
#endif
command->loadParams(params);
CommandExecutionEvent ev(command);
BeforeCommandExecution(ev);
if (ev.isCanceled()) {
LOG(VERBOSE, "CTXT: Command %s was canceled/simulated.\n", command->id().c_str());
}
else if (command->isEnabled(this)) {
command->execute(this);
LOG(VERBOSE, "CTXT: Command %s executed successfully\n", command->id().c_str());
}
else {
LOG(VERBOSE, "CTXT: Command %s is disabled\n", command->id().c_str());
}
AfterCommandExecution(ev);
// TODO move this code to another place (e.g. a Workplace/Tabs widget)
if (isUIAvailable())
app_rebuild_documents_tabs();
}
catch (base::Exception& e) {
LOG(ERROR, "CTXT: Exception caught executing %s command\n%s\n",
command->id().c_str(), e.what());
Console::showException(e);
}
catch (std::exception& e) {
LOG(ERROR, "CTXT: std::exception caught executing %s command\n%s\n",
command->id().c_str(), e.what());
console.printf("An error ocurred executing the command.\n\nDetails:\n%s", e.what());
}
#ifdef NDEBUG
catch (...) {
LOG(ERROR, "CTXT: Unknown exception executing %s command\n",
command->id().c_str());
console.printf("An unknown error ocurred executing the command.\n"
"Please save your work, close the program, try it\n"
"again, and report this bug.\n\n"
"Details: Unknown exception caught. This can be bad\n"
"memory access, divison by zero, etc.");
}
#endif
}
void Context::onAddDocument(Doc* doc)
{
m_lastSelectedDoc = doc;
if (m_activeSiteHandler)
m_activeSiteHandler->addDoc(doc);
notifyActiveSiteChanged();
}
void Context::onRemoveDocument(Doc* doc)
{
if (m_activeSiteHandler)
m_activeSiteHandler->removeDoc(doc);
if (doc == m_lastSelectedDoc) {
m_lastSelectedDoc = nullptr;
notifyActiveSiteChanged();
}
}
void Context::onGetActiveSite(Site* site) const
{
// Default/dummy site (maybe for batch/command line mode)
if (Doc* doc = m_lastSelectedDoc)
activeSiteHandler()->getActiveSiteForDoc(doc, site);
}
void Context::onSetActiveDocument(Doc* doc, bool notify)
{
m_lastSelectedDoc = doc;
if (notify)
notifyActiveSiteChanged();
}
void Context::onSetActiveLayer(doc::Layer* layer)
{
Doc* newDoc = (layer ? static_cast<Doc*>(layer->sprite()->document()): nullptr);
if (!newDoc)
return;
activeSiteHandler()->setActiveLayerInDoc(newDoc, layer);
if (newDoc != m_lastSelectedDoc)
setActiveDocument(newDoc);
else
notifyActiveSiteChanged();
}
void Context::onSetActiveFrame(const doc::frame_t frame)
{
if (m_lastSelectedDoc)
activeSiteHandler()->setActiveFrameInDoc(m_lastSelectedDoc, frame);
notifyActiveSiteChanged();
}
void Context::onSetRange(const DocRange& range)
{
if (m_lastSelectedDoc)
activeSiteHandler()->setRangeInDoc(m_lastSelectedDoc, range);
}
void Context::onSetSelectedColors(const doc::PalettePicks& picks)
{
if (m_lastSelectedDoc)
activeSiteHandler()->setSelectedColorsInDoc(m_lastSelectedDoc, picks);
}
ActiveSiteHandler* Context::activeSiteHandler() const
{
if (!m_activeSiteHandler)
m_activeSiteHandler.reset(new ActiveSiteHandler);
return m_activeSiteHandler.get();
}
void Context::onCloseDocument(Doc* doc)
{
ASSERT(doc != nullptr);
ASSERT(doc->context() == nullptr);
delete doc;
}
} // namespace app