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