xref: /haiku/src/apps/showimage/ImageFileNavigator.cpp (revision 328932ac9edb30c87a3928b6d2a21b126bcaa3d4)
1 /*
2  * Copyright 2003-2010, Haiku, Inc. All Rights Reserved.
3  * Copyright 2004-2005 yellowTAB GmbH. All Rights Reserverd.
4  * Copyright 2006 Bernd Korz. All Rights Reserved
5  * Distributed under the terms of the MIT License.
6  *
7  * Authors:
8  *		Fernando Francisco de Oliveira
9  *		Michael Wilber
10  *		Michael Pfeiffer
11  *		Ryan Leavengood
12  *		yellowTAB GmbH
13  *		Bernd Korz
14  *		Stephan Aßmus <superstippi@gmx.de>
15  *		Axel Dörfler, axeld@pinc-software.de
16  */
17 
18 
19 #include "ImageFileNavigator.h"
20 
21 #include <deque>
22 #include <map>
23 #include <new>
24 #include <set>
25 
26 #include <stdio.h>
27 
28 #include <Bitmap.h>
29 #include <BitmapStream.h>
30 #include <Directory.h>
31 #include <Entry.h>
32 #include <File.h>
33 #include <Locker.h>
34 #include <ObjectList.h>
35 #include <Path.h>
36 #include <TranslatorRoster.h>
37 
38 #include <AutoDeleter.h>
39 #include <tracker_private.h>
40 #include <kernel/util/DoublyLinkedList.h>
41 
42 #include "ProgressWindow.h"
43 #include "ShowImageConstants.h"
44 
45 
46 class Navigator {
47 public:
48 								Navigator();
49 	virtual						~Navigator();
50 
51 	virtual	bool				FindNextImage(const entry_ref& currentRef,
52 									entry_ref& ref, bool next, bool rewind) = 0;
53 	virtual void				UpdateSelection(const entry_ref& ref) = 0;
54 
55 protected:
56 			bool				IsImage(const entry_ref& ref);
57 };
58 
59 
60 // Navigation to the next/previous image file is based on
61 // communication with Tracker, the folder containing the current
62 // image needs to be open for this to work. The routine first tries
63 // to find the next candidate file, then tries to load it as image.
64 // As long as loading fails, the operation is repeated for the next
65 // candidate file.
66 
67 class TrackerNavigator : public Navigator {
68 public:
69 								TrackerNavigator(
70 									const BMessenger& trackerMessenger);
71 	virtual						~TrackerNavigator();
72 
73 	virtual	bool				FindNextImage(const entry_ref& currentRef,
74 									entry_ref& ref, bool next, bool rewind);
75 	virtual void				UpdateSelection(const entry_ref& ref);
76 
77 private:
78 			BMessenger			fTrackerMessenger;
79 				// of the window that this was launched from
80 };
81 
82 
83 class FolderNavigator : public Navigator {
84 public:
85 								FolderNavigator(const entry_ref& ref);
86 	virtual						~FolderNavigator();
87 
88 	virtual	bool				FindNextImage(const entry_ref& currentRef,
89 									entry_ref& ref, bool next, bool rewind);
90 	virtual void				UpdateSelection(const entry_ref& ref);
91 
92 private:
93 			void				_BuildEntryList();
94 	static	int					_CompareRefs(const entry_ref* refA,
95 									const entry_ref* refB);
96 
97 private:
98 			BDirectory			fFolder;
99 			BObjectList<entry_ref> fEntries;
100 };
101 
102 
103 // TODO: Remove this and use Tracker's Command.h once it is moved into the
104 // private headers!
105 namespace BPrivate {
106 	const uint32 kMoveToTrash = 'Ttrs';
107 }
108 
109 
110 static bool
111 entry_ref_is_file(const entry_ref& ref)
112 {
113 	BEntry entry(&ref, true);
114 	if (entry.InitCheck() != B_OK)
115 		return false;
116 
117 	return entry.IsFile();
118 }
119 
120 
121 // #pragma mark -
122 
123 
124 Navigator::Navigator()
125 {
126 }
127 
128 
129 Navigator::~Navigator()
130 {
131 }
132 
133 
134 bool
135 Navigator::IsImage(const entry_ref& ref)
136 {
137 	if (!entry_ref_is_file(ref))
138 		return false;
139 
140 	BFile file(&ref, B_READ_ONLY);
141 	if (file.InitCheck() != B_OK)
142 		return false;
143 
144 	BTranslatorRoster* roster = BTranslatorRoster::Default();
145 	if (roster == NULL)
146 		return false;
147 
148 	translator_info info;
149 	memset(&info, 0, sizeof(translator_info));
150 	return roster->Identify(&file, NULL, &info, 0, NULL,
151 		B_TRANSLATOR_BITMAP) == B_OK;
152 }
153 
154 
155 // #pragma mark -
156 
157 
158 TrackerNavigator::TrackerNavigator(const BMessenger& trackerMessenger)
159 	:
160 	fTrackerMessenger(trackerMessenger)
161 {
162 }
163 
164 
165 TrackerNavigator::~TrackerNavigator()
166 {
167 }
168 
169 
170 bool
171 TrackerNavigator::FindNextImage(const entry_ref& currentRef, entry_ref& ref,
172 	bool next, bool rewind)
173 {
174 	// Based on GetTrackerWindowFile function from BeMail
175 	if (!fTrackerMessenger.IsValid())
176 		return false;
177 
178 	// Ask the Tracker what the next/prev file in the window is.
179 	// Continue asking for the next reference until a valid
180 	// image is found.
181 	entry_ref nextRef = currentRef;
182 	bool foundRef = false;
183 	while (!foundRef) {
184 		BMessage request(B_GET_PROPERTY);
185 		BMessage specifier;
186 		if (rewind)
187 			specifier.what = B_DIRECT_SPECIFIER;
188 		else if (next)
189 			specifier.what = 'snxt';
190 		else
191 			specifier.what = 'sprv';
192 		specifier.AddString("property", "Entry");
193 		if (rewind) {
194 			// if rewinding, ask for the ref to the
195 			// first item in the directory
196 			specifier.AddInt32("data", 0);
197 		} else
198 			specifier.AddRef("data", &nextRef);
199 		request.AddSpecifier(&specifier);
200 
201 		BMessage reply;
202 		if (fTrackerMessenger.SendMessage(&request, &reply) != B_OK)
203 			return false;
204 		if (reply.FindRef("result", &nextRef) != B_OK)
205 			return false;
206 
207 		if (IsImage(nextRef))
208 			foundRef = true;
209 
210 		rewind = false;
211 			// stop asking for the first ref in the directory
212 	}
213 
214 	ref = nextRef;
215 	return foundRef;
216 }
217 
218 
219 void
220 TrackerNavigator::UpdateSelection(const entry_ref& ref)
221 {
222 	BMessage setSelection(B_SET_PROPERTY);
223 	setSelection.AddSpecifier("Selection");
224 	setSelection.AddRef("data", &ref);
225 	fTrackerMessenger.SendMessage(&setSelection);
226 }
227 
228 
229 // #pragma mark -
230 
231 
232 FolderNavigator::FolderNavigator(const entry_ref& ref)
233 	:
234 	fEntries(true)
235 {
236 	node_ref nodeRef;
237 	nodeRef.device = ref.device;
238 	nodeRef.node = ref.directory;
239 
240 	fFolder.SetTo(&nodeRef);
241 	_BuildEntryList();
242 
243 	// TODO: monitor the directory for changes, sort it naturally
244 }
245 
246 
247 FolderNavigator::~FolderNavigator()
248 {
249 }
250 
251 
252 bool
253 FolderNavigator::FindNextImage(const entry_ref& currentRef, entry_ref& nextRef,
254 	bool next, bool rewind)
255 {
256 	int32 index;
257 	if (rewind) {
258 		index = next ? fEntries.CountItems() : 0;
259 		next = !next;
260 	} else {
261 		index = fEntries.BinarySearchIndex(currentRef,
262 			&FolderNavigator::_CompareRefs);
263 		if (next)
264 			index++;
265 		else
266 			index--;
267 	}
268 
269 	while (index < fEntries.CountItems() && index >= 0) {
270 		const entry_ref& ref = *fEntries.ItemAt(index);
271 		if (IsImage(ref)) {
272 			nextRef = ref;
273 			return true;
274 		} else {
275 			// remove non-image entries
276 			delete fEntries.RemoveItemAt(index);
277 			if (!next)
278 				index--;
279 		}
280 	}
281 
282 	return false;
283 }
284 
285 
286 void
287 FolderNavigator::UpdateSelection(const entry_ref& ref)
288 {
289 	// nothing to do for us here
290 }
291 
292 
293 void
294 FolderNavigator::_BuildEntryList()
295 {
296 	fEntries.MakeEmpty();
297 	fFolder.Rewind();
298 
299 	while (true) {
300 		entry_ref* ref = new entry_ref();
301 		status_t status = fFolder.GetNextRef(ref);
302 		if (status != B_OK)
303 			break;
304 
305 		fEntries.AddItem(ref);
306 	}
307 
308 	fEntries.SortItems(&FolderNavigator::_CompareRefs);
309 }
310 
311 
312 /*static*/ int
313 FolderNavigator::_CompareRefs(const entry_ref* refA, const entry_ref* refB)
314 {
315 	// TODO: natural sorting? Collating via current locale?
316 	return strcasecmp(refA->name, refB->name);
317 }
318 
319 
320 // #pragma mark -
321 
322 
323 struct CacheEntry : DoublyLinkedListLinkImpl<CacheEntry> {
324 	entry_ref				ref;
325 	int32					page;
326 	int32					pageCount;
327 	BBitmap*				bitmap;
328 	BString					type;
329 	BString					mimeType;
330 };
331 
332 
333 struct QueueEntry {
334 	entry_ref				ref;
335 	int32					page;
336 	std::set<BMessenger>	listeners;
337 };
338 
339 
340 class ImageCache {
341 public:
342 								ImageCache();
343 	virtual						~ImageCache();
344 
345 			void				RetrieveImage(const entry_ref& ref,
346 									const BMessenger* target);
347 
348 private:
349 	static	status_t			_QueueWorkerThread(void* self);
350 
351 			status_t			_RetrieveImage(QueueEntry* entry,
352 									BMessage& message);
353 			void				_NotifyListeners(QueueEntry* entry,
354 									BMessage& message);
355 
356 private:
357 			typedef std::pair<entry_ref, int32> ImageSelector;
358 			typedef std::map<ImageSelector, CacheEntry*> CacheMap;
359 			typedef std::map<ImageSelector, QueueEntry*> QueueMap;
360 			typedef std::deque<QueueEntry*> QueueDeque;
361 			typedef DoublyLinkedList<CacheEntry> CacheList;
362 
363 			BLocker				fCacheLocker;
364 			CacheMap			fCacheMap;
365 			CacheList			fCacheEntriesByAge;
366 			BLocker				fQueueLocker;
367 			QueueMap			fQueueMap;
368 			QueueDeque			fQueue;
369 			vint32				fThreadCount;
370 			int32				fMaxThreadCount;
371 			uint64				fBytes;
372 			uint64				fMaxBytes;
373 			size_t				fMaxEntries;
374 };
375 
376 
377 ImageCache::ImageCache()
378 	:
379 	fCacheLocker("image cache"),
380 	fQueueLocker("image queue"),
381 	fThreadCount(0),
382 	fBytes(0)
383 {
384 	system_info info;
385 	get_system_info(&info);
386 
387 	fMaxThreadCount = (info.cpu_count + 1) / 2;
388 	fMaxBytes = info.max_pages * B_PAGE_SIZE / 8;
389 	fMaxEntries = 10;
390 }
391 
392 
393 ImageCache::~ImageCache()
394 {
395 	// TODO: delete CacheEntries, and QueueEntries
396 }
397 
398 
399 void
400 ImageCache::RetrieveImage(const entry_ref& ref, const BMessenger* target)
401 {
402 	// TODO!
403 }
404 
405 
406 /*static*/ status_t
407 ImageCache::_QueueWorkerThread(void* _self)
408 {
409 	ImageCache* self = (ImageCache*)_self;
410 
411 	// get next queue entry
412 	while (true) {
413 		self->fQueueLocker.Lock();
414 		if (self->fQueue.empty()) {
415 			self->fQueueLocker.Unlock();
416 			break;
417 		}
418 
419 		QueueEntry* entry = *self->fQueue.begin();
420 		self->fQueue.pop_front();
421 		self->fQueueLocker.Unlock();
422 
423 		if (entry == NULL)
424 			break;
425 
426 		BMessage notification(kMsgImageLoaded);
427 		status_t status = self->_RetrieveImage(entry, notification);
428 		if (status != B_OK)
429 			notification.AddInt32("error", status);
430 
431 		self->fQueueLocker.Lock();
432 		self->fQueueMap.erase(std::make_pair(entry->ref, entry->page));
433 		self->fQueueLocker.Unlock();
434 
435 		self->_NotifyListeners(entry, notification);
436 		delete entry;
437 	}
438 
439 	atomic_add(&self->fThreadCount, -1);
440 	return B_OK;
441 }
442 
443 
444 status_t
445 ImageCache::_RetrieveImage(QueueEntry* queueEntry, BMessage& message)
446 {
447 	CacheEntry* entry = new(std::nothrow) CacheEntry();
448 	if (entry == NULL)
449 		return B_NO_MEMORY;
450 
451 	ObjectDeleter<CacheEntry> deleter(entry);
452 
453 	BTranslatorRoster* roster = BTranslatorRoster::Default();
454 	if (roster == NULL)
455 		return B_ERROR;
456 
457 	if (!entry_ref_is_file(queueEntry->ref))
458 		return B_IS_A_DIRECTORY;
459 
460 	BFile file;
461 	status_t status = file.SetTo(&queueEntry->ref, B_READ_ONLY);
462 	if (status != B_OK)
463 		return status;
464 
465 	translator_info info;
466 	memset(&info, 0, sizeof(translator_info));
467 	BMessage ioExtension;
468 
469 	if (queueEntry->page != 0
470 		&& ioExtension.AddInt32("/documentIndex", queueEntry->page) != B_OK)
471 		return B_NO_MEMORY;
472 
473 // TODO: rethink this!
474 #if 0
475 	if (fProgressWindow != NULL) {
476 		BMessage progress(kMsgProgressStatusUpdate);
477 		if (ioExtension.AddMessenger("/progressMonitor",
478 				fProgressWindow) == B_OK
479 			&& ioExtension.AddMessage("/progressMessage", &progress) == B_OK)
480 			fProgressWindow->Start();
481 	}
482 #endif
483 
484 	// Translate image data and create a new ShowImage window
485 
486 	BBitmapStream outstream;
487 
488 	status = roster->Identify(&file, &ioExtension, &info, 0, NULL,
489 		B_TRANSLATOR_BITMAP);
490 	if (status == B_OK) {
491 		status = roster->Translate(&file, &info, &ioExtension, &outstream,
492 			B_TRANSLATOR_BITMAP);
493 	}
494 
495 #if 0
496 	if (fProgressWindow != NULL)
497 		fProgressWindow->Stop();
498 #endif
499 
500 	if (status != B_OK)
501 		return status;
502 
503 	BBitmap* bitmap;
504 	if (outstream.DetachBitmap(&bitmap) != B_OK)
505 		return B_ERROR;
506 
507 	entry->ref = queueEntry->ref;
508 	entry->page = queueEntry->page;
509 	entry->bitmap = bitmap;
510 	entry->type = info.name;
511 	entry->mimeType = info.MIME;
512 
513 	// get the number of documents (pages) if it has been supplied
514 	int32 documentCount = 0;
515 	if (ioExtension.FindInt32("/documentCount", &documentCount) == B_OK
516 		&& documentCount > 0)
517 		entry->pageCount = documentCount;
518 	else
519 		entry->pageCount = 1;
520 
521 	message.AddString("type", info.name);
522 	message.AddString("mime", info.MIME);
523 	message.AddRef("ref", &entry->ref);
524 	message.AddInt32("page", entry->page);
525 	message.AddPointer("bitmap", (void*)bitmap);
526 
527 	deleter.Detach();
528 
529 	fCacheLocker.Lock();
530 
531 	fCacheMap.insert(std::make_pair(
532 		std::make_pair(entry->ref, entry->page), entry));
533 	fCacheEntriesByAge.Add(entry);
534 
535 	fBytes += bitmap->BitsLength();
536 
537 	while (fBytes > fMaxBytes || fCacheMap.size() > fMaxEntries) {
538 		if (fCacheMap.size() <= 2)
539 			break;
540 
541 		// Remove the oldest entry
542 		entry = fCacheEntriesByAge.RemoveHead();
543 		fBytes -= entry->bitmap->BitsLength();
544 		fCacheMap.erase(std::make_pair(entry->ref, entry->page));
545 		delete entry;
546 	}
547 
548 	fCacheLocker.Unlock();
549 	return B_OK;
550 }
551 
552 
553 void
554 ImageCache::_NotifyListeners(QueueEntry* entry, BMessage& message)
555 {
556 	std::set<BMessenger>::iterator iterator = entry->listeners.begin();
557 	for (; iterator != entry->listeners.end(); iterator++) {
558 		iterator->SendMessage(&message);
559 	}
560 }
561 
562 
563 // #pragma mark -
564 
565 
566 ImageFileNavigator::ImageFileNavigator(const BMessenger& target,
567 	const entry_ref& ref, const BMessenger& trackerMessenger)
568 	:
569 	fTarget(target),
570 	fProgressWindow(NULL),
571 	fDocumentIndex(1),
572 	fDocumentCount(1)
573 {
574 	if (trackerMessenger.IsValid())
575 		fNavigator = new TrackerNavigator(trackerMessenger);
576 	else
577 		fNavigator = new FolderNavigator(ref);
578 }
579 
580 
581 ImageFileNavigator::~ImageFileNavigator()
582 {
583 	delete fNavigator;
584 }
585 
586 
587 void
588 ImageFileNavigator::SetProgressWindow(ProgressWindow* progressWindow)
589 {
590 	fProgressWindow = progressWindow;
591 }
592 
593 
594 status_t
595 ImageFileNavigator::LoadImage(const entry_ref& ref, int32 page)
596 {
597 	BTranslatorRoster* roster = BTranslatorRoster::Default();
598 	if (roster == NULL)
599 		return B_ERROR;
600 
601 	if (!entry_ref_is_file(ref))
602 		return B_ERROR;
603 
604 	BFile file(&ref, B_READ_ONLY);
605 	translator_info info;
606 	memset(&info, 0, sizeof(translator_info));
607 	BMessage ioExtension;
608 
609 	if (page != 0 && ioExtension.AddInt32("/documentIndex", page) != B_OK)
610 		return B_ERROR;
611 
612 	if (fProgressWindow != NULL) {
613 		BMessage progress(kMsgProgressStatusUpdate);
614 		if (ioExtension.AddMessenger("/progressMonitor",
615 				fProgressWindow) == B_OK
616 			&& ioExtension.AddMessage("/progressMessage", &progress) == B_OK)
617 			fProgressWindow->Start();
618 	}
619 
620 	// Translate image data and create a new ShowImage window
621 
622 	BBitmapStream outstream;
623 
624 	status_t status = roster->Identify(&file, &ioExtension, &info, 0, NULL,
625 		B_TRANSLATOR_BITMAP);
626 	if (status == B_OK) {
627 		status = roster->Translate(&file, &info, &ioExtension, &outstream,
628 			B_TRANSLATOR_BITMAP);
629 	}
630 
631 	if (fProgressWindow != NULL)
632 		fProgressWindow->Stop();
633 
634 	if (status != B_OK)
635 		return status;
636 
637 	BBitmap* bitmap;
638 	if (outstream.DetachBitmap(&bitmap) != B_OK)
639 		return B_ERROR;
640 
641 	fCurrentRef = ref;
642 	fDocumentIndex = page;
643 
644 	// get the number of documents (pages) if it has been supplied
645 	int32 documentCount = 0;
646 	if (ioExtension.FindInt32("/documentCount", &documentCount) == B_OK
647 		&& documentCount > 0)
648 		fDocumentCount = documentCount;
649 	else
650 		fDocumentCount = 1;
651 
652 	BMessage message(kMsgImageLoaded);
653 	message.AddString("type", info.name);
654 	message.AddString("mime", info.MIME);
655 	message.AddRef("ref", &ref);
656 	message.AddInt32("page", page);
657 	message.AddPointer("bitmap", (void*)bitmap);
658 	status = fTarget.SendMessage(&message);
659 	if (status != B_OK) {
660 		delete bitmap;
661 		return status;
662 	}
663 
664 	return B_OK;
665 }
666 
667 
668 void
669 ImageFileNavigator::GetPath(BString* outPath)
670 {
671 	BEntry entry(&fCurrentRef);
672 	BPath path;
673 	if (entry.InitCheck() < B_OK || entry.GetPath(&path) < B_OK)
674 		outPath->SetTo("");
675 	else
676 		outPath->SetTo(path.Path());
677 }
678 
679 
680 int32
681 ImageFileNavigator::CurrentPage()
682 {
683 	return fDocumentIndex;
684 }
685 
686 
687 int32
688 ImageFileNavigator::PageCount()
689 {
690 	return fDocumentCount;
691 }
692 
693 
694 bool
695 ImageFileNavigator::FirstPage()
696 {
697 	if (fDocumentIndex != 1) {
698 		LoadImage(fCurrentRef, 1);
699 		return true;
700 	}
701 	return false;
702 }
703 
704 
705 bool
706 ImageFileNavigator::LastPage()
707 {
708 	if (fDocumentIndex != fDocumentCount) {
709 		LoadImage(fCurrentRef, fDocumentCount);
710 		return true;
711 	}
712 	return false;
713 }
714 
715 
716 bool
717 ImageFileNavigator::NextPage()
718 {
719 	if (fDocumentIndex < fDocumentCount) {
720 		LoadImage(fCurrentRef, ++fDocumentIndex);
721 		return true;
722 	}
723 	return false;
724 }
725 
726 
727 bool
728 ImageFileNavigator::PreviousPage()
729 {
730 	if (fDocumentIndex > 1) {
731 		LoadImage(fCurrentRef, --fDocumentIndex);
732 		return true;
733 	}
734 	return false;
735 }
736 
737 
738 bool
739 ImageFileNavigator::GoToPage(int32 page)
740 {
741 	if (page > 0 && page <= fDocumentCount && page != fDocumentIndex) {
742 		fDocumentIndex = page;
743 		LoadImage(fCurrentRef, fDocumentIndex);
744 		return true;
745 	}
746 	return false;
747 }
748 
749 
750 void
751 ImageFileNavigator::FirstFile()
752 {
753 	_LoadNextImage(true, true);
754 }
755 
756 
757 void
758 ImageFileNavigator::NextFile()
759 {
760 	_LoadNextImage(true, false);
761 }
762 
763 
764 void
765 ImageFileNavigator::PreviousFile()
766 {
767 	_LoadNextImage(false, false);
768 }
769 
770 
771 bool
772 ImageFileNavigator::HasNextFile()
773 {
774 	entry_ref ref;
775 	return fNavigator->FindNextImage(fCurrentRef, ref, true, false);
776 }
777 
778 
779 bool
780 ImageFileNavigator::HasPreviousFile()
781 {
782 	entry_ref ref;
783 	return fNavigator->FindNextImage(fCurrentRef, ref, false, false);
784 }
785 
786 
787 /*!	Moves the current file into the trash.
788 	Returns true if a new file is being loaded, false if not.
789 */
790 bool
791 ImageFileNavigator::MoveFileToTrash()
792 {
793 	entry_ref nextRef;
794 	if (!fNavigator->FindNextImage(fCurrentRef, nextRef, true, false)
795 		&& !fNavigator->FindNextImage(fCurrentRef, nextRef, false, false))
796 		nextRef.device = -1;
797 
798 	// Move image to Trash
799 	BMessage trash(BPrivate::kMoveToTrash);
800 	trash.AddRef("refs", &fCurrentRef);
801 
802 	// We create our own messenger because the member fTrackerMessenger
803 	// could be invalid
804 	BMessenger tracker(kTrackerSignature);
805 	if (tracker.SendMessage(&trash) != B_OK)
806 		return true;
807 
808 	if (nextRef.device != -1 && LoadImage(nextRef) == B_OK) {
809 		fNavigator->UpdateSelection(nextRef);
810 		return true;
811 	}
812 
813 	return false;
814 }
815 
816 
817 // #pragma mark -
818 
819 
820 status_t
821 ImageFileNavigator::_LoadNextImage(bool next, bool rewind)
822 {
823 	entry_ref currentRef = fCurrentRef;
824 	entry_ref ref;
825 	if (fNavigator->FindNextImage(currentRef, ref, next, rewind)) {
826 		// Keep trying to load images until:
827 		// 1. The image loads successfully
828 		// 2. The last file in the directory is found (for find next or find
829 		//    first)
830 		// 3. The first file in the directory is found (for find prev)
831 		// 4. The call to _FindNextImage fails for any other reason
832 		while (LoadImage(ref) != B_OK) {
833 			currentRef = ref;
834 			if (!fNavigator->FindNextImage(currentRef, ref, next, false))
835 				return B_ENTRY_NOT_FOUND;
836 		}
837 		fNavigator->UpdateSelection(fCurrentRef);
838 		return B_OK;
839 	}
840 	return B_ENTRY_NOT_FOUND;
841 }
842 
843 
844