xref: /haiku/src/add-ons/screen_savers/slideshowsaver/SlideShowSaver.cpp (revision e81a954787e50e56a7f06f72705b7859b6ab06d1)
1 /*****************************************************************************/
2 // SlideShowSaver
3 // Written by Michael Wilber
4 // Slide show code derived from ShowImage code, written by Michael Pfeiffer
5 //
6 // SlideShowSaver.cpp
7 //
8 //
9 // Copyright (C) Haiku
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining a
12 // copy of this software and associated documentation files (the "Software"),
13 // to deal in the Software without restriction, including without limitation
14 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 // and/or sell copies of the Software, and to permit persons to whom the
16 // Software is furnished to do so, subject to the following conditions:
17 //
18 // The above copyright notice and this permission notice shall be included
19 // in all copies or substantial portions of the Software.
20 //
21 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22 // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24 // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 // DEALINGS IN THE SOFTWARE.
28 /*****************************************************************************/
29 
30 
31 #include "SlideShowSaver.h"
32 
33 #include <stdio.h>
34 #include <stdlib.h>
35 #include <string.h>
36 
37 #include <BitmapStream.h>
38 #include <Catalog.h>
39 #include <Directory.h>
40 #include <File.h>
41 #include <List.h>
42 #include <Path.h>
43 #include <StringView.h>
44 #include <TranslatorRoster.h>
45 
46 #include "SlideShowConfigView.h"
47 
48 
49 // Called by system to get the screen saver
50 extern "C" _EXPORT BScreenSaver *
51 instantiate_screen_saver(BMessage *message, image_id id)
52 {
53 	return new SlideShowSaver(message, id);
54 }
55 
56 // returns B_ERROR if problems reading ref
57 // B_OK if ref is not a directory
58 // B_OK + 1 if ref is a directory
59 status_t
60 ent_is_dir(const entry_ref *ref)
61 {
62 	BEntry ent(ref);
63 	if (ent.InitCheck() != B_OK)
64 		return B_ERROR;
65 
66 	struct stat st;
67 	if (ent.GetStat(&st) != B_OK)
68 		return B_ERROR;
69 
70 	return S_ISDIR(st.st_mode) ? (B_OK + 1) : B_OK;
71 }
72 
73 int CompareEntries(const void* a, const void* b)
74 {
75 	entry_ref *r1, *r2;
76 	r1 = *(entry_ref**)a;
77 	r2 = *(entry_ref**)b;
78 	return strcasecmp(r1->name, r2->name);
79 }
80 
81 // Default settings for the Translator
82 LiveSetting gDefaultSettings[] = {
83 	LiveSetting(CHANGE_CAPTION, SAVER_SETTING_CAPTION, true),
84 		// Show image caption by default
85 	LiveSetting(CHANGE_BORDER, SAVER_SETTING_BORDER, true),
86 		// Show image border by default
87 	LiveSetting(CHANGE_DIRECTORY, SAVER_SETTING_DIRECTORY, "/boot/home"),
88 		// Set default image directory to home
89 	LiveSetting(CHANGE_DELAY, SAVER_SETTING_DELAY, (int32) 3000)
90 		// Default delay: 3 seconds
91 };
92 
93 SlideShowSaver::SlideShowSaver(BMessage *archive, image_id image)
94 	:
95 	BScreenSaver(archive, image), fLock("SlideShow Lock")
96 {
97 	B_TRANSLATE_MARK_SYSTEM_NAME_VOID("SlideShowSaver");
98 
99 	fNewDirectory = true;
100 	fBitmap = NULL;
101 	fShowBorder = true;
102 	fShowCaption = true;
103 
104 	fSettings = new LiveSettings("SlideShowSaver_Settings",
105 		gDefaultSettings, sizeof(gDefaultSettings) / sizeof(LiveSetting));
106 	fSettings->LoadSettings();
107 		// load settings from the settings file
108 
109 	fSettings->AddObserver(this);
110 }
111 
112 SlideShowSaver::~SlideShowSaver()
113 {
114 	delete fBitmap;
115 	fBitmap = NULL;
116 
117 	fSettings->RemoveObserver(this);
118 	fSettings->Release();
119 }
120 
121 // Called by fSettings to notify that someone has changed
122 // a setting. For example, if the user changes a setting
123 // on the config panel, this will be called to notify this
124 // object.
125 void
126 SlideShowSaver::SettingChanged(uint32 setting)
127 {
128 	switch (setting) {
129 		case CHANGE_CAPTION:
130 			UpdateShowCaption();
131 			break;
132 		case CHANGE_BORDER:
133 			UpdateShowBorder();
134 			break;
135 		case CHANGE_DIRECTORY:
136 			UpdateDirectory();
137 			break;
138 		case CHANGE_DELAY:
139 			UpdateTickSize();
140 			break;
141 
142 		default:
143 			break;
144 	}
145 }
146 
147 status_t
148 SlideShowSaver::UpdateTickSize()
149 {
150 	// Tick size is in microseconds, but is stored in settings as
151 	// milliseconds
152 	bigtime_t ticks = static_cast<bigtime_t>
153 		(fSettings->SetGetInt32(SAVER_SETTING_DELAY)) * 1000;
154 	SetTickSize(ticks);
155 
156 	return B_OK;
157 }
158 
159 status_t
160 SlideShowSaver::UpdateShowCaption()
161 {
162 	fShowCaption = fSettings->SetGetBool(SAVER_SETTING_CAPTION);
163 	return B_OK;
164 }
165 
166 status_t
167 SlideShowSaver::UpdateShowBorder()
168 {
169 	fShowBorder = fSettings->SetGetBool(SAVER_SETTING_BORDER);
170 	return B_OK;
171 }
172 
173 status_t
174 SlideShowSaver::UpdateDirectory()
175 {
176 	status_t result = B_OK;
177 
178 	fLock.Lock();
179 
180 	BString strDirectory;
181 	fSettings->GetString(SAVER_SETTING_DIRECTORY, strDirectory);
182 	BDirectory dir(strDirectory.String());
183 	if (dir.InitCheck() != B_OK || dir.GetNextRef(&fCurrentRef) != B_OK)
184 		result = B_ERROR;
185 	// Use ShowNextImage to find which translatable image is
186 	// alphabetically first in the given directory, and load it
187 	if (result == B_OK && ShowNextImage(true, true) == false)
188 		result = B_ERROR;
189 
190 	fNewDirectory = true;
191 
192 	fLock.Unlock();
193 
194 	return result;
195 }
196 
197 void
198 SlideShowSaver::StartConfig(BView *view)
199 {
200 	view->AddChild(new SlideShowConfigView(
201 		BRect(10, 10, 250, 300), "SlideShowSaver Config",
202 		B_FOLLOW_ALL, B_WILL_DRAW, fSettings->Acquire()));
203 }
204 
205 status_t
206 SlideShowSaver::StartSaver(BView *view, bool preview)
207 {
208 	UpdateShowCaption();
209 	UpdateShowBorder();
210 
211 	if (UpdateDirectory() != B_OK)
212 		return B_ERROR;
213 
214 	// Read ticksize setting and set it as the delay
215 	UpdateTickSize();
216 
217 	return B_OK;
218 }
219 
220 void
221 SlideShowSaver::Draw(BView *view, int32 frame)
222 {
223 	fLock.Lock();
224 
225 	view->SetLowColor(0, 0, 0);
226 	view->SetHighColor(192, 192, 192);
227 	view->SetViewColor(192, 192, 192);
228 
229 	bool bResult = false;
230 	if (fNewDirectory == true) {
231 		// Already have a bitmap on the first frame
232 		bResult = true;
233 	} else {
234 		bResult = ShowNextImage(true, false);
235 		// try rewinding to beginning
236 		if (bResult == false)
237 			bResult = ShowNextImage(true, true);
238 	}
239 	fNewDirectory = false;
240 
241 	if (bResult == true && fBitmap != NULL) {
242 		BRect destRect(0, 0, fBitmap->Bounds().Width(), fBitmap->Bounds().Height()),
243 			vwBounds = view->Bounds();
244 
245 		if (destRect.Width() < vwBounds.Width()) {
246 			destRect.OffsetBy((vwBounds.Width() - destRect.Width()) / 2, 0);
247 		}
248 		if (destRect.Height() < vwBounds.Height()) {
249 			destRect.OffsetBy(0, (vwBounds.Height() - destRect.Height()) / 2);
250 		}
251 
252 		BRect border = destRect, bounds = view->Bounds();
253 		// top
254 		view->FillRect(BRect(0, 0, bounds.right, border.top-1), B_SOLID_LOW);
255 		// left
256 		view->FillRect(BRect(0, border.top, border.left-1, border.bottom), B_SOLID_LOW);
257 		// right
258 		view->FillRect(BRect(border.right+1, border.top, bounds.right, border.bottom), B_SOLID_LOW);
259 		// bottom
260 		view->FillRect(BRect(0, border.bottom+1, bounds.right, bounds.bottom), B_SOLID_LOW);
261 
262 		if (fShowBorder == true) {
263 			BRect strokeRect = destRect;
264 			strokeRect.InsetBy(-1, -1);
265 			view->StrokeRect(strokeRect);
266 		}
267 
268 		view->DrawBitmap(fBitmap, fBitmap->Bounds(), destRect);
269 
270 		if (fShowCaption == true)
271 			DrawCaption(view);
272 	}
273 
274 	fLock.Unlock();
275 }
276 
277 status_t
278 SlideShowSaver::SetImage(const entry_ref *pref)
279 {
280 	entry_ref ref;
281 	if (!pref)
282 		ref = fCurrentRef;
283 	else
284 		ref = *pref;
285 
286 	BTranslatorRoster *proster = BTranslatorRoster::Default();
287 	if (!proster)
288 		return B_ERROR;
289 
290 	if (ent_is_dir(pref) != B_OK)
291 		// if ref is erroneous or a directory, return error
292 		return B_ERROR;
293 
294 	BFile file(&ref, B_READ_ONLY);
295 	translator_info info;
296 	memset(&info, 0, sizeof(translator_info));
297 	BMessage ioExtension;
298 	//if (ref != fCurrentRef)
299 		// if new image, reset to first document
300 	//	fDocumentIndex = 1;
301 	if (ioExtension.AddInt32("/documentIndex", 1 /*fDocumentIndex*/) != B_OK)
302 		return B_ERROR;
303 	if (proster->Identify(&file, &ioExtension, &info, 0, NULL,
304 		B_TRANSLATOR_BITMAP) != B_OK)
305 		return B_ERROR;
306 
307 	// Translate image data and create a new ShowImage window
308 	BBitmapStream outstream;
309 	if (proster->Translate(&file, &info, &ioExtension, &outstream,
310 		B_TRANSLATOR_BITMAP) != B_OK)
311 		return B_ERROR;
312 	BBitmap *newBitmap = NULL;
313 	if (outstream.DetachBitmap(&newBitmap) != B_OK)
314 		return B_ERROR;
315 
316 	// Now that I've successfully loaded the new bitmap,
317 	// I can be sure it is safe to delete the old one,
318 	// and clear everything
319 	delete fBitmap;
320 	fBitmap = newBitmap;
321 	newBitmap = NULL;
322 	fCurrentRef = ref;
323 
324 	// Get path to use in caption
325 	fCaption = "<< Unable to read the path >>";
326 	BEntry entry(&fCurrentRef);
327 	if (entry.InitCheck() == B_OK) {
328 		BPath path(&entry);
329 		if (path.InitCheck() == B_OK) {
330 			fCaption = path.Path();
331 		}
332 	}
333 
334 	return B_OK;
335 }
336 
337 // Function originally from Haiku ShowImage
338 bool
339 SlideShowSaver::ShowNextImage(bool next, bool rewind)
340 {
341 	bool found;
342 	entry_ref curRef, imgRef;
343 
344 	curRef = fCurrentRef;
345 	found = FindNextImage(&curRef, &imgRef, next, rewind);
346 	if (found) {
347 		// Keep trying to load images until:
348 		// 1. The image loads successfully
349 		// 2. The last file in the directory is found (for find next or find first)
350 		// 3. The first file in the directory is found (for find prev)
351 		// 4. The call to FindNextImage fails for any other reason
352 		while (SetImage(&imgRef) != B_OK) {
353 			curRef = imgRef;
354 			found = FindNextImage(&curRef, &imgRef, next, false);
355 			if (!found)
356 				return false;
357 		}
358 		return true;
359 	}
360 	return false;
361 }
362 
363 // Function taken from Haiku ShowImage,
364 // function originally written by Michael Pfeiffer
365 bool
366 SlideShowSaver::IsImage(const entry_ref *pref)
367 {
368 	if (!pref)
369 		return false;
370 
371 	if (ent_is_dir(pref) != B_OK)
372 		// if ref is erroneous or a directory, return false
373 		return false;
374 
375 	BFile file(pref, B_READ_ONLY);
376 	if (file.InitCheck() != B_OK)
377 		return false;
378 
379 	BTranslatorRoster *proster = BTranslatorRoster::Default();
380 	if (!proster)
381 		return false;
382 
383 	BMessage ioExtension;
384 	if (ioExtension.AddInt32("/documentIndex", 1) != B_OK)
385 		return false;
386 
387 	translator_info info;
388 	memset(&info, 0, sizeof(translator_info));
389 	if (proster->Identify(&file, &ioExtension, &info, 0, NULL,
390 		B_TRANSLATOR_BITMAP) != B_OK)
391 		return false;
392 
393 	return true;
394 }
395 
396 // Function taken from Haiku ShowImage,
397 // function originally written by Michael Pfeiffer
398 bool
399 SlideShowSaver::FindNextImage(entry_ref *in_current, entry_ref *out_image, bool next, bool rewind)
400 {
401 //	ASSERT(next || !rewind);
402 	BEntry curImage(in_current);
403 	entry_ref entry, *ref;
404 	BDirectory parent;
405 	BList entries;
406 	bool found = false;
407 	int32 cur;
408 
409 	if (curImage.GetParent(&parent) != B_OK)
410 		return false;
411 
412 	while (parent.GetNextRef(&entry) == B_OK) {
413 		if (entry != *in_current) {
414 			entries.AddItem(new entry_ref(entry));
415 		} else {
416 			// insert current ref, so we can find it easily after sorting
417 			entries.AddItem(in_current);
418 		}
419 	}
420 
421 	entries.SortItems(CompareEntries);
422 
423 	cur = entries.IndexOf(in_current);
424 //	ASSERT(cur >= 0);
425 
426 	// remove it so FreeEntries() does not delete it
427 	entries.RemoveItem(in_current);
428 
429 	if (next) {
430 		// find the next image in the list
431 		if (rewind) cur = 0; // start with first
432 		for (; (ref = (entry_ref*)entries.ItemAt(cur)) != NULL; cur ++) {
433 			if (IsImage(ref)) {
434 				found = true;
435 				*out_image = (const entry_ref)*ref;
436 				break;
437 			}
438 		}
439 	} else {
440 		// find the previous image in the list
441 		cur --;
442 		for (; cur >= 0; cur --) {
443 			ref = (entry_ref*)entries.ItemAt(cur);
444 			if (IsImage(ref)) {
445 				found = true;
446 				*out_image = (const entry_ref)*ref;
447 				break;
448 			}
449 		}
450 	}
451 
452 	FreeEntries(&entries);
453 	return found;
454 }
455 
456 // Function taken from Haiku ShowImage,
457 // function originally written by Michael Pfeiffer
458 void
459 SlideShowSaver::FreeEntries(BList *entries)
460 {
461 	const int32 n = entries->CountItems();
462 	for (int32 i = 0; i < n; i ++) {
463 		entry_ref *ref = (entry_ref *)entries->ItemAt(i);
464 		delete ref;
465 	}
466 	entries->MakeEmpty();
467 }
468 
469 void
470 SlideShowSaver::LayoutCaption(BView *view, BFont &font, BPoint &pos, BRect &rect)
471 {
472 	font_height fontHeight;
473 	float width, height;
474 	BRect bounds(view->Bounds());
475 	font = be_plain_font;
476 	width = font.StringWidth(fCaption.String()) + 1; // 1 for text shadow
477 	font.GetHeight(&fontHeight);
478 	height = fontHeight.ascent + fontHeight.descent;
479 	// center text horizontally
480 	pos.x = (bounds.left + bounds.right - width)/2;
481 	// flush bottom
482 	pos.y = bounds.bottom - fontHeight.descent - 5;
483 
484 	// background rectangle
485 	rect.Set(0, 0, (width-1)+2, (height-1)+2+1); // 2 for border and 1 for text shadow
486 	rect.OffsetTo(pos);
487 	rect.OffsetBy(-1, -1-fontHeight.ascent); // -1 for border
488 }
489 
490 void
491 SlideShowSaver::DrawCaption(BView *view)
492 {
493 	BFont font;
494 	BPoint pos;
495 	BRect rect;
496 	LayoutCaption(view, font, pos, rect);
497 
498 	view->PushState();
499 	// draw background
500 	view->SetDrawingMode(B_OP_ALPHA);
501 	view->SetHighColor(0, 0, 255, 128);
502 	view->FillRect(rect);
503 	// draw text
504 	view->SetDrawingMode(B_OP_OVER);
505 	view->SetFont(&font);
506 	view->SetLowColor(B_TRANSPARENT_COLOR);
507 	// text shadow
508 	pos += BPoint(1, 1);
509 	view->SetHighColor(0, 0, 0);
510 	view->SetPenSize(1);
511 	view->DrawString(fCaption.String(), pos);
512 	// text
513 	pos -= BPoint(1, 1);
514 	view->SetHighColor(255, 255, 0);
515 	view->DrawString(fCaption.String(), pos);
516 	view->PopState();
517 }
518 
519 
520