xref: /haiku/src/apps/haikudepot/ui/ScreenshotWindow.cpp (revision c237c4ce593ee823d9867fd997e51e4c447f5623)
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 
30 static 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 
34 static BitmapRef sNextButtonIcon(
35 	new(std::nothrow) SharedBitmap(RSRC_ARROW_LEFT), true);
36 static BitmapRef sPreviousButtonIcon(
37 	new(std::nothrow) SharedBitmap(RSRC_ARROW_RIGHT), true);
38 
39 
40 ScreenshotWindow::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 
98 ScreenshotWindow::~ScreenshotWindow()
99 {
100 	BAutolock locker(&fLock);
101 
102 	if (fWorkerThread >= 0)
103 		wait_for_thread(fWorkerThread, NULL);
104 }
105 
106 
107 bool
108 ScreenshotWindow::QuitRequested()
109 {
110 	if (fOnCloseTarget.IsValid() && fOnCloseMessage.what != 0)
111 		fOnCloseTarget.SendMessage(&fOnCloseMessage);
112 
113 	Hide();
114 	return false;
115 }
116 
117 
118 void
119 ScreenshotWindow::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 
159 void
160 ScreenshotWindow::SetOnCloseMessage(
161 	const BMessenger& messenger, const BMessage& message)
162 {
163 	fOnCloseTarget = messenger;
164 	fOnCloseMessage = message;
165 }
166 
167 
168 void
169 ScreenshotWindow::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
190 ScreenshotWindow::CleanupIcons()
191 {
192 	sNextButtonIcon.Unset();
193 	sPreviousButtonIcon.Unset();
194 }
195 
196 
197 // #pragma mark - private
198 
199 
200 void
201 ScreenshotWindow::_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 
217 void
218 ScreenshotWindow::_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 
245 int32
246 ScreenshotWindow::_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 
256 void
257 ScreenshotWindow::_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 
314 BSize
315 ScreenshotWindow::_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 
339 void
340 ScreenshotWindow::_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 
356 void
357 ScreenshotWindow::_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