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