xref: /haiku/src/apps/showimage/ImageFileNavigator.cpp (revision 68ea01249e1e2088933cb12f9c28d4e5c5d1c9ef)
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 <new>
22 
23 #include <stdio.h>
24 
25 #include <BitmapStream.h>
26 #include <Directory.h>
27 #include <Entry.h>
28 #include <File.h>
29 #include <NaturalCompare.h>
30 #include <ObjectList.h>
31 #include <TranslatorRoster.h>
32 
33 #include <tracker_private.h>
34 
35 #include "ProgressWindow.h"
36 #include "ShowImageConstants.h"
37 
38 
39 class Navigator {
40 public:
41 								Navigator();
42 	virtual						~Navigator();
43 
44 	virtual	bool				FindNextImage(const entry_ref& currentRef,
45 									entry_ref& ref, bool next, bool rewind) = 0;
46 	virtual void				UpdateSelection(const entry_ref& ref) = 0;
47 
48 protected:
49 			bool				IsImage(const entry_ref& ref);
50 };
51 
52 
53 // Navigation to the next/previous image file is based on
54 // communication with Tracker, the folder containing the current
55 // image needs to be open for this to work. The routine first tries
56 // to find the next candidate file, then tries to load it as image.
57 // As long as loading fails, the operation is repeated for the next
58 // candidate file.
59 
60 class TrackerNavigator : public Navigator {
61 public:
62 								TrackerNavigator(
63 									const BMessenger& trackerMessenger);
64 	virtual						~TrackerNavigator();
65 
66 	virtual	bool				FindNextImage(const entry_ref& currentRef,
67 									entry_ref& ref, bool next, bool rewind);
68 	virtual void				UpdateSelection(const entry_ref& ref);
69 
70 			bool				IsValid();
71 
72 private:
73 			BMessenger			fTrackerMessenger;
74 				// of the window that this was launched from
75 };
76 
77 
78 class FolderNavigator : public Navigator {
79 public:
80 								FolderNavigator(entry_ref& ref);
81 	virtual						~FolderNavigator();
82 
83 	virtual	bool				FindNextImage(const entry_ref& currentRef,
84 									entry_ref& ref, bool next, bool rewind);
85 	virtual void				UpdateSelection(const entry_ref& ref);
86 
87 private:
88 			void				_BuildEntryList();
89 	static	int					_CompareRefs(const entry_ref* refA,
90 									const entry_ref* refB);
91 
92 private:
93 			BDirectory			fFolder;
94 			BObjectList<entry_ref> fEntries;
95 };
96 
97 
98 // This class handles the case of the user closing the Tracker window after
99 // opening ShowImage from that window.
100 class AutoAdjustingNavigator : public Navigator {
101 public:
102 								AutoAdjustingNavigator(entry_ref& ref,
103 									const BMessenger& trackerMessenger);
104 	virtual						~AutoAdjustingNavigator();
105 
106 	virtual	bool				FindNextImage(const entry_ref& currentRef,
107 									entry_ref& ref, bool next, bool rewind);
108 	virtual void				UpdateSelection(const entry_ref& ref);
109 
110 private:
111 			bool				_CheckForTracker(const entry_ref& ref);
112 
113 			TrackerNavigator*	fTrackerNavigator;
114 			FolderNavigator*	fFolderNavigator;
115 };
116 
117 
118 static bool
119 entry_ref_is_file(const entry_ref& ref)
120 {
121 	BEntry entry(&ref, true);
122 	if (entry.InitCheck() != B_OK)
123 		return false;
124 
125 	return entry.IsFile();
126 }
127 
128 
129 // #pragma mark -
130 
131 
132 Navigator::Navigator()
133 {
134 }
135 
136 
137 Navigator::~Navigator()
138 {
139 }
140 
141 
142 bool
143 Navigator::IsImage(const entry_ref& ref)
144 {
145 	if (!entry_ref_is_file(ref))
146 		return false;
147 
148 	BFile file(&ref, B_READ_ONLY);
149 	if (file.InitCheck() != B_OK)
150 		return false;
151 
152 	BTranslatorRoster* roster = BTranslatorRoster::Default();
153 	if (roster == NULL)
154 		return false;
155 
156 	translator_info info;
157 	memset(&info, 0, sizeof(translator_info));
158 	return roster->Identify(&file, NULL, &info, 0, NULL,
159 		B_TRANSLATOR_BITMAP) == B_OK;
160 }
161 
162 
163 // #pragma mark -
164 
165 
166 TrackerNavigator::TrackerNavigator(const BMessenger& trackerMessenger)
167 	:
168 	fTrackerMessenger(trackerMessenger)
169 {
170 }
171 
172 
173 TrackerNavigator::~TrackerNavigator()
174 {
175 }
176 
177 
178 bool
179 TrackerNavigator::FindNextImage(const entry_ref& currentRef, entry_ref& ref,
180 	bool next, bool rewind)
181 {
182 	// Based on GetTrackerWindowFile function from BeMail
183 	if (!fTrackerMessenger.IsValid())
184 		return false;
185 
186 	// Ask the Tracker what the next/prev file in the window is.
187 	// Continue asking for the next reference until a valid
188 	// image is found.
189 	entry_ref nextRef = currentRef;
190 	bool foundRef = false;
191 	while (!foundRef) {
192 		BMessage request(B_GET_PROPERTY);
193 		BMessage specifier;
194 		if (rewind)
195 			specifier.what = B_DIRECT_SPECIFIER;
196 		else if (next)
197 			specifier.what = 'snxt';
198 		else
199 			specifier.what = 'sprv';
200 		specifier.AddString("property", "Entry");
201 		if (rewind) {
202 			// if rewinding, ask for the ref to the
203 			// first item in the directory
204 			specifier.AddInt32("data", 0);
205 		} else
206 			specifier.AddRef("data", &nextRef);
207 		request.AddSpecifier(&specifier);
208 
209 		BMessage reply;
210 		if (fTrackerMessenger.SendMessage(&request, &reply) != B_OK)
211 			return false;
212 		if (reply.FindRef("result", &nextRef) != B_OK)
213 			return false;
214 
215 		if (IsImage(nextRef))
216 			foundRef = true;
217 
218 		rewind = false;
219 			// stop asking for the first ref in the directory
220 	}
221 
222 	ref = nextRef;
223 	return foundRef;
224 }
225 
226 
227 void
228 TrackerNavigator::UpdateSelection(const entry_ref& ref)
229 {
230 	BMessage setSelection(B_SET_PROPERTY);
231 	setSelection.AddSpecifier("Selection");
232 	setSelection.AddRef("data", &ref);
233 	fTrackerMessenger.SendMessage(&setSelection);
234 }
235 
236 
237 bool
238 TrackerNavigator::IsValid()
239 {
240 	return fTrackerMessenger.IsValid();
241 }
242 
243 
244 // #pragma mark -
245 
246 
247 FolderNavigator::FolderNavigator(entry_ref& ref)
248 	:
249 	fEntries(true)
250 {
251 	BEntry entry(&ref);
252 	if (entry.IsDirectory())
253 		fFolder.SetTo(&ref);
254 	else {
255 		node_ref nodeRef;
256 		nodeRef.device = ref.device;
257 		nodeRef.node = ref.directory;
258 
259 		fFolder.SetTo(&nodeRef);
260 	}
261 
262 	_BuildEntryList();
263 
264 	// TODO: monitor the directory for changes, sort it naturally
265 
266 	if (entry.IsDirectory())
267 		FindNextImage(ref, ref, false, true);
268 }
269 
270 
271 FolderNavigator::~FolderNavigator()
272 {
273 }
274 
275 
276 bool
277 FolderNavigator::FindNextImage(const entry_ref& currentRef, entry_ref& nextRef,
278 	bool next, bool rewind)
279 {
280 	int32 index;
281 	if (rewind) {
282 		index = next ? fEntries.CountItems() : 0;
283 		next = !next;
284 	} else {
285 		index = fEntries.BinarySearchIndex(currentRef,
286 			&FolderNavigator::_CompareRefs);
287 		if (next)
288 			index++;
289 		else
290 			index--;
291 	}
292 
293 	while (index < fEntries.CountItems() && index >= 0) {
294 		const entry_ref& ref = *fEntries.ItemAt(index);
295 		if (IsImage(ref)) {
296 			nextRef = ref;
297 			return true;
298 		} else {
299 			// remove non-image entries
300 			delete fEntries.RemoveItemAt(index);
301 			if (!next)
302 				index--;
303 		}
304 	}
305 
306 	return false;
307 }
308 
309 
310 void
311 FolderNavigator::UpdateSelection(const entry_ref& ref)
312 {
313 	// nothing to do for us here
314 }
315 
316 
317 void
318 FolderNavigator::_BuildEntryList()
319 {
320 	fEntries.MakeEmpty();
321 	fFolder.Rewind();
322 
323 	while (true) {
324 		entry_ref* ref = new entry_ref();
325 		status_t status = fFolder.GetNextRef(ref);
326 		if (status != B_OK) {
327 			delete ref;
328 			break;
329 		}
330 
331 		fEntries.AddItem(ref);
332 	}
333 
334 	fEntries.SortItems(&FolderNavigator::_CompareRefs);
335 }
336 
337 
338 /*static*/ int
339 FolderNavigator::_CompareRefs(const entry_ref* refA, const entry_ref* refB)
340 {
341 	return BPrivate::NaturalCompare(refA->name, refB->name);
342 }
343 
344 
345 // #pragma mark -
346 
347 
348 AutoAdjustingNavigator::AutoAdjustingNavigator(entry_ref& ref,
349 	const BMessenger& trackerMessenger)
350 	:
351 	fTrackerNavigator(NULL),
352 	fFolderNavigator(NULL)
353 {
354 	// TODO: allow selecting a folder from Tracker as well!
355 	if (trackerMessenger.IsValid())
356 		fTrackerNavigator = new TrackerNavigator(trackerMessenger);
357 	else
358 		fFolderNavigator = new FolderNavigator(ref);
359 }
360 
361 
362 AutoAdjustingNavigator::~AutoAdjustingNavigator()
363 {
364 	delete fTrackerNavigator;
365 	delete fFolderNavigator;
366 }
367 
368 
369 bool
370 AutoAdjustingNavigator::FindNextImage(const entry_ref& currentRef,
371 	entry_ref& nextRef,	bool next, bool rewind)
372 {
373 	if (_CheckForTracker(currentRef))
374 		return fTrackerNavigator->FindNextImage(currentRef, nextRef, next,
375 			rewind);
376 
377 	if (fFolderNavigator != NULL)
378 		return fFolderNavigator->FindNextImage(currentRef, nextRef, next,
379 			rewind);
380 
381 	return false;
382 }
383 
384 
385 void
386 AutoAdjustingNavigator::UpdateSelection(const entry_ref& ref)
387 {
388 	if (_CheckForTracker(ref)) {
389 		fTrackerNavigator->UpdateSelection(ref);
390 		return;
391 	}
392 
393 	if (fFolderNavigator != NULL)
394 		fFolderNavigator->UpdateSelection(ref);
395 }
396 
397 
398 bool
399 AutoAdjustingNavigator::_CheckForTracker(const entry_ref& ref)
400 {
401 	if (fTrackerNavigator != NULL) {
402 		if (fTrackerNavigator->IsValid())
403 			return true;
404 		else {
405 			delete fTrackerNavigator;
406 			fTrackerNavigator = NULL;
407 
408 			// If for some reason we already have one
409 			delete fFolderNavigator;
410 			entry_ref currentRef = ref;
411 			fFolderNavigator = new FolderNavigator(currentRef);
412 		}
413 	}
414 
415 	return false;
416 }
417 
418 
419 // #pragma mark -
420 
421 
422 ImageFileNavigator::ImageFileNavigator(const entry_ref& ref,
423 	const BMessenger& trackerMessenger)
424 	:
425 	fCurrentRef(ref),
426 	fDocumentIndex(1),
427 	fDocumentCount(1)
428 {
429 	fNavigator = new AutoAdjustingNavigator(fCurrentRef, trackerMessenger);
430 }
431 
432 
433 ImageFileNavigator::~ImageFileNavigator()
434 {
435 	delete fNavigator;
436 }
437 
438 
439 void
440 ImageFileNavigator::SetTo(const entry_ref& ref, int32 page, int32 pageCount)
441 {
442 	fCurrentRef = ref;
443 	fDocumentIndex = page;
444 	fDocumentCount = pageCount;
445 }
446 
447 
448 int32
449 ImageFileNavigator::CurrentPage()
450 {
451 	return fDocumentIndex;
452 }
453 
454 
455 int32
456 ImageFileNavigator::PageCount()
457 {
458 	return fDocumentCount;
459 }
460 
461 
462 bool
463 ImageFileNavigator::FirstPage()
464 {
465 	if (fDocumentIndex != 1) {
466 		fDocumentIndex = 1;
467 		return true;
468 	}
469 	return false;
470 }
471 
472 
473 bool
474 ImageFileNavigator::LastPage()
475 {
476 	if (fDocumentIndex != fDocumentCount) {
477 		fDocumentIndex = fDocumentCount;
478 		return true;
479 	}
480 	return false;
481 }
482 
483 
484 bool
485 ImageFileNavigator::NextPage()
486 {
487 	if (fDocumentIndex < fDocumentCount) {
488 		fDocumentIndex++;
489 		return true;
490 	}
491 	return false;
492 }
493 
494 
495 bool
496 ImageFileNavigator::PreviousPage()
497 {
498 	if (fDocumentIndex > 1) {
499 		fDocumentIndex--;
500 		return true;
501 	}
502 	return false;
503 }
504 
505 
506 bool
507 ImageFileNavigator::HasNextPage()
508 {
509 	return fDocumentIndex < fDocumentCount;
510 }
511 
512 
513 bool
514 ImageFileNavigator::HasPreviousPage()
515 {
516 	return fDocumentIndex > 1;
517 }
518 
519 
520 bool
521 ImageFileNavigator::GoToPage(int32 page)
522 {
523 	if (page > 0 && page <= fDocumentCount && page != fDocumentIndex) {
524 		fDocumentIndex = page;
525 		return true;
526 	}
527 	return false;
528 }
529 
530 
531 bool
532 ImageFileNavigator::FirstFile()
533 {
534 	entry_ref ref;
535 	if (fNavigator->FindNextImage(fCurrentRef, ref, false, true)) {
536 		SetTo(ref, 1, 1);
537 		fNavigator->UpdateSelection(fCurrentRef);
538 		return true;
539 	}
540 
541 	return false;
542 }
543 
544 
545 bool
546 ImageFileNavigator::NextFile()
547 {
548 	entry_ref ref;
549 	if (fNavigator->FindNextImage(fCurrentRef, ref, true, false)) {
550 		SetTo(ref, 1, 1);
551 		fNavigator->UpdateSelection(fCurrentRef);
552 		return true;
553 	}
554 
555 	return false;
556 }
557 
558 
559 bool
560 ImageFileNavigator::PreviousFile()
561 {
562 	entry_ref ref;
563 	if (fNavigator->FindNextImage(fCurrentRef, ref, false, false)) {
564 		SetTo(ref, 1, 1);
565 		fNavigator->UpdateSelection(fCurrentRef);
566 		return true;
567 	}
568 
569 	return false;
570 }
571 
572 
573 bool
574 ImageFileNavigator::HasNextFile()
575 {
576 	entry_ref ref;
577 	return fNavigator->FindNextImage(fCurrentRef, ref, true, false);
578 }
579 
580 
581 bool
582 ImageFileNavigator::HasPreviousFile()
583 {
584 	entry_ref ref;
585 	return fNavigator->FindNextImage(fCurrentRef, ref, false, false);
586 }
587 
588 
589 bool
590 ImageFileNavigator::GetNextFile(const entry_ref& ref, entry_ref& nextRef)
591 {
592 	return fNavigator->FindNextImage(ref, nextRef, true, false);
593 }
594 
595 
596 bool
597 ImageFileNavigator::GetPreviousFile(const entry_ref& ref,
598 	entry_ref& previousRef)
599 {
600 	return fNavigator->FindNextImage(ref, previousRef, false, false);
601 }
602 
603 
604 /*!	Moves the current file into the trash.
605 	Returns true if a new file should be loaded, false if not.
606 */
607 bool
608 ImageFileNavigator::MoveFileToTrash()
609 {
610 	entry_ref nextRef;
611 	if (!fNavigator->FindNextImage(fCurrentRef, nextRef, true, false)
612 		&& !fNavigator->FindNextImage(fCurrentRef, nextRef, false, false))
613 		nextRef.device = -1;
614 
615 	// Move image to Trash
616 	BMessage trash(BPrivate::kMoveToTrash);
617 	trash.AddRef("refs", &fCurrentRef);
618 
619 	// We create our own messenger because the member fTrackerMessenger
620 	// could be invalid
621 	BMessenger tracker(kTrackerSignature);
622 	if (tracker.SendMessage(&trash) != B_OK)
623 		return false;
624 
625 	if (nextRef.device != -1) {
626 		SetTo(nextRef, 1, 1);
627 		return true;
628 	}
629 
630 	return false;
631 }
632