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