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
39class Navigator {
40public:
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
48protected:
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
60class TrackerNavigator : public Navigator {
61public:
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
72private:
73			BMessenger			fTrackerMessenger;
74				// of the window that this was launched from
75};
76
77
78class FolderNavigator : public Navigator {
79public:
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
87private:
88			void				_BuildEntryList();
89	static	int					_CompareRefs(const entry_ref* refA,
90									const entry_ref* refB);
91
92private:
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.
100class AutoAdjustingNavigator : public Navigator {
101public:
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
110private:
111			bool				_CheckForTracker(const entry_ref& ref);
112
113			TrackerNavigator*	fTrackerNavigator;
114			FolderNavigator*	fFolderNavigator;
115};
116
117
118static bool
119entry_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
132Navigator::Navigator()
133{
134}
135
136
137Navigator::~Navigator()
138{
139}
140
141
142bool
143Navigator::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
166TrackerNavigator::TrackerNavigator(const BMessenger& trackerMessenger)
167	:
168	fTrackerMessenger(trackerMessenger)
169{
170}
171
172
173TrackerNavigator::~TrackerNavigator()
174{
175}
176
177
178bool
179TrackerNavigator::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
227void
228TrackerNavigator::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
237bool
238TrackerNavigator::IsValid()
239{
240	return fTrackerMessenger.IsValid();
241}
242
243
244// #pragma mark -
245
246
247FolderNavigator::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
271FolderNavigator::~FolderNavigator()
272{
273}
274
275
276bool
277FolderNavigator::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
310void
311FolderNavigator::UpdateSelection(const entry_ref& ref)
312{
313	// nothing to do for us here
314}
315
316
317void
318FolderNavigator::_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
339FolderNavigator::_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
348AutoAdjustingNavigator::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
362AutoAdjustingNavigator::~AutoAdjustingNavigator()
363{
364	delete fTrackerNavigator;
365	delete fFolderNavigator;
366}
367
368
369bool
370AutoAdjustingNavigator::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
385void
386AutoAdjustingNavigator::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
398bool
399AutoAdjustingNavigator::_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
422ImageFileNavigator::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
433ImageFileNavigator::~ImageFileNavigator()
434{
435	delete fNavigator;
436}
437
438
439void
440ImageFileNavigator::SetTo(const entry_ref& ref, int32 page, int32 pageCount)
441{
442	fCurrentRef = ref;
443	fDocumentIndex = page;
444	fDocumentCount = pageCount;
445}
446
447
448int32
449ImageFileNavigator::CurrentPage()
450{
451	return fDocumentIndex;
452}
453
454
455int32
456ImageFileNavigator::PageCount()
457{
458	return fDocumentCount;
459}
460
461
462bool
463ImageFileNavigator::FirstPage()
464{
465	if (fDocumentIndex != 1) {
466		fDocumentIndex = 1;
467		return true;
468	}
469	return false;
470}
471
472
473bool
474ImageFileNavigator::LastPage()
475{
476	if (fDocumentIndex != fDocumentCount) {
477		fDocumentIndex = fDocumentCount;
478		return true;
479	}
480	return false;
481}
482
483
484bool
485ImageFileNavigator::NextPage()
486{
487	if (fDocumentIndex < fDocumentCount) {
488		fDocumentIndex++;
489		return true;
490	}
491	return false;
492}
493
494
495bool
496ImageFileNavigator::PreviousPage()
497{
498	if (fDocumentIndex > 1) {
499		fDocumentIndex--;
500		return true;
501	}
502	return false;
503}
504
505
506bool
507ImageFileNavigator::HasNextPage()
508{
509	return fDocumentIndex < fDocumentCount;
510}
511
512
513bool
514ImageFileNavigator::HasPreviousPage()
515{
516	return fDocumentIndex > 1;
517}
518
519
520bool
521ImageFileNavigator::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
531bool
532ImageFileNavigator::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
545bool
546ImageFileNavigator::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
559bool
560ImageFileNavigator::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
573bool
574ImageFileNavigator::HasNextFile()
575{
576	entry_ref ref;
577	return fNavigator->FindNextImage(fCurrentRef, ref, true, false);
578}
579
580
581bool
582ImageFileNavigator::HasPreviousFile()
583{
584	entry_ref ref;
585	return fNavigator->FindNextImage(fCurrentRef, ref, false, false);
586}
587
588
589bool
590ImageFileNavigator::GetNextFile(const entry_ref& ref, entry_ref& nextRef)
591{
592	return fNavigator->FindNextImage(ref, nextRef, true, false);
593}
594
595
596bool
597ImageFileNavigator::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*/
607bool
608ImageFileNavigator::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