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