xref: /haiku/src/apps/haikudepot/ui/ScreenshotWindow.cpp (revision 06ed32b8c4ed857f589da5eb5a5a4d668f271645)
1 /*
2  * Copyright 2014, Stephan Aßmus <superstippi@gmx.de>.
3  * Copyright 2017, Julian Harnath <julian.harnath@rwth-aachen.de>.
4  * Copyright 2020, 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.Get() != NULL) {
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 	if (!Lock()) {
258 		HDERROR("failed to lock screenshot window");
259 		return;
260 	}
261 
262 	fScreenshotView->UnsetBitmap();
263 
264 	ScreenshotInfoList screenshotInfos;
265 	if (fPackage.Get() != NULL)
266 		screenshotInfos = fPackage->ScreenshotInfos();
267 
268 	Unlock();
269 
270 	if (screenshotInfos.CountItems() == 0) {
271 		HDINFO("package has no screenshots");
272 		return;
273 	}
274 
275 	// Obtain the correct code for the screenshot to display
276 	// TODO: Once navigation buttons are added, we could use the
277 	// ScreenshotInfo at the "current" index.
278 	const ScreenshotInfo& info = screenshotInfos.ItemAtFast(
279 		atomic_get(&fCurrentScreenshotIndex));
280 
281 	BMallocIO buffer;
282 	WebAppInterface interface;
283 
284 	// Only indicate being busy with the download if it takes a little while
285 	BMessenger messenger(this);
286 	BMessageRunner delayedMessenger(messenger,
287 		new BMessage(MSG_DOWNLOAD_START),
288 		kProgressIndicatorDelay, 1);
289 
290 	// Retrieve screenshot from web-app
291 	status_t status = interface.RetrieveScreenshot(info.Code(),
292 		info.Width(), info.Height(), &buffer);
293 
294 	delayedMessenger.SetCount(0);
295 	messenger.SendMessage(MSG_DOWNLOAD_STOP);
296 
297 	if (status == B_OK && Lock()) {
298 		HDINFO("got screenshot");
299 		fScreenshot = BitmapRef(new(std::nothrow)SharedBitmap(buffer), true);
300 		fScreenshotView->SetBitmap(fScreenshot);
301 		_ResizeToFitAndCenter();
302 		Unlock();
303 	} else
304 		HDERROR("failed to download screenshot");
305 }
306 
307 
308 void
309 ScreenshotWindow::_ResizeToFitAndCenter()
310 {
311 	// Find out dimensions of the largest screenshot of this package
312 	ScreenshotInfoList screenshotInfos;
313 	if (fPackage.Get() != NULL)
314 		screenshotInfos = fPackage->ScreenshotInfos();
315 
316 	int32 largestScreenshotWidth = 0;
317 	int32 largestScreenshotHeight = 0;
318 
319 	const uint32 numScreenshots = fPackage->ScreenshotInfos().CountItems();
320 	for (uint32 i = 0; i < numScreenshots; i++) {
321 		const ScreenshotInfo& info = screenshotInfos.ItemAtFast(i);
322 		if (info.Width() > largestScreenshotWidth)
323 			largestScreenshotWidth = info.Width();
324 		if (info.Height() > largestScreenshotHeight)
325 			largestScreenshotHeight = info.Height();
326 	}
327 
328 	fScreenshotView->SetExplicitMinSize(
329 		BSize(largestScreenshotWidth, largestScreenshotHeight));
330 	Layout(false);
331 
332 	// TODO: Limit window size to screen size (with a little margin),
333 	//       the image should then become scrollable.
334 
335 	float minWidth;
336 	float minHeight;
337 	GetSizeLimits(&minWidth, NULL, &minHeight, NULL);
338 	ResizeTo(minWidth, minHeight);
339 	CenterOnScreen();
340 }
341 
342 
343 void
344 ScreenshotWindow::_UpdateToolBar()
345 {
346 	const int32 numScreenshots = fPackage->ScreenshotInfos().CountItems();
347 	const int32 currentIndex = atomic_get(&fCurrentScreenshotIndex);
348 
349 	fToolBar->SetActionEnabled(MSG_PREVIOUS_SCREENSHOT,
350 		currentIndex > 0);
351 	fToolBar->SetActionEnabled(MSG_NEXT_SCREENSHOT,
352 		currentIndex < numScreenshots - 1);
353 
354 	BString text;
355 	text << currentIndex + 1;
356 	text << " / ";
357 	text << numScreenshots;
358 	fIndexView->SetText(text);
359 }
360