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
50extern "C" _EXPORT BScreenSaver *
51instantiate_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
59status_t
60ent_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
73int 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
82LiveSetting 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
93SlideShowSaver::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
112SlideShowSaver::~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.
125void
126SlideShowSaver::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
147status_t
148SlideShowSaver::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
159status_t
160SlideShowSaver::UpdateShowCaption()
161{
162	fShowCaption = fSettings->SetGetBool(SAVER_SETTING_CAPTION);
163	return B_OK;
164}
165
166status_t
167SlideShowSaver::UpdateShowBorder()
168{
169	fShowBorder = fSettings->SetGetBool(SAVER_SETTING_BORDER);
170	return B_OK;
171}
172
173status_t
174SlideShowSaver::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
197void
198SlideShowSaver::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
205status_t
206SlideShowSaver::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
220void
221SlideShowSaver::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
277status_t
278SlideShowSaver::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
338bool
339SlideShowSaver::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
365bool
366SlideShowSaver::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
398bool
399SlideShowSaver::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
458void
459SlideShowSaver::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
469void
470SlideShowSaver::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
490void
491SlideShowSaver::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