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