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 | #include "app/app_menus.h" |
14 | #include "app/commands/cmd_open_file.h" |
15 | #include "app/commands/command.h" |
16 | #include "app/commands/commands.h" |
17 | #include "app/commands/params.h" |
18 | #include "app/console.h" |
19 | #include "app/crash/data_recovery.h" |
20 | #include "app/doc.h" |
21 | #include "app/ini_file.h" |
22 | #include "app/modules/editors.h" |
23 | #include "app/modules/gfx.h" |
24 | #include "app/modules/gui.h" |
25 | #include "app/modules/palettes.h" |
26 | #include "app/pref/preferences.h" |
27 | #include "app/tools/ink.h" |
28 | #include "app/tools/tool_box.h" |
29 | #include "app/ui/editor/editor.h" |
30 | #include "app/ui/keyboard_shortcuts.h" |
31 | #include "app/ui/main_menu_bar.h" |
32 | #include "app/ui/main_menu_bar.h" |
33 | #include "app/ui/main_window.h" |
34 | #include "app/ui/skin/skin_property.h" |
35 | #include "app/ui/skin/skin_theme.h" |
36 | #include "app/ui/status_bar.h" |
37 | #include "app/ui/toolbar.h" |
38 | #include "app/ui_context.h" |
39 | #include "app/util/open_batch.h" |
40 | #include "base/fs.h" |
41 | #include "base/memory.h" |
42 | #include "base/string.h" |
43 | #include "doc/sprite.h" |
44 | #include "os/error.h" |
45 | #include "os/screen.h" |
46 | #include "os/surface.h" |
47 | #include "os/system.h" |
48 | #include "os/window.h" |
49 | #include "ui/intern.h" |
50 | #include "ui/ui.h" |
51 | |
52 | #ifdef ENABLE_STEAM |
53 | #include "steam/steam.h" |
54 | #endif |
55 | |
56 | #include <algorithm> |
57 | #include <cstdio> |
58 | #include <list> |
59 | #include <vector> |
60 | |
61 | #if defined(_DEBUG) && defined(ENABLE_DATA_RECOVERY) |
62 | #include "app/crash/data_recovery.h" |
63 | #include "app/modules/editors.h" |
64 | #endif |
65 | |
66 | namespace app { |
67 | |
68 | using namespace gfx; |
69 | using namespace ui; |
70 | using namespace app::skin; |
71 | |
72 | static struct { |
73 | int width; |
74 | int height; |
75 | int scale; |
76 | } try_resolutions[] = { { 1024, 768, 2 }, |
77 | { 800, 600, 2 }, |
78 | { 640, 480, 2 }, |
79 | { 320, 240, 1 }, |
80 | { 320, 200, 1 }, |
81 | { 0, 0, 0 } }; |
82 | |
83 | ////////////////////////////////////////////////////////////////////// |
84 | |
85 | class CustomizedGuiManager : public ui::Manager |
86 | , public ui::LayoutIO { |
87 | public: |
88 | CustomizedGuiManager(const os::WindowRef& nativeWindow) |
89 | : ui::Manager(nativeWindow) { |
90 | } |
91 | |
92 | protected: |
93 | bool onProcessMessage(Message* msg) override; |
94 | #if ENABLE_DEVMODE |
95 | bool onProcessDevModeKeyDown(KeyMessage* msg); |
96 | #endif |
97 | void onInitTheme(InitThemeEvent& ev) override; |
98 | LayoutIO* onGetLayoutIO() override { return this; } |
99 | void onNewDisplayConfiguration(Display* display) override; |
100 | |
101 | // LayoutIO implementation |
102 | std::string loadLayout(Widget* widget) override; |
103 | void saveLayout(Widget* widget, const std::string& str) override; |
104 | }; |
105 | |
106 | static os::WindowRef main_window = nullptr; |
107 | static CustomizedGuiManager* manager = nullptr; |
108 | static Theme* gui_theme = nullptr; |
109 | |
110 | static ui::Timer* defered_invalid_timer = nullptr; |
111 | static gfx::Region defered_invalid_region; |
112 | |
113 | // Load & save graphics configuration |
114 | static bool load_gui_config(os::WindowSpec& spec, bool& maximized); |
115 | static void save_gui_config(); |
116 | |
117 | static bool create_main_window(bool gpuAccel, |
118 | bool& maximized, |
119 | std::string& lastError) |
120 | { |
121 | os::WindowSpec spec; |
122 | if (!load_gui_config(spec, maximized)) |
123 | return false; |
124 | |
125 | // Scale is equal to 0 when it's the first time the program is |
126 | // executed. |
127 | int scale = Preferences::instance().general.screenScale(); |
128 | |
129 | os::instance()->setGpuAcceleration(gpuAccel); |
130 | |
131 | try { |
132 | if (!spec.frame().isEmpty() || |
133 | !spec.contentRect().isEmpty()) { |
134 | spec.scale(scale == 0 ? 2: std::clamp(scale, 1, 4)); |
135 | main_window = os::instance()->makeWindow(spec); |
136 | } |
137 | } |
138 | catch (const os::WindowCreationException& e) { |
139 | lastError = e.what(); |
140 | } |
141 | |
142 | if (!main_window) { |
143 | for (int c=0; try_resolutions[c].width; ++c) { |
144 | try { |
145 | spec.frame(); |
146 | spec.position(os::WindowSpec::Position::Default); |
147 | spec.scale(scale == 0 ? try_resolutions[c].scale: scale); |
148 | spec.contentRect(gfx::Rect(0, 0, |
149 | try_resolutions[c].width * spec.scale(), |
150 | try_resolutions[c].height * spec.scale())); |
151 | main_window = os::instance()->makeWindow(spec); |
152 | break; |
153 | } |
154 | catch (const os::WindowCreationException& e) { |
155 | lastError = e.what(); |
156 | } |
157 | } |
158 | } |
159 | |
160 | if (main_window) { |
161 | // Change the scale value only in the first run (this will be |
162 | // saved when the program is closed). |
163 | if (scale == 0) |
164 | Preferences::instance().general.screenScale(main_window->scale()); |
165 | |
166 | if (main_window->isMinimized()) |
167 | main_window->maximize(); |
168 | } |
169 | |
170 | return (main_window != nullptr); |
171 | } |
172 | |
173 | // Initializes GUI. |
174 | int init_module_gui() |
175 | { |
176 | auto& pref = Preferences::instance(); |
177 | bool maximized = false; |
178 | std::string lastError = "Unknown error" ; |
179 | bool gpuAccel = pref.general.gpuAcceleration(); |
180 | |
181 | if (!create_main_window(gpuAccel, maximized, lastError)) { |
182 | // If we've created the native window with hardware acceleration, |
183 | // now we try to do it without hardware acceleration. |
184 | if (gpuAccel && |
185 | os::instance()->hasCapability(os::Capabilities::GpuAccelerationSwitch)) { |
186 | if (create_main_window(false, maximized, lastError)) { |
187 | // Disable hardware acceleration |
188 | pref.general.gpuAcceleration(false); |
189 | } |
190 | } |
191 | } |
192 | |
193 | if (!main_window) { |
194 | os::error_message( |
195 | ("Unable to create a user-interface window.\nDetails: " +lastError+"\n" ).c_str()); |
196 | return -1; |
197 | } |
198 | |
199 | // Create the default-manager |
200 | manager = new CustomizedGuiManager(main_window); |
201 | |
202 | // Setup the GUI theme for all widgets |
203 | gui_theme = new SkinTheme; |
204 | ui::set_theme(gui_theme, pref.general.uiScale()); |
205 | |
206 | if (maximized) |
207 | main_window->maximize(); |
208 | |
209 | // Handle live resize too redraw the entire manager, dispatch the UI |
210 | // messages, and flip the window. |
211 | os::instance()->handleWindowResize = |
212 | [](os::Window* window) { |
213 | Display* display = Manager::getDisplayFromNativeWindow(window); |
214 | if (!display) |
215 | display = manager->display(); |
216 | ASSERT(display); |
217 | |
218 | Message* msg = new Message(kResizeDisplayMessage); |
219 | msg->setDisplay(display); |
220 | msg->setRecipient(manager); |
221 | msg->setPropagateToChildren(false); |
222 | |
223 | manager->enqueueMessage(msg); |
224 | manager->dispatchMessages(); |
225 | }; |
226 | |
227 | // Set graphics options for next time |
228 | save_gui_config(); |
229 | |
230 | update_windows_color_profile_from_preferences(); |
231 | |
232 | return 0; |
233 | } |
234 | |
235 | void exit_module_gui() |
236 | { |
237 | save_gui_config(); |
238 | |
239 | delete defered_invalid_timer; |
240 | delete manager; |
241 | |
242 | // Now we can destroy theme |
243 | ui::set_theme(nullptr, ui::guiscale()); |
244 | delete gui_theme; |
245 | |
246 | // This should be the last unref() of the display to delete it. |
247 | main_window.reset(); |
248 | } |
249 | |
250 | void update_windows_color_profile_from_preferences() |
251 | { |
252 | auto system = os::instance(); |
253 | |
254 | gen::WindowColorProfile windowProfile; |
255 | if (Preferences::instance().color.manage()) |
256 | windowProfile = Preferences::instance().color.windowProfile(); |
257 | else |
258 | windowProfile = gen::WindowColorProfile::SRGB; |
259 | |
260 | os::ColorSpaceRef osCS = nullptr; |
261 | |
262 | switch (windowProfile) { |
263 | case gen::WindowColorProfile::MONITOR: |
264 | osCS = nullptr; |
265 | break; |
266 | case gen::WindowColorProfile::SRGB: |
267 | osCS = system->makeColorSpace(gfx::ColorSpace::MakeSRGB()); |
268 | break; |
269 | case gen::WindowColorProfile::SPECIFIC: { |
270 | std::string name = |
271 | Preferences::instance().color.windowProfileName(); |
272 | |
273 | std::vector<os::ColorSpaceRef> colorSpaces; |
274 | system->listColorSpaces(colorSpaces); |
275 | |
276 | for (auto& cs : colorSpaces) { |
277 | auto gfxCs = cs->gfxColorSpace(); |
278 | if (gfxCs->type() == gfx::ColorSpace::ICC && |
279 | gfxCs->name() == name) { |
280 | osCS = cs; |
281 | break; |
282 | } |
283 | } |
284 | break; |
285 | } |
286 | } |
287 | |
288 | // Set the default color space for all windows (osCS can be nullptr |
289 | // which means that each window should use its monitor color space) |
290 | system->setWindowsColorSpace(osCS); |
291 | |
292 | // Set the color space of all windows |
293 | for (ui::Widget* widget : manager->children()) { |
294 | ASSERT(widget->type() == ui::kWindowWidget); |
295 | auto window = static_cast<ui::Window*>(widget); |
296 | if (window->ownDisplay()) { |
297 | if (auto display = window->display()) |
298 | display->nativeWindow()->setColorSpace(osCS); |
299 | } |
300 | } |
301 | } |
302 | |
303 | static bool load_gui_config(os::WindowSpec& spec, bool& maximized) |
304 | { |
305 | os::ScreenRef screen = os::instance()->mainScreen(); |
306 | #ifdef LAF_SKIA |
307 | ASSERT(screen); |
308 | #else |
309 | // Compiled without Skia (none backend), without screen. |
310 | if (!screen) { |
311 | std::printf( |
312 | "\n" |
313 | " Aseprite cannot initialize GUI because it was compiled with LAF_BACKEND=none\n" |
314 | "\n" |
315 | " Check the documentation in:\n" |
316 | " https://github.com/aseprite/laf/blob/main/README.md\n" |
317 | " https://github.com/aseprite/aseprite/blob/main/INSTALL.md\n" |
318 | "\n" ); |
319 | return false; |
320 | } |
321 | #endif |
322 | |
323 | spec.screen(screen); |
324 | |
325 | gfx::Rect frame; |
326 | frame = get_config_rect("GfxMode" , "Frame" , frame); |
327 | if (!frame.isEmpty()) { |
328 | spec.position(os::WindowSpec::Position::Frame); |
329 | |
330 | // Limit the content rect position into the available workarea, |
331 | // e.g. this is needed in case that the user closed Aseprite in a |
332 | // 2nd monitor that then unplugged and start Aseprite again. |
333 | bool ok = false; |
334 | os::ScreenList screens; |
335 | os::instance()->listScreens(screens); |
336 | for (const auto& screen : screens) { |
337 | gfx::Rect wa = screen->workarea(); |
338 | gfx::Rect intersection = (frame & wa); |
339 | if (intersection.w >= 32 && |
340 | intersection.h >= 32) { |
341 | ok = true; |
342 | break; |
343 | } |
344 | } |
345 | |
346 | // Reset content rect |
347 | if (!ok) { |
348 | spec.position(os::WindowSpec::Position::Default); |
349 | frame = gfx::Rect(); |
350 | } |
351 | } |
352 | |
353 | if (frame.isEmpty()) { |
354 | frame = screen->workarea().shrink(64); |
355 | |
356 | // Try to get Width/Height from previous Aseprite versions |
357 | frame.w = get_config_int("GfxMode" , "Width" , frame.w); |
358 | frame.h = get_config_int("GfxMode" , "Height" , frame.h); |
359 | } |
360 | spec.frame(frame); |
361 | |
362 | maximized = get_config_bool("GfxMode" , "Maximized" , true); |
363 | |
364 | ui::set_multiple_displays(Preferences::instance().experimental.multipleWindows()); |
365 | return true; |
366 | } |
367 | |
368 | static void save_gui_config() |
369 | { |
370 | os::Window* window = manager->display()->nativeWindow(); |
371 | if (window) { |
372 | const bool maximized = (window->isMaximized() || |
373 | window->isFullscreen()); |
374 | const gfx::Rect frame = (maximized ? window->restoredFrame(): |
375 | window->frame()); |
376 | |
377 | set_config_bool("GfxMode" , "Maximized" , maximized); |
378 | set_config_rect("GfxMode" , "Frame" , frame); |
379 | } |
380 | } |
381 | |
382 | void update_screen_for_document(const Doc* document) |
383 | { |
384 | // Without document. |
385 | if (!document) { |
386 | // Well, change to the default palette. |
387 | if (set_current_palette(NULL, false)) { |
388 | // If the palette changes, refresh the whole screen. |
389 | if (manager) |
390 | manager->invalidate(); |
391 | } |
392 | } |
393 | // With a document. |
394 | else { |
395 | const_cast<Doc*>(document)->notifyGeneralUpdate(); |
396 | |
397 | // Update the tabs (maybe the modified status has been changed). |
398 | app_rebuild_documents_tabs(); |
399 | } |
400 | } |
401 | |
402 | void load_window_pos(Window* window, const char* section, |
403 | const bool limitMinSize) |
404 | { |
405 | Display* parentDisplay = |
406 | (window->display() ? window->display(): |
407 | window->manager()->display()); |
408 | Rect workarea = |
409 | (get_multiple_displays() ? |
410 | parentDisplay->nativeWindow()->screen()->workarea(): |
411 | parentDisplay->bounds()); |
412 | |
413 | // Default position |
414 | Rect origPos = window->bounds(); |
415 | |
416 | // Load configurated position |
417 | Rect pos = get_config_rect(section, "WindowPos" , origPos); |
418 | |
419 | if (limitMinSize) { |
420 | pos.w = std::clamp(pos.w, origPos.w, workarea.w); |
421 | pos.h = std::clamp(pos.h, origPos.h, workarea.h); |
422 | } |
423 | else { |
424 | pos.w = std::min(pos.w, workarea.w); |
425 | pos.h = std::min(pos.h, workarea.h); |
426 | } |
427 | |
428 | pos.setOrigin(Point(std::clamp(pos.x, workarea.x, workarea.x2()-pos.w), |
429 | std::clamp(pos.y, workarea.y, workarea.y2()-pos.h))); |
430 | |
431 | window->setBounds(pos); |
432 | |
433 | if (get_multiple_displays()) { |
434 | Rect frame = get_config_rect(section, "WindowFrame" , gfx::Rect()); |
435 | if (!frame.isEmpty()) { |
436 | limit_with_workarea(parentDisplay, frame); |
437 | window->loadNativeFrame(frame); |
438 | } |
439 | } |
440 | else { |
441 | del_config_value(section, "WindowFrame" ); |
442 | } |
443 | } |
444 | |
445 | void save_window_pos(Window* window, const char* section) |
446 | { |
447 | gfx::Rect rc; |
448 | |
449 | if (!window->lastNativeFrame().isEmpty()) { |
450 | const os::Window* mainNativeWindow = manager->display()->nativeWindow(); |
451 | rc = window->lastNativeFrame(); |
452 | set_config_rect(section, "WindowFrame" , rc); |
453 | rc.offset(-mainNativeWindow->frame().origin()); |
454 | rc /= mainNativeWindow->scale(); |
455 | } |
456 | else { |
457 | del_config_value(section, "WindowFrame" ); |
458 | rc = window->bounds(); |
459 | } |
460 | |
461 | set_config_rect(section, "WindowPos" , rc); |
462 | } |
463 | |
464 | // TODO Replace this with new theme styles |
465 | Widget* setup_mini_font(Widget* widget) |
466 | { |
467 | auto skinProp = get_skin_property(widget); |
468 | skinProp->setMiniFont(); |
469 | return widget; |
470 | } |
471 | |
472 | // TODO Replace this with new theme styles |
473 | Widget* setup_mini_look(Widget* widget) |
474 | { |
475 | auto skinProp = get_skin_property(widget); |
476 | skinProp->setLook(MiniLook); |
477 | return widget; |
478 | } |
479 | |
480 | ////////////////////////////////////////////////////////////////////// |
481 | // Button style (convert radio or check buttons and draw it like |
482 | // normal buttons) |
483 | |
484 | void defer_invalid_rect(const gfx::Rect& rc) |
485 | { |
486 | if (!defered_invalid_timer) |
487 | defered_invalid_timer = new ui::Timer(250, manager); |
488 | |
489 | defered_invalid_timer->stop(); |
490 | defered_invalid_timer->start(); |
491 | defered_invalid_region.createUnion(defered_invalid_region, gfx::Region(rc)); |
492 | } |
493 | |
494 | ////////////////////////////////////////////////////////////////////// |
495 | // Manager event handler. |
496 | |
497 | bool CustomizedGuiManager::onProcessMessage(Message* msg) |
498 | { |
499 | #ifdef ENABLE_STEAM |
500 | if (auto steamAPI = steam::SteamAPI::instance()) |
501 | steamAPI->runCallbacks(); |
502 | #endif |
503 | |
504 | switch (msg->type()) { |
505 | |
506 | case kCloseDisplayMessage: |
507 | // Only call the exit command/close the app when the the main |
508 | // display is the closed window in this kCloseDisplayMessage |
509 | // message and it's the current running foreground window. |
510 | if (msg->display() == this->display() && |
511 | getForegroundWindow() == App::instance()->mainWindow()) { |
512 | // Execute the "Exit" command. |
513 | Command* command = Commands::instance()->byId(CommandId::Exit()); |
514 | UIContext::instance()->executeCommandFromMenuOrShortcut(command); |
515 | return true; |
516 | } |
517 | break; |
518 | |
519 | case kDropFilesMessage: |
520 | // Files are processed only when the main window is the current |
521 | // window running. |
522 | // |
523 | // TODO could we send the files to each dialog? |
524 | if (getForegroundWindow() == App::instance()->mainWindow()) { |
525 | base::paths files = static_cast<DropFilesMessage*>(msg)->files(); |
526 | UIContext* ctx = UIContext::instance(); |
527 | OpenBatchOfFiles batch; |
528 | |
529 | while (!files.empty()) { |
530 | auto fn = files.front(); |
531 | files.erase(files.begin()); |
532 | |
533 | // If the document is already open, select it. |
534 | Doc* doc = ctx->documents().getByFileName(fn); |
535 | if (doc) { |
536 | DocView* docView = ctx->getFirstDocView(doc); |
537 | if (docView) |
538 | ctx->setActiveView(docView); |
539 | else { |
540 | ASSERT(false); // Must be some DocView available |
541 | } |
542 | } |
543 | // Load the file |
544 | else { |
545 | // Depending on the file type we will want to do different things: |
546 | std::string extension = base::string_to_lower( |
547 | base::get_file_extension(fn)); |
548 | |
549 | // Install the extension |
550 | if (extension == "aseprite-extension" ) { |
551 | Command* cmd = Commands::instance()->byId(CommandId::Options()); |
552 | Params params; |
553 | params.set("installExtension" , fn.c_str()); |
554 | ctx->executeCommandFromMenuOrShortcut(cmd, params); |
555 | } |
556 | // Other extensions will be handled as an image/sprite |
557 | else { |
558 | batch.open(ctx, fn, |
559 | false); // Open all frames |
560 | |
561 | // Remove all used file names from the "dropped files" |
562 | for (const auto& usedFn : batch.usedFiles()) { |
563 | auto it = std::find(files.begin(), files.end(), usedFn); |
564 | if (it != files.end()) |
565 | files.erase(it); |
566 | } |
567 | } |
568 | } |
569 | } |
570 | } |
571 | break; |
572 | |
573 | case kKeyDownMessage: { |
574 | #if ENABLE_DEVMODE |
575 | if (onProcessDevModeKeyDown(static_cast<KeyMessage*>(msg))) |
576 | return true; |
577 | #endif // ENABLE_DEVMODE |
578 | |
579 | // Call base impl to check if there is a foreground window as |
580 | // top level that needs keys. (In this way we just do not |
581 | // process keyboard shortcuts for menus and tools). |
582 | if (Manager::onProcessMessage(msg)) |
583 | return true; |
584 | |
585 | KeyboardShortcuts* keys = KeyboardShortcuts::instance(); |
586 | for (const KeyPtr& key : *keys) { |
587 | if (key->isPressed(msg, *keys)) { |
588 | // Cancel menu-bar loops (to close any popup menu) |
589 | App::instance()->mainWindow()->getMenuBar()->cancelMenuLoop(); |
590 | |
591 | switch (key->type()) { |
592 | |
593 | case KeyType::Tool: { |
594 | tools::Tool* current_tool = App::instance()->activeTool(); |
595 | tools::Tool* select_this_tool = key->tool(); |
596 | tools::ToolBox* toolbox = App::instance()->toolBox(); |
597 | std::vector<tools::Tool*> possibles; |
598 | |
599 | // Collect all tools with the pressed keyboard-shortcut |
600 | for (tools::Tool* tool : *toolbox) { |
601 | const KeyPtr key = KeyboardShortcuts::instance()->tool(tool); |
602 | if (key && key->isPressed(msg, *keys)) |
603 | possibles.push_back(tool); |
604 | } |
605 | |
606 | if (possibles.size() >= 2) { |
607 | bool done = false; |
608 | |
609 | for (size_t i=0; i<possibles.size(); ++i) { |
610 | if (possibles[i] != current_tool && |
611 | ToolBar::instance()->isToolVisible(possibles[i])) { |
612 | select_this_tool = possibles[i]; |
613 | done = true; |
614 | break; |
615 | } |
616 | } |
617 | |
618 | if (!done) { |
619 | for (size_t i=0; i<possibles.size(); ++i) { |
620 | // If one of the possibilities is the current tool |
621 | if (possibles[i] == current_tool) { |
622 | // We select the next tool in the possibilities |
623 | select_this_tool = possibles[(i+1) % possibles.size()]; |
624 | break; |
625 | } |
626 | } |
627 | } |
628 | } |
629 | |
630 | ToolBar::instance()->selectTool(select_this_tool); |
631 | return true; |
632 | } |
633 | |
634 | case KeyType::Command: { |
635 | Command* command = key->command(); |
636 | |
637 | // Commands are executed only when the main window is |
638 | // the current window running. |
639 | if (getForegroundWindow() == App::instance()->mainWindow()) { |
640 | // OK, so we can execute the command represented |
641 | // by the pressed-key in the message... |
642 | UIContext::instance()->executeCommandFromMenuOrShortcut( |
643 | command, key->params()); |
644 | return true; |
645 | } |
646 | break; |
647 | } |
648 | |
649 | case KeyType::Quicktool: { |
650 | // Do nothing, it is used in the editor through the |
651 | // KeyboardShortcuts::getCurrentQuicktool() function. |
652 | break; |
653 | } |
654 | |
655 | } |
656 | break; |
657 | } |
658 | } |
659 | break; |
660 | } |
661 | |
662 | case kTimerMessage: |
663 | if (static_cast<TimerMessage*>(msg)->timer() == defered_invalid_timer) { |
664 | invalidateRegion(defered_invalid_region); |
665 | defered_invalid_region.clear(); |
666 | defered_invalid_timer->stop(); |
667 | } |
668 | break; |
669 | |
670 | } |
671 | |
672 | return Manager::onProcessMessage(msg); |
673 | } |
674 | |
675 | #if ENABLE_DEVMODE |
676 | bool CustomizedGuiManager::onProcessDevModeKeyDown(KeyMessage* msg) |
677 | { |
678 | // Ctrl+Shift+Q generates a crash (useful to test the anticrash feature) |
679 | if (msg->ctrlPressed() && |
680 | msg->shiftPressed() && |
681 | msg->scancode() == kKeyQ) { |
682 | int* p = nullptr; |
683 | *p = 0; // *Crash* |
684 | return true; // This line should not be executed anyway |
685 | } |
686 | |
687 | // Ctrl+F1 switches screen/UI scaling |
688 | if (msg->ctrlPressed() && |
689 | msg->scancode() == kKeyF1) { |
690 | try { |
691 | os::Window* window = display()->nativeWindow(); |
692 | int screenScale = window->scale(); |
693 | int uiScale = ui::guiscale(); |
694 | |
695 | if (msg->shiftPressed()) { |
696 | if (screenScale == 2 && uiScale == 1) { |
697 | screenScale = 1; |
698 | uiScale = 1; |
699 | } |
700 | else if (screenScale == 1 && uiScale == 1) { |
701 | screenScale = 1; |
702 | uiScale = 2; |
703 | } |
704 | else if (screenScale == 1 && uiScale == 2) { |
705 | screenScale = 2; |
706 | uiScale = 1; |
707 | } |
708 | } |
709 | else { |
710 | if (screenScale == 2 && uiScale == 1) { |
711 | screenScale = 1; |
712 | uiScale = 2; |
713 | } |
714 | else if (screenScale == 1 && uiScale == 2) { |
715 | screenScale = 1; |
716 | uiScale = 1; |
717 | } |
718 | else if (screenScale == 1 && uiScale == 1) { |
719 | screenScale = 2; |
720 | uiScale = 1; |
721 | } |
722 | } |
723 | |
724 | if (uiScale != ui::guiscale()) { |
725 | ui::set_theme(ui::get_theme(), uiScale); |
726 | } |
727 | if (screenScale != window->scale()) { |
728 | updateAllDisplaysWithNewScale(screenScale); |
729 | } |
730 | } |
731 | catch (const std::exception& ex) { |
732 | Console::showException(ex); |
733 | } |
734 | return true; |
735 | } |
736 | |
737 | #ifdef ENABLE_DATA_RECOVERY |
738 | // Ctrl+Shift+R recover active sprite from the backup store |
739 | if (msg->ctrlPressed() && |
740 | msg->shiftPressed() && |
741 | msg->scancode() == kKeyR && |
742 | App::instance()->dataRecovery() && |
743 | App::instance()->dataRecovery()->activeSession() && |
744 | current_editor && |
745 | current_editor->document()) { |
746 | Doc* doc = App::instance() |
747 | ->dataRecovery() |
748 | ->activeSession() |
749 | ->restoreBackupById(current_editor->document()->id(), nullptr); |
750 | if (doc) |
751 | UIContext::instance()->documents().add(doc); |
752 | return true; |
753 | } |
754 | #endif // ENABLE_DATA_RECOVERY |
755 | |
756 | return false; |
757 | } |
758 | #endif // ENABLE_DEVMODE |
759 | |
760 | void CustomizedGuiManager::onInitTheme(InitThemeEvent& ev) |
761 | { |
762 | Manager::onInitTheme(ev); |
763 | |
764 | // Update the theme on all menus |
765 | AppMenus::instance()->initTheme(); |
766 | } |
767 | |
768 | void CustomizedGuiManager::onNewDisplayConfiguration(Display* display) |
769 | { |
770 | Manager::onNewDisplayConfiguration(display); |
771 | |
772 | // Only whne the main display/window is modified |
773 | if (display == this->display()) { |
774 | save_gui_config(); |
775 | |
776 | // TODO Should we provide a more generic way for all ui::Window to |
777 | // detect the os::Window (or UI Screen Scaling) change? |
778 | Console::notifyNewDisplayConfiguration(); |
779 | } |
780 | } |
781 | |
782 | std::string CustomizedGuiManager::loadLayout(Widget* widget) |
783 | { |
784 | if (widget->window() == nullptr) |
785 | return "" ; |
786 | |
787 | std::string windowId = widget->window()->id(); |
788 | std::string widgetId = widget->id(); |
789 | |
790 | return get_config_string(("layout:" +windowId).c_str(), widgetId.c_str(), "" ); |
791 | } |
792 | |
793 | void CustomizedGuiManager::saveLayout(Widget* widget, const std::string& str) |
794 | { |
795 | if (widget->window() == NULL) |
796 | return; |
797 | |
798 | std::string windowId = widget->window()->id(); |
799 | std::string widgetId = widget->id(); |
800 | |
801 | set_config_string(("layout:" +windowId).c_str(), |
802 | widgetId.c_str(), |
803 | str.c_str()); |
804 | } |
805 | |
806 | } // namespace app |
807 | |