1 /*
2 * Copyright 2014, Stephan Aßmus <superstippi@gmx.de>.
3 * Copyright 2017, Julian Harnath <julian.harnath@rwth-aachen.de>.
4 * Copyright 2020-2024, Andrew Lindesay <apl@lindesay.co.nz>.
5 * All rights reserved. Distributed under the terms of the MIT License.
6 */
7
8 #include "ScreenshotWindow.h"
9
10 #include <algorithm>
11
12 #include <Autolock.h>
13 #include <Catalog.h>
14 #include <LayoutBuilder.h>
15 #include <MessageRunner.h>
16 #include <StringView.h>
17
18 #include "BarberPole.h"
19 #include "BitmapView.h"
20 #include "HaikuDepotConstants.h"
21 #include "Logger.h"
22 #include "Model.h"
23 #include "PackageUtils.h"
24 #include "SharedIcons.h"
25 #include "WebAppInterface.h"
26
27
28 #undef B_TRANSLATION_CONTEXT
29 #define B_TRANSLATION_CONTEXT "ScreenshotWindow"
30
31
32 static const rgb_color kBackgroundColor = { 51, 102, 152, 255 };
33 // Drawn as a border around the screenshots and also what's behind their
34 // transparent regions
35
36
ScreenshotWindow(BWindow * parent,BRect frame,Model * model)37 ScreenshotWindow::ScreenshotWindow(BWindow* parent, BRect frame, Model* model)
38 :
39 BWindow(frame, B_TRANSLATE("Screenshot"),
40 B_FLOATING_WINDOW_LOOK, B_FLOATING_SUBSET_WINDOW_FEEL,
41 B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
42 fBarberPoleShown(false),
43 fDownloadPending(false),
44 fWorkerThread(-1),
45 fModel(model)
46 {
47 AddToSubset(parent);
48
49 atomic_set(&fCurrentScreenshotIndex, 0);
50
51 fBarberPole = new BarberPole("barber pole");
52 fBarberPole->SetExplicitMaxSize(BSize(100, B_SIZE_UNLIMITED));
53 fBarberPole->Hide();
54
55 fIndexView = new BStringView("screenshot index", NULL);
56
57 fToolBar = new BToolBar();
58 fToolBar->AddAction(MSG_PREVIOUS_SCREENSHOT, this,
59 SharedIcons::IconArrowLeft22Scaled()->Bitmap(), NULL, NULL);
60 fToolBar->AddAction(MSG_NEXT_SCREENSHOT, this, SharedIcons::IconArrowRight22Scaled()->Bitmap(),
61 NULL, NULL);
62 fToolBar->AddView(fIndexView);
63 fToolBar->AddGlue();
64 fToolBar->AddView(fBarberPole);
65
66 fScreenshotView = new BitmapView("screenshot view");
67 fScreenshotView->SetExplicitMaxSize(
68 BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
69 fScreenshotView->SetScaleBitmap(false);
70
71 BGroupView* groupView = new BGroupView(B_VERTICAL);
72 groupView->SetViewColor(kBackgroundColor);
73
74 // Build layout
75 BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
76 .SetInsets(0, 3, 0, 0)
77 .Add(fToolBar)
78 .AddStrut(3)
79 .AddGroup(groupView)
80 .Add(fScreenshotView)
81 .SetInsets(B_USE_WINDOW_INSETS)
82 .End()
83 ;
84
85 fScreenshotView->SetLowColor(kBackgroundColor);
86 // Set after attaching all views to prevent it from being overridden
87 // again by BitmapView::AllAttached()
88
89 CenterOnScreen();
90 }
91
92
~ScreenshotWindow()93 ScreenshotWindow::~ScreenshotWindow()
94 {
95 BAutolock locker(&fLock);
96
97 if (fWorkerThread >= 0)
98 wait_for_thread(fWorkerThread, NULL);
99 }
100
101
102 bool
QuitRequested()103 ScreenshotWindow::QuitRequested()
104 {
105 if (fOnCloseTarget.IsValid() && fOnCloseMessage.what != 0)
106 fOnCloseTarget.SendMessage(&fOnCloseMessage);
107
108 Hide();
109 return false;
110 }
111
112
113 void
MessageReceived(BMessage * message)114 ScreenshotWindow::MessageReceived(BMessage* message)
115 {
116 switch (message->what) {
117 case MSG_NEXT_SCREENSHOT:
118 {
119 atomic_add(&fCurrentScreenshotIndex, 1);
120 _UpdateToolBar();
121 _DownloadScreenshot();
122 break;
123 }
124
125 case MSG_PREVIOUS_SCREENSHOT:
126 atomic_add(&fCurrentScreenshotIndex, -1);
127 _UpdateToolBar();
128 _DownloadScreenshot();
129 break;
130
131 case MSG_DOWNLOAD_START:
132 if (!fBarberPoleShown) {
133 fBarberPole->Start();
134 fBarberPole->Show();
135 fBarberPoleShown = true;
136 }
137 break;
138
139 case MSG_DOWNLOAD_STOP:
140 if (fBarberPoleShown) {
141 fBarberPole->Hide();
142 fBarberPole->Stop();
143 fBarberPoleShown = true;
144 }
145 break;
146
147 default:
148 BWindow::MessageReceived(message);
149 break;
150 }
151 }
152
153
154 void
SetOnCloseMessage(const BMessenger & messenger,const BMessage & message)155 ScreenshotWindow::SetOnCloseMessage(
156 const BMessenger& messenger, const BMessage& message)
157 {
158 fOnCloseTarget = messenger;
159 fOnCloseMessage = message;
160 }
161
162
163 void
SetPackage(const PackageInfoRef & package)164 ScreenshotWindow::SetPackage(const PackageInfoRef& package)
165 {
166 if (!package.IsSet())
167 HDFATAL("attempt to provide an unset package");
168
169 if (fPackage == package)
170 return;
171
172 fPackage = package;
173 BString title = B_TRANSLATE("Screenshot");
174 PackageUtils::TitleOrName(fPackage, title);
175 SetTitle(title);
176
177 if (package.IsSet())
178 _DownloadScreenshot();
179
180 atomic_set(&fCurrentScreenshotIndex, 0);
181
182 _UpdateToolBar();
183 }
184
185
186 // #pragma mark - private
187
188
189 void
_DownloadScreenshot()190 ScreenshotWindow::_DownloadScreenshot()
191 {
192 BAutolock locker(&fLock);
193
194 if (fWorkerThread >= 0) {
195 fDownloadPending = true;
196 return;
197 }
198
199 thread_id thread = spawn_thread(&_DownloadThreadEntry,
200 "Screenshot Loader", B_NORMAL_PRIORITY, this);
201 if (thread >= 0)
202 _SetWorkerThread(thread);
203 }
204
205
206 void
_SetWorkerThread(thread_id thread)207 ScreenshotWindow::_SetWorkerThread(thread_id thread)
208 {
209 if (!Lock())
210 return;
211
212 // bool enabled = thread < 0;
213 //
214 // fPreviewsButton->SetEnabled(enabled);
215 // fNextButton->SetEnabled(enabled);
216 // fCloseButton->SetEnabled(enabled);
217
218 if (thread >= 0) {
219 fWorkerThread = thread;
220 resume_thread(fWorkerThread);
221 } else {
222 fWorkerThread = -1;
223
224 if (fDownloadPending) {
225 _DownloadScreenshot();
226 fDownloadPending = false;
227 }
228 }
229
230 Unlock();
231 }
232
233
234 int32
_DownloadThreadEntry(void * data)235 ScreenshotWindow::_DownloadThreadEntry(void* data)
236 {
237 ScreenshotWindow* window
238 = reinterpret_cast<ScreenshotWindow*>(data);
239 window->_DownloadThread();
240 window->_SetWorkerThread(-1);
241 return 0;
242 }
243
244
245 void
_DownloadThread()246 ScreenshotWindow::_DownloadThread()
247 {
248 ScreenshotInfoRef info;
249
250 if (!Lock()) {
251 HDERROR("failed to lock screenshot window");
252 return;
253 }
254
255 fScreenshotView->UnsetBitmap();
256 _ResizeToFitAndCenter();
257
258 if (!fPackage.IsSet())
259 HDINFO("package not set");
260 else {
261 PackageScreenshotInfoRef screenshotInfo = fPackage->ScreenshotInfo();
262
263 if (!screenshotInfo.IsSet() || screenshotInfo->Count() == 0) {
264 HDINFO("package has no screenshots");
265 } else {
266 int32 index = atomic_get(&fCurrentScreenshotIndex);
267 info = screenshotInfo->ScreenshotAtIndex(index);
268 }
269 }
270
271 Unlock();
272
273 if (!info.IsSet()) {
274 HDINFO("screenshot not set");
275 return;
276 }
277
278 // Only indicate being busy with the download if it takes a little while
279 BMessenger messenger(this);
280 BMessageRunner delayedMessenger(messenger,
281 new BMessage(MSG_DOWNLOAD_START),
282 kProgressIndicatorDelay, 1);
283
284 BitmapHolderRef screenshot;
285
286 // Retrieve screenshot from web-app
287 status_t status = fModel->GetPackageScreenshotRepository()->LoadScreenshot(
288 ScreenshotCoordinate(info->Code(), info->Width(), info->Height()), screenshot);
289
290 delayedMessenger.SetCount(0);
291 messenger.SendMessage(MSG_DOWNLOAD_STOP);
292
293 if (status == B_OK && Lock()) {
294 HDINFO("got screenshot");
295 fScreenshot = screenshot;
296 fScreenshotView->SetBitmap(fScreenshot);
297 _ResizeToFitAndCenter();
298 Unlock();
299 } else
300 HDERROR("failed to download screenshot");
301 }
302
303
304 BSize
_MaxWidthAndHeightOfAllScreenshots()305 ScreenshotWindow::_MaxWidthAndHeightOfAllScreenshots()
306 {
307 BSize size(0, 0);
308
309 // Find out dimensions of the largest screenshot of this package
310 if (fPackage.IsSet()) {
311 PackageScreenshotInfoRef screenshotInfo = fPackage->ScreenshotInfo();
312 int count = 0;
313
314 if (screenshotInfo.IsSet())
315 count = screenshotInfo->Count();
316
317 for(int32 i = 0; i < count; i++) {
318 const ScreenshotInfoRef& screenshot = screenshotInfo->ScreenshotAtIndex(i);
319
320 if (screenshot.IsSet()) {
321 float w = static_cast<float>(screenshot->Width());
322 float h = static_cast<float>(screenshot->Height());
323 if (w > size.Width())
324 size.SetWidth(w);
325 if (h > size.Height())
326 size.SetHeight(h);
327 }
328 }
329 }
330
331 return size;
332 }
333
334
335 void
_ResizeToFitAndCenter()336 ScreenshotWindow::_ResizeToFitAndCenter()
337 {
338 fScreenshotView->SetExplicitMinSize(_MaxWidthAndHeightOfAllScreenshots());
339 Layout(false);
340
341 // TODO: Limit window size to screen size (with a little margin),
342 // the image should then become scrollable.
343
344 float minWidth;
345 float minHeight;
346 GetSizeLimits(&minWidth, NULL, &minHeight, NULL);
347 ResizeTo(minWidth, minHeight);
348 CenterOnScreen();
349 }
350
351
352 void
_UpdateToolBar()353 ScreenshotWindow::_UpdateToolBar()
354 {
355 int32 numScreenshots = 0;
356
357 if (fPackage.IsSet()) {
358 PackageScreenshotInfoRef screenshotInfo = fPackage->ScreenshotInfo();
359 if (screenshotInfo.IsSet())
360 numScreenshots = screenshotInfo->Count();
361 }
362
363 const int32 currentIndex = atomic_get(&fCurrentScreenshotIndex);
364
365 fToolBar->SetActionEnabled(MSG_PREVIOUS_SCREENSHOT,
366 currentIndex > 0);
367 fToolBar->SetActionEnabled(MSG_NEXT_SCREENSHOT,
368 currentIndex < numScreenshots - 1);
369
370 BString text;
371 text << currentIndex + 1;
372 text << " / ";
373 text << numScreenshots;
374 fIndexView->SetText(text);
375 }
376