1 // Aseprite
2 // Copyright (C) 2001-2018  David Capello
3 //
4 // This program is distributed under the terms of
5 // the End-User License Agreement for Aseprite.
6 
7 #ifdef HAVE_CONFIG_H
8 #include "config.h"
9 #endif
10 
11 #include "app/app.h"
12 #include "app/doc.h"
13 #include "app/modules/editors.h"
14 #include "app/pref/preferences.h"
15 #include "app/site.h"
16 #include "app/ui/color_bar.h"
17 #include "app/ui/doc_view.h"
18 #include "app/ui/editor/editor.h"
19 #include "app/ui/input_chain.h"
20 #include "app/ui/main_window.h"
21 #include "app/ui/preview_editor.h"
22 #include "app/ui/status_bar.h"
23 #include "app/ui/timeline/timeline.h"
24 #include "app/ui/workspace.h"
25 #include "app/ui/workspace_tabs.h"
26 #include "app/ui_context.h"
27 #include "base/mutex.h"
28 #include "doc/sprite.h"
29 
30 namespace app {
31 
32 UIContext* UIContext::m_instance = nullptr;
33 
UIContext()34 UIContext::UIContext()
35   : m_lastSelectedView(nullptr)
36 {
37   documents().add_observer(&Preferences::instance());
38 
39   ASSERT(m_instance == NULL);
40   m_instance = this;
41 }
42 
~UIContext()43 UIContext::~UIContext()
44 {
45   ASSERT(m_instance == this);
46   m_instance = NULL;
47 
48   documents().remove_observer(&Preferences::instance());
49 
50   // The context must be empty at this point. (It's to check if the UI
51   // is working correctly, i.e. closing all files when the user can
52   // take any action about it.)
53   ASSERT(documents().empty());
54 }
55 
isUIAvailable() const56 bool UIContext::isUIAvailable() const
57 {
58   return App::instance()->isGui();
59 }
60 
activeView() const61 DocView* UIContext::activeView() const
62 {
63   if (!isUIAvailable())
64     return nullptr;
65 
66   Workspace* workspace = App::instance()->workspace();
67   if (!workspace)
68     return nullptr;
69 
70   WorkspaceView* view = workspace->activeView();
71   if (DocView* docView = dynamic_cast<DocView*>(view))
72     return docView;
73   else
74     return nullptr;
75 }
76 
setActiveView(DocView * docView)77 void UIContext::setActiveView(DocView* docView)
78 {
79   MainWindow* mainWin = App::instance()->mainWindow();
80 
81   // Prioritize workspace for user input.
82   App::instance()->inputChain().prioritize(mainWin->getWorkspace(), nullptr);
83 
84   // Do nothing cases: 1) the view is already selected, or 2) the view
85   // is the a preview.
86   if (m_lastSelectedView == docView ||
87       (docView && docView->isPreview()))
88     return;
89 
90   if (docView) {
91     mainWin->getTabsBar()->selectTab(docView);
92 
93     if (mainWin->getWorkspace()->activeView() != docView)
94       mainWin->getWorkspace()->setActiveView(docView);
95   }
96 
97   current_editor = (docView ? docView->editor(): nullptr);
98 
99   if (current_editor)
100     current_editor->requestFocus();
101 
102   mainWin->getPreviewEditor()->updateUsingEditor(current_editor);
103   mainWin->getTimeline()->updateUsingEditor(current_editor);
104 
105   // Change the image-type of color bar.
106   ColorBar::instance()->setPixelFormat(app_get_current_pixel_format());
107 
108   // Restore the palette of the selected document.
109   app_refresh_screen();
110 
111   // Change the main frame title.
112   App::instance()->updateDisplayTitleBar();
113 
114   m_lastSelectedView = docView;
115 
116   // TODO all the calls to functions like updateUsingEditor(),
117   // setPixelFormat(), app_refresh_screen(), updateDisplayTitleBar()
118   // Can be replaced with a ContextObserver listening to the
119   // onActiveSiteChange() event.
120   notifyActiveSiteChanged();
121 }
122 
onSetActiveDocument(Doc * document)123 void UIContext::onSetActiveDocument(Doc* document)
124 {
125   bool notify = (lastSelectedDoc() != document);
126   app::Context::onSetActiveDocument(document);
127 
128   DocView* docView = getFirstDocView(document);
129   if (docView) {     // The view can be null if we are in --batch mode
130     setActiveView(docView);
131     notify = false;
132   }
133 
134   if (notify)
135     notifyActiveSiteChanged();
136 }
137 
getFirstDocView(Doc * document) const138 DocView* UIContext::getFirstDocView(Doc* document) const
139 {
140   Workspace* workspace = App::instance()->workspace();
141   if (!workspace) // Workspace (main window) can be null if we are in --batch mode
142     return nullptr;
143 
144   for (WorkspaceView* view : *workspace) {
145     if (DocView* docView = dynamic_cast<DocView*>(view)) {
146       if (docView->document() == document) {
147         return docView;
148       }
149     }
150   }
151 
152   return nullptr;
153 }
154 
getAllDocViews(Doc * document) const155 DocViews UIContext::getAllDocViews(Doc* document) const
156 {
157   Workspace* workspace = App::instance()->workspace();
158   DocViews docViews;
159 
160   for (WorkspaceView* view : *workspace) {
161     if (DocView* docView = dynamic_cast<DocView*>(view)) {
162       if (docView->document() == document) {
163         docViews.push_back(docView);
164       }
165     }
166   }
167 
168   return docViews;
169 }
170 
getAllEditorsIncludingPreview(Doc * document) const171 Editors UIContext::getAllEditorsIncludingPreview(Doc* document) const
172 {
173   std::vector<Editor*> editors;
174   for (DocView* docView : getAllDocViews(document)) {
175     if (docView->editor())
176       editors.push_back(docView->editor());
177   }
178 
179   if (MainWindow* mainWin = App::instance()->mainWindow()) {
180     PreviewEditorWindow* previewWin = mainWin->getPreviewEditor();
181     if (previewWin) {
182       Editor* miniEditor = previewWin->previewEditor();
183       if (miniEditor && miniEditor->document() == document)
184         editors.push_back(miniEditor);
185     }
186   }
187   return editors;
188 }
189 
activeEditor()190 Editor* UIContext::activeEditor()
191 {
192   DocView* view = activeView();
193   if (view)
194     return view->editor();
195   else
196     return NULL;
197 }
198 
onAddDocument(Doc * doc)199 void UIContext::onAddDocument(Doc* doc)
200 {
201   app::Context::onAddDocument(doc);
202 
203   // We don't create views in batch mode.
204   if (!App::instance()->isGui())
205     return;
206 
207   // Add a new view for this document
208   DocView* view = new DocView(
209     lastSelectedDoc(),
210     DocView::Normal,
211     App::instance()->mainWindow()->getPreviewEditor());
212 
213   // Add a tab with the new view for the document
214   App::instance()->workspace()->addView(view);
215 
216   setActiveView(view);
217   view->editor()->setDefaultScroll();
218 }
219 
onRemoveDocument(Doc * doc)220 void UIContext::onRemoveDocument(Doc* doc)
221 {
222   app::Context::onRemoveDocument(doc);
223 
224   // We don't destroy views in batch mode.
225   if (isUIAvailable()) {
226     Workspace* workspace = App::instance()->workspace();
227 
228     for (DocView* docView : getAllDocViews(doc)) {
229       workspace->removeView(docView);
230       delete docView;
231     }
232   }
233 }
234 
onGetActiveSite(Site * site) const235 void UIContext::onGetActiveSite(Site* site) const
236 {
237   DocView* view = activeView();
238   if (view) {
239     view->getSite(site);
240 
241     if (site->sprite()) {
242       // Selected layers
243       Timeline* timeline = App::instance()->timeline();
244       if (timeline &&
245           timeline->range().enabled()) {
246         switch (timeline->range().type()) {
247           case DocRange::kCels:   site->focus(Site::InCels); break;
248           case DocRange::kFrames: site->focus(Site::InFrames); break;
249           case DocRange::kLayers: site->focus(Site::InLayers); break;
250         }
251         site->selectedLayers(timeline->selectedLayers());
252         site->selectedFrames(timeline->selectedFrames());
253       }
254       else {
255         ColorBar* colorBar = ColorBar::instance();
256         if (colorBar &&
257             colorBar->getPaletteView()->getSelectedEntriesCount() > 0) {
258           site->focus(Site::InColorBar);
259         }
260         else {
261           site->focus(Site::InEditor);
262         }
263       }
264     }
265   }
266   else if (!isUIAvailable()) {
267     return app::Context::onGetActiveSite(site);
268   }
269 }
270 
271 } // namespace app
272