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