/*****************************************************************************/ // SlideShowSaver // Written by Michael Wilber // Slide show code derived from ShowImage code, written by Michael Pfeiffer // // SlideShowSaver.cpp // // // Copyright (C) Haiku // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL // THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. /*****************************************************************************/ #include "SlideShowSaver.h" #include #include #include #include #include #include #include #include #include #include #include #include "SlideShowConfigView.h" // Called by system to get the screen saver extern "C" _EXPORT BScreenSaver * instantiate_screen_saver(BMessage *message, image_id id) { return new SlideShowSaver(message, id); } // returns B_ERROR if problems reading ref // B_OK if ref is not a directory // B_OK + 1 if ref is a directory status_t ent_is_dir(const entry_ref *ref) { BEntry ent(ref); if (ent.InitCheck() != B_OK) return B_ERROR; struct stat st; if (ent.GetStat(&st) != B_OK) return B_ERROR; return S_ISDIR(st.st_mode) ? (B_OK + 1) : B_OK; } int CompareEntries(const void* a, const void* b) { entry_ref *r1, *r2; r1 = *(entry_ref**)a; r2 = *(entry_ref**)b; return strcasecmp(r1->name, r2->name); } // Default settings for the Translator LiveSetting gDefaultSettings[] = { LiveSetting(CHANGE_CAPTION, SAVER_SETTING_CAPTION, true), // Show image caption by default LiveSetting(CHANGE_BORDER, SAVER_SETTING_BORDER, true), // Show image border by default LiveSetting(CHANGE_DIRECTORY, SAVER_SETTING_DIRECTORY, "/boot/home"), // Set default image directory to home LiveSetting(CHANGE_DELAY, SAVER_SETTING_DELAY, (int32) 3000) // Default delay: 3 seconds }; SlideShowSaver::SlideShowSaver(BMessage *archive, image_id image) : BScreenSaver(archive, image), fLock("SlideShow Lock") { B_TRANSLATE_MARK_SYSTEM_NAME_VOID("SlideShowSaver"); fNewDirectory = true; fBitmap = NULL; fShowBorder = true; fShowCaption = true; fSettings = new LiveSettings("SlideShowSaver_Settings", gDefaultSettings, sizeof(gDefaultSettings) / sizeof(LiveSetting)); fSettings->LoadSettings(); // load settings from the settings file fSettings->AddObserver(this); } SlideShowSaver::~SlideShowSaver() { delete fBitmap; fBitmap = NULL; fSettings->RemoveObserver(this); fSettings->Release(); } // Called by fSettings to notify that someone has changed // a setting. For example, if the user changes a setting // on the config panel, this will be called to notify this // object. void SlideShowSaver::SettingChanged(uint32 setting) { switch (setting) { case CHANGE_CAPTION: UpdateShowCaption(); break; case CHANGE_BORDER: UpdateShowBorder(); break; case CHANGE_DIRECTORY: UpdateDirectory(); break; case CHANGE_DELAY: UpdateTickSize(); break; default: break; } } status_t SlideShowSaver::UpdateTickSize() { // Tick size is in microseconds, but is stored in settings as // milliseconds bigtime_t ticks = static_cast (fSettings->SetGetInt32(SAVER_SETTING_DELAY)) * 1000; SetTickSize(ticks); return B_OK; } status_t SlideShowSaver::UpdateShowCaption() { fShowCaption = fSettings->SetGetBool(SAVER_SETTING_CAPTION); return B_OK; } status_t SlideShowSaver::UpdateShowBorder() { fShowBorder = fSettings->SetGetBool(SAVER_SETTING_BORDER); return B_OK; } status_t SlideShowSaver::UpdateDirectory() { status_t result = B_OK; fLock.Lock(); BString strDirectory; fSettings->GetString(SAVER_SETTING_DIRECTORY, strDirectory); BDirectory dir(strDirectory.String()); if (dir.InitCheck() != B_OK || dir.GetNextRef(&fCurrentRef) != B_OK) result = B_ERROR; // Use ShowNextImage to find which translatable image is // alphabetically first in the given directory, and load it if (result == B_OK && ShowNextImage(true, true) == false) result = B_ERROR; fNewDirectory = true; fLock.Unlock(); return result; } void SlideShowSaver::StartConfig(BView *view) { view->AddChild(new SlideShowConfigView( BRect(10, 10, 250, 300), "SlideShowSaver Config", B_FOLLOW_ALL, B_WILL_DRAW, fSettings->Acquire())); } status_t SlideShowSaver::StartSaver(BView *view, bool preview) { UpdateShowCaption(); UpdateShowBorder(); if (UpdateDirectory() != B_OK) return B_ERROR; // Read ticksize setting and set it as the delay UpdateTickSize(); return B_OK; } void SlideShowSaver::Draw(BView *view, int32 frame) { fLock.Lock(); view->SetLowColor(0, 0, 0); view->SetHighColor(192, 192, 192); view->SetViewColor(192, 192, 192); bool bResult = false; if (fNewDirectory == true) { // Already have a bitmap on the first frame bResult = true; } else { bResult = ShowNextImage(true, false); // try rewinding to beginning if (bResult == false) bResult = ShowNextImage(true, true); } fNewDirectory = false; if (bResult == true && fBitmap != NULL) { BRect destRect(0, 0, fBitmap->Bounds().Width(), fBitmap->Bounds().Height()), vwBounds = view->Bounds(); if (destRect.Width() < vwBounds.Width()) { destRect.OffsetBy((vwBounds.Width() - destRect.Width()) / 2, 0); } if (destRect.Height() < vwBounds.Height()) { destRect.OffsetBy(0, (vwBounds.Height() - destRect.Height()) / 2); } BRect border = destRect, bounds = view->Bounds(); // top view->FillRect(BRect(0, 0, bounds.right, border.top-1), B_SOLID_LOW); // left view->FillRect(BRect(0, border.top, border.left-1, border.bottom), B_SOLID_LOW); // right view->FillRect(BRect(border.right+1, border.top, bounds.right, border.bottom), B_SOLID_LOW); // bottom view->FillRect(BRect(0, border.bottom+1, bounds.right, bounds.bottom), B_SOLID_LOW); if (fShowBorder == true) { BRect strokeRect = destRect; strokeRect.InsetBy(-1, -1); view->StrokeRect(strokeRect); } view->DrawBitmap(fBitmap, fBitmap->Bounds(), destRect); if (fShowCaption == true) DrawCaption(view); } fLock.Unlock(); } status_t SlideShowSaver::SetImage(const entry_ref *pref) { entry_ref ref; if (!pref) ref = fCurrentRef; else ref = *pref; BTranslatorRoster *proster = BTranslatorRoster::Default(); if (!proster) return B_ERROR; if (ent_is_dir(pref) != B_OK) // if ref is erroneous or a directory, return error return B_ERROR; BFile file(&ref, B_READ_ONLY); translator_info info; memset(&info, 0, sizeof(translator_info)); BMessage ioExtension; //if (ref != fCurrentRef) // if new image, reset to first document // fDocumentIndex = 1; if (ioExtension.AddInt32("/documentIndex", 1 /*fDocumentIndex*/) != B_OK) return B_ERROR; if (proster->Identify(&file, &ioExtension, &info, 0, NULL, B_TRANSLATOR_BITMAP) != B_OK) return B_ERROR; // Translate image data and create a new ShowImage window BBitmapStream outstream; if (proster->Translate(&file, &info, &ioExtension, &outstream, B_TRANSLATOR_BITMAP) != B_OK) return B_ERROR; BBitmap *newBitmap = NULL; if (outstream.DetachBitmap(&newBitmap) != B_OK) return B_ERROR; // Now that I've successfully loaded the new bitmap, // I can be sure it is safe to delete the old one, // and clear everything delete fBitmap; fBitmap = newBitmap; newBitmap = NULL; fCurrentRef = ref; // Get path to use in caption fCaption = "<< Unable to read the path >>"; BEntry entry(&fCurrentRef); if (entry.InitCheck() == B_OK) { BPath path(&entry); if (path.InitCheck() == B_OK) { fCaption = path.Path(); } } return B_OK; } // Function originally from Haiku ShowImage bool SlideShowSaver::ShowNextImage(bool next, bool rewind) { bool found; entry_ref curRef, imgRef; curRef = fCurrentRef; found = FindNextImage(&curRef, &imgRef, next, rewind); if (found) { // Keep trying to load images until: // 1. The image loads successfully // 2. The last file in the directory is found (for find next or find first) // 3. The first file in the directory is found (for find prev) // 4. The call to FindNextImage fails for any other reason while (SetImage(&imgRef) != B_OK) { curRef = imgRef; found = FindNextImage(&curRef, &imgRef, next, false); if (!found) return false; } return true; } return false; } // Function taken from Haiku ShowImage, // function originally written by Michael Pfeiffer bool SlideShowSaver::IsImage(const entry_ref *pref) { if (!pref) return false; if (ent_is_dir(pref) != B_OK) // if ref is erroneous or a directory, return false return false; BFile file(pref, B_READ_ONLY); if (file.InitCheck() != B_OK) return false; BTranslatorRoster *proster = BTranslatorRoster::Default(); if (!proster) return false; BMessage ioExtension; if (ioExtension.AddInt32("/documentIndex", 1) != B_OK) return false; translator_info info; memset(&info, 0, sizeof(translator_info)); if (proster->Identify(&file, &ioExtension, &info, 0, NULL, B_TRANSLATOR_BITMAP) != B_OK) return false; return true; } // Function taken from Haiku ShowImage, // function originally written by Michael Pfeiffer bool SlideShowSaver::FindNextImage(entry_ref *in_current, entry_ref *out_image, bool next, bool rewind) { // ASSERT(next || !rewind); BEntry curImage(in_current); entry_ref entry, *ref; BDirectory parent; BList entries; bool found = false; int32 cur; if (curImage.GetParent(&parent) != B_OK) return false; while (parent.GetNextRef(&entry) == B_OK) { if (entry != *in_current) { entries.AddItem(new entry_ref(entry)); } else { // insert current ref, so we can find it easily after sorting entries.AddItem(in_current); } } entries.SortItems(CompareEntries); cur = entries.IndexOf(in_current); // ASSERT(cur >= 0); // remove it so FreeEntries() does not delete it entries.RemoveItem(in_current); if (next) { // find the next image in the list if (rewind) cur = 0; // start with first for (; (ref = (entry_ref*)entries.ItemAt(cur)) != NULL; cur ++) { if (IsImage(ref)) { found = true; *out_image = (const entry_ref)*ref; break; } } } else { // find the previous image in the list cur --; for (; cur >= 0; cur --) { ref = (entry_ref*)entries.ItemAt(cur); if (IsImage(ref)) { found = true; *out_image = (const entry_ref)*ref; break; } } } FreeEntries(&entries); return found; } // Function taken from Haiku ShowImage, // function originally written by Michael Pfeiffer void SlideShowSaver::FreeEntries(BList *entries) { const int32 n = entries->CountItems(); for (int32 i = 0; i < n; i ++) { entry_ref *ref = (entry_ref *)entries->ItemAt(i); delete ref; } entries->MakeEmpty(); } void SlideShowSaver::LayoutCaption(BView *view, BFont &font, BPoint &pos, BRect &rect) { font_height fontHeight; float width, height; BRect bounds(view->Bounds()); font = be_plain_font; width = font.StringWidth(fCaption.String()) + 1; // 1 for text shadow font.GetHeight(&fontHeight); height = fontHeight.ascent + fontHeight.descent; // center text horizontally pos.x = (bounds.left + bounds.right - width)/2; // flush bottom pos.y = bounds.bottom - fontHeight.descent - 5; // background rectangle rect.Set(0, 0, (width-1)+2, (height-1)+2+1); // 2 for border and 1 for text shadow rect.OffsetTo(pos); rect.OffsetBy(-1, -1-fontHeight.ascent); // -1 for border } void SlideShowSaver::DrawCaption(BView *view) { BFont font; BPoint pos; BRect rect; LayoutCaption(view, font, pos, rect); view->PushState(); // draw background view->SetDrawingMode(B_OP_ALPHA); view->SetHighColor(0, 0, 255, 128); view->FillRect(rect); // draw text view->SetDrawingMode(B_OP_OVER); view->SetFont(&font); view->SetLowColor(B_TRANSPARENT_COLOR); // text shadow pos += BPoint(1, 1); view->SetHighColor(0, 0, 0); view->SetPenSize(1); view->DrawString(fCaption.String(), pos); // text pos -= BPoint(1, 1); view->SetHighColor(255, 255, 0); view->DrawString(fCaption.String(), pos); view->PopState(); }