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
entry_ref_is_file(const entry_ref & ref)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
Navigator()132 Navigator::Navigator()
133 {
134 }
135
136
~Navigator()137 Navigator::~Navigator()
138 {
139 }
140
141
142 bool
IsImage(const entry_ref & ref)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
TrackerNavigator(const BMessenger & trackerMessenger)166 TrackerNavigator::TrackerNavigator(const BMessenger& trackerMessenger)
167 :
168 fTrackerMessenger(trackerMessenger)
169 {
170 }
171
172
~TrackerNavigator()173 TrackerNavigator::~TrackerNavigator()
174 {
175 }
176
177
178 bool
FindNextImage(const entry_ref & currentRef,entry_ref & ref,bool next,bool rewind)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
UpdateSelection(const entry_ref & ref)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
IsValid()238 TrackerNavigator::IsValid()
239 {
240 return fTrackerMessenger.IsValid();
241 }
242
243
244 // #pragma mark -
245
246
FolderNavigator(entry_ref & ref)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
~FolderNavigator()271 FolderNavigator::~FolderNavigator()
272 {
273 }
274
275
276 bool
FindNextImage(const entry_ref & currentRef,entry_ref & nextRef,bool next,bool rewind)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
UpdateSelection(const entry_ref & ref)311 FolderNavigator::UpdateSelection(const entry_ref& ref)
312 {
313 // nothing to do for us here
314 }
315
316
317 void
_BuildEntryList()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
_CompareRefs(const entry_ref * refA,const entry_ref * refB)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
AutoAdjustingNavigator(entry_ref & ref,const BMessenger & trackerMessenger)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
~AutoAdjustingNavigator()362 AutoAdjustingNavigator::~AutoAdjustingNavigator()
363 {
364 delete fTrackerNavigator;
365 delete fFolderNavigator;
366 }
367
368
369 bool
FindNextImage(const entry_ref & currentRef,entry_ref & nextRef,bool next,bool rewind)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
UpdateSelection(const entry_ref & ref)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
_CheckForTracker(const entry_ref & ref)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
ImageFileNavigator(const entry_ref & ref,const BMessenger & trackerMessenger)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
~ImageFileNavigator()433 ImageFileNavigator::~ImageFileNavigator()
434 {
435 delete fNavigator;
436 }
437
438
439 void
SetTo(const entry_ref & ref,int32 page,int32 pageCount)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
CurrentPage()449 ImageFileNavigator::CurrentPage()
450 {
451 return fDocumentIndex;
452 }
453
454
455 int32
PageCount()456 ImageFileNavigator::PageCount()
457 {
458 return fDocumentCount;
459 }
460
461
462 bool
FirstPage()463 ImageFileNavigator::FirstPage()
464 {
465 if (fDocumentIndex != 1) {
466 fDocumentIndex = 1;
467 return true;
468 }
469 return false;
470 }
471
472
473 bool
LastPage()474 ImageFileNavigator::LastPage()
475 {
476 if (fDocumentIndex != fDocumentCount) {
477 fDocumentIndex = fDocumentCount;
478 return true;
479 }
480 return false;
481 }
482
483
484 bool
NextPage()485 ImageFileNavigator::NextPage()
486 {
487 if (fDocumentIndex < fDocumentCount) {
488 fDocumentIndex++;
489 return true;
490 }
491 return false;
492 }
493
494
495 bool
PreviousPage()496 ImageFileNavigator::PreviousPage()
497 {
498 if (fDocumentIndex > 1) {
499 fDocumentIndex--;
500 return true;
501 }
502 return false;
503 }
504
505
506 bool
HasNextPage()507 ImageFileNavigator::HasNextPage()
508 {
509 return fDocumentIndex < fDocumentCount;
510 }
511
512
513 bool
HasPreviousPage()514 ImageFileNavigator::HasPreviousPage()
515 {
516 return fDocumentIndex > 1;
517 }
518
519
520 bool
GoToPage(int32 page)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
FirstFile()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
NextFile()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
PreviousFile()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
HasNextFile()574 ImageFileNavigator::HasNextFile()
575 {
576 entry_ref ref;
577 return fNavigator->FindNextImage(fCurrentRef, ref, true, false);
578 }
579
580
581 bool
HasPreviousFile()582 ImageFileNavigator::HasPreviousFile()
583 {
584 entry_ref ref;
585 return fNavigator->FindNextImage(fCurrentRef, ref, false, false);
586 }
587
588
589 bool
GetNextFile(const entry_ref & ref,entry_ref & nextRef)590 ImageFileNavigator::GetNextFile(const entry_ref& ref, entry_ref& nextRef)
591 {
592 return fNavigator->FindNextImage(ref, nextRef, true, false);
593 }
594
595
596 bool
GetPreviousFile(const entry_ref & ref,entry_ref & previousRef)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
MoveFileToTrash()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