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