1 | // Aseprite |
2 | // Copyright (C) 2018-2022 Igara Studio S.A. |
3 | // Copyright (C) 2001-2018 David Capello |
4 | // |
5 | // This program is distributed under the terms of |
6 | // the End-User License Agreement for Aseprite. |
7 | |
8 | #ifdef HAVE_CONFIG_H |
9 | #include "config.h" |
10 | #endif |
11 | |
12 | #include "app/app.h" |
13 | |
14 | #include "app/app_mod.h" |
15 | #include "app/check_update.h" |
16 | #include "app/cli/app_options.h" |
17 | #include "app/cli/cli_processor.h" |
18 | #include "app/cli/default_cli_delegate.h" |
19 | #include "app/cli/preview_cli_delegate.h" |
20 | #include "app/color_spaces.h" |
21 | #include "app/color_utils.h" |
22 | #include "app/commands/commands.h" |
23 | #include "app/console.h" |
24 | #include "app/crash/data_recovery.h" |
25 | #include "app/drm.h" |
26 | #include "app/extensions.h" |
27 | #include "app/file/file.h" |
28 | #include "app/file/file_formats_manager.h" |
29 | #include "app/file_system.h" |
30 | #include "app/gui_xml.h" |
31 | #include "app/i18n/strings.h" |
32 | #include "app/ini_file.h" |
33 | #include "app/log.h" |
34 | #include "app/modules.h" |
35 | #include "app/modules/gfx.h" |
36 | #include "app/modules/gui.h" |
37 | #include "app/modules/palettes.h" |
38 | #include "app/pref/preferences.h" |
39 | #include "app/recent_files.h" |
40 | #include "app/resource_finder.h" |
41 | #include "app/send_crash.h" |
42 | #include "app/site.h" |
43 | #include "app/tools/active_tool.h" |
44 | #include "app/tools/tool_box.h" |
45 | #include "app/ui/backup_indicator.h" |
46 | #include "app/ui/color_bar.h" |
47 | #include "app/ui/doc_view.h" |
48 | #include "app/ui/editor/editor.h" |
49 | #include "app/ui/editor/editor_view.h" |
50 | #include "app/ui/input_chain.h" |
51 | #include "app/ui/keyboard_shortcuts.h" |
52 | #include "app/ui/main_window.h" |
53 | #include "app/ui/status_bar.h" |
54 | #include "app/ui/toolbar.h" |
55 | #include "app/ui/workspace.h" |
56 | #include "app/ui_context.h" |
57 | #include "app/util/clipboard.h" |
58 | #include "base/exception.h" |
59 | #include "base/fs.h" |
60 | #include "base/split_string.h" |
61 | #include "doc/sprite.h" |
62 | #include "fmt/format.h" |
63 | #include "os/error.h" |
64 | #include "os/surface.h" |
65 | #include "os/system.h" |
66 | #include "os/window.h" |
67 | #include "render/render.h" |
68 | #include "ui/intern.h" |
69 | #include "ui/ui.h" |
70 | #include "updater/user_agent.h" |
71 | #include "ver/info.h" |
72 | |
73 | #if LAF_MACOS |
74 | #include "os/osx/system.h" |
75 | #elif LAF_LINUX |
76 | #include "os/x11/system.h" |
77 | #endif |
78 | |
79 | #include <iostream> |
80 | #include <memory> |
81 | |
82 | #ifdef ENABLE_SCRIPTING |
83 | #include "app/script/engine.h" |
84 | #include "app/shell.h" |
85 | #endif |
86 | |
87 | #ifdef ENABLE_STEAM |
88 | #include "steam/steam.h" |
89 | #endif |
90 | |
91 | #include <memory> |
92 | #include <optional> |
93 | |
94 | namespace app { |
95 | |
96 | using namespace ui; |
97 | |
98 | #ifdef ENABLE_SCRIPTING |
99 | |
100 | namespace { |
101 | |
102 | class ConsoleEngineDelegate : public script::EngineDelegate { |
103 | public: |
104 | ConsoleEngineDelegate(Console& console) : m_console(console) { } |
105 | void onConsoleError(const char* text) override { |
106 | onConsolePrint(text); |
107 | } |
108 | void onConsolePrint(const char* text) override { |
109 | m_console.printf("%s\n" , text); |
110 | } |
111 | private: |
112 | Console& m_console; |
113 | }; |
114 | |
115 | } // anonymous namespace |
116 | |
117 | #endif // ENABLER_SCRIPTING |
118 | |
119 | class App::CoreModules { |
120 | public: |
121 | #ifdef ENABLE_UI |
122 | typedef app::UIContext ContextT; |
123 | #else |
124 | typedef app::Context ContextT; |
125 | #endif |
126 | |
127 | ConfigModule m_configModule; |
128 | ContextT m_context; |
129 | }; |
130 | |
131 | class App::LoadLanguage { |
132 | public: |
133 | LoadLanguage(Preferences& pref, |
134 | Extensions& exts) { |
135 | Strings::createInstance(pref, exts); |
136 | } |
137 | }; |
138 | |
139 | class App::Modules { |
140 | public: |
141 | LoggerModule m_loggerModule; |
142 | FileSystemModule m_file_system_module; |
143 | Extensions m_extensions; |
144 | // Load main language (after loading the extensions) |
145 | LoadLanguage m_loadLanguage; |
146 | tools::ToolBox m_toolbox; |
147 | tools::ActiveToolManager m_activeToolManager; |
148 | Commands m_commands; |
149 | #ifdef ENABLE_UI |
150 | RecentFiles m_recent_files; |
151 | InputChain m_inputChain; |
152 | Clipboard m_clipboard; |
153 | #endif |
154 | #ifdef ENABLE_DATA_RECOVERY |
155 | // This is a raw pointer because we want to delete it explicitly. |
156 | // (e.g. if an exception occurs, the ~Modules() doesn't have to |
157 | // delete m_recovery) |
158 | std::unique_ptr<app::crash::DataRecovery> m_recovery; |
159 | #endif |
160 | |
161 | Modules(const bool createLogInDesktop, |
162 | Preferences& pref) |
163 | : m_loggerModule(createLogInDesktop) |
164 | , m_loadLanguage(pref, m_extensions) |
165 | , m_activeToolManager(&m_toolbox) |
166 | #ifdef ENABLE_UI |
167 | , m_recent_files(pref.general.recentItems()) |
168 | #endif |
169 | #ifdef ENABLE_DATA_RECOVERY |
170 | , m_recovery(nullptr) |
171 | #endif |
172 | { |
173 | } |
174 | |
175 | ~Modules() { |
176 | #ifdef ENABLE_DATA_RECOVERY |
177 | ASSERT(m_recovery == nullptr || |
178 | ui::get_app_state() == ui::AppState::kClosingWithException); |
179 | #endif |
180 | } |
181 | |
182 | app::crash::DataRecovery* recovery() { |
183 | #ifdef ENABLE_DATA_RECOVERY |
184 | return m_recovery.get(); |
185 | #else |
186 | return nullptr; |
187 | #endif |
188 | } |
189 | |
190 | void createDataRecovery(Context* ctx) { |
191 | #ifdef ENABLE_DATA_RECOVERY |
192 | |
193 | #ifdef ENABLE_TRIAL_MODE |
194 | DRM_INVALID{ |
195 | return; |
196 | } |
197 | #endif |
198 | |
199 | m_recovery = std::make_unique<app::crash::DataRecovery>(ctx); |
200 | m_recovery->SessionsListIsReady.connect( |
201 | [] { |
202 | ui::assert_ui_thread(); |
203 | auto app = App::instance(); |
204 | if (app && app->mainWindow()) { |
205 | // Notify that the list of sessions is ready. |
206 | app->mainWindow()->dataRecoverySessionsAreReady(); |
207 | } |
208 | }); |
209 | #endif |
210 | } |
211 | |
212 | void searchDataRecoverySessions() { |
213 | #ifdef ENABLE_DATA_RECOVERY |
214 | |
215 | #ifdef ENABLE_TRIAL_MODE |
216 | DRM_INVALID{ |
217 | return; |
218 | } |
219 | #endif |
220 | |
221 | ASSERT(m_recovery); |
222 | if (m_recovery) |
223 | m_recovery->launchSearch(); |
224 | #endif |
225 | } |
226 | |
227 | void deleteDataRecovery() { |
228 | #ifdef ENABLE_DATA_RECOVERY |
229 | |
230 | #ifdef ENABLE_TRIAL_MODE |
231 | DRM_INVALID{ |
232 | return; |
233 | } |
234 | #endif |
235 | |
236 | m_recovery.reset(); |
237 | #endif |
238 | } |
239 | |
240 | }; |
241 | |
242 | App* App::m_instance = nullptr; |
243 | |
244 | App::App(AppMod* mod) |
245 | : m_mod(mod) |
246 | , m_coreModules(nullptr) |
247 | , m_modules(nullptr) |
248 | , m_legacy(nullptr) |
249 | , m_isGui(false) |
250 | , m_isShell(false) |
251 | #ifdef ENABLE_UI |
252 | , m_backupIndicator(nullptr) |
253 | #endif |
254 | #ifdef ENABLE_SCRIPTING |
255 | , m_engine(new script::Engine) |
256 | #endif |
257 | { |
258 | ASSERT(m_instance == nullptr); |
259 | m_instance = this; |
260 | } |
261 | |
262 | int App::initialize(const AppOptions& options) |
263 | { |
264 | os::System* system = os::instance(); |
265 | |
266 | #ifdef ENABLE_UI |
267 | m_isGui = options.startUI() && !options.previewCLI(); |
268 | #else |
269 | m_isGui = false; |
270 | #endif |
271 | m_isShell = options.startShell(); |
272 | m_coreModules = std::make_unique<CoreModules>(); |
273 | |
274 | #if LAF_WINDOWS |
275 | |
276 | if (options.disableWintab() || |
277 | !preferences().experimental.loadWintabDriver() || |
278 | preferences().tablet.api() == "pointer" ) { |
279 | system->setTabletAPI(os::TabletAPI::WindowsPointerInput); |
280 | } |
281 | else if (preferences().tablet.api() == "wintab_packets" ) |
282 | system->setTabletAPI(os::TabletAPI::WintabPackets); |
283 | else // preferences().tablet.api() == "wintab" |
284 | system->setTabletAPI(os::TabletAPI::Wintab); |
285 | |
286 | #elif LAF_MACOS |
287 | |
288 | if (!preferences().general.osxAsyncView()) |
289 | os::osx_set_async_view(false); |
290 | |
291 | #elif LAF_LINUX |
292 | |
293 | { |
294 | const std::string& stylusId = preferences().general.x11StylusId(); |
295 | if (!stylusId.empty()) |
296 | os::x11_set_user_defined_string_to_detect_stylus(stylusId); |
297 | } |
298 | |
299 | #endif |
300 | |
301 | system->setAppName(get_app_name()); |
302 | system->setAppMode(m_isGui ? os::AppMode::GUI: |
303 | os::AppMode::CLI); |
304 | |
305 | if (m_isGui) |
306 | m_uiSystem.reset(new ui::UISystem); |
307 | |
308 | bool createLogInDesktop = false; |
309 | switch (options.verboseLevel()) { |
310 | case AppOptions::kNoVerbose: |
311 | base::set_log_level(ERROR); |
312 | break; |
313 | case AppOptions::kVerbose: |
314 | base::set_log_level(INFO); |
315 | break; |
316 | case AppOptions::kHighlyVerbose: |
317 | base::set_log_level(VERBOSE); |
318 | createLogInDesktop = true; |
319 | break; |
320 | } |
321 | |
322 | initialize_color_spaces(preferences()); |
323 | |
324 | #ifdef ENABLE_DRM |
325 | LOG("APP: Initializing DRM...\n" ); |
326 | app_configure_drm(); |
327 | #endif |
328 | |
329 | // Load modules |
330 | m_modules = std::make_unique<Modules>(createLogInDesktop, preferences()); |
331 | m_legacy = std::make_unique<LegacyModules>(isGui() ? REQUIRE_INTERFACE: 0); |
332 | #ifdef ENABLE_UI |
333 | m_brushes = std::make_unique<AppBrushes>(); |
334 | #endif |
335 | |
336 | // Data recovery is enabled only in GUI mode |
337 | if (isGui() && preferences().general.dataRecovery()) |
338 | m_modules->createDataRecovery(context()); |
339 | |
340 | if (isPortable()) |
341 | LOG("APP: Running in portable mode\n" ); |
342 | |
343 | // Load or create the default palette, or migrate the default |
344 | // palette from an old format palette to the new one, etc. |
345 | load_default_palette(); |
346 | |
347 | #ifdef ENABLE_UI |
348 | // Initialize GUI interface |
349 | if (isGui()) { |
350 | LOG("APP: GUI mode\n" ); |
351 | |
352 | // Set the ClipboardDelegate impl to copy/paste text in the native |
353 | // clipboard from the ui::Entry control. |
354 | m_uiSystem->setClipboardDelegate(&m_modules->m_clipboard); |
355 | |
356 | // Setup the GUI cursor and redraw screen |
357 | ui::set_use_native_cursors(preferences().cursor.useNativeCursor()); |
358 | ui::set_mouse_cursor_scale(preferences().cursor.cursorScale()); |
359 | ui::set_mouse_cursor(kArrowCursor); |
360 | |
361 | auto manager = ui::Manager::getDefault(); |
362 | manager->invalidate(); |
363 | |
364 | // Create the main window. |
365 | m_mainWindow.reset(new MainWindow); |
366 | if (m_mod) |
367 | m_mod->modMainWindow(m_mainWindow.get()); |
368 | |
369 | // Data recovery is enabled only in GUI mode |
370 | if (preferences().general.dataRecovery()) |
371 | m_modules->searchDataRecoverySessions(); |
372 | |
373 | // Default status of the main window. |
374 | app_rebuild_documents_tabs(); |
375 | m_mainWindow->statusBar()->showDefaultText(); |
376 | |
377 | // Show the main window (this is not modal, the code continues) |
378 | m_mainWindow->openWindow(); |
379 | |
380 | #if LAF_LINUX // TODO check why this is required and we cannot call |
381 | // updateAllDisplaysWithNewScale() on Linux/X11 |
382 | // Redraw the whole screen. |
383 | manager->invalidate(); |
384 | #else |
385 | // To know the initial manager size we call to |
386 | // Manager::updateAllDisplaysWithNewScale(...) so we receive a |
387 | // Manager::onNewDisplayConfiguration() (which will update the |
388 | // bounds of the manager for first time). This is required so if |
389 | // the OpenFileCommand (called when we're processing the CLI with |
390 | // OpenBatchOfFiles) shows a dialog to open a sequence of files, |
391 | // the dialog is centered correctly to the manager bounds. |
392 | const int scale = Preferences::instance().general.screenScale(); |
393 | manager->updateAllDisplaysWithNewScale(scale); |
394 | #endif |
395 | } |
396 | #endif // ENABLE_UI |
397 | |
398 | #ifdef ENABLE_SCRIPTING |
399 | // Call the init() function from all plugins |
400 | LOG("APP: Initializing scripts...\n" ); |
401 | extensions().executeInitActions(); |
402 | #endif |
403 | |
404 | // Process options |
405 | LOG("APP: Processing options...\n" ); |
406 | int code; |
407 | { |
408 | std::unique_ptr<CliDelegate> delegate; |
409 | if (options.previewCLI()) |
410 | delegate.reset(new PreviewCliDelegate); |
411 | else |
412 | delegate.reset(new DefaultCliDelegate); |
413 | |
414 | CliProcessor cli(delegate.get(), options); |
415 | code = cli.process(context()); |
416 | } |
417 | |
418 | LOG("APP: Finish launching...\n" ); |
419 | system->finishLaunching(); |
420 | return code; |
421 | } |
422 | |
423 | namespace { |
424 | |
425 | #ifdef ENABLE_UI |
426 | struct CloseMainWindow { |
427 | std::unique_ptr<MainWindow>& m_win; |
428 | CloseMainWindow(std::unique_ptr<MainWindow>& win) : m_win(win) { } |
429 | ~CloseMainWindow() { m_win.reset(nullptr); } |
430 | }; |
431 | #endif |
432 | |
433 | struct CloseAllDocs { |
434 | Context* m_ctx; |
435 | CloseAllDocs(Context* ctx) : m_ctx(ctx) { } |
436 | ~CloseAllDocs() { |
437 | std::vector<Doc*> docs; |
438 | #ifdef ENABLE_UI |
439 | for (Doc* doc : static_cast<UIContext*>(m_ctx)->getAndRemoveAllClosedDocs()) |
440 | docs.push_back(doc); |
441 | #endif |
442 | for (Doc* doc : m_ctx->documents()) |
443 | docs.push_back(doc); |
444 | for (Doc* doc : docs) { |
445 | // First we close the document. In this way we receive recent |
446 | // notifications related to the document as a app::Doc. If |
447 | // we delete the document directly, we destroy the app::Doc |
448 | // too early, and then doc::~Document() call |
449 | // DocsObserver::onRemoveDocument(). In this way, observers |
450 | // could think that they have a fully created app::Doc when |
451 | // in reality it's a doc::Document (in the middle of a |
452 | // destruction process). |
453 | // |
454 | // TODO: This problem is because we're extending doc::Document, |
455 | // in the future, we should remove app::Doc. |
456 | doc->close(); |
457 | delete doc; |
458 | } |
459 | } |
460 | }; |
461 | |
462 | } // anonymous namespace |
463 | |
464 | void App::run() |
465 | { |
466 | #ifdef ENABLE_UI |
467 | CloseMainWindow closeMainWindow(m_mainWindow); |
468 | #endif |
469 | CloseAllDocs closeAllDocsAtExit(context()); |
470 | |
471 | #ifdef ENABLE_UI |
472 | // Run the GUI |
473 | if (isGui()) { |
474 | auto manager = ui::Manager::getDefault(); |
475 | #if LAF_WINDOWS |
476 | // How to interpret one finger on Windows tablets. |
477 | manager->display()->nativeWindow() |
478 | ->setInterpretOneFingerGestureAsMouseMovement( |
479 | preferences().experimental.oneFingerAsMouseMovement()); |
480 | #endif |
481 | |
482 | #if LAF_LINUX |
483 | // Setup app icon for Linux window managers |
484 | try { |
485 | os::Window* window = os::instance()->defaultWindow(); |
486 | os::SurfaceList icons; |
487 | |
488 | for (const int size : { 32, 64, 128 }) { |
489 | ResourceFinder rf; |
490 | rf.includeDataDir(fmt::format("icons/ase{0}.png" , size).c_str()); |
491 | if (rf.findFirst()) { |
492 | os::SurfaceRef surf = os::instance()->loadRgbaSurface(rf.filename().c_str()); |
493 | if (surf) { |
494 | surf->setImmutable(); |
495 | icons.push_back(surf); |
496 | } |
497 | } |
498 | } |
499 | |
500 | window->setIcons(icons); |
501 | } |
502 | catch (const std::exception&) { |
503 | // Just ignore the exception, we couldn't change the app icon, no |
504 | // big deal. |
505 | } |
506 | #endif |
507 | |
508 | // Initialize Steam API |
509 | #ifdef ENABLE_STEAM |
510 | steam::SteamAPI steam; |
511 | if (steam.initialized()) |
512 | os::instance()->activateApp(); |
513 | #endif |
514 | |
515 | #if defined(_DEBUG) || defined(ENABLE_DEVMODE) |
516 | // On OS X, when we compile Aseprite on devmode, we're using it |
517 | // outside an app bundle, so we must active the app explicitly. |
518 | if (isGui()) |
519 | os::instance()->activateApp(); |
520 | #endif |
521 | |
522 | #ifdef ENABLE_UPDATER |
523 | // Launch the thread to check for updates. |
524 | app::CheckUpdateThreadLauncher checkUpdate( |
525 | m_mainWindow->getCheckUpdateDelegate()); |
526 | checkUpdate.launch(); |
527 | #endif |
528 | |
529 | #if !ENABLE_SENTRY |
530 | app::SendCrash sendCrash; |
531 | sendCrash.search(); |
532 | #endif |
533 | |
534 | // Keep the console alive the whole program execute (just in case |
535 | // we've to print errors). |
536 | Console console; |
537 | #ifdef ENABLE_SCRIPTING |
538 | // Use the app::Console() for script errors |
539 | ConsoleEngineDelegate delegate(console); |
540 | script::ScopedEngineDelegate setEngineDelegate(m_engine.get(), &delegate); |
541 | #endif |
542 | |
543 | // Run the GUI main message loop |
544 | try { |
545 | manager->run(); |
546 | set_app_state(AppState::kClosing); |
547 | } |
548 | catch (...) { |
549 | set_app_state(AppState::kClosingWithException); |
550 | throw; |
551 | } |
552 | } |
553 | #endif // ENABLE_UI |
554 | |
555 | #ifdef ENABLE_SCRIPTING |
556 | // Start shell to execute scripts. |
557 | if (m_isShell) { |
558 | m_engine->printLastResult(); // TODO is this needed? |
559 | Shell shell; |
560 | shell.run(*m_engine); |
561 | } |
562 | #endif // ENABLE_SCRIPTING |
563 | |
564 | // ---------------------------------------------------------------------- |
565 | |
566 | #ifdef ENABLE_SCRIPTING |
567 | // Call the exit() function from all plugins |
568 | extensions().executeExitActions(); |
569 | #endif |
570 | |
571 | close(); |
572 | } |
573 | |
574 | void App::close() |
575 | { |
576 | #ifdef ENABLE_UI |
577 | if (isGui()) { |
578 | // Select no document |
579 | static_cast<UIContext*>(context())->setActiveView(nullptr); |
580 | |
581 | // Delete backups (this is a normal shutdown, we are not handling |
582 | // exceptions, and we are not in a destructor). |
583 | m_modules->deleteDataRecovery(); |
584 | } |
585 | #endif |
586 | } |
587 | |
588 | // Finishes the Aseprite application. |
589 | App::~App() |
590 | { |
591 | try { |
592 | LOG("APP: Exit\n" ); |
593 | ASSERT(m_instance == this); |
594 | |
595 | #ifdef ENABLE_SCRIPTING |
596 | // Destroy scripting engine calling a method (instead of using |
597 | // reset()) because we need to keep the "m_engine" pointer valid |
598 | // until the very end, just in case that some Lua error happens |
599 | // now and we have to print that error using |
600 | // App::instance()->scriptEngine() in some way. E.g. if a Dialog |
601 | // onclose event handler fails with a Lua error when we are |
602 | // closing the app, a Lua error must be printed, and we need a |
603 | // valid m_engine pointer. |
604 | m_engine->destroy(); |
605 | m_engine.reset(); |
606 | #endif |
607 | |
608 | // Delete file formats. |
609 | FileFormatsManager::destroyInstance(); |
610 | |
611 | // Fire App Exit signal. |
612 | App::instance()->Exit(); |
613 | |
614 | #ifdef ENABLE_UI |
615 | // Finalize modules, configuration and core. |
616 | Editor::destroyEditorSharedInternals(); |
617 | |
618 | m_backupIndicator.reset(); |
619 | |
620 | // Save brushes |
621 | m_brushes.reset(); |
622 | #endif |
623 | |
624 | m_legacy.reset(); |
625 | m_modules.reset(); |
626 | |
627 | // Save preferences only if we are running in GUI mode. when we |
628 | // run in batch mode we might want to reset some preferences so |
629 | // the scripts have a reproducible behavior. Those reset |
630 | // preferences must not be saved. |
631 | if (isGui()) |
632 | preferences().save(); |
633 | |
634 | m_coreModules.reset(); |
635 | |
636 | #ifdef ENABLE_UI |
637 | // Destroy the loaded gui.xml data. |
638 | KeyboardShortcuts::destroyInstance(); |
639 | GuiXml::destroyInstance(); |
640 | #endif |
641 | } |
642 | catch (const std::exception& e) { |
643 | LOG(ERROR, "APP: Error: %s\n" , e.what()); |
644 | os::error_message(e.what()); |
645 | |
646 | // no re-throw |
647 | } |
648 | catch (...) { |
649 | os::error_message("Error closing the program.\n(uncaught exception)" ); |
650 | |
651 | // no re-throw |
652 | } |
653 | |
654 | m_instance = nullptr; |
655 | } |
656 | |
657 | Context* App::context() |
658 | { |
659 | return &m_coreModules->m_context; |
660 | } |
661 | |
662 | bool App::isPortable() |
663 | { |
664 | static std::optional<bool> is_portable; |
665 | if (!is_portable) { |
666 | is_portable = |
667 | base::is_file(base::join_path( |
668 | base::get_file_path(base::get_app_path()), |
669 | "aseprite.ini" )); |
670 | } |
671 | return *is_portable; |
672 | } |
673 | |
674 | tools::ToolBox* App::toolBox() const |
675 | { |
676 | ASSERT(m_modules != NULL); |
677 | return &m_modules->m_toolbox; |
678 | } |
679 | |
680 | tools::Tool* App::activeTool() const |
681 | { |
682 | return m_modules->m_activeToolManager.activeTool(); |
683 | } |
684 | |
685 | tools::ActiveToolManager* App::activeToolManager() const |
686 | { |
687 | return &m_modules->m_activeToolManager; |
688 | } |
689 | |
690 | RecentFiles* App::recentFiles() const |
691 | { |
692 | #ifdef ENABLE_UI |
693 | ASSERT(m_modules != NULL); |
694 | return &m_modules->m_recent_files; |
695 | #else |
696 | return nullptr; |
697 | #endif |
698 | } |
699 | |
700 | Workspace* App::workspace() const |
701 | { |
702 | if (m_mainWindow) |
703 | return m_mainWindow->getWorkspace(); |
704 | else |
705 | return nullptr; |
706 | } |
707 | |
708 | ContextBar* App::contextBar() const |
709 | { |
710 | if (m_mainWindow) |
711 | return m_mainWindow->getContextBar(); |
712 | else |
713 | return nullptr; |
714 | } |
715 | |
716 | Timeline* App::timeline() const |
717 | { |
718 | if (m_mainWindow) |
719 | return m_mainWindow->getTimeline(); |
720 | else |
721 | return nullptr; |
722 | } |
723 | |
724 | Preferences& App::preferences() const |
725 | { |
726 | return m_coreModules->m_context.preferences(); |
727 | } |
728 | |
729 | Extensions& App::extensions() const |
730 | { |
731 | return m_modules->m_extensions; |
732 | } |
733 | |
734 | crash::DataRecovery* App::dataRecovery() const |
735 | { |
736 | return m_modules->recovery(); |
737 | } |
738 | |
739 | #ifdef ENABLE_UI |
740 | void App::showNotification(INotificationDelegate* del) |
741 | { |
742 | if (m_mainWindow) |
743 | m_mainWindow->showNotification(del); |
744 | } |
745 | |
746 | void App::showBackupNotification(bool state) |
747 | { |
748 | assert_ui_thread(); |
749 | if (state) { |
750 | if (!m_backupIndicator) |
751 | m_backupIndicator = std::make_unique<BackupIndicator>(); |
752 | m_backupIndicator->start(); |
753 | } |
754 | else { |
755 | if (m_backupIndicator) |
756 | m_backupIndicator->stop(); |
757 | } |
758 | } |
759 | |
760 | void App::updateDisplayTitleBar() |
761 | { |
762 | std::string defaultTitle = fmt::format("{} v{}" , get_app_name(), get_app_version()); |
763 | std::string title; |
764 | |
765 | DocView* docView = UIContext::instance()->activeView(); |
766 | if (docView) { |
767 | // Prepend the document's filename. |
768 | title += docView->document()->name(); |
769 | title += " - " ; |
770 | } |
771 | |
772 | title += defaultTitle; |
773 | os::instance()->defaultWindow()->setTitle(title); |
774 | } |
775 | |
776 | InputChain& App::inputChain() |
777 | { |
778 | return m_modules->m_inputChain; |
779 | } |
780 | #endif |
781 | |
782 | // Updates palette and redraw the screen. |
783 | void app_refresh_screen() |
784 | { |
785 | #ifdef ENABLE_UI |
786 | Context* ctx = UIContext::instance(); |
787 | ASSERT(ctx != NULL); |
788 | |
789 | Site site = ctx->activeSite(); |
790 | if (Palette* pal = site.palette()) |
791 | set_current_palette(pal, false); |
792 | else |
793 | set_current_palette(nullptr, false); |
794 | |
795 | // Invalidate the whole screen. |
796 | ui::Manager::getDefault()->invalidate(); |
797 | #endif // ENABLE_UI |
798 | } |
799 | |
800 | // TODO remove app_rebuild_documents_tabs() and replace it by |
801 | // observable events in the document (so a tab can observe if the |
802 | // document is modified). |
803 | void app_rebuild_documents_tabs() |
804 | { |
805 | #ifdef ENABLE_UI |
806 | if (App::instance()->isGui()) { |
807 | App::instance()->workspace()->updateTabs(); |
808 | App::instance()->updateDisplayTitleBar(); |
809 | } |
810 | #endif // ENABLE_UI |
811 | } |
812 | |
813 | PixelFormat app_get_current_pixel_format() |
814 | { |
815 | Context* ctx = App::instance()->context(); |
816 | ASSERT(ctx); |
817 | |
818 | Doc* doc = ctx->activeDocument(); |
819 | if (doc) |
820 | return doc->sprite()->pixelFormat(); |
821 | else |
822 | return IMAGE_RGB; |
823 | } |
824 | |
825 | int app_get_color_to_clear_layer(Layer* layer) |
826 | { |
827 | ASSERT(layer != NULL); |
828 | |
829 | app::Color color; |
830 | |
831 | // The `Background' is erased with the `Background Color' |
832 | if (layer->isBackground()) { |
833 | #ifdef ENABLE_UI |
834 | if (ColorBar::instance()) |
835 | color = ColorBar::instance()->getBgColor(); |
836 | else |
837 | #endif |
838 | color = app::Color::fromRgb(0, 0, 0); // TODO get background color color from doc::Settings |
839 | } |
840 | else // All transparent layers are cleared with the mask color |
841 | color = app::Color::fromMask(); |
842 | |
843 | return color_utils::color_for_layer(color, layer); |
844 | } |
845 | |
846 | #ifdef ENABLE_DRM |
847 | void app_configure_drm() { |
848 | ResourceFinder userDirRf, dataDirRf; |
849 | userDirRf.includeUserDir("" ); |
850 | dataDirRf.includeDataDir("" ); |
851 | std::map<std::string, std::string> config = { |
852 | {"data" , dataDirRf.getFirstOrCreateDefault()} |
853 | }; |
854 | DRM_CONFIGURE(get_app_url(), get_app_name(), get_app_version(), userDirRf.getFirstOrCreateDefault(), updater::getUserAgent(), config); |
855 | } |
856 | #endif |
857 | |
858 | } // namespace app |
859 | |