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/app_menus.h"
13 #include "app/commands/cmd_open_file.h"
14 #include "app/commands/command.h"
15 #include "app/commands/commands.h"
16 #include "app/commands/params.h"
17 #include "app/console.h"
18 #include "app/crash/data_recovery.h"
19 #include "app/doc.h"
20 #include "app/ini_file.h"
21 #include "app/modules/editors.h"
22 #include "app/modules/gfx.h"
23 #include "app/modules/gui.h"
24 #include "app/modules/palettes.h"
25 #include "app/pref/preferences.h"
26 #include "app/tools/ink.h"
27 #include "app/tools/tool_box.h"
28 #include "app/ui/editor/editor.h"
29 #include "app/ui/keyboard_shortcuts.h"
30 #include "app/ui/main_menu_bar.h"
31 #include "app/ui/main_menu_bar.h"
32 #include "app/ui/main_window.h"
33 #include "app/ui/skin/skin_property.h"
34 #include "app/ui/skin/skin_theme.h"
35 #include "app/ui/status_bar.h"
36 #include "app/ui/toolbar.h"
37 #include "app/ui_context.h"
38 #include "base/memory.h"
39 #include "base/shared_ptr.h"
40 #include "base/unique_ptr.h"
41 #include "doc/sprite.h"
42 #include "she/display.h"
43 #include "she/error.h"
44 #include "she/surface.h"
45 #include "she/system.h"
46 #include "ui/intern.h"
47 #include "ui/ui.h"
48
49 #include <algorithm>
50 #include <list>
51 #include <vector>
52
53 #if defined(_DEBUG) && defined(ENABLE_DATA_RECOVERY)
54 #include "app/crash/data_recovery.h"
55 #include "app/modules/editors.h"
56 #endif
57
58 namespace app {
59
60 using namespace gfx;
61 using namespace ui;
62 using namespace app::skin;
63
64 static struct {
65 int width;
66 int height;
67 int scale;
68 } try_resolutions[] = { { 1024, 768, 2 },
69 { 800, 600, 2 },
70 { 640, 480, 2 },
71 { 320, 240, 1 },
72 { 320, 200, 1 },
73 { 0, 0, 0 } };
74
75 //////////////////////////////////////////////////////////////////////
76
77 class CustomizedGuiManager : public Manager
78 , public LayoutIO {
79 protected:
80 bool onProcessMessage(Message* msg) override;
81 #if ENABLE_DEVMODE
82 bool onProcessDevModeKeyDown(KeyMessage* msg);
83 #endif
84 void onInitTheme(InitThemeEvent& ev) override;
onGetLayoutIO()85 LayoutIO* onGetLayoutIO() override { return this; }
86 void onNewDisplayConfiguration() override;
87
88 // LayoutIO implementation
89 std::string loadLayout(Widget* widget) override;
90 void saveLayout(Widget* widget, const std::string& str) override;
91 };
92
93 static she::Display* main_display = NULL;
94 static CustomizedGuiManager* manager = NULL;
95 static Theme* gui_theme = NULL;
96
97 static ui::Timer* defered_invalid_timer = nullptr;
98 static gfx::Region defered_invalid_region;
99
100 // Load & save graphics configuration
101 static void load_gui_config(int& w, int& h, bool& maximized,
102 std::string& windowLayout);
103 static void save_gui_config();
104
create_main_display(bool gpuAccel,bool & maximized,std::string & lastError)105 static bool create_main_display(bool gpuAccel,
106 bool& maximized,
107 std::string& lastError)
108 {
109 int w, h;
110 std::string windowLayout;
111 load_gui_config(w, h, maximized, windowLayout);
112
113 // Scale is equal to 0 when it's the first time the program is
114 // executed.
115 int scale = Preferences::instance().general.screenScale();
116
117 she::instance()->setGpuAcceleration(gpuAccel);
118
119 try {
120 if (w > 0 && h > 0) {
121 main_display = she::instance()->createDisplay(
122 w, h, (scale == 0 ? 2: MID(1, scale, 4)));
123 }
124 }
125 catch (const she::DisplayCreationException& e) {
126 lastError = e.what();
127 }
128
129 if (!main_display) {
130 for (int c=0; try_resolutions[c].width; ++c) {
131 try {
132 main_display =
133 she::instance()->createDisplay(
134 try_resolutions[c].width,
135 try_resolutions[c].height,
136 (scale == 0 ? try_resolutions[c].scale: scale));
137 break;
138 }
139 catch (const she::DisplayCreationException& e) {
140 lastError = e.what();
141 }
142 }
143 }
144
145 if (main_display) {
146 // Change the scale value only in the first run (this will be
147 // saved when the program is closed).
148 if (scale == 0)
149 Preferences::instance().general.screenScale(main_display->scale());
150
151 if (!windowLayout.empty()) {
152 main_display->setLayout(windowLayout);
153 if (main_display->isMinimized())
154 main_display->maximize();
155 }
156 }
157
158 return (main_display != nullptr);
159 }
160
161 // Initializes GUI.
init_module_gui()162 int init_module_gui()
163 {
164 auto& pref = Preferences::instance();
165 bool maximized = false;
166 std::string lastError = "Unknown error";
167 bool gpuAccel = pref.general.gpuAcceleration();
168
169 if (!create_main_display(gpuAccel, maximized, lastError)) {
170 // If we've created the display with hardware acceleration,
171 // now we try to do it without hardware acceleration.
172 if (gpuAccel &&
173 (int(she::instance()->capabilities()) &
174 int(she::Capabilities::GpuAccelerationSwitch)) == int(she::Capabilities::GpuAccelerationSwitch)) {
175 if (create_main_display(false, maximized, lastError)) {
176 // Disable hardware acceleration
177 pref.general.gpuAcceleration(false);
178 }
179 }
180 }
181
182 if (!main_display) {
183 she::error_message(
184 ("Unable to create a user-interface display.\nDetails: "+lastError+"\n").c_str());
185 return -1;
186 }
187
188 // Create the default-manager
189 manager = new CustomizedGuiManager();
190 manager->setDisplay(main_display);
191
192 // Setup the GUI theme for all widgets
193 gui_theme = new SkinTheme;
194 ui::set_theme(gui_theme, pref.general.uiScale());
195
196 if (maximized)
197 main_display->maximize();
198
199 // Set graphics options for next time
200 save_gui_config();
201
202 return 0;
203 }
204
exit_module_gui()205 void exit_module_gui()
206 {
207 save_gui_config();
208
209 delete defered_invalid_timer;
210 delete manager;
211
212 // Now we can destroy theme
213 ui::set_theme(nullptr, ui::guiscale());
214 delete gui_theme;
215
216 main_display->dispose();
217 }
218
load_gui_config(int & w,int & h,bool & maximized,std::string & windowLayout)219 static void load_gui_config(int& w, int& h, bool& maximized,
220 std::string& windowLayout)
221 {
222 gfx::Size defSize = she::instance()->defaultNewDisplaySize();
223
224 w = get_config_int("GfxMode", "Width", defSize.w);
225 h = get_config_int("GfxMode", "Height", defSize.h);
226 maximized = get_config_bool("GfxMode", "Maximized", false);
227 windowLayout = get_config_string("GfxMode", "WindowLayout", "");
228 }
229
save_gui_config()230 static void save_gui_config()
231 {
232 she::Display* display = manager->getDisplay();
233 if (display) {
234 set_config_bool("GfxMode", "Maximized", display->isMaximized());
235 set_config_int("GfxMode", "Width", display->originalWidth());
236 set_config_int("GfxMode", "Height", display->originalHeight());
237
238 std::string windowLayout = display->getLayout();
239 if (!windowLayout.empty())
240 set_config_string("GfxMode", "WindowLayout", windowLayout.c_str());
241 }
242 }
243
update_screen_for_document(const Doc * document)244 void update_screen_for_document(const Doc* document)
245 {
246 // Without document.
247 if (!document) {
248 // Well, change to the default palette.
249 if (set_current_palette(NULL, false)) {
250 // If the palette changes, refresh the whole screen.
251 if (manager)
252 manager->invalidate();
253 }
254 }
255 // With a document.
256 else {
257 const_cast<Doc*>(document)->notifyGeneralUpdate();
258
259 // Update the tabs (maybe the modified status has been changed).
260 app_rebuild_documents_tabs();
261 }
262 }
263
load_window_pos(Widget * window,const char * section)264 void load_window_pos(Widget* window, const char *section)
265 {
266 // Default position
267 Rect orig_pos = window->bounds();
268 Rect pos = orig_pos;
269
270 // Load configurated position
271 pos = get_config_rect(section, "WindowPos", pos);
272
273 pos.w = MID(orig_pos.w, pos.w, ui::display_w());
274 pos.h = MID(orig_pos.h, pos.h, ui::display_h());
275
276 pos.setOrigin(Point(MID(0, pos.x, ui::display_w()-pos.w),
277 MID(0, pos.y, ui::display_h()-pos.h)));
278
279 window->setBounds(pos);
280 }
281
save_window_pos(Widget * window,const char * section)282 void save_window_pos(Widget* window, const char *section)
283 {
284 set_config_rect(section, "WindowPos", window->bounds());
285 }
286
287 // TODO Replace this with new theme styles
setup_mini_font(Widget * widget)288 Widget* setup_mini_font(Widget* widget)
289 {
290 SkinPropertyPtr skinProp = get_skin_property(widget);
291 skinProp->setMiniFont();
292 return widget;
293 }
294
295 // TODO Replace this with new theme styles
setup_mini_look(Widget * widget)296 Widget* setup_mini_look(Widget* widget)
297 {
298 SkinPropertyPtr skinProp = get_skin_property(widget);
299 skinProp->setLook(MiniLook);
300 return widget;
301 }
302
303 //////////////////////////////////////////////////////////////////////
304 // Button style (convert radio or check buttons and draw it like
305 // normal buttons)
306
defer_invalid_rect(const gfx::Rect & rc)307 void defer_invalid_rect(const gfx::Rect& rc)
308 {
309 if (!defered_invalid_timer)
310 defered_invalid_timer = new ui::Timer(250, manager);
311
312 defered_invalid_timer->stop();
313 defered_invalid_timer->start();
314 defered_invalid_region.createUnion(defered_invalid_region, gfx::Region(rc));
315 }
316
317 // Manager event handler.
onProcessMessage(Message * msg)318 bool CustomizedGuiManager::onProcessMessage(Message* msg)
319 {
320 switch (msg->type()) {
321
322 case kCloseDisplayMessage:
323 {
324 // Execute the "Exit" command.
325 Command* command = Commands::instance()->byId(CommandId::Exit());
326 UIContext::instance()->executeCommand(command);
327 }
328 break;
329
330 case kDropFilesMessage:
331 {
332 base::paths files = static_cast<DropFilesMessage*>(msg)->files();
333 UIContext* ctx = UIContext::instance();
334 OpenFileCommand cmd;
335
336 while (!files.empty()) {
337 auto fn = files.front();
338 files.erase(files.begin());
339
340 // If the document is already open, select it.
341 Doc* doc = ctx->documents().getByFileName(fn);
342 if (doc) {
343 DocView* docView = ctx->getFirstDocView(doc);
344 if (docView)
345 ctx->setActiveView(docView);
346 else {
347 ASSERT(false); // Must be some DocView available
348 }
349 }
350 // Load the file
351 else {
352 Params params;
353 params.set("filename", fn.c_str());
354 params.set("repeat_checkbox", "true");
355 ctx->executeCommand(&cmd, params);
356
357 // Remove all used file names from the "dropped files"
358 for (const auto& usedFn : cmd.usedFiles()) {
359 auto it = std::find(files.begin(), files.end(), usedFn);
360 if (it != files.end())
361 files.erase(it);
362 }
363 }
364 }
365 }
366 break;
367
368 case kKeyDownMessage: {
369 #if ENABLE_DEVMODE
370 if (onProcessDevModeKeyDown(static_cast<KeyMessage*>(msg)))
371 return true;
372 #endif // ENABLE_DEVMODE
373
374 // Call base impl to check if there is a foreground window as
375 // top level that needs keys. (In this way we just do not
376 // process keyboard shortcuts for menus and tools).
377 if (Manager::onProcessMessage(msg))
378 return true;
379
380 KeyboardShortcuts* keys = KeyboardShortcuts::instance();
381 for (const KeyPtr& key : *keys) {
382 if (key->isPressed(msg, *keys)) {
383 // Cancel menu-bar loops (to close any popup menu)
384 App::instance()->mainWindow()->getMenuBar()->cancelMenuLoop();
385
386 switch (key->type()) {
387
388 case KeyType::Tool: {
389 tools::Tool* current_tool = App::instance()->activeTool();
390 tools::Tool* select_this_tool = key->tool();
391 tools::ToolBox* toolbox = App::instance()->toolBox();
392 std::vector<tools::Tool*> possibles;
393
394 // Collect all tools with the pressed keyboard-shortcut
395 for (tools::Tool* tool : *toolbox) {
396 const KeyPtr key = KeyboardShortcuts::instance()->tool(tool);
397 if (key && key->isPressed(msg, *keys))
398 possibles.push_back(tool);
399 }
400
401 if (possibles.size() >= 2) {
402 bool done = false;
403
404 for (size_t i=0; i<possibles.size(); ++i) {
405 if (possibles[i] != current_tool &&
406 ToolBar::instance()->isToolVisible(possibles[i])) {
407 select_this_tool = possibles[i];
408 done = true;
409 break;
410 }
411 }
412
413 if (!done) {
414 for (size_t i=0; i<possibles.size(); ++i) {
415 // If one of the possibilities is the current tool
416 if (possibles[i] == current_tool) {
417 // We select the next tool in the possibilities
418 select_this_tool = possibles[(i+1) % possibles.size()];
419 break;
420 }
421 }
422 }
423 }
424
425 ToolBar::instance()->selectTool(select_this_tool);
426 return true;
427 }
428
429 case KeyType::Command: {
430 Command* command = key->command();
431
432 // Commands are executed only when the main window is
433 // the current window running.
434 if (getForegroundWindow() == App::instance()->mainWindow()) {
435 // OK, so we can execute the command represented
436 // by the pressed-key in the message...
437 UIContext::instance()->executeCommand(
438 command, key->params());
439 return true;
440 }
441 break;
442 }
443
444 case KeyType::Quicktool: {
445 // Do nothing, it is used in the editor through the
446 // KeyboardShortcuts::getCurrentQuicktool() function.
447 break;
448 }
449
450 }
451 break;
452 }
453 }
454 break;
455 }
456
457 case kTimerMessage:
458 if (static_cast<TimerMessage*>(msg)->timer() == defered_invalid_timer) {
459 invalidateDisplayRegion(defered_invalid_region);
460 defered_invalid_region.clear();
461 defered_invalid_timer->stop();
462 }
463 break;
464
465 }
466
467 return Manager::onProcessMessage(msg);
468 }
469
470 #if ENABLE_DEVMODE
onProcessDevModeKeyDown(KeyMessage * msg)471 bool CustomizedGuiManager::onProcessDevModeKeyDown(KeyMessage* msg)
472 {
473 // Ctrl+Shift+Q generates a crash (useful to test the anticrash feature)
474 if (msg->ctrlPressed() &&
475 msg->shiftPressed() &&
476 msg->scancode() == kKeyQ) {
477 int* p = nullptr;
478 *p = 0; // *Crash*
479 return true; // This line should not be executed anyway
480 }
481
482 // F1 switches screen/UI scaling
483 if (msg->ctrlPressed() &&
484 msg->scancode() == kKeyF1) {
485 try {
486 she::Display* display = getDisplay();
487 int screenScale = display->scale();
488 int uiScale = ui::guiscale();
489
490 if (msg->shiftPressed()) {
491 if (screenScale == 2 && uiScale == 1) {
492 screenScale = 1;
493 uiScale = 1;
494 }
495 else if (screenScale == 1 && uiScale == 1) {
496 screenScale = 1;
497 uiScale = 2;
498 }
499 else if (screenScale == 1 && uiScale == 2) {
500 screenScale = 2;
501 uiScale = 1;
502 }
503 }
504 else {
505 if (screenScale == 2 && uiScale == 1) {
506 screenScale = 1;
507 uiScale = 2;
508 }
509 else if (screenScale == 1 && uiScale == 2) {
510 screenScale = 1;
511 uiScale = 1;
512 }
513 else if (screenScale == 1 && uiScale == 1) {
514 screenScale = 2;
515 uiScale = 1;
516 }
517 }
518
519 if (uiScale != ui::guiscale()) {
520 ui::set_theme(ui::get_theme(), uiScale);
521 }
522 if (screenScale != display->scale()) {
523 display->setScale(screenScale);
524 setDisplay(display);
525 }
526 }
527 catch (const std::exception& ex) {
528 Console::showException(ex);
529 }
530 return true;
531 }
532
533 #ifdef ENABLE_DATA_RECOVERY
534 // Ctrl+Shift+R recover active sprite from the backup store
535 if (msg->ctrlPressed() &&
536 msg->shiftPressed() &&
537 msg->scancode() == kKeyR &&
538 App::instance()->dataRecovery() &&
539 App::instance()->dataRecovery()->activeSession() &&
540 current_editor &&
541 current_editor->document()) {
542 App::instance()
543 ->dataRecovery()
544 ->activeSession()
545 ->restoreBackupById(current_editor->document()->id());
546 return true;
547 }
548 #endif // ENABLE_DATA_RECOVERY
549
550 return false;
551 }
552 #endif // ENABLE_DEVMODE
553
onInitTheme(InitThemeEvent & ev)554 void CustomizedGuiManager::onInitTheme(InitThemeEvent& ev)
555 {
556 Manager::onInitTheme(ev);
557
558 // Update the theme on all menus
559 AppMenus::instance()->initTheme();
560 }
561
onNewDisplayConfiguration()562 void CustomizedGuiManager::onNewDisplayConfiguration()
563 {
564 Manager::onNewDisplayConfiguration();
565 save_gui_config();
566 }
567
loadLayout(Widget * widget)568 std::string CustomizedGuiManager::loadLayout(Widget* widget)
569 {
570 if (widget->window() == nullptr)
571 return "";
572
573 std::string windowId = widget->window()->id();
574 std::string widgetId = widget->id();
575
576 return get_config_string(("layout:"+windowId).c_str(), widgetId.c_str(), "");
577 }
578
saveLayout(Widget * widget,const std::string & str)579 void CustomizedGuiManager::saveLayout(Widget* widget, const std::string& str)
580 {
581 if (widget->window() == NULL)
582 return;
583
584 std::string windowId = widget->window()->id();
585 std::string widgetId = widget->id();
586
587 set_config_string(("layout:"+windowId).c_str(),
588 widgetId.c_str(),
589 str.c_str());
590 }
591
592 } // namespace app
593