1// Aseprite
2// Copyright (C) 2019-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/ui/home_view.h"
13
14#include "app/app.h"
15#include "app/app_menus.h"
16#include "app/commands/commands.h"
17#include "app/commands/params.h"
18#include "app/crash/data_recovery.h"
19#include "app/i18n/strings.h"
20#include "app/ui/data_recovery_view.h"
21#include "app/ui/main_window.h"
22#include "app/ui/recent_listbox.h"
23#include "app/ui/skin/skin_theme.h"
24#include "app/ui/status_bar.h"
25#include "app/ui/workspace.h"
26#include "app/ui/workspace_tabs.h"
27#include "app/ui_context.h"
28#include "base/exception.h"
29#include "fmt/format.h"
30#include "ui/label.h"
31#include "ui/resize_event.h"
32#include "ui/system.h"
33#include "ui/textbox.h"
34#include "ui/view.h"
35#include "ver/info.h"
36
37#ifdef ENABLE_NEWS
38#include "app/ui/news_listbox.h"
39#endif
40
41#if ENABLE_SENTRY
42#include "app/sentry_wrapper.h"
43#endif
44
45#ifdef ENABLE_DRM
46#include "drm/drm.h"
47#include "aseprite_update.h"
48#endif
49
50namespace app {
51
52using namespace ui;
53using namespace app::skin;
54
55HomeView::HomeView()
56 : m_files(new RecentFilesListBox)
57 , m_folders(new RecentFoldersListBox)
58#ifdef ENABLE_NEWS
59 , m_news(new NewsListBox)
60#endif
61 , m_dataRecovery(App::instance()->dataRecovery())
62 , m_dataRecoveryView(nullptr)
63{
64 newFile()->Click.connect([this]{ onNewFile(); });
65 openFile()->Click.connect([this]{ onOpenFile(); });
66 if (m_dataRecovery)
67 recoverSprites()->Click.connect([this]{ onRecoverSprites(); });
68 else
69 recoverSprites()->setVisible(false);
70
71 filesView()->attachToView(m_files);
72 foldersView()->attachToView(m_folders);
73#ifdef ENABLE_NEWS
74 newsView()->attachToView(m_news);
75#endif
76
77 checkUpdate()->setVisible(false);
78 shareCrashdb()->setVisible(false);
79
80#if ENABLE_SENTRY
81 // Show this option in home tab only when we require consent for the
82 // first time and there is crash data available to report
83 if (Sentry::requireConsent() &&
84 Sentry::areThereCrashesToReport()) {
85 shareCrashdb()->setVisible(true);
86 shareCrashdb()->Click.connect(
87 [this]{
88 if (shareCrashdb()->isSelected())
89 Sentry::giveConsent();
90 else
91 Sentry::revokeConsent();
92 });
93 }
94#endif
95
96 InitTheme.connect(
97 [this]{
98 auto theme = SkinTheme::get(this);
99 setBgColor(theme->colors.workspace());
100 setChildSpacing(8 * guiscale());
101 });
102 initTheme();
103}
104
105HomeView::~HomeView()
106{
107#ifdef ENABLE_DATA_RECOVERY
108 if (m_dataRecoveryView) {
109 ASSERT(!m_dataRecoveryView->parent());
110 delete m_dataRecoveryView;
111 }
112#endif
113}
114
115void HomeView::dataRecoverySessionsAreReady()
116{
117#ifdef ENABLE_DATA_RECOVERY
118
119#ifdef ENABLE_TRIAL_MODE
120 DRM_INVALID{
121 return;
122 }
123#endif
124
125 if (App::instance()->dataRecovery()->hasRecoverySessions()) {
126 // We highlight the "Recover Files" options because we came from a crash
127 auto theme = SkinTheme::get(this);
128 recoverSprites()->setStyle(theme->styles.workspaceUpdateLink());
129 layout();
130 }
131 if (m_dataRecoveryView) {
132 m_dataRecoveryView->refreshListNotification();
133 }
134#endif
135}
136
137#if ENABLE_SENTRY
138void HomeView::updateConsentCheckbox()
139{
140 if (Sentry::requireConsent()) {
141 shareCrashdb()->setVisible(true);
142 shareCrashdb()->setSelected(false);
143 }
144 else if (Sentry::consentGiven()) {
145 shareCrashdb()->setVisible(false);
146 shareCrashdb()->setSelected(true);
147 }
148 layout();
149}
150#endif
151
152std::string HomeView::getTabText()
153{
154 return Strings::home_view_title();
155}
156
157TabIcon HomeView::getTabIcon()
158{
159 return TabIcon::HOME;
160}
161
162gfx::Color HomeView::getTabColor()
163{
164 return gfx::ColorNone;
165}
166
167bool HomeView::onCloseView(Workspace* workspace, bool quitting)
168{
169 workspace->removeView(this);
170 return true;
171}
172
173void HomeView::onAfterRemoveView(Workspace* workspace)
174{
175 if (m_dataRecoveryView &&
176 m_dataRecoveryView->parent()) {
177 workspace->removeView(m_dataRecoveryView);
178 }
179}
180
181void HomeView::onTabPopup(Workspace* workspace)
182{
183 Menu* menu = AppMenus::instance()->getTabPopupMenu();
184 if (!menu)
185 return;
186
187 menu->showPopup(mousePosInDisplay(), display());
188}
189
190void HomeView::onWorkspaceViewSelected()
191{
192 StatusBar::instance()->showDefaultText();
193}
194
195void HomeView::onNewFile()
196{
197 Command* command = Commands::instance()->byId(CommandId::NewFile());
198 UIContext::instance()->executeCommandFromMenuOrShortcut(command);
199}
200
201void HomeView::onOpenFile()
202{
203 Command* command = Commands::instance()->byId(CommandId::OpenFile());
204 UIContext::instance()->executeCommandFromMenuOrShortcut(command);
205}
206
207void HomeView::onResize(ui::ResizeEvent& ev)
208{
209 headerPlaceholder()->setVisible(ev.bounds().h > 200*ui::guiscale());
210 foldersPlaceholder()->setVisible(ev.bounds().h > 150*ui::guiscale());
211#ifdef ENABLE_NEWS
212 newsPlaceholder()->setVisible(ev.bounds().w > 200*ui::guiscale());
213#else
214 newsPlaceholder()->setVisible(false);
215#endif
216
217 ui::VBox::onResize(ev);
218}
219
220#ifdef ENABLE_UPDATER
221
222void HomeView::onCheckingUpdates()
223{
224 checkUpdate()->setText(Strings::home_view_checking_updates());
225 checkUpdate()->setVisible(true);
226
227 layout();
228}
229
230void HomeView::onUpToDate()
231{
232 checkUpdate()->setVisible(false);
233
234 layout();
235}
236
237void HomeView::onNewUpdate(const std::string& url, const std::string& version)
238{
239 checkUpdate()->setText(
240 fmt::format(Strings::home_view_new_version_available(),
241 get_app_name(), version));
242#ifdef ENABLE_DRM
243 DRM_INVALID {
244 checkUpdate()->setUrl(url);
245 }
246 else {
247 checkUpdate()->setUrl("");
248 checkUpdate()->Click.connect([version] {
249 app::AsepriteUpdate dlg(version);
250 dlg.openWindowInForeground();
251 });
252 }
253#else
254 checkUpdate()->setUrl(url);
255#endif
256 checkUpdate()->setVisible(true);
257 checkUpdate()->InitTheme.connect(
258 [this]{
259 auto theme = SkinTheme::get(this);
260 checkUpdate()->setStyle(theme->styles.workspaceUpdateLink());
261 });
262 checkUpdate()->initTheme();
263
264 layout();
265}
266
267#endif // ENABLE_UPDATER
268
269void HomeView::onRecoverSprites()
270{
271#ifdef ENABLE_DATA_RECOVERY
272
273#ifdef ENABLE_TRIAL_MODE
274 DRM_INVALID{
275 return;
276 }
277#endif
278
279 ASSERT(m_dataRecovery); // "Recover Files" button is hidden when
280 // data recovery is disabled (m_dataRecovery == nullptr)
281 if (!m_dataRecovery)
282 return;
283
284 if (!m_dataRecoveryView) {
285 m_dataRecoveryView = new DataRecoveryView(m_dataRecovery);
286
287 // Restore the "Recover Files" link style when the
288 // DataRecoveryView is empty (so there is no more warning icon on
289 // it).
290 m_dataRecoveryView->Empty.connect(
291 [this]{
292 auto theme = SkinTheme::get(this);
293 recoverSprites()->setStyle(theme->styles.workspaceLink());
294 layout();
295 });
296 }
297
298 if (!m_dataRecoveryView->parent())
299 App::instance()->workspace()->addView(m_dataRecoveryView);
300
301 App::instance()->mainWindow()->getTabsBar()->selectTab(m_dataRecoveryView);
302#endif
303}
304
305} // namespace app
306