xref: /haiku/src/apps/haikudepot/ui/ScreenshotWindow.cpp (revision 344ded80d400028c8f561b4b876257b94c12db4a)
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 
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 
93 ScreenshotWindow::~ScreenshotWindow()
94 {
95 	BAutolock locker(&fLock);
96 
97 	if (fWorkerThread >= 0)
98 		wait_for_thread(fWorkerThread, NULL);
99 }
100 
101 
102 bool
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
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
155 ScreenshotWindow::SetOnCloseMessage(
156 	const BMessenger& messenger, const BMessage& message)
157 {
158 	fOnCloseTarget = messenger;
159 	fOnCloseMessage = message;
160 }
161 
162 
163 void
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
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
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
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
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
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
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
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