diff --git a/src/app/context.cpp b/src/app/context.cpp index 419fc2a21..36a402f72 100644 --- a/src/app/context.cpp +++ b/src/app/context.cpp @@ -137,9 +137,15 @@ bool Context::hasModifiedDocuments() const return false; } +void Context::notifyBeforeActiveSiteChanged() +{ + const Site site = activeSite(); + notify_observers(&ContextObserver::onBeforeActiveSiteChange, site); +} + void Context::notifyActiveSiteChanged() { - Site site = activeSite(); + const Site site = activeSite(); notify_observers(&ContextObserver::onActiveSiteChange, site); } @@ -250,6 +256,11 @@ void Context::setCommandResult(const CommandResult& result) m_result = result; } +void Context::onBeforeAddDocument(Doc* doc) +{ + notifyBeforeActiveSiteChanged(); +} + void Context::onAddDocument(Doc* doc) { m_lastSelectedDoc = doc; @@ -260,6 +271,11 @@ void Context::onAddDocument(Doc* doc) notifyActiveSiteChanged(); } +void Context::onBeforeRemoveDocument(Doc* doc) +{ + notifyBeforeActiveSiteChanged(); +} + void Context::onRemoveDocument(Doc* doc) { if (m_activeSiteHandler) diff --git a/src/app/context.h b/src/app/context.h index 5d7ffa886..c192d3000 100644 --- a/src/app/context.h +++ b/src/app/context.h @@ -140,6 +140,7 @@ namespace app { void setSelectedTiles(const doc::PalettePicks& picks); bool hasModifiedDocuments() const; void notifyActiveSiteChanged(); + void notifyBeforeActiveSiteChanged(); void setDraggedData(std::unique_ptr draggedData) { m_draggedData = std::move(draggedData); @@ -161,7 +162,9 @@ namespace app { protected: // DocsObserver impl + void onBeforeAddDocument(Doc* doc) override; void onAddDocument(Doc* doc) override; + void onBeforeRemoveDocument(Doc* doc) override; void onRemoveDocument(Doc* doc) override; virtual void onGetActiveSite(Site* site) const; diff --git a/src/app/context_observer.h b/src/app/context_observer.h index 88033a9f9..ed8898c54 100644 --- a/src/app/context_observer.h +++ b/src/app/context_observer.h @@ -16,7 +16,8 @@ namespace app { class ContextObserver { public: virtual ~ContextObserver() { } - virtual void onActiveSiteChange(const Site& site) { } + virtual void onActiveSiteChange(const Site& site) { }; + virtual void onBeforeActiveSiteChange(const Site& fromSite) { }; }; } // namespace app diff --git a/src/app/docs.cpp b/src/app/docs.cpp index ae775423e..4a63deed0 100644 --- a/src/app/docs.cpp +++ b/src/app/docs.cpp @@ -40,6 +40,8 @@ Doc* Docs::add(Doc* doc) return doc; } + notify_observers(&DocsObserver::onBeforeAddDocument, doc); + m_docs.insert(begin(), doc); notify_observers(&DocsObserver::onAddDocument, doc); @@ -62,6 +64,8 @@ void Docs::remove(Doc* doc) if (it == end()) // Already removed. return; + notify_observers(&DocsObserver::onBeforeRemoveDocument, doc); + m_docs.erase(it); notify_observers(&DocsObserver::onRemoveDocument, doc); diff --git a/src/app/docs_observer.h b/src/app/docs_observer.h index 8c230ebb8..102501f03 100644 --- a/src/app/docs_observer.h +++ b/src/app/docs_observer.h @@ -24,7 +24,9 @@ namespace app { class DocsObserver { public: virtual ~DocsObserver() { } + virtual void onBeforeAddDocument(Doc* doc) { } virtual void onAddDocument(Doc* doc) { } + virtual void onBeforeRemoveDocument(Doc* doc) { } virtual void onRemoveDocument(Doc* doc) { } }; diff --git a/src/app/script/events_class.cpp b/src/app/script/events_class.cpp index 2bfcc3bc3..ccdd91ae0 100644 --- a/src/app/script/events_class.cpp +++ b/src/app/script/events_class.cpp @@ -157,6 +157,7 @@ class AppEvents : public Events public: enum : EventType { Unknown = -1, + BeforeSiteChange, SiteChange, FgColorChange, BgColorChange, @@ -164,12 +165,16 @@ public: AfterCommand, }; - AppEvents() { + AppEvents() + : m_addedObserver(0) + { } EventType eventType(const char* eventName) const override { if (std::strcmp(eventName, "sitechange") == 0) return SiteChange; + else if (std::strcmp(eventName, "beforesitechange") == 0) + return BeforeSiteChange; else if (std::strcmp(eventName, "fgcolorchange") == 0) return FgColorChange; else if (std::strcmp(eventName, "bgcolorchange") == 0) @@ -189,9 +194,14 @@ private: auto ctx = app->context(); auto& pref = Preferences::instance(); switch (eventType) { - case SiteChange: - ctx->add_observer(this); - break; + case BeforeSiteChange: + [[fallthrough]]; + case SiteChange: { + if (m_addedObserver == 0) + ctx->add_observer(this); + + ++m_addedObserver; + } break; case FgColorChange: m_fgConn = pref.colorBar.fgColor.AfterChange .connect([this]{ onFgColorChange(); }); @@ -213,8 +223,13 @@ private: void onRemoveLastListener(EventType eventType) override { switch (eventType) { + case BeforeSiteChange: + [[fallthrough]]; case SiteChange: - App::instance()->context()->remove_observer(this); + --m_addedObserver; + + if (m_addedObserver == 0) + App::instance()->context()->remove_observer(this); break; case FgColorChange: m_fgConn.disconnect(); @@ -265,11 +280,19 @@ private: call(SiteChange, { { "fromUndo", fromUndo } }); } + void onBeforeActiveSiteChange(const Site& fromSite) override + { + const bool fromUndo = (fromSite.document() && + fromSite.document()->isUndoing()); + call(BeforeSiteChange, { { "fromUndo", fromUndo } }); + } + obs::scoped_connection m_fgConn; obs::scoped_connection m_bgConn; obs::scoped_connection m_beforeCmdConn; obs::scoped_connection m_afterCmdConn; obs::scoped_connection m_beforePaintConn; + int m_addedObserver; }; class WindowEvents : public Events diff --git a/src/app/script/values.cpp b/src/app/script/values.cpp index 4151c106d..4ebd10082 100644 --- a/src/app/script/values.cpp +++ b/src/app/script/values.cpp @@ -8,8 +8,8 @@ #include "config.h" #endif +#include "app/site.h" #include "app/script/values.h" - #include "app/pref/preferences.h" #include "app/script/docobj.h" #include "app/script/engine.h" diff --git a/src/app/ui/editor/editor.cpp b/src/app/ui/editor/editor.cpp index 055e53e39..bd700d794 100644 --- a/src/app/ui/editor/editor.cpp +++ b/src/app/ui/editor/editor.cpp @@ -361,6 +361,9 @@ void Editor::setLayer(const Layer* layer) if (gridVisible) oldGrid = getSite().grid(); + if (isActive()) + UIContext::instance()->notifyBeforeActiveSiteChanged(); + m_observers.notifyBeforeLayerChanged(this); // Remove extra cel information if we change between different layer @@ -409,6 +412,9 @@ void Editor::setFrame(frame_t frame) if (m_frame == frame) return; + if (isActive()) + UIContext::instance()->notifyBeforeActiveSiteChanged(); + m_observers.notifyBeforeFrameChanged(this); { HideBrushPreview hide(m_brushPreview); @@ -1914,6 +1920,9 @@ bool Editor::isSliceSelected(const doc::Slice* slice) const void Editor::clearSlicesSelection() { if (!m_selectedSlices.empty()) { + if (isActive()) + UIContext::instance()->notifyBeforeActiveSiteChanged(); + m_selectedSlices.clear(); invalidate(); @@ -1925,6 +1934,9 @@ void Editor::clearSlicesSelection() void Editor::selectSlice(const doc::Slice* slice) { ASSERT(slice); + if (isActive()) + UIContext::instance()->notifyBeforeActiveSiteChanged(); + m_selectedSlices.insert(slice->id()); invalidate(); @@ -1934,6 +1946,9 @@ void Editor::selectSlice(const doc::Slice* slice) bool Editor::selectSliceBox(const gfx::Rect& box) { + if (isActive()) + UIContext::instance()->notifyBeforeActiveSiteChanged(); + m_selectedSlices.clear(); for (auto slice : m_sprite->slices()) { auto key = slice->getByFrame(m_frame); @@ -1950,6 +1965,9 @@ bool Editor::selectSliceBox(const gfx::Rect& box) void Editor::selectAllSlices() { + if (isActive()) + UIContext::instance()->notifyBeforeActiveSiteChanged(); + for (auto slice : m_sprite->slices()) m_selectedSlices.insert(slice->id()); invalidate(); diff --git a/src/app/ui/main_window.cpp b/src/app/ui/main_window.cpp index 2d91e5fb1..f5b58feee 100644 --- a/src/app/ui/main_window.cpp +++ b/src/app/ui/main_window.cpp @@ -134,6 +134,7 @@ void MainWindow::initialize() m_timeline = new Timeline(m_tooltipManager); m_workspace->setTabsBar(m_tabsBar); + m_workspace->BeforeViewChanged.connect(&MainWindow::onBeforeViewChange, this); m_workspace->ActiveViewChanged.connect(&MainWindow::onActiveViewChange, this); // configure all widgets to expansives @@ -417,6 +418,11 @@ void MainWindow::onResize(ui::ResizeEvent& ev) } } +void MainWindow::onBeforeViewChange() +{ + UIContext::instance()->notifyBeforeActiveSiteChanged(); +} + // When the active view is changed from methods like // Workspace::splitView(), this function is called, and we have to // inform to the UIContext that the current view has changed. diff --git a/src/app/ui/main_window.h b/src/app/ui/main_window.h index f2b7adb9b..1fe1dd678 100644 --- a/src/app/ui/main_window.h +++ b/src/app/ui/main_window.h @@ -116,6 +116,7 @@ namespace app { void onInitTheme(ui::InitThemeEvent& ev) override; void onSaveLayout(ui::SaveLayoutEvent& ev) override; void onResize(ui::ResizeEvent& ev) override; + void onBeforeViewChange(); void onActiveViewChange(); void onLanguageChange(); diff --git a/src/app/ui/workspace.cpp b/src/app/ui/workspace.cpp index 1dedbf13c..a17c2d27b 100644 --- a/src/app/ui/workspace.cpp +++ b/src/app/ui/workspace.cpp @@ -100,6 +100,8 @@ void Workspace::setActiveView(WorkspaceView* view) if (!m_activePanel) return; + BeforeViewChanged(); + m_activePanel->setActiveView(view); ActiveViewChanged(); // Fire ActiveViewChanged event @@ -107,6 +109,8 @@ void Workspace::setActiveView(WorkspaceView* view) void Workspace::setMainPanelAsActive() { + BeforeViewChanged(); + m_activePanel = &m_mainPanel; removeDropViewPreview(); diff --git a/src/app/ui/workspace.h b/src/app/ui/workspace.h index 4a64e22f3..4f1e254f7 100644 --- a/src/app/ui/workspace.h +++ b/src/app/ui/workspace.h @@ -78,6 +78,7 @@ namespace app { WorkspacePanel* mainPanel() { return &m_mainPanel; } + obs::signal BeforeViewChanged; obs::signal ActiveViewChanged; protected: diff --git a/src/app/ui_context.cpp b/src/app/ui_context.cpp index 1725f170b..65d125379 100644 --- a/src/app/ui_context.cpp +++ b/src/app/ui_context.cpp @@ -144,6 +144,10 @@ void UIContext::setActiveView(DocView* docView) void UIContext::onSetActiveDocument(Doc* document, bool notify) { notify = (notify && lastSelectedDoc() != document); + + if (notify) + notifyBeforeActiveSiteChanged(); + app::Context::onSetActiveDocument(document, false); DocView* docView = getFirstDocView(document); diff --git a/tests/scripts/events.lua b/tests/scripts/events.lua index 0a523bb45..c22d7709a 100644 --- a/tests/scripts/events.lua +++ b/tests/scripts/events.lua @@ -7,21 +7,69 @@ dofile('./test_utils.lua') -- Test app.events do - local i = 0 + local bc = 0 + local c = 0 + local beforeListener = app.events:on('beforesitechange', + function() bc = bc + 1 end) local listener = app.events:on('sitechange', - function() i = i + 1 end) - assert(i == 0) + function() c = c + 1 end) + + assert(bc == 0) + assert(c == 0) local a = Sprite(32, 32) expect_eq(a, app.activeSprite) - expect_eq(1, i) + expect_eq(1, bc) + expect_eq(1, c) + local b = Sprite(32, 32) expect_eq(b, app.activeSprite) - expect_eq(2, i) + expect_eq(2, bc) + expect_eq(2, c) + app.activeSprite = a - expect_eq(3, i) + expect_eq(3, bc) + expect_eq(3, c) + app.events:off(listener) + app.events:off(beforeListener) + app.activeSprite = b - expect_eq(3, i) + expect_eq(3, bc) + expect_eq(3, c) +end + +-- Alternate version of the events test to ensure proper observer disconnection +do + local bc = 0 + local c = 0 + local beforeListener = app.events:on('beforesitechange', + function() bc = bc + 1 end) + local listener = app.events:on('sitechange', + function() c = c + 1 end) + + assert(bc == 0) + assert(c == 0) + local a = Sprite(32, 32) + expect_eq(a, app.activeSprite) + expect_eq(1, bc) + expect_eq(1, c) + + app.events:off(beforeListener) + + local b = Sprite(32, 32) + expect_eq(b, app.activeSprite) + expect_eq(1, bc) + expect_eq(2, c) + + app.activeSprite = a + expect_eq(1, bc) + expect_eq(3, c) + + app.events:off(listener) + + app.activeSprite = b + expect_eq(1, bc) + expect_eq(3, c) end do