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