1d5ef985eSStephan Aßmus /*
2d5ef985eSStephan Aßmus * Copyright 2014, Stephan Aßmus <superstippi@gmx.de>.
339f99591SJulian Harnath * Copyright 2017, Julian Harnath <julian.harnath@rwth-aachen.de>.
488af15cfSAndrew Lindesay * Copyright 2020-2024, Andrew Lindesay <apl@lindesay.co.nz>.
5d5ef985eSStephan Aßmus * All rights reserved. Distributed under the terms of the MIT License.
6d5ef985eSStephan Aßmus */
7d5ef985eSStephan Aßmus
8d5ef985eSStephan Aßmus #include "ScreenshotWindow.h"
9d5ef985eSStephan Aßmus
10d5ef985eSStephan Aßmus #include <algorithm>
11d5ef985eSStephan Aßmus
12d5ef985eSStephan Aßmus #include <Autolock.h>
13d5ef985eSStephan Aßmus #include <Catalog.h>
14d5ef985eSStephan Aßmus #include <LayoutBuilder.h>
153ca9d5e9SJulian Harnath #include <MessageRunner.h>
16c210060fSJulian Harnath #include <StringView.h>
17d5ef985eSStephan Aßmus
18c210060fSJulian Harnath #include "BarberPole.h"
19d5ef985eSStephan Aßmus #include "BitmapView.h"
20a9edb9bfSAndrew Lindesay #include "HaikuDepotConstants.h"
21f96d1f4dSAndrew Lindesay #include "Logger.h"
2288af15cfSAndrew Lindesay #include "Model.h"
23c65ff9f1SAndrew Lindesay #include "PackageUtils.h"
2466ee6532SAndrew Lindesay #include "SharedIcons.h"
25d5ef985eSStephan Aßmus #include "WebAppInterface.h"
26d5ef985eSStephan Aßmus
27d5ef985eSStephan Aßmus
28d5ef985eSStephan Aßmus #undef B_TRANSLATION_CONTEXT
29d5ef985eSStephan Aßmus #define B_TRANSLATION_CONTEXT "ScreenshotWindow"
30d5ef985eSStephan Aßmus
31d5ef985eSStephan Aßmus
3239f99591SJulian Harnath static const rgb_color kBackgroundColor = { 51, 102, 152, 255 };
3339f99591SJulian Harnath // Drawn as a border around the screenshots and also what's behind their
3439f99591SJulian Harnath // transparent regions
3539f99591SJulian Harnath
3639f99591SJulian Harnath
ScreenshotWindow(BWindow * parent,BRect frame,Model * model)3788af15cfSAndrew Lindesay ScreenshotWindow::ScreenshotWindow(BWindow* parent, BRect frame, Model* model)
38d5ef985eSStephan Aßmus :
39d5ef985eSStephan Aßmus BWindow(frame, B_TRANSLATE("Screenshot"),
40d5ef985eSStephan Aßmus B_FLOATING_WINDOW_LOOK, B_FLOATING_SUBSET_WINDOW_FEEL,
41d5ef985eSStephan Aßmus B_ASYNCHRONOUS_CONTROLS | B_AUTO_UPDATE_SIZE_LIMITS),
423ca9d5e9SJulian Harnath fBarberPoleShown(false),
43d5ef985eSStephan Aßmus fDownloadPending(false),
4488af15cfSAndrew Lindesay fWorkerThread(-1),
4588af15cfSAndrew Lindesay fModel(model)
46d5ef985eSStephan Aßmus {
47d5ef985eSStephan Aßmus AddToSubset(parent);
48d5ef985eSStephan Aßmus
49c210060fSJulian Harnath atomic_set(&fCurrentScreenshotIndex, 0);
50c210060fSJulian Harnath
51c210060fSJulian Harnath fBarberPole = new BarberPole("barber pole");
523ca9d5e9SJulian Harnath fBarberPole->SetExplicitMaxSize(BSize(100, B_SIZE_UNLIMITED));
533ca9d5e9SJulian Harnath fBarberPole->Hide();
54c210060fSJulian Harnath
55c210060fSJulian Harnath fIndexView = new BStringView("screenshot index", NULL);
56c210060fSJulian Harnath
57c210060fSJulian Harnath fToolBar = new BToolBar();
58622e144fSJulian Harnath fToolBar->AddAction(MSG_PREVIOUS_SCREENSHOT, this,
5966ee6532SAndrew Lindesay SharedIcons::IconArrowLeft22Scaled()->Bitmap(), NULL, NULL);
6066ee6532SAndrew Lindesay fToolBar->AddAction(MSG_NEXT_SCREENSHOT, this, SharedIcons::IconArrowRight22Scaled()->Bitmap(),
61622e144fSJulian Harnath NULL, NULL);
62c210060fSJulian Harnath fToolBar->AddView(fIndexView);
63c210060fSJulian Harnath fToolBar->AddGlue();
64c210060fSJulian Harnath fToolBar->AddView(fBarberPole);
65c210060fSJulian Harnath
66d5ef985eSStephan Aßmus fScreenshotView = new BitmapView("screenshot view");
67d5ef985eSStephan Aßmus fScreenshotView->SetExplicitMaxSize(
68d5ef985eSStephan Aßmus BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
69416c1160SJulian Harnath fScreenshotView->SetScaleBitmap(false);
70d5ef985eSStephan Aßmus
712afa3f3bSStephan Aßmus BGroupView* groupView = new BGroupView(B_VERTICAL);
7239f99591SJulian Harnath groupView->SetViewColor(kBackgroundColor);
732afa3f3bSStephan Aßmus
74d5ef985eSStephan Aßmus // Build layout
75c210060fSJulian Harnath BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
76622e144fSJulian Harnath .SetInsets(0, 3, 0, 0)
77c210060fSJulian Harnath .Add(fToolBar)
78622e144fSJulian Harnath .AddStrut(3)
792afa3f3bSStephan Aßmus .AddGroup(groupView)
80d5ef985eSStephan Aßmus .Add(fScreenshotView)
81d5ef985eSStephan Aßmus .SetInsets(B_USE_WINDOW_INSETS)
822afa3f3bSStephan Aßmus .End()
83d5ef985eSStephan Aßmus ;
84d5ef985eSStephan Aßmus
8539f99591SJulian Harnath fScreenshotView->SetLowColor(kBackgroundColor);
8666ee6532SAndrew Lindesay // Set after attaching all views to prevent it from being overridden
8739f99591SJulian Harnath // again by BitmapView::AllAttached()
8839f99591SJulian Harnath
892afa3f3bSStephan Aßmus CenterOnScreen();
90d5ef985eSStephan Aßmus }
91d5ef985eSStephan Aßmus
92d5ef985eSStephan Aßmus
~ScreenshotWindow()93d5ef985eSStephan Aßmus ScreenshotWindow::~ScreenshotWindow()
94d5ef985eSStephan Aßmus {
95d5ef985eSStephan Aßmus BAutolock locker(&fLock);
96d5ef985eSStephan Aßmus
97d5ef985eSStephan Aßmus if (fWorkerThread >= 0)
98d5ef985eSStephan Aßmus wait_for_thread(fWorkerThread, NULL);
99d5ef985eSStephan Aßmus }
100d5ef985eSStephan Aßmus
101d5ef985eSStephan Aßmus
102d5ef985eSStephan Aßmus bool
QuitRequested()103d5ef985eSStephan Aßmus ScreenshotWindow::QuitRequested()
104d5ef985eSStephan Aßmus {
105d5ef985eSStephan Aßmus if (fOnCloseTarget.IsValid() && fOnCloseMessage.what != 0)
106d5ef985eSStephan Aßmus fOnCloseTarget.SendMessage(&fOnCloseMessage);
107d5ef985eSStephan Aßmus
108d5ef985eSStephan Aßmus Hide();
109d5ef985eSStephan Aßmus return false;
110d5ef985eSStephan Aßmus }
111d5ef985eSStephan Aßmus
112d5ef985eSStephan Aßmus
113d5ef985eSStephan Aßmus void
MessageReceived(BMessage * message)114d5ef985eSStephan Aßmus ScreenshotWindow::MessageReceived(BMessage* message)
115d5ef985eSStephan Aßmus {
116d5ef985eSStephan Aßmus switch (message->what) {
117c210060fSJulian Harnath case MSG_NEXT_SCREENSHOT:
118c210060fSJulian Harnath {
119c210060fSJulian Harnath atomic_add(&fCurrentScreenshotIndex, 1);
120c210060fSJulian Harnath _UpdateToolBar();
121c210060fSJulian Harnath _DownloadScreenshot();
122c210060fSJulian Harnath break;
123c210060fSJulian Harnath }
124c210060fSJulian Harnath
125c210060fSJulian Harnath case MSG_PREVIOUS_SCREENSHOT:
126c210060fSJulian Harnath atomic_add(&fCurrentScreenshotIndex, -1);
127c210060fSJulian Harnath _UpdateToolBar();
128c210060fSJulian Harnath _DownloadScreenshot();
129c210060fSJulian Harnath break;
130c210060fSJulian Harnath
131c210060fSJulian Harnath case MSG_DOWNLOAD_START:
1323ca9d5e9SJulian Harnath if (!fBarberPoleShown) {
133c210060fSJulian Harnath fBarberPole->Start();
1343ca9d5e9SJulian Harnath fBarberPole->Show();
1353ca9d5e9SJulian Harnath fBarberPoleShown = true;
1363ca9d5e9SJulian Harnath }
137c210060fSJulian Harnath break;
138c210060fSJulian Harnath
139c210060fSJulian Harnath case MSG_DOWNLOAD_STOP:
1403ca9d5e9SJulian Harnath if (fBarberPoleShown) {
1413ca9d5e9SJulian Harnath fBarberPole->Hide();
142c210060fSJulian Harnath fBarberPole->Stop();
1433ca9d5e9SJulian Harnath fBarberPoleShown = true;
1443ca9d5e9SJulian Harnath }
145c210060fSJulian Harnath break;
146c210060fSJulian Harnath
147d5ef985eSStephan Aßmus default:
148d5ef985eSStephan Aßmus BWindow::MessageReceived(message);
149d5ef985eSStephan Aßmus break;
150d5ef985eSStephan Aßmus }
151d5ef985eSStephan Aßmus }
152d5ef985eSStephan Aßmus
153d5ef985eSStephan Aßmus
154d5ef985eSStephan Aßmus void
SetOnCloseMessage(const BMessenger & messenger,const BMessage & message)155d5ef985eSStephan Aßmus ScreenshotWindow::SetOnCloseMessage(
156d5ef985eSStephan Aßmus const BMessenger& messenger, const BMessage& message)
157d5ef985eSStephan Aßmus {
158d5ef985eSStephan Aßmus fOnCloseTarget = messenger;
159d5ef985eSStephan Aßmus fOnCloseMessage = message;
160d5ef985eSStephan Aßmus }
161d5ef985eSStephan Aßmus
162d5ef985eSStephan Aßmus
163d5ef985eSStephan Aßmus void
SetPackage(const PackageInfoRef & package)164d5ef985eSStephan Aßmus ScreenshotWindow::SetPackage(const PackageInfoRef& package)
165d5ef985eSStephan Aßmus {
166c65ff9f1SAndrew Lindesay if (!package.IsSet())
167c65ff9f1SAndrew Lindesay HDFATAL("attempt to provide an unset package");
168c65ff9f1SAndrew Lindesay
169d5ef985eSStephan Aßmus if (fPackage == package)
170d5ef985eSStephan Aßmus return;
171d5ef985eSStephan Aßmus
172d5ef985eSStephan Aßmus fPackage = package;
173d5ef985eSStephan Aßmus BString title = B_TRANSLATE("Screenshot");
174c65ff9f1SAndrew Lindesay PackageUtils::TitleOrName(fPackage, title);
175d5ef985eSStephan Aßmus SetTitle(title);
176c210060fSJulian Harnath
177c65ff9f1SAndrew Lindesay if (package.IsSet())
178c65ff9f1SAndrew Lindesay _DownloadScreenshot();
179c65ff9f1SAndrew Lindesay
180c210060fSJulian Harnath atomic_set(&fCurrentScreenshotIndex, 0);
181c210060fSJulian Harnath
182c210060fSJulian Harnath _UpdateToolBar();
183d5ef985eSStephan Aßmus }
184d5ef985eSStephan Aßmus
185d5ef985eSStephan Aßmus
186d5ef985eSStephan Aßmus // #pragma mark - private
187d5ef985eSStephan Aßmus
188d5ef985eSStephan Aßmus
189d5ef985eSStephan Aßmus void
_DownloadScreenshot()190d5ef985eSStephan Aßmus ScreenshotWindow::_DownloadScreenshot()
191d5ef985eSStephan Aßmus {
192d5ef985eSStephan Aßmus BAutolock locker(&fLock);
193d5ef985eSStephan Aßmus
194d5ef985eSStephan Aßmus if (fWorkerThread >= 0) {
195d5ef985eSStephan Aßmus fDownloadPending = true;
196d5ef985eSStephan Aßmus return;
197d5ef985eSStephan Aßmus }
198d5ef985eSStephan Aßmus
199d5ef985eSStephan Aßmus thread_id thread = spawn_thread(&_DownloadThreadEntry,
200d5ef985eSStephan Aßmus "Screenshot Loader", B_NORMAL_PRIORITY, this);
201d5ef985eSStephan Aßmus if (thread >= 0)
202d5ef985eSStephan Aßmus _SetWorkerThread(thread);
203d5ef985eSStephan Aßmus }
204d5ef985eSStephan Aßmus
205d5ef985eSStephan Aßmus
206d5ef985eSStephan Aßmus void
_SetWorkerThread(thread_id thread)207d5ef985eSStephan Aßmus ScreenshotWindow::_SetWorkerThread(thread_id thread)
208d5ef985eSStephan Aßmus {
209d5ef985eSStephan Aßmus if (!Lock())
210d5ef985eSStephan Aßmus return;
211d5ef985eSStephan Aßmus
212d5ef985eSStephan Aßmus // bool enabled = thread < 0;
213d5ef985eSStephan Aßmus //
214d5ef985eSStephan Aßmus // fPreviewsButton->SetEnabled(enabled);
215d5ef985eSStephan Aßmus // fNextButton->SetEnabled(enabled);
216d5ef985eSStephan Aßmus // fCloseButton->SetEnabled(enabled);
217d5ef985eSStephan Aßmus
218d5ef985eSStephan Aßmus if (thread >= 0) {
219d5ef985eSStephan Aßmus fWorkerThread = thread;
220d5ef985eSStephan Aßmus resume_thread(fWorkerThread);
221d5ef985eSStephan Aßmus } else {
222d5ef985eSStephan Aßmus fWorkerThread = -1;
223d5ef985eSStephan Aßmus
224d5ef985eSStephan Aßmus if (fDownloadPending) {
225d5ef985eSStephan Aßmus _DownloadScreenshot();
226d5ef985eSStephan Aßmus fDownloadPending = false;
227d5ef985eSStephan Aßmus }
228d5ef985eSStephan Aßmus }
229d5ef985eSStephan Aßmus
230d5ef985eSStephan Aßmus Unlock();
231d5ef985eSStephan Aßmus }
232d5ef985eSStephan Aßmus
233d5ef985eSStephan Aßmus
234d5ef985eSStephan Aßmus int32
_DownloadThreadEntry(void * data)235d5ef985eSStephan Aßmus ScreenshotWindow::_DownloadThreadEntry(void* data)
236d5ef985eSStephan Aßmus {
237d5ef985eSStephan Aßmus ScreenshotWindow* window
238d5ef985eSStephan Aßmus = reinterpret_cast<ScreenshotWindow*>(data);
239d5ef985eSStephan Aßmus window->_DownloadThread();
240d5ef985eSStephan Aßmus window->_SetWorkerThread(-1);
241d5ef985eSStephan Aßmus return 0;
242d5ef985eSStephan Aßmus }
243d5ef985eSStephan Aßmus
244d5ef985eSStephan Aßmus
245d5ef985eSStephan Aßmus void
_DownloadThread()246d5ef985eSStephan Aßmus ScreenshotWindow::_DownloadThread()
247d5ef985eSStephan Aßmus {
2489984ca59SAndrew Lindesay ScreenshotInfoRef info;
2499984ca59SAndrew Lindesay
250d5ef985eSStephan Aßmus if (!Lock()) {
251fa5c8097SAndrew Lindesay HDERROR("failed to lock screenshot window");
252d5ef985eSStephan Aßmus return;
253d5ef985eSStephan Aßmus }
254d5ef985eSStephan Aßmus
2552a36368bSMichael Lotz fScreenshotView->UnsetBitmap();
2561a5b7d9dSJessica Tallon _ResizeToFitAndCenter();
257d5ef985eSStephan Aßmus
258779ab335SX512 if (!fPackage.IsSet())
2599984ca59SAndrew Lindesay HDINFO("package not set");
2609984ca59SAndrew Lindesay else {
261*97a8cc6cSAndrew Lindesay PackageScreenshotInfoRef screenshotInfo = fPackage->ScreenshotInfo();
262*97a8cc6cSAndrew Lindesay
263*97a8cc6cSAndrew Lindesay if (!screenshotInfo.IsSet() || screenshotInfo->Count() == 0) {
2649984ca59SAndrew Lindesay HDINFO("package has no screenshots");
265*97a8cc6cSAndrew Lindesay } else {
2669984ca59SAndrew Lindesay int32 index = atomic_get(&fCurrentScreenshotIndex);
267*97a8cc6cSAndrew Lindesay info = screenshotInfo->ScreenshotAtIndex(index);
2689984ca59SAndrew Lindesay }
2699984ca59SAndrew Lindesay }
270d5ef985eSStephan Aßmus
271d5ef985eSStephan Aßmus Unlock();
272d5ef985eSStephan Aßmus
273779ab335SX512 if (!info.IsSet()) {
2749984ca59SAndrew Lindesay HDINFO("screenshot not set");
275d5ef985eSStephan Aßmus return;
276d5ef985eSStephan Aßmus }
277d5ef985eSStephan Aßmus
2783ca9d5e9SJulian Harnath // Only indicate being busy with the download if it takes a little while
279c210060fSJulian Harnath BMessenger messenger(this);
2803ca9d5e9SJulian Harnath BMessageRunner delayedMessenger(messenger,
2813ca9d5e9SJulian Harnath new BMessage(MSG_DOWNLOAD_START),
2823ca9d5e9SJulian Harnath kProgressIndicatorDelay, 1);
283c210060fSJulian Harnath
28466ee6532SAndrew Lindesay BitmapHolderRef screenshot;
28588af15cfSAndrew Lindesay
286d5ef985eSStephan Aßmus // Retrieve screenshot from web-app
28788af15cfSAndrew Lindesay status_t status = fModel->GetPackageScreenshotRepository()->LoadScreenshot(
28866ee6532SAndrew Lindesay ScreenshotCoordinate(info->Code(), info->Width(), info->Height()), screenshot);
289c210060fSJulian Harnath
2903ca9d5e9SJulian Harnath delayedMessenger.SetCount(0);
291c210060fSJulian Harnath messenger.SendMessage(MSG_DOWNLOAD_STOP);
292c210060fSJulian Harnath
293d5ef985eSStephan Aßmus if (status == B_OK && Lock()) {
294fa5c8097SAndrew Lindesay HDINFO("got screenshot");
29588af15cfSAndrew Lindesay fScreenshot = screenshot;
2962a36368bSMichael Lotz fScreenshotView->SetBitmap(fScreenshot);
2972afa3f3bSStephan Aßmus _ResizeToFitAndCenter();
298d5ef985eSStephan Aßmus Unlock();
299fa5c8097SAndrew Lindesay } else
300fa5c8097SAndrew Lindesay HDERROR("failed to download screenshot");
301d5ef985eSStephan Aßmus }
3022afa3f3bSStephan Aßmus
3032afa3f3bSStephan Aßmus
3049984ca59SAndrew Lindesay BSize
_MaxWidthAndHeightOfAllScreenshots()3059984ca59SAndrew Lindesay ScreenshotWindow::_MaxWidthAndHeightOfAllScreenshots()
3069984ca59SAndrew Lindesay {
3079984ca59SAndrew Lindesay BSize size(0, 0);
3089984ca59SAndrew Lindesay
3099984ca59SAndrew Lindesay // Find out dimensions of the largest screenshot of this package
310779ab335SX512 if (fPackage.IsSet()) {
311*97a8cc6cSAndrew Lindesay PackageScreenshotInfoRef screenshotInfo = fPackage->ScreenshotInfo();
312*97a8cc6cSAndrew Lindesay int count = 0;
313*97a8cc6cSAndrew Lindesay
314*97a8cc6cSAndrew Lindesay if (screenshotInfo.IsSet())
315*97a8cc6cSAndrew Lindesay count = screenshotInfo->Count();
316*97a8cc6cSAndrew Lindesay
3179984ca59SAndrew Lindesay for(int32 i = 0; i < count; i++) {
318*97a8cc6cSAndrew Lindesay const ScreenshotInfoRef& screenshot = screenshotInfo->ScreenshotAtIndex(i);
319*97a8cc6cSAndrew Lindesay
320*97a8cc6cSAndrew Lindesay if (screenshot.IsSet()) {
321*97a8cc6cSAndrew Lindesay float w = static_cast<float>(screenshot->Width());
322*97a8cc6cSAndrew Lindesay float h = static_cast<float>(screenshot->Height());
3239984ca59SAndrew Lindesay if (w > size.Width())
3249984ca59SAndrew Lindesay size.SetWidth(w);
3259984ca59SAndrew Lindesay if (h > size.Height())
3269984ca59SAndrew Lindesay size.SetHeight(h);
3279984ca59SAndrew Lindesay }
3289984ca59SAndrew Lindesay }
3299984ca59SAndrew Lindesay }
3309984ca59SAndrew Lindesay
3319984ca59SAndrew Lindesay return size;
3329984ca59SAndrew Lindesay }
3339984ca59SAndrew Lindesay
3349984ca59SAndrew Lindesay
3352afa3f3bSStephan Aßmus void
_ResizeToFitAndCenter()3362afa3f3bSStephan Aßmus ScreenshotWindow::_ResizeToFitAndCenter()
3372afa3f3bSStephan Aßmus {
3389984ca59SAndrew Lindesay fScreenshotView->SetExplicitMinSize(_MaxWidthAndHeightOfAllScreenshots());
339416c1160SJulian Harnath Layout(false);
340416c1160SJulian Harnath
341416c1160SJulian Harnath // TODO: Limit window size to screen size (with a little margin),
342416c1160SJulian Harnath // the image should then become scrollable.
343416c1160SJulian Harnath
3442afa3f3bSStephan Aßmus float minWidth;
3452afa3f3bSStephan Aßmus float minHeight;
3462afa3f3bSStephan Aßmus GetSizeLimits(&minWidth, NULL, &minHeight, NULL);
3472afa3f3bSStephan Aßmus ResizeTo(minWidth, minHeight);
3482afa3f3bSStephan Aßmus CenterOnScreen();
3492afa3f3bSStephan Aßmus }
350c210060fSJulian Harnath
351c210060fSJulian Harnath
352c210060fSJulian Harnath void
_UpdateToolBar()353c210060fSJulian Harnath ScreenshotWindow::_UpdateToolBar()
354c210060fSJulian Harnath {
355*97a8cc6cSAndrew Lindesay int32 numScreenshots = 0;
356*97a8cc6cSAndrew Lindesay
357*97a8cc6cSAndrew Lindesay if (fPackage.IsSet()) {
358*97a8cc6cSAndrew Lindesay PackageScreenshotInfoRef screenshotInfo = fPackage->ScreenshotInfo();
359*97a8cc6cSAndrew Lindesay if (screenshotInfo.IsSet())
360*97a8cc6cSAndrew Lindesay numScreenshots = screenshotInfo->Count();
361*97a8cc6cSAndrew Lindesay }
362*97a8cc6cSAndrew Lindesay
363416c1160SJulian Harnath const int32 currentIndex = atomic_get(&fCurrentScreenshotIndex);
364416c1160SJulian Harnath
365c210060fSJulian Harnath fToolBar->SetActionEnabled(MSG_PREVIOUS_SCREENSHOT,
366c210060fSJulian Harnath currentIndex > 0);
367c210060fSJulian Harnath fToolBar->SetActionEnabled(MSG_NEXT_SCREENSHOT,
368416c1160SJulian Harnath currentIndex < numScreenshots - 1);
369c210060fSJulian Harnath
370c210060fSJulian Harnath BString text;
371c210060fSJulian Harnath text << currentIndex + 1;
372c210060fSJulian Harnath text << " / ";
373416c1160SJulian Harnath text << numScreenshots;
374c210060fSJulian Harnath fIndexView->SetText(text);
375c210060fSJulian Harnath }
376