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