1 /* 2 Open Tracker License 3 4 Terms and Conditions 5 6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved. 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy of 9 this software and associated documentation files (the "Software"), to deal in 10 the Software without restriction, including without limitation the rights to 11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 12 of the Software, and to permit persons to whom the Software is furnished to do 13 so, subject to the following conditions: 14 15 The above copyright notice and this permission notice applies to all licensees 16 and shall be included in all copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION 23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 25 Except as contained in this notice, the name of Be Incorporated shall not be 26 used in advertising or otherwise to promote the sale, use or other dealings in 27 this Software without prior written authorization from Be Incorporated. 28 29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks 30 of Be Incorporated in the United States and other countries. Other brand product 31 names are registered trademarks or trademarks of their respective holders. 32 All rights reserved. 33 */ 34 35 36 #include "PoseView.h" 37 38 #include <algorithm> 39 #include <functional> 40 #include <map> 41 42 #include <ctype.h> 43 #include <errno.h> 44 #include <float.h> 45 #include <stdlib.h> 46 #include <strings.h> 47 48 #include <compat/sys/stat.h> 49 50 #include <Alert.h> 51 #include <Application.h> 52 #include <Catalog.h> 53 #include <Clipboard.h> 54 #include <Debug.h> 55 #include <Dragger.h> 56 #include <fs_attr.h> 57 #include <fs_info.h> 58 #include <Screen.h> 59 #include <Query.h> 60 #include <List.h> 61 #include <Locale.h> 62 #include <LongAndDragTrackingFilter.h> 63 #include <MenuItem.h> 64 #include <NodeMonitor.h> 65 #include <Path.h> 66 #include <StopWatch.h> 67 #include <String.h> 68 #include <SymLink.h> 69 #include <TextView.h> 70 #include <VolumeRoster.h> 71 #include <Volume.h> 72 #include <Window.h> 73 74 #include <ObjectListPrivate.h> 75 #include <PathMonitor.h> 76 77 #include "Attributes.h" 78 #include "AutoLock.h" 79 #include "BackgroundImage.h" 80 #include "Bitmaps.h" 81 #include "Commands.h" 82 #include "CountView.h" 83 #include "DeskWindow.h" 84 #include "DesktopPoseView.h" 85 #include "FilePanelPriv.h" 86 #include "FSClipboard.h" 87 #include "FSUtils.h" 88 #include "FunctionObject.h" 89 #include "MimeTypes.h" 90 #include "Navigator.h" 91 #include "Pose.h" 92 #include "InfoWindow.h" 93 #include "Tests.h" 94 #include "Thread.h" 95 #include "Tracker.h" 96 #include "TrackerString.h" 97 #include "WidgetAttributeText.h" 98 #include "WidthBuffer.h" 99 100 101 #undef B_TRANSLATION_CONTEXT 102 #define B_TRANSLATION_CONTEXT "PoseView" 103 104 105 const float kDoubleClickTresh = 6; 106 107 const uint32 kAddNewPoses = 'Tanp'; 108 const uint32 kAddPosesCompleted = 'Tapc'; 109 const int32 kMaxAddPosesChunk = 50; 110 const uint32 kMsgMouseDragged = 'Mdrg'; 111 const uint32 kMsgMouseLongDown = 'Mold'; 112 113 const int32 kRoomForLine = 2; 114 115 const int32 kMenuTrackMargin = 20; 116 117 const float kSlowScrollBucket = 30; 118 const float kBorderHeight = 20; 119 120 enum { 121 kAutoScrollOff, 122 kWaitForTransition, 123 kDelayAutoScroll, 124 kAutoScrollOn 125 }; 126 127 enum { 128 kWasDragged, 129 kContextMenuShown, 130 kNotDragged 131 }; 132 133 enum { 134 kInsertAtFront, 135 kInsertAfter 136 }; 137 138 const BPoint kTransparentDragThreshold(256, 192); 139 // maximum size of the transparent drag bitmap, use a drag rect 140 // if larger in any direction 141 142 struct attr_column_relation { 143 uint32 attrHash; 144 int32 fieldMask; 145 }; 146 147 static struct attr_column_relation sAttrColumnMap[] = { 148 { AttrHashString(kAttrStatModified, B_TIME_TYPE), 149 B_STAT_MODIFICATION_TIME }, 150 { AttrHashString(kAttrStatSize, B_OFF_T_TYPE), 151 B_STAT_SIZE }, 152 { AttrHashString(kAttrStatCreated, B_TIME_TYPE), 153 B_STAT_CREATION_TIME }, 154 { AttrHashString(kAttrStatMode, B_STRING_TYPE), 155 B_STAT_MODE } 156 }; 157 158 struct AddPosesResult { 159 ~AddPosesResult(); 160 void ReleaseModels(); 161 162 Model* fModels[kMaxAddPosesChunk]; 163 PoseInfo fPoseInfos[kMaxAddPosesChunk]; 164 int32 fCount; 165 }; 166 167 168 AddPosesResult::~AddPosesResult(void) 169 { 170 for (int32 i = 0; i < fCount; i++) 171 delete fModels[i]; 172 } 173 174 175 void 176 AddPosesResult::ReleaseModels(void) 177 { 178 for (int32 i = 0; i < kMaxAddPosesChunk; i++) 179 fModels[i] = NULL; 180 } 181 182 183 static BPose* 184 BSearch(PoseList* table, const BPose* key, BPoseView* view, 185 int (*cmp)(const BPose*, const BPose*, BPoseView*), 186 bool returnClosest = true); 187 188 static int 189 PoseCompareAddWidget(const BPose* p1, const BPose* p2, BPoseView* view); 190 191 192 static bool 193 OneMatches(BPose* pose, BPoseView*, void* castToPose) 194 { 195 return pose == (const BPose*)castToPose; 196 } 197 198 199 static void 200 CopySelectionListToEntryRefList(const PoseList* original, 201 BObjectList<entry_ref>* copy) 202 { 203 int32 count = original->CountItems(); 204 for (int32 index = 0; index < count; index++) { 205 copy->AddItem(new entry_ref(*(original->ItemAt( 206 index)->TargetModel()->EntryRef()))); 207 } 208 } 209 210 211 // #pragma mark - BPoseView 212 213 214 BPoseView::BPoseView(Model* model, uint32 viewMode) 215 : 216 BView("PoseView", B_WILL_DRAW | B_PULSE_NEEDED), 217 fIsDrawingSelectionRect(false), 218 fHScrollBar(NULL), 219 fVScrollBar(NULL), 220 fModel(model), 221 fActivePose(NULL), 222 fExtent(INT32_MAX, INT32_MAX, INT32_MIN, INT32_MIN), 223 fPoseList(new PoseList(40, true)), 224 fFilteredPoseList(new PoseList()), 225 fVSPoseList(new PoseList()), 226 fSelectionList(new PoseList()), 227 fMimeTypesInSelectionCache(20, true), 228 fZombieList(new BObjectList<Model>(10, true)), 229 fColumnList(new BObjectList<BColumn>(4, true)), 230 fMimeTypeList(new BObjectList<BString>(10, true)), 231 fBrokenLinks(new BObjectList<Model>(10, false)), 232 fMimeTypeListIsDirty(false), 233 fViewState(new BViewState), 234 fStateNeedsSaving(false), 235 fCountView(NULL), 236 fListElemHeight(0.0f), 237 fIconPoseHeight(0.0f), 238 fDropTarget(NULL), 239 fAlreadySelectedDropTarget(NULL), 240 fSelectionHandler(be_app), 241 fLastClickPoint(INT32_MAX, INT32_MAX), 242 fLastClickButtons(0), 243 fLastClickedPose(NULL), 244 fLastExtent(INT32_MAX, INT32_MAX, INT32_MIN, INT32_MIN), 245 fTitleView(NULL), 246 fRefFilter(NULL), 247 fAutoScrollInc(20), 248 fAutoScrollState(kAutoScrollOff), 249 fWidgetTextOutline(false), 250 fSelectionPivotPose(NULL), 251 fRealPivotPose(NULL), 252 fKeyRunner(NULL), 253 fTrackRightMouseUp(false), 254 fSelectionVisible(true), 255 fMultipleSelection(true), 256 fDragEnabled(true), 257 fDropEnabled(true), 258 fSelectionRectEnabled(true), 259 fAlwaysAutoPlace(false), 260 fAllowPoseEditing(true), 261 fSelectionChangedHook(false), 262 fSavePoseLocations(true), 263 fShowHideSelection(true), 264 fOkToMapIcons(false), 265 fEnsurePosesVisible(false), 266 fShouldAutoScroll(true), 267 fIsDesktopWindow(false), 268 fIsWatchingDateFormatChange(false), 269 fHasPosesInClipboard(false), 270 fCursorCheck(false), 271 fFiltering(false), 272 fFilterStrings(4, true), 273 fLastFilterStringCount(1), 274 fLastFilterStringLength(0), 275 fLastKeyTime(0), 276 fLastDeskbarFrameCheckTime(LONGLONG_MIN), 277 fDeskbarFrame(0, 0, -1, -1), 278 fTextWidgetToCheck(NULL) 279 { 280 fViewState->SetViewMode(viewMode); 281 if (viewMode == kIconMode && fViewState->IconSize() == B_MINI_ICON) 282 fViewState->SetIconSize(B_LARGE_ICON); 283 284 fShowSelectionWhenInactive 285 = TrackerSettings().ShowSelectionWhenInactive(); 286 fTransparentSelection = TrackerSettings().TransparentSelection(); 287 fFilterStrings.AddItem(new BString("")); 288 } 289 290 291 BPoseView::~BPoseView() 292 { 293 delete fPoseList; 294 delete fFilteredPoseList; 295 delete fVSPoseList; 296 delete fColumnList; 297 delete fSelectionList; 298 delete fMimeTypeList; 299 delete fZombieList; 300 delete fViewState; 301 delete fModel; 302 delete fKeyRunner; 303 delete fBrokenLinks; 304 305 IconCache::sIconCache->Deleting(this); 306 } 307 308 309 void 310 BPoseView::Init(AttributeStreamNode* node) 311 { 312 RestoreState(node); 313 InitCommon(); 314 } 315 316 317 void 318 BPoseView::Init(const BMessage &message) 319 { 320 RestoreState(message); 321 InitCommon(); 322 } 323 324 325 void 326 BPoseView::InitCommon() 327 { 328 BContainerWindow* window = ContainerWindow(); 329 330 // Create the TitleView and CountView 331 fTitleView = new BTitleView(this); 332 if (ViewMode() != kListMode) 333 fTitleView->Hide(); 334 if (fHScrollBar != NULL) 335 fHScrollBar->SetTitleView(fTitleView); 336 337 fCountView = new BCountView(this); 338 339 BPoint origin; 340 if (ViewMode() == kListMode) 341 origin = fViewState->ListOrigin(); 342 else 343 origin = fViewState->IconOrigin(); 344 345 PinPointToValidRange(origin); 346 347 // init things related to laying out items 348 349 SetIconPoseHeight(); 350 GetLayoutInfo(ViewMode(), &fGrid, &fOffset); 351 ResetPosePlacementHint(); 352 353 if (ViewMode() == kListMode) 354 SetListElemHeight(); 355 356 DisableScrollBars(); 357 ScrollTo(origin); 358 UpdateScrollRange(); 359 SetScrollBarsTo(origin); 360 EnableScrollBars(); 361 362 StartWatching(); 363 // turn on volume node monitor, metamime monitor, etc. 364 365 // populate the window 366 if (window != NULL && window->IsTrash()) 367 AddTrashPoses(); 368 else 369 AddPoses(TargetModel()); 370 } 371 372 373 static int 374 CompareColumns(const BColumn* c1, const BColumn* c2) 375 { 376 if (c1->Offset() > c2->Offset()) 377 return 1; 378 else if (c1->Offset() < c2->Offset()) 379 return -1; 380 381 return 0; 382 } 383 384 385 void 386 BPoseView::RestoreColumnState(AttributeStreamNode* node) 387 { 388 fColumnList->MakeEmpty(); 389 if (node != NULL) { 390 const char* columnsAttr; 391 const char* columnsAttrForeign; 392 if (TargetModel() && TargetModel()->IsRoot()) { 393 columnsAttr = kAttrDisksColumns; 394 columnsAttrForeign = kAttrDisksColumnsForeign; 395 } else { 396 columnsAttr = kAttrColumns; 397 columnsAttrForeign = kAttrColumnsForeign; 398 } 399 400 bool wrongEndianness = false; 401 const char* name = columnsAttr; 402 size_t size = (size_t)node->Contains(name, B_RAW_TYPE); 403 if (size == 0) { 404 name = columnsAttrForeign; 405 wrongEndianness = true; 406 size = (size_t)node->Contains(name, B_RAW_TYPE); 407 } 408 409 if (size > 0 && size < 10000) { 410 // check for invalid sizes here to protect against 411 // munged attributes 412 char* buffer = new char[size]; 413 off_t result = node->Read(name, 0, B_RAW_TYPE, size, buffer); 414 if (result) { 415 BMallocIO stream; 416 stream.WriteAt(0, buffer, size); 417 stream.Seek(0, SEEK_SET); 418 419 // Clear old column list if neccessary 420 421 // Put items in the list in order so they can be checked 422 // for overlaps below. 423 BObjectList<BColumn> tempSortedList; 424 for (;;) { 425 BColumn* column = BColumn::InstantiateFromStream(&stream, 426 wrongEndianness); 427 if (column == NULL) 428 break; 429 tempSortedList.AddItem(column); 430 } 431 AddColumnList(&tempSortedList); 432 } 433 delete[] buffer; 434 } 435 } 436 437 SetUpDefaultColumnsIfNeeded(); 438 if (!ColumnFor(PrimarySort())) { 439 fViewState->SetPrimarySort(FirstColumn()->AttrHash()); 440 fViewState->SetPrimarySortType(FirstColumn()->AttrType()); 441 } 442 443 if (PrimarySort() == SecondarySort()) 444 fViewState->SetSecondarySort(0); 445 } 446 447 448 void 449 BPoseView::RestoreColumnState(const BMessage &message) 450 { 451 fColumnList->MakeEmpty(); 452 453 BObjectList<BColumn> tempSortedList; 454 for (int32 index = 0; ; index++) { 455 BColumn* column = BColumn::InstantiateFromMessage(message, index); 456 if (column == NULL) 457 break; 458 459 tempSortedList.AddItem(column); 460 } 461 462 AddColumnList(&tempSortedList); 463 464 SetUpDefaultColumnsIfNeeded(); 465 if (!ColumnFor(PrimarySort())) { 466 fViewState->SetPrimarySort(FirstColumn()->AttrHash()); 467 fViewState->SetPrimarySortType(FirstColumn()->AttrType()); 468 } 469 470 if (PrimarySort() == SecondarySort()) 471 fViewState->SetSecondarySort(0); 472 } 473 474 475 void 476 BPoseView::AddColumnList(BObjectList<BColumn>* list) 477 { 478 list->SortItems(&CompareColumns); 479 480 float nextLeftEdge = 0; 481 for (int32 columIndex = 0; columIndex < list->CountItems(); 482 columIndex++) { 483 BColumn* column = list->ItemAt(columIndex); 484 485 // Make sure that columns don't overlap 486 if (column->Offset() < nextLeftEdge) { 487 PRINT(("\t**Overlapped columns in archived column state\n")); 488 column->SetOffset(nextLeftEdge); 489 } 490 491 nextLeftEdge = column->Offset() + column->Width() 492 - kRoomForLine / 2.0f + kTitleColumnExtraMargin; 493 fColumnList->AddItem(column); 494 495 if (!IsWatchingDateFormatChange() 496 && column->AttrType() == B_TIME_TYPE) { 497 StartWatchDateFormatChange(); 498 } 499 } 500 } 501 502 503 void 504 BPoseView::RestoreState(AttributeStreamNode* node) 505 { 506 RestoreColumnState(node); 507 508 if (node != NULL) { 509 const char* viewStateAttr; 510 const char* viewStateAttrForeign; 511 512 if (TargetModel() && TargetModel()->IsRoot()) { 513 viewStateAttr = kAttrDisksViewState; 514 viewStateAttrForeign = kAttrDisksViewStateForeign; 515 } else { 516 viewStateAttr = ViewStateAttributeName(); 517 viewStateAttrForeign = ForeignViewStateAttributeName(); 518 } 519 520 bool wrongEndianness = false; 521 const char* name = viewStateAttr; 522 size_t size = (size_t)node->Contains(name, B_RAW_TYPE); 523 if (!size) { 524 name = viewStateAttrForeign; 525 wrongEndianness = true; 526 size = (size_t)node->Contains(name, B_RAW_TYPE); 527 } 528 529 if (size > 0 && size < 10000) { 530 // check for invalid sizes here to protect against 531 // munged attributes 532 char* buffer = new char[size]; 533 off_t result = node->Read(name, 0, B_RAW_TYPE, size, buffer); 534 if (result) { 535 BMallocIO stream; 536 stream.WriteAt(0, buffer, size); 537 stream.Seek(0, SEEK_SET); 538 BViewState* viewstate 539 = BViewState::InstantiateFromStream(&stream, 540 wrongEndianness); 541 if (viewstate) { 542 delete fViewState; 543 fViewState = viewstate; 544 } 545 } 546 delete[] buffer; 547 } 548 } 549 550 if (IsDesktopWindow() && ViewMode() == kListMode) { 551 // recover if desktop window view state set wrong 552 fViewState->SetViewMode(kIconMode); 553 } 554 } 555 556 557 void 558 BPoseView::RestoreState(const BMessage &message) 559 { 560 RestoreColumnState(message); 561 562 BViewState* viewstate = BViewState::InstantiateFromMessage(message); 563 if (viewstate != NULL) { 564 delete fViewState; 565 fViewState = viewstate; 566 } 567 568 if (IsDesktopWindow() && ViewMode() == kListMode) { 569 // recover if desktop window view state set wrong 570 fViewState->SetViewMode(kIconMode); 571 } 572 } 573 574 575 namespace BPrivate { 576 577 bool 578 ClearViewOriginOne(const char* DEBUG_ONLY(name), uint32 type, off_t size, 579 void* viewStateArchive, void*) 580 { 581 ASSERT(strcmp(name, kAttrViewState) == 0); 582 583 if (viewStateArchive == NULL) 584 return false; 585 586 if (type != B_RAW_TYPE) 587 return false; 588 589 BMallocIO stream; 590 stream.WriteAt(0, viewStateArchive, (size_t)size); 591 stream.Seek(0, SEEK_SET); 592 BViewState* viewstate = BViewState::InstantiateFromStream(&stream, false); 593 if (!viewstate) 594 return false; 595 596 // this is why we are here - zero out 597 viewstate->SetListOrigin(BPoint(0, 0)); 598 viewstate->SetIconOrigin(BPoint(0, 0)); 599 600 stream.Seek(0, SEEK_SET); 601 viewstate->ArchiveToStream(&stream); 602 stream.ReadAt(0, viewStateArchive, (size_t)size); 603 604 return true; 605 } 606 607 } // namespace BPrivate 608 609 610 void 611 BPoseView::SetUpDefaultColumnsIfNeeded() 612 { 613 // in case there were errors getting some columns 614 if (fColumnList->CountItems() != 0) 615 return; 616 617 fColumnList->AddItem(new BColumn(B_TRANSLATE("Name"), StartOffset(), 145, 618 B_ALIGN_LEFT, kAttrStatName, B_STRING_TYPE, true, true)); 619 fColumnList->AddItem(new BColumn(B_TRANSLATE("Size"), 200, 80, 620 B_ALIGN_RIGHT, kAttrStatSize, B_OFF_T_TYPE, true, false)); 621 fColumnList->AddItem(new BColumn(B_TRANSLATE("Modified"), 295, 150, 622 B_ALIGN_LEFT, kAttrStatModified, B_TIME_TYPE, true, false)); 623 624 if (!IsWatchingDateFormatChange()) 625 StartWatchDateFormatChange(); 626 } 627 628 629 const char* 630 BPoseView::ViewStateAttributeName() const 631 { 632 return IsDesktopView() ? kAttrDesktopViewState : kAttrViewState; 633 } 634 635 636 const char* 637 BPoseView::ForeignViewStateAttributeName() const 638 { 639 return IsDesktopView() ? kAttrDesktopViewStateForeign 640 : kAttrViewStateForeign; 641 } 642 643 644 void 645 BPoseView::SaveColumnState(AttributeStreamNode* node) 646 { 647 BMallocIO stream; 648 for (int32 index = 0; ; index++) { 649 const BColumn* column = ColumnAt(index); 650 if (column == NULL) 651 break; 652 column->ArchiveToStream(&stream); 653 } 654 655 const char* columnsAttr; 656 const char* columnsAttrForeign; 657 if (TargetModel() && TargetModel()->IsRoot()) { 658 columnsAttr = kAttrDisksColumns; 659 columnsAttrForeign = kAttrDisksColumnsForeign; 660 } else { 661 columnsAttr = kAttrColumns; 662 columnsAttrForeign = kAttrColumnsForeign; 663 } 664 665 node->Write(columnsAttr, columnsAttrForeign, B_RAW_TYPE, 666 stream.Position(), stream.Buffer()); 667 } 668 669 670 void 671 BPoseView::SaveColumnState(BMessage& message) const 672 { 673 for (int32 index = 0; ; index++) { 674 const BColumn* column = ColumnAt(index); 675 if (column == NULL) 676 break; 677 678 column->ArchiveToMessage(message); 679 } 680 } 681 682 683 void 684 BPoseView::SaveState(AttributeStreamNode* node) 685 { 686 SaveColumnState(node); 687 688 // save view state into object 689 BMallocIO stream; 690 691 stream.Seek(0, SEEK_SET); 692 fViewState->ArchiveToStream(&stream); 693 694 const char* viewStateAttr; 695 const char* viewStateAttrForeign; 696 if (TargetModel() != NULL && TargetModel()->IsRoot()) { 697 viewStateAttr = kAttrDisksViewState; 698 viewStateAttrForeign = kAttrDisksViewStateForeign; 699 } else { 700 viewStateAttr = ViewStateAttributeName(); 701 viewStateAttrForeign = ForeignViewStateAttributeName(); 702 } 703 704 node->Write(viewStateAttr, viewStateAttrForeign, B_RAW_TYPE, 705 stream.Position(), stream.Buffer()); 706 707 fStateNeedsSaving = false; 708 } 709 710 711 void 712 BPoseView::SaveState(BMessage& message) const 713 { 714 SaveColumnState(message); 715 fViewState->ArchiveToMessage(message); 716 } 717 718 719 float 720 BPoseView::StringWidth(const char* str) const 721 { 722 return BPrivate::gWidthBuffer->StringWidth(str, 0, (int32)strlen(str), 723 &sCurrentFont); 724 } 725 726 727 float 728 BPoseView::StringWidth(const char* str, int32 len) const 729 { 730 ASSERT(strlen(str) == (uint32)len); 731 732 return BPrivate::gWidthBuffer->StringWidth(str, 0, len, &sCurrentFont); 733 } 734 735 736 void 737 BPoseView::SavePoseLocations(BRect* frameIfDesktop) 738 { 739 PoseInfo poseInfo; 740 741 if (!fSavePoseLocations) 742 return; 743 744 ASSERT(Window()->IsLocked()); 745 746 Model* targetModel = TargetModel(); 747 ThrowOnAssert(targetModel != NULL); 748 749 BVolume volume(TargetModel()->NodeRef()->device); 750 if (volume.InitCheck() != B_OK) 751 return; 752 753 if (!targetModel->IsRoot() 754 && (volume.IsReadOnly() || !volume.KnowsAttr())) { 755 // check that we can write out attrs; Root should always work 756 // because it gets saved on the boot disk but the above checks 757 // will fail 758 return; 759 } 760 761 bool isDesktop = IsDesktopWindow() && (frameIfDesktop != NULL); 762 763 int32 count = fPoseList->CountItems(); 764 for (int32 index = 0; index < count; index++) { 765 BPose* pose = fPoseList->ItemAt(index); 766 if (pose->NeedsSaveLocation() && pose->HasLocation()) { 767 Model* model = pose->TargetModel(); 768 poseInfo.fInvisible = false; 769 770 if (model->IsRoot()) 771 poseInfo.fInitedDirectory = targetModel->NodeRef()->node; 772 else 773 poseInfo.fInitedDirectory = model->EntryRef()->directory; 774 775 poseInfo.fLocation = pose->Location(this); 776 777 ExtendedPoseInfo* extendedPoseInfo = NULL; 778 size_t extendedPoseInfoSize = 0; 779 ModelNodeLazyOpener opener(model, true); 780 781 if (isDesktop) { 782 opener.OpenNode(true); 783 // if saving desktop icons, save an extended pose info too 784 extendedPoseInfo = ReadExtendedPoseInfo(model); 785 // read the pre-existing one 786 787 if (!extendedPoseInfo) { 788 // don't have one yet, allocate one 789 size_t size = ExtendedPoseInfo::Size(1); 790 extendedPoseInfo = (ExtendedPoseInfo*) 791 new char [size]; 792 793 memset(extendedPoseInfo, 0, size); 794 extendedPoseInfo->fWorkspaces = 0xffffffff; 795 extendedPoseInfo->fInvisible = false; 796 extendedPoseInfo->fShowFromBootOnly = false; 797 extendedPoseInfo->fNumFrames = 0; 798 } 799 ASSERT(extendedPoseInfo); 800 801 extendedPoseInfo->SetLocationForFrame(pose->Location(this), 802 *frameIfDesktop); 803 extendedPoseInfoSize = extendedPoseInfo->Size(); 804 } 805 806 if (model->InitCheck() != B_OK) { 807 delete[] (char*)extendedPoseInfo; 808 continue; 809 } 810 811 ASSERT(model); 812 ASSERT(model->InitCheck() == B_OK); 813 // special handling for "root" disks icon 814 // and Trash pose on Desktop directory 815 bool isTrash = model->IsTrash() && IsDesktopView(); 816 if (model->IsRoot() || isTrash) { 817 BDirectory deskDir; 818 if (FSGetDeskDir(&deskDir) == B_OK) { 819 const char* poseInfoAttr = isTrash ? kAttrTrashPoseInfo 820 : kAttrDisksPoseInfo; 821 const char* poseInfoAttrForeign = isTrash 822 ? kAttrTrashPoseInfoForeign 823 : kAttrDisksPoseInfoForeign; 824 if (deskDir.WriteAttr(poseInfoAttr, B_RAW_TYPE, 0, 825 &poseInfo, sizeof(poseInfo)) == sizeof(poseInfo)) { 826 // nuke opposite endianness 827 deskDir.RemoveAttr(poseInfoAttrForeign); 828 } 829 830 if (!isTrash && isDesktop 831 && deskDir.WriteAttr(kAttrExtendedDisksPoseInfo, 832 B_RAW_TYPE, 0, extendedPoseInfo, extendedPoseInfoSize) 833 == (ssize_t)extendedPoseInfoSize) { 834 // nuke opposite endianness 835 deskDir.RemoveAttr(kAttrExtendedDisksPoseInfoForegin); 836 } 837 } 838 } else { 839 model->WriteAttrKillForeign(kAttrPoseInfo, 840 kAttrPoseInfoForeign, B_RAW_TYPE, 0, &poseInfo, 841 sizeof(poseInfo)); 842 843 if (isDesktop) { 844 model->WriteAttrKillForeign(kAttrExtendedPoseInfo, 845 kAttrExtendedPoseInfoForegin, 846 B_RAW_TYPE, 0, extendedPoseInfo, 847 extendedPoseInfoSize); 848 } 849 } 850 851 delete[] (char*)extendedPoseInfo; 852 // TODO: fix up this mess 853 } 854 } 855 } 856 857 858 void 859 BPoseView::StartWatching() 860 { 861 // watch volumes 862 TTracker::WatchNode(NULL, B_WATCH_MOUNT, this); 863 864 Model* targetModel = TargetModel(); 865 if (targetModel != NULL) 866 TTracker::WatchNode(targetModel->NodeRef(), B_WATCH_ATTR, this); 867 868 BMimeType::StartWatching(BMessenger(this)); 869 } 870 871 872 void 873 BPoseView::StopWatching() 874 { 875 stop_watching(this); 876 BMimeType::StopWatching(BMessenger(this)); 877 } 878 879 880 void 881 BPoseView::DetachedFromWindow() 882 { 883 if (fTitleView && !fTitleView->Window()) 884 delete fTitleView; 885 886 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 887 if (tracker != NULL && tracker->Lock()) { 888 tracker->StopWatching(this, kShowSelectionWhenInactiveChanged); 889 tracker->StopWatching(this, kTransparentSelectionChanged); 890 tracker->StopWatching(this, kSortFolderNamesFirstChanged); 891 tracker->StopWatching(this, kHideDotFilesChanged); 892 tracker->StopWatching(this, kTypeAheadFilteringChanged); 893 tracker->Unlock(); 894 } 895 896 StopWatching(); 897 CommitActivePose(); 898 SavePoseLocations(); 899 900 FSClipboardStopWatch(this); 901 } 902 903 904 void 905 BPoseView::Pulse() 906 { 907 BContainerWindow* window = ContainerWindow(); 908 if (window == NULL) 909 return; 910 911 window->PulseTaskLoop(); 912 // make sure task loop gets pulsed properly, if installed 913 914 // update item count view in window if necessary 915 UpdateCount(); 916 917 if (fAutoScrollState != kAutoScrollOff) 918 HandleAutoScroll(); 919 920 // do we need to update scrollbars? 921 BRect extent = Extent(); 922 if ((fLastExtent != extent) || (fLastLeftTop != LeftTop())) { 923 uint32 buttons; 924 BPoint mouse; 925 GetMouse(&mouse, &buttons); 926 if (buttons == 0) { 927 UpdateScrollRange(); 928 fLastExtent = extent; 929 fLastLeftTop = LeftTop(); 930 } 931 } 932 933 // do we have a TextWidget waiting for expiracy of its double-click 934 // check? 935 if (fTextWidgetToCheck != NULL) 936 fTextWidgetToCheck->CheckExpiration(); 937 } 938 939 940 void 941 BPoseView::ScrollTo(BPoint where) 942 { 943 _inherited::ScrollTo(where); 944 945 // keep the view state in sync 946 if (ViewMode() == kListMode) 947 fViewState->SetListOrigin(LeftTop()); 948 else 949 fViewState->SetIconOrigin(LeftTop()); 950 } 951 952 953 void 954 BPoseView::AttachedToWindow() 955 { 956 fIsDesktopWindow = dynamic_cast<BDeskWindow*>(Window()) != NULL; 957 if (fIsDesktopWindow) 958 AddFilter(new TPoseViewFilter(this)); 959 else { 960 SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR); 961 SetLowUIColor(ViewUIColor()); 962 } 963 964 AddFilter(new ShortcutFilter(B_RETURN, B_OPTION_KEY, kOpenSelection, 965 this)); 966 // add Option-Return as a shortcut filter because AddShortcut 967 // doesn't allow us to have shortcuts without Command yet 968 AddFilter(new ShortcutFilter(B_ESCAPE, 0, B_CANCEL, this)); 969 // Escape key, used to abort an on-going clipboard cut or filtering 970 AddFilter(new ShortcutFilter(B_ESCAPE, B_SHIFT_KEY, 971 kCancelSelectionToClipboard, this)); 972 // Escape + SHIFT will remove current selection from clipboard, 973 // or all poses from current folder if 0 selected 974 975 AddFilter(new LongAndDragTrackingFilter(kMsgMouseLongDown, 976 kMsgMouseDragged)); 977 978 fLastLeftTop = LeftTop(); 979 BFont font(be_plain_font); 980 font.SetSpacing(B_BITMAP_SPACING); 981 SetFont(&font); 982 GetFont(&sCurrentFont); 983 984 // static - init just once 985 if (sFontHeight == -1) { 986 font.GetHeight(&sFontInfo); 987 sFontHeight = sFontInfo.ascent + sFontInfo.descent 988 + sFontInfo.leading; 989 } 990 991 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 992 if (tracker != NULL && tracker->Lock()) { 993 tracker->StartWatching(this, kShowSelectionWhenInactiveChanged); 994 tracker->StartWatching(this, kTransparentSelectionChanged); 995 tracker->StartWatching(this, kSortFolderNamesFirstChanged); 996 tracker->StartWatching(this, kHideDotFilesChanged); 997 tracker->StartWatching(this, kTypeAheadFilteringChanged); 998 tracker->Unlock(); 999 } 1000 1001 FSClipboardStartWatch(this); 1002 } 1003 1004 1005 void 1006 BPoseView::SetIconPoseHeight() 1007 { 1008 switch (ViewMode()) { 1009 case kIconMode: 1010 // IconSize should already be set in MessageReceived() 1011 fIconPoseHeight = ceilf(IconSizeInt() + sFontHeight + 1); 1012 break; 1013 1014 case kMiniIconMode: 1015 fViewState->SetIconSize(B_MINI_ICON); 1016 fIconPoseHeight = ceilf(sFontHeight < 1017 IconSizeInt() ? IconSizeInt() : sFontHeight + 1); 1018 break; 1019 1020 case kListMode: 1021 default: 1022 { 1023 SetListElemHeight(); 1024 fIconPoseHeight = fListElemHeight; 1025 break; 1026 } 1027 } 1028 } 1029 1030 1031 void 1032 BPoseView::GetLayoutInfo(uint32 mode, BPoint* grid, BPoint* offset) const 1033 { 1034 switch (mode) { 1035 case kMiniIconMode: 1036 grid->Set(96, 20); 1037 offset->Set(10, 5); 1038 break; 1039 1040 case kIconMode: 1041 grid->Set(IconSizeInt() + 28, IconSizeInt() + 28); 1042 offset->Set(20, 20); 1043 break; 1044 1045 default: 1046 grid->Set(0, 0); 1047 offset->Set(5, 5); 1048 break; 1049 } 1050 } 1051 1052 1053 void 1054 BPoseView::ScrollView(int32 type) 1055 { 1056 if (fVScrollBar == NULL) 1057 return; 1058 1059 float max, min; 1060 fVScrollBar->GetSteps(&min, &max); 1061 1062 switch (type) { 1063 case B_HOME: 1064 fVScrollBar->SetValue(0); 1065 break; 1066 case B_END: 1067 fVScrollBar->SetValue(max); 1068 break; 1069 case B_PAGE_UP: 1070 fVScrollBar->SetValue(fVScrollBar->Value() - max); 1071 break; 1072 case B_PAGE_DOWN: 1073 fVScrollBar->SetValue(fVScrollBar->Value() + max); 1074 break; 1075 } 1076 } 1077 1078 1079 void 1080 BPoseView::MakeFocus(bool focused) 1081 { 1082 bool invalidate = false; 1083 if (focused != IsFocus()) 1084 invalidate = true; 1085 1086 _inherited::MakeFocus(focused); 1087 1088 if (invalidate) { 1089 BorderedView* view = dynamic_cast<BorderedView*>(Parent()); 1090 if (view != NULL) 1091 view->PoseViewFocused(focused); 1092 } 1093 } 1094 1095 1096 BSize 1097 BPoseView::MinSize() 1098 { 1099 // Between the BTitleView, BCountView, and scrollbars, 1100 // we don't need any extra room. 1101 return BSize(0, 0); 1102 } 1103 1104 1105 void 1106 BPoseView::WindowActivated(bool active) 1107 { 1108 if (!active) 1109 CommitActivePose(); 1110 1111 if (fShowHideSelection) 1112 ShowSelection(active); 1113 1114 if (active && ActivePose() == NULL && !IsFilePanel()) 1115 MakeFocus(); 1116 } 1117 1118 1119 void 1120 BPoseView::SetActivePose(BPose* pose) 1121 { 1122 if (pose != ActivePose()) { 1123 CommitActivePose(); 1124 fActivePose = pose; 1125 } 1126 } 1127 1128 1129 void 1130 BPoseView::CommitActivePose(bool saveChanges) 1131 { 1132 BPose* activePose = ActivePose(); 1133 if (activePose != NULL) { 1134 int32 index = fPoseList->IndexOf(ActivePose()); 1135 if (fFiltering) 1136 index = fFilteredPoseList->IndexOf(ActivePose()); 1137 1138 BPoint loc(0, index * fListElemHeight); 1139 if (ViewMode() != kListMode) 1140 loc = ActivePose()->Location(this); 1141 1142 activePose->Commit(saveChanges, loc, this, index); 1143 BPose* pose = fActivePose; 1144 fActivePose = NULL; 1145 if (fFiltering && !FilterPose(pose)) 1146 RemoveFilteredPose(pose, index); 1147 } 1148 } 1149 1150 1151 EntryListBase* 1152 BPoseView::InitDirentIterator(const entry_ref* ref) 1153 { 1154 // set up a directory iteration 1155 Model sourceModel(ref, false, true); 1156 if (sourceModel.InitCheck() != B_OK) 1157 return NULL; 1158 1159 ASSERT(!sourceModel.IsQuery()); 1160 ASSERT(!sourceModel.IsVirtualDirectory()); 1161 ASSERT(sourceModel.Node() != NULL); 1162 1163 BDirectory* directory = dynamic_cast<BDirectory*>(sourceModel.Node()); 1164 1165 ASSERT(directory != NULL); 1166 1167 if (directory == NULL) { 1168 HideBarberPole(); 1169 return NULL; 1170 } 1171 1172 EntryListBase* entryList = new CachedDirectoryEntryList(*directory); 1173 if (entryList->Rewind() != B_OK) { 1174 delete entryList; 1175 HideBarberPole(); 1176 return NULL; 1177 } 1178 1179 TTracker::WatchNode(sourceModel.NodeRef(), B_WATCH_DIRECTORY 1180 | B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR, this); 1181 1182 return entryList; 1183 } 1184 1185 1186 void 1187 BPoseView::ReturnDirentIterator(EntryListBase* iterator) 1188 { 1189 delete iterator; 1190 } 1191 1192 1193 uint32 1194 BPoseView::WatchNewNodeMask() 1195 { 1196 return B_WATCH_STAT | B_WATCH_INTERIM_STAT | B_WATCH_ATTR; 1197 } 1198 1199 1200 status_t 1201 BPoseView::WatchNewNode(const node_ref* item) 1202 { 1203 return WatchNewNode(item, WatchNewNodeMask(), BMessenger(this)); 1204 } 1205 1206 1207 status_t 1208 BPoseView::WatchNewNode(const node_ref* item, uint32 mask, BMessenger messenger) 1209 { 1210 status_t result = TTracker::WatchNode(item, mask, messenger); 1211 1212 #if DEBUG 1213 if (result != B_OK) 1214 PRINT(("failed to watch node %s\n", strerror(result))); 1215 #endif 1216 1217 return result; 1218 } 1219 1220 1221 struct AddPosesParams { 1222 BMessenger target; 1223 entry_ref ref; 1224 }; 1225 1226 1227 bool 1228 BPoseView::IsValidAddPosesThread(thread_id currentThread) const 1229 { 1230 return fAddPosesThreads.find(currentThread) != fAddPosesThreads.end(); 1231 } 1232 1233 1234 void 1235 BPoseView::AddPoses(Model* model) 1236 { 1237 // if model is zero, PoseView has other means of iterating through all 1238 // the entries that it adds 1239 if (model != NULL) { 1240 TrackerSettings settings; 1241 if (model->IsRoot()) { 1242 AddRootPoses(true, settings.MountSharedVolumesOntoDesktop()); 1243 return; 1244 } else if (IsDesktopView() 1245 && (settings.MountVolumesOntoDesktop() || settings.ShowDisksIcon() 1246 || (IsFilePanel() && settings.DesktopFilePanelRoot()))) 1247 AddRootPoses(true, settings.MountSharedVolumesOntoDesktop()); 1248 } 1249 1250 ShowBarberPole(); 1251 1252 AddPosesParams* params = new AddPosesParams(); 1253 BMessenger tmp(this); 1254 params->target = tmp; 1255 1256 if (model != NULL) 1257 params->ref = *model->EntryRef(); 1258 1259 thread_id addPosesThread = spawn_thread(&BPoseView::AddPosesTask, 1260 "add poses", B_DISPLAY_PRIORITY, params); 1261 1262 if (addPosesThread >= B_OK) { 1263 fAddPosesThreads.insert(addPosesThread); 1264 resume_thread(addPosesThread); 1265 } else 1266 delete params; 1267 } 1268 1269 1270 class AutoLockingMessenger { 1271 // Note: 1272 // this locker requires that you lock/unlock the messenger and associated 1273 // looper only through the autolocker interface, otherwise the hasLock 1274 // flag gets out of sync 1275 // 1276 // Also, this class represents the entire BMessenger, not just it's 1277 // autolocker (unlike MessengerAutoLocker) 1278 public: 1279 AutoLockingMessenger(const BMessenger &target, bool lockLater = false) 1280 : 1281 messenger(target), 1282 hasLock(false) 1283 { 1284 if (!lockLater) 1285 hasLock = messenger.LockTarget(); 1286 } 1287 1288 ~AutoLockingMessenger() 1289 { 1290 if (hasLock) { 1291 BLooper* looper; 1292 messenger.Target(&looper); 1293 ASSERT(looper->IsLocked()); 1294 looper->Unlock(); 1295 } 1296 } 1297 1298 bool Lock() 1299 { 1300 if (!hasLock) 1301 hasLock = messenger.LockTarget(); 1302 1303 return hasLock; 1304 } 1305 1306 bool IsLocked() const 1307 { 1308 return hasLock; 1309 } 1310 1311 void Unlock() 1312 { 1313 if (hasLock) { 1314 BLooper* looper; 1315 messenger.Target(&looper); 1316 ASSERT(looper); 1317 looper->Unlock(); 1318 hasLock = false; 1319 } 1320 } 1321 1322 BLooper* Looper() const 1323 { 1324 BLooper* looper; 1325 messenger.Target(&looper); 1326 return looper; 1327 } 1328 1329 BHandler* Handler() const 1330 { 1331 ASSERT(hasLock); 1332 return messenger.Target(0); 1333 } 1334 1335 BMessenger Target() const 1336 { 1337 return messenger; 1338 } 1339 1340 private: 1341 BMessenger messenger; 1342 bool hasLock; 1343 }; 1344 1345 1346 class failToLock { /* exception in AddPoses */ }; 1347 1348 1349 status_t 1350 BPoseView::AddPosesTask(void* castToParams) 1351 { 1352 // AddPosesTask reads a bunch of models and passes them off to 1353 // the pose placing and drawing routine. 1354 1355 AddPosesParams* params = (AddPosesParams*)castToParams; 1356 BMessenger target(params->target); 1357 entry_ref ref(params->ref); 1358 1359 delete params; 1360 1361 AutoLockingMessenger lock(target); 1362 1363 if (!lock.IsLocked()) 1364 return B_ERROR; 1365 1366 thread_id threadID = find_thread(NULL); 1367 1368 BPoseView* view = dynamic_cast<BPoseView*>(lock.Handler()); 1369 ThrowOnAssert(view != NULL); 1370 1371 BWindow* window = dynamic_cast<BWindow*>(lock.Looper()); 1372 ThrowOnAssert(window != NULL); 1373 1374 // allocate the iterator we will use for adding poses; this 1375 // can be a directory or any other collection of entry_refs, such 1376 // as results of a query; subclasses override this to provide 1377 // other than standard directory iterations 1378 EntryListBase* container = view->InitDirentIterator(&ref); 1379 if (container == NULL) { 1380 view->HideBarberPole(); 1381 return B_ERROR; 1382 } 1383 1384 AddPosesResult* posesResult = new AddPosesResult; 1385 posesResult->fCount = 0; 1386 int32 modelChunkIndex = -1; 1387 bigtime_t nextChunkTime = 0; 1388 uint32 watchMask = view->WatchNewNodeMask(); 1389 1390 bool hideDotFiles = TrackerSettings().HideDotFiles(); 1391 #if DEBUG 1392 for (int32 index = 0; index < kMaxAddPosesChunk; index++) 1393 posesResult->fModels[index] = (Model*)0xdeadbeef; 1394 #endif 1395 1396 try { 1397 for (;;) { 1398 lock.Unlock(); 1399 1400 status_t result = B_OK; 1401 char entBuf[1024]; 1402 dirent* eptr = (dirent*)entBuf; 1403 Model* model = 0; 1404 node_ref dirNode; 1405 node_ref itemNode; 1406 1407 int32 count = container->GetNextDirents(eptr, 1024, 1); 1408 if (count <= 0 && modelChunkIndex == -1) 1409 break; 1410 1411 if (count > 0) { 1412 ASSERT(count == 1); 1413 1414 if ((!hideDotFiles && (!strcmp(eptr->d_name, ".") 1415 || !strcmp(eptr->d_name, ".."))) 1416 || (hideDotFiles && eptr->d_name[0] == '.')) { 1417 continue; 1418 } 1419 1420 dirNode.device = eptr->d_pdev; 1421 dirNode.node = eptr->d_pino; 1422 itemNode.device = eptr->d_dev; 1423 itemNode.node = eptr->d_ino; 1424 1425 BPoseView::WatchNewNode(&itemNode, watchMask, lock.Target()); 1426 // have to node monitor ahead of time because Model will 1427 // cache up the file type and preferred app 1428 // OK to call when poseView is not locked 1429 model = new Model(&dirNode, &itemNode, eptr->d_name, false); 1430 result = model->InitCheck(); 1431 modelChunkIndex++; 1432 posesResult->fModels[modelChunkIndex] = model; 1433 } 1434 1435 // before we access the pose view, lock down the window 1436 1437 if (!lock.Lock()) { 1438 PRINT(("failed to lock\n")); 1439 posesResult->fCount = modelChunkIndex + 1; 1440 throw failToLock(); 1441 } 1442 1443 if (!view->IsValidAddPosesThread(threadID)) { 1444 // this handles the case of a file panel when the directory is 1445 // switched and an old AddPosesTask needs to die. 1446 // we might no longer be the current async thread 1447 // for this view - if not then we're done 1448 view->HideBarberPole(); 1449 1450 view->ReturnDirentIterator(container); 1451 container = NULL; 1452 1453 // for now use the same cleanup as failToLock does 1454 posesResult->fCount = modelChunkIndex + 1; 1455 throw failToLock(); 1456 } 1457 1458 if (count > 0) { 1459 // try to watch the model, no matter what 1460 1461 if (result != B_OK) { 1462 // failed to init pose, model is a zombie, add to zombie 1463 // list 1464 PRINT(("1 adding model %s to zombie list, error %s\n", 1465 model->Name(), strerror(model->InitCheck()))); 1466 view->fZombieList->AddItem(model); 1467 modelChunkIndex--; 1468 continue; 1469 } 1470 1471 view->ReadPoseInfo(model, 1472 &posesResult->fPoseInfos[modelChunkIndex]); 1473 1474 if (!PoseVisible(model, 1475 &posesResult->fPoseInfos[modelChunkIndex])) { 1476 modelChunkIndex--; 1477 continue; 1478 } 1479 1480 if (model->IsSymLink()) 1481 view->CreateSymlinkPoseTarget(model); 1482 } 1483 1484 bigtime_t now = system_time(); 1485 1486 if (count <= 0 || modelChunkIndex >= kMaxAddPosesChunk - 1 1487 || now > nextChunkTime) { 1488 // keep getting models until we get <kMaxAddPosesChunk> of them 1489 // or until 300000 runs out 1490 1491 ASSERT(modelChunkIndex >= 0); 1492 1493 // send of the created poses 1494 1495 posesResult->fCount = modelChunkIndex + 1; 1496 BMessage creationData(kAddNewPoses); 1497 creationData.AddPointer("currentPoses", posesResult); 1498 creationData.AddRef("ref", &ref); 1499 1500 lock.Target().SendMessage(&creationData); 1501 1502 modelChunkIndex = -1; 1503 nextChunkTime = now + 300000; 1504 1505 posesResult = new AddPosesResult; 1506 posesResult->fCount = 0; 1507 1508 snooze(500); 1509 // be nice 1510 } 1511 1512 if (count <= 0) 1513 break; 1514 } 1515 1516 BMessage finishedSending(kAddPosesCompleted); 1517 lock.Target().SendMessage(&finishedSending); 1518 1519 } catch (failToLock) { 1520 // we are here because the window got closed or otherwise failed to 1521 // lock 1522 1523 PRINT(("add_poses cleanup \n")); 1524 // failed to lock window, bail 1525 delete posesResult; 1526 delete container; 1527 1528 return B_ERROR; 1529 } 1530 1531 ASSERT(modelChunkIndex == -1); 1532 1533 delete posesResult; 1534 1535 if (lock.Lock()) { 1536 view->ReturnDirentIterator(container); 1537 view->fAddPosesThreads.erase(threadID); 1538 } else 1539 delete container; 1540 1541 return B_OK; 1542 } 1543 1544 1545 void 1546 BPoseView::AddRootPoses(bool watchIndividually, bool mountShared) 1547 { 1548 BVolumeRoster roster; 1549 roster.Rewind(); 1550 BVolume volume; 1551 1552 if (TrackerSettings().ShowDisksIcon() && !TargetModel()->IsRoot()) { 1553 BEntry entry("/"); 1554 Model model(&entry); 1555 if (model.InitCheck() == B_OK) { 1556 BMessage monitorMsg; 1557 monitorMsg.what = B_NODE_MONITOR; 1558 1559 monitorMsg.AddInt32("opcode", B_ENTRY_CREATED); 1560 1561 monitorMsg.AddInt32("device", model.NodeRef()->device); 1562 monitorMsg.AddInt64("node", model.NodeRef()->node); 1563 monitorMsg.AddInt64("directory", model.EntryRef()->directory); 1564 monitorMsg.AddString("name", model.EntryRef()->name); 1565 if (Window()) 1566 Window()->PostMessage(&monitorMsg, this); 1567 } 1568 } else { 1569 while (roster.GetNextVolume(&volume) == B_OK) { 1570 if (!volume.IsPersistent()) 1571 continue; 1572 1573 if (volume.IsShared() && !mountShared) 1574 continue; 1575 1576 CreateVolumePose(&volume, watchIndividually); 1577 } 1578 } 1579 1580 SortPoses(); 1581 UpdateCount(); 1582 Invalidate(); 1583 } 1584 1585 1586 void 1587 BPoseView::RemoveRootPoses() 1588 { 1589 int32 index; 1590 int32 count = fPoseList->CountItems(); 1591 for (index = 0; index < count;) { 1592 BPose* pose = fPoseList->ItemAt(index); 1593 if (pose != NULL) { 1594 Model* model = pose->TargetModel(); 1595 if (model != NULL) { 1596 if (model->IsVolume()) { 1597 DeletePose(model->NodeRef()); 1598 count--; 1599 } else 1600 index++; 1601 } 1602 } 1603 } 1604 1605 SortPoses(); 1606 UpdateCount(); 1607 Invalidate(); 1608 } 1609 1610 1611 void 1612 BPoseView::AddTrashPoses() 1613 { 1614 // the trash window needs to display a union of all the 1615 // trash folders from all the mounted volumes 1616 BVolumeRoster volRoster; 1617 volRoster.Rewind(); 1618 BVolume volume; 1619 while (volRoster.GetNextVolume(&volume) == B_OK) { 1620 if (!volume.IsPersistent()) 1621 continue; 1622 1623 BDirectory trashDir; 1624 BEntry entry; 1625 if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK 1626 && trashDir.GetEntry(&entry) == B_OK) { 1627 Model model(&entry); 1628 if (model.InitCheck() == B_OK) 1629 AddPoses(&model); 1630 } 1631 } 1632 } 1633 1634 1635 void 1636 BPoseView::AddPosesCompleted() 1637 { 1638 BContainerWindow* containerWindow = ContainerWindow(); 1639 if (containerWindow != NULL) 1640 containerWindow->AddMimeTypesToMenu(); 1641 1642 // if we're not in icon mode then we need to check for poses that 1643 // were "auto" placed to see if they overlap with other icons 1644 if (ViewMode() != kListMode) 1645 CheckAutoPlacedPoses(); 1646 1647 UpdateScrollRange(); 1648 HideBarberPole(); 1649 1650 // make sure that the last item in the list is not placed 1651 // above the top of the view (leaving you with an empty window) 1652 if (ViewMode() == kListMode) { 1653 BRect bounds(Bounds()); 1654 float lastItemTop = (CurrentPoseList()->CountItems() - 1) 1655 * fListElemHeight; 1656 if (bounds.top > lastItemTop) 1657 BView::ScrollTo(bounds.left, std::max(lastItemTop, 0.0f)); 1658 } 1659 } 1660 1661 1662 void 1663 BPoseView::CreateVolumePose(BVolume* volume, bool watchIndividually) 1664 { 1665 if (volume->InitCheck() != B_OK || !volume->IsPersistent()) { 1666 // We never want to create poses for those volumes; the file 1667 // system root, /pipe, /dev, etc. are all non-persistent 1668 return; 1669 } 1670 1671 BDirectory root; 1672 if (volume->GetRootDirectory(&root) != B_OK) 1673 return; 1674 1675 BEntry entry; 1676 root.GetEntry(&entry); 1677 1678 entry_ref ref; 1679 entry.GetRef(&ref); 1680 1681 // If the volume is mounted at a directory of a persistent volume, we don't 1682 // want it on the desktop or in the disks window. 1683 BVolume parentVolume(ref.device); 1684 if (parentVolume.InitCheck() == B_OK && parentVolume.IsPersistent()) 1685 return; 1686 1687 node_ref itemNode; 1688 root.GetNodeRef(&itemNode); 1689 1690 node_ref dirNode; 1691 dirNode.device = ref.device; 1692 dirNode.node = ref.directory; 1693 1694 BPose* pose = EntryCreated(&dirNode, &itemNode, ref.name, 0); 1695 if (pose != NULL && watchIndividually) { 1696 // make sure volume names still get watched, even though 1697 // they are on the desktop which is not their physical parent 1698 pose->TargetModel()->WatchVolumeAndMountPoint(B_WATCH_NAME 1699 | B_WATCH_STAT | B_WATCH_ATTR, this); 1700 } 1701 } 1702 1703 1704 void 1705 BPoseView::CreateTrashPose() 1706 { 1707 BVolume volume; 1708 if (BVolumeRoster().GetBootVolume(&volume) == B_OK) { 1709 BDirectory trash; 1710 BEntry entry; 1711 node_ref ref; 1712 if (FSGetTrashDir(&trash, volume.Device()) == B_OK 1713 && trash.GetEntry(&entry) == B_OK 1714 && entry.GetNodeRef(&ref) == B_OK) { 1715 WatchNewNode(&ref); 1716 Model* model = new Model(&entry); 1717 PoseInfo info; 1718 ReadPoseInfo(model, &info); 1719 CreatePose(model, &info, false, NULL, NULL, true); 1720 } 1721 } 1722 } 1723 1724 1725 BPose* 1726 BPoseView::CreatePose(Model* model, PoseInfo* poseInfo, bool insertionSort, 1727 int32* indexPtr, BRect* boundsPointer, bool forceDraw) 1728 { 1729 BPose* result; 1730 CreatePoses(&model, poseInfo, 1, &result, insertionSort, indexPtr, 1731 boundsPointer, forceDraw); 1732 1733 return result; 1734 } 1735 1736 1737 void 1738 BPoseView::FinishPendingScroll(float &listViewScrollBy, BRect srcRect) 1739 { 1740 if (listViewScrollBy == 0.0) 1741 return; 1742 1743 // copy top contents to bottom and 1744 // redraw from top to top of part that could be copied 1745 1746 if (srcRect.Width() > listViewScrollBy) { 1747 BRect dstRect = srcRect; 1748 srcRect.bottom -= listViewScrollBy; 1749 dstRect.top += listViewScrollBy; 1750 CopyBits(srcRect, dstRect); 1751 listViewScrollBy = 0; 1752 srcRect.bottom = dstRect.top; 1753 } 1754 SynchronousUpdate(srcRect); 1755 } 1756 1757 1758 bool 1759 BPoseView::AddPosesThreadValid(const entry_ref* ref) const 1760 { 1761 return *(TargetModel()->EntryRef()) == *ref || ContainerWindow()->IsTrash(); 1762 } 1763 1764 1765 void 1766 BPoseView::AddPoseToList(PoseList* list, bool visibleList, bool insertionSort, 1767 BPose* pose, BRect &viewBounds, float &listViewScrollBy, bool forceDraw, 1768 int32* indexPtr) 1769 { 1770 int32 poseIndex = list->CountItems(); 1771 1772 BRect poseBounds; 1773 bool havePoseBounds = false; 1774 bool addedItem = false; 1775 bool needToDraw = true; 1776 1777 if (insertionSort && list->CountItems() > 0) { 1778 int32 orientation = BSearchList(list, pose, &poseIndex, 0); 1779 1780 if (orientation == kInsertAfter) 1781 poseIndex++; 1782 1783 if (visibleList) { 1784 // we only care about the positions if this is a visible list 1785 poseBounds = CalcPoseRectList(pose, poseIndex); 1786 havePoseBounds = true; 1787 1788 BRect srcRect(Extent()); 1789 srcRect.top = poseBounds.top; 1790 srcRect = srcRect & viewBounds; 1791 BRect destRect(srcRect); 1792 destRect.OffsetBy(0, fListElemHeight); 1793 1794 // special case the addition of a pose that scrolls 1795 // the extent into the view for the first time: 1796 if (destRect.bottom > viewBounds.top 1797 && destRect.top > destRect.bottom) { 1798 // make destRect valid 1799 destRect.top = viewBounds.top; 1800 } 1801 1802 if (srcRect.Intersects(viewBounds) 1803 || destRect.Intersects(viewBounds)) { 1804 // The visual area is affected by the insertion. 1805 // If items have been added above the visual area, 1806 // delay the scrolling. srcRect.bottom holds the 1807 // current Extent(). So if the bottom is still above 1808 // the viewBounds top, it means the view is scrolled 1809 // to show the area below the items that have already 1810 // been added. 1811 if (srcRect.top == viewBounds.top 1812 && srcRect.bottom >= viewBounds.top 1813 && poseIndex != 0) { 1814 // if new pose above current view bounds, cache up 1815 // the draw and do it later 1816 listViewScrollBy += fListElemHeight; 1817 needToDraw = false; 1818 } else { 1819 FinishPendingScroll(listViewScrollBy, viewBounds); 1820 list->AddItem(pose, poseIndex); 1821 1822 fMimeTypeListIsDirty = true; 1823 addedItem = true; 1824 if (srcRect.IsValid()) { 1825 CopyBits(srcRect, destRect); 1826 srcRect.bottom = destRect.top; 1827 SynchronousUpdate(srcRect); 1828 } else { 1829 SynchronousUpdate(destRect); 1830 } 1831 needToDraw = false; 1832 } 1833 } 1834 } 1835 } 1836 1837 if (!addedItem) { 1838 list->AddItem(pose, poseIndex); 1839 fMimeTypeListIsDirty = true; 1840 } 1841 1842 if (visibleList && needToDraw && forceDraw) { 1843 if (!havePoseBounds) 1844 poseBounds = CalcPoseRectList(pose, poseIndex); 1845 1846 if (viewBounds.Intersects(poseBounds)) 1847 SynchronousUpdate(poseBounds); 1848 } 1849 1850 if (indexPtr) 1851 *indexPtr = poseIndex; 1852 } 1853 1854 1855 void 1856 BPoseView::CreatePoses(Model** models, PoseInfo* poseInfoArray, int32 count, 1857 BPose** resultingPoses, bool insertionSort, int32* lastPoseIndexPointer, 1858 BRect* boundsPointer, bool forceDraw) 1859 { 1860 // were we passed the bounds of the view? 1861 BRect viewBounds; 1862 if (boundsPointer != NULL) 1863 viewBounds = *boundsPointer; 1864 else 1865 viewBounds = Bounds(); 1866 1867 bool clipboardLocked = be_clipboard->Lock(); 1868 1869 int32 poseIndex = 0; 1870 uint32 clipboardMode = 0; 1871 float listViewScrollBy = 0; 1872 for (int32 modelIndex = 0; modelIndex < count; modelIndex++) { 1873 Model* model = models[modelIndex]; 1874 1875 // pose adopts model and deletes it when done 1876 if (fInsertedNodes.find(*(model->NodeRef())) != fInsertedNodes.end() 1877 || FindZombie(model->NodeRef())) { 1878 watch_node(model->NodeRef(), B_STOP_WATCHING, this); 1879 delete model; 1880 if (resultingPoses) 1881 resultingPoses[modelIndex] = NULL; 1882 1883 continue; 1884 } else 1885 fInsertedNodes.insert(*(model->NodeRef())); 1886 1887 if ((clipboardMode = FSClipboardFindNodeMode(model, !clipboardLocked, 1888 true)) != 0 && !HasPosesInClipboard()) { 1889 SetHasPosesInClipboard(true); 1890 } 1891 1892 model->OpenNode(); 1893 ASSERT(model->IsNodeOpen()); 1894 PoseInfo* poseInfo = &poseInfoArray[modelIndex]; 1895 BPose* pose = new BPose(model, this, clipboardMode); 1896 1897 if (resultingPoses) 1898 resultingPoses[modelIndex] = pose; 1899 1900 // set location from poseinfo if saved loc was for this dir 1901 if (poseInfo->fInitedDirectory != -1LL) { 1902 PinPointToValidRange(poseInfo->fLocation); 1903 pose->SetLocation(poseInfo->fLocation, this); 1904 AddToVSList(pose); 1905 } 1906 1907 BRect poseBounds; 1908 1909 switch (ViewMode()) { 1910 case kListMode: 1911 { 1912 AddPoseToList(fPoseList, !fFiltering, insertionSort, pose, 1913 viewBounds, listViewScrollBy, forceDraw, &poseIndex); 1914 1915 if (fFiltering && FilterPose(pose)) { 1916 AddPoseToList(fFilteredPoseList, true, insertionSort, pose, 1917 viewBounds, listViewScrollBy, forceDraw, &poseIndex); 1918 } 1919 1920 break; 1921 } 1922 1923 case kIconMode: 1924 case kMiniIconMode: 1925 if (poseInfo->fInitedDirectory == -1LL || fAlwaysAutoPlace) { 1926 if (pose->HasLocation()) 1927 RemoveFromVSList(pose); 1928 1929 PlacePose(pose, viewBounds); 1930 1931 // we set a flag in the pose here to signify that we were 1932 // auto placed - after adding all poses to window, we're 1933 // going to go back and make sure that the auto placed poses 1934 // don't overlap previously positioned icons. If so, we'll 1935 // move them to new positions. 1936 if (!fAlwaysAutoPlace) 1937 pose->SetAutoPlaced(true); 1938 1939 AddToVSList(pose); 1940 } 1941 1942 // add item to list and draw if necessary 1943 fPoseList->AddItem(pose); 1944 fMimeTypeListIsDirty = true; 1945 1946 poseBounds = pose->CalcRect(this); 1947 1948 if (fEnsurePosesVisible && !viewBounds.Intersects(poseBounds)) { 1949 viewBounds.InsetBy(20, 20); 1950 RemoveFromVSList(pose); 1951 BPoint loc(pose->Location(this)); 1952 loc.ConstrainTo(viewBounds); 1953 pose->SetLocation(loc, this); 1954 pose->SetSaveLocation(); 1955 AddToVSList(pose); 1956 poseBounds = pose->CalcRect(this); 1957 viewBounds.InsetBy(-20, -20); 1958 } 1959 1960 if (forceDraw && viewBounds.Intersects(poseBounds)) 1961 Invalidate(poseBounds); 1962 1963 // if this is the first item then we set extent here 1964 if (!fExtent.IsValid()) 1965 fExtent = poseBounds; 1966 else 1967 AddToExtent(poseBounds); 1968 1969 break; 1970 } 1971 if (model->IsSymLink()) 1972 model->ResolveIfLink()->CloseNode(); 1973 1974 model->CloseNode(); 1975 } 1976 1977 if (clipboardLocked) 1978 be_clipboard->Unlock(); 1979 1980 FinishPendingScroll(listViewScrollBy, viewBounds); 1981 1982 if (lastPoseIndexPointer != NULL) 1983 *lastPoseIndexPointer = poseIndex; 1984 } 1985 1986 1987 bool 1988 BPoseView::PoseVisible(const Model* model, const PoseInfo* poseInfo) 1989 { 1990 return !poseInfo->fInvisible; 1991 } 1992 1993 1994 bool 1995 BPoseView::ShouldShowPose(const Model* model, const PoseInfo* poseInfo) 1996 { 1997 if (!PoseVisible(model, poseInfo)) 1998 return false; 1999 2000 // check filter before adding item 2001 if (!fRefFilter) 2002 return true; 2003 2004 struct stat_beos stat; 2005 convert_to_stat_beos(model->StatBuf(), &stat); 2006 2007 return fRefFilter->Filter(model->EntryRef(), model->Node(), &stat, 2008 model->MimeType()); 2009 } 2010 2011 2012 const char* 2013 BPoseView::MimeTypeAt(int32 index) 2014 { 2015 if (fMimeTypeListIsDirty) 2016 RefreshMimeTypeList(); 2017 2018 return fMimeTypeList->ItemAt(index)->String(); 2019 } 2020 2021 2022 int32 2023 BPoseView::CountMimeTypes() 2024 { 2025 if (fMimeTypeListIsDirty) 2026 RefreshMimeTypeList(); 2027 2028 return fMimeTypeList->CountItems(); 2029 } 2030 2031 2032 void 2033 BPoseView::AddMimeType(const char* mimeType) 2034 { 2035 int32 count = fMimeTypeList->CountItems(); 2036 for (int32 index = 0; index < count; index++) { 2037 if (*fMimeTypeList->ItemAt(index) == mimeType) 2038 return; 2039 } 2040 2041 fMimeTypeList->AddItem(new BString(mimeType)); 2042 } 2043 2044 2045 void 2046 BPoseView::RefreshMimeTypeList() 2047 { 2048 fMimeTypeList->MakeEmpty(); 2049 fMimeTypeListIsDirty = false; 2050 2051 for (int32 index = 0;; index++) { 2052 BPose* pose = PoseAtIndex(index); 2053 if (pose == NULL) 2054 break; 2055 2056 Model* targetModel = pose->TargetModel(); 2057 if (targetModel != NULL) 2058 AddMimeType(targetModel->MimeType()); 2059 } 2060 } 2061 2062 2063 void 2064 BPoseView::InsertPoseAfter(BPose* pose, int32* index, int32 orientation, 2065 BRect* invalidRect) 2066 { 2067 if (orientation == kInsertAfter) { 2068 // TODO: get rid of this 2069 (*index)++; 2070 } 2071 2072 BRect bounds(Bounds()); 2073 // copy the good bits in the list 2074 BRect srcRect(Extent()); 2075 srcRect.top = CalcPoseRectList(pose, *index).top; 2076 srcRect = srcRect & bounds; 2077 BRect destRect(srcRect); 2078 destRect.OffsetBy(0, fListElemHeight); 2079 2080 if (srcRect.Intersects(bounds) || destRect.Intersects(bounds)) 2081 CopyBits(srcRect, destRect); 2082 2083 // this is the invalid rectangle 2084 srcRect.bottom = destRect.top; 2085 *invalidRect = srcRect; 2086 } 2087 2088 2089 void 2090 BPoseView::DisableScrollBars() 2091 { 2092 if (fHScrollBar != NULL) 2093 fHScrollBar->SetTarget((BView*)NULL); 2094 2095 if (fVScrollBar != NULL) 2096 fVScrollBar->SetTarget((BView*)NULL); 2097 } 2098 2099 2100 void 2101 BPoseView::EnableScrollBars() 2102 { 2103 if (fHScrollBar != NULL) 2104 fHScrollBar->SetTarget(this); 2105 2106 if (fVScrollBar != NULL) 2107 fVScrollBar->SetTarget(this); 2108 } 2109 2110 2111 void 2112 BPoseView::AddScrollBars() 2113 { 2114 fHScrollBar = new TScrollBar("HScrollBar", this, 0, 100); 2115 fVScrollBar = new BScrollBar("VScrollBar", this, 0, 100, B_VERTICAL); 2116 } 2117 2118 2119 void 2120 BPoseView::UpdateCount() 2121 { 2122 if (fCountView != NULL) 2123 fCountView->CheckCount(); 2124 } 2125 2126 2127 void 2128 BPoseView::MessageReceived(BMessage* message) 2129 { 2130 if (message->WasDropped() && HandleMessageDropped(message)) 2131 return; 2132 2133 if (HandleScriptingMessage(message)) 2134 return; 2135 2136 switch (message->what) { 2137 case kAddNewPoses: 2138 { 2139 AddPosesResult* currentPoses; 2140 entry_ref ref; 2141 if (message->FindPointer("currentPoses", 2142 reinterpret_cast<void**>(¤tPoses)) == B_OK 2143 && message->FindRef("ref", &ref) == B_OK) { 2144 // check if CreatePoses should be called 2145 // (abort if dir has been switched under normal 2146 // circumstances, ignore in several special cases) 2147 if (AddPosesThreadValid(&ref)) { 2148 CreatePoses(currentPoses->fModels, 2149 currentPoses->fPoseInfos, 2150 currentPoses->fCount, NULL, true, 0, 0, true); 2151 currentPoses->ReleaseModels(); 2152 } 2153 delete currentPoses; 2154 } 2155 break; 2156 } 2157 2158 case kAddPosesCompleted: 2159 AddPosesCompleted(); 2160 break; 2161 2162 case kRestoreBackgroundImage: 2163 ContainerWindow()->UpdateBackgroundImage(); 2164 break; 2165 2166 case B_META_MIME_CHANGED: 2167 NoticeMetaMimeChanged(message); 2168 break; 2169 2170 case B_NODE_MONITOR: 2171 case B_PATH_MONITOR: 2172 case B_QUERY_UPDATE: 2173 if (!FSNotification(message)) 2174 pendingNodeMonitorCache.Add(message); 2175 break; 2176 2177 case kIconMode: { 2178 int32 size; 2179 int32 scale; 2180 if (message->FindInt32("size", &size) == B_OK) { 2181 if (size != (int32)IconSizeInt()) 2182 fViewState->SetIconSize(size); 2183 } else if (message->FindInt32("scale", &scale) == B_OK 2184 && fViewState->ViewMode() == kIconMode) { 2185 if (scale == 0 && (int32)IconSizeInt() != 32) { 2186 switch ((int32)IconSizeInt()) { 2187 case 40: 2188 fViewState->SetIconSize(32); 2189 break; 2190 2191 case 48: 2192 fViewState->SetIconSize(40); 2193 break; 2194 2195 case 64: 2196 fViewState->SetIconSize(48); 2197 break; 2198 2199 case 96: 2200 fViewState->SetIconSize(64); 2201 break; 2202 2203 case 128: 2204 fViewState->SetIconSize(96); 2205 break; 2206 } 2207 } else if (scale == 1 && (int32)IconSizeInt() != 128) { 2208 switch ((int32)IconSizeInt()) { 2209 case 32: 2210 fViewState->SetIconSize(40); 2211 break; 2212 2213 case 40: 2214 fViewState->SetIconSize(48); 2215 break; 2216 2217 case 48: 2218 fViewState->SetIconSize(64); 2219 break; 2220 2221 case 64: 2222 fViewState->SetIconSize(96); 2223 break; 2224 2225 case 96: 2226 fViewState->SetIconSize(128); 2227 break; 2228 } 2229 } 2230 } else { 2231 int32 iconSize = fViewState->LastIconSize(); 2232 if (iconSize < 32 || iconSize > 128) { 2233 // uninitialized last icon size? 2234 iconSize = 32; 2235 } 2236 fViewState->SetIconSize(iconSize); 2237 } 2238 SetViewMode(message->what); 2239 break; 2240 } 2241 2242 case kListMode: 2243 { 2244 uint32 oldMode = fViewState->ViewMode(); 2245 int32 oldIconSize = fViewState->IconSize(); 2246 int32 iconSize; 2247 if (message->FindInt32("icon_size", &iconSize) == B_OK) { 2248 // sanatize 2249 if ((icon_size)iconSize != B_LARGE_ICON) 2250 iconSize = B_MINI_ICON; 2251 } else 2252 iconSize = B_MINI_ICON; 2253 2254 fViewState->SetIconSize(iconSize); 2255 SetViewMode(message->what); 2256 2257 if (oldMode == kListMode && iconSize == oldIconSize) 2258 break; 2259 2260 if (iconSize == B_MINI_ICON) 2261 ResizeColumn(FirstColumn(), FirstColumn()->Width() - B_MINI_ICON); 2262 else 2263 ResizeColumn(FirstColumn(), FirstColumn()->Width() + B_MINI_ICON); 2264 2265 SetListElemHeight(); 2266 Invalidate(); 2267 if (fTitleView != NULL && !fTitleView->IsHidden()) 2268 fTitleView->Invalidate(); 2269 2270 break; 2271 } 2272 2273 case kMiniIconMode: 2274 SetViewMode(message->what); 2275 break; 2276 2277 case kMsgMouseDragged: 2278 MouseDragged(message); 2279 break; 2280 2281 case kMsgMouseLongDown: 2282 MouseLongDown(message); 2283 break; 2284 2285 case B_MOUSE_IDLE: 2286 MouseIdle(message); 2287 break; 2288 2289 case B_SELECT_ALL: 2290 { 2291 // Select widget if there is an active one 2292 BTextWidget* widget; 2293 if (ActivePose() && ((widget = ActivePose()->ActiveWidget())) != 0) 2294 widget->SelectAll(this); 2295 else 2296 SelectAll(); 2297 break; 2298 } 2299 2300 case B_CUT: 2301 { 2302 Model* targetModel = TargetModel(); 2303 if (targetModel != NULL) { 2304 FSClipboardAddPoses(targetModel->NodeRef(), fSelectionList, 2305 kMoveSelectionTo, true); 2306 } 2307 break; 2308 } 2309 2310 case kCutMoreSelectionToClipboard: 2311 { 2312 Model* targetModel = TargetModel(); 2313 if (targetModel != NULL) { 2314 FSClipboardAddPoses(targetModel->NodeRef(), fSelectionList, 2315 kMoveSelectionTo, false); 2316 } 2317 break; 2318 } 2319 2320 case B_COPY: 2321 { 2322 Model* targetModel = TargetModel(); 2323 if (targetModel != NULL) { 2324 FSClipboardAddPoses(targetModel->NodeRef(), fSelectionList, 2325 kCopySelectionTo, true); 2326 } 2327 break; 2328 } 2329 2330 case kCopyMoreSelectionToClipboard: 2331 { 2332 Model* targetModel = TargetModel(); 2333 if (targetModel != NULL) { 2334 FSClipboardAddPoses(targetModel->NodeRef(), fSelectionList, 2335 kCopySelectionTo, false); 2336 } 2337 break; 2338 } 2339 2340 case B_PASTE: 2341 FSClipboardPaste(TargetModel()); 2342 break; 2343 2344 case kPasteLinksFromClipboard: 2345 FSClipboardPaste(TargetModel(), kCreateLink); 2346 break; 2347 2348 case B_CANCEL: 2349 if (FSClipboardHasRefs()) 2350 FSClipboardClear(); 2351 else if (fFiltering) 2352 StopFiltering(); 2353 break; 2354 2355 case kCancelSelectionToClipboard: 2356 { 2357 Model* targetModel = TargetModel(); 2358 if (targetModel != NULL) { 2359 FSClipboardRemovePoses(targetModel->NodeRef(), 2360 fSelectionList != NULL && fSelectionList->CountItems() > 0 2361 ? fSelectionList : fPoseList); 2362 } 2363 break; 2364 } 2365 2366 case kFSClipboardChanges: 2367 { 2368 node_ref node; 2369 message->FindInt32("device", &node.device); 2370 message->FindInt64("directory", &node.node); 2371 2372 Model* targetModel = TargetModel(); 2373 if (targetModel != NULL && *targetModel->NodeRef() == node) 2374 UpdatePosesClipboardModeFromClipboard(message); 2375 else if (message->FindBool("clearClipboard") 2376 && HasPosesInClipboard()) { 2377 // just remove all poses from clipboard 2378 SetHasPosesInClipboard(false); 2379 SetPosesClipboardMode(0); 2380 } 2381 break; 2382 } 2383 2384 case kInvertSelection: 2385 InvertSelection(); 2386 break; 2387 2388 case kShowSelectionWindow: 2389 ShowSelectionWindow(); 2390 break; 2391 2392 case kDuplicateSelection: 2393 DuplicateSelection(); 2394 break; 2395 2396 case kOpenSelection: 2397 OpenSelection(); 2398 break; 2399 2400 case kOpenSelectionWith: 2401 OpenSelectionUsing(); 2402 break; 2403 2404 case kRestoreFromTrash: 2405 RestoreSelectionFromTrash(); 2406 break; 2407 2408 case kDelete: 2409 ExcludeTrashFromSelection(); 2410 if (ContainerWindow()->IsTrash()) 2411 // if trash delete instantly 2412 DeleteSelection(true, false); 2413 else 2414 DeleteSelection(); 2415 break; 2416 2417 case kMoveToTrash: 2418 { 2419 ExcludeTrashFromSelection(); 2420 TrackerSettings settings; 2421 2422 if ((modifiers() & B_SHIFT_KEY) != 0 2423 || settings.DontMoveFilesToTrash()) { 2424 DeleteSelection(true, settings.AskBeforeDeleteFile()); 2425 } else 2426 MoveSelectionToTrash(); 2427 2428 break; 2429 } 2430 2431 case kCleanupAll: 2432 Cleanup(true); 2433 break; 2434 2435 case kCleanup: 2436 Cleanup(); 2437 break; 2438 2439 case kEditQuery: 2440 EditQueries(); 2441 break; 2442 2443 case kRunAutomounterSettings: 2444 be_app->PostMessage(message); 2445 break; 2446 2447 case kNewEntryFromTemplate: 2448 if (message->HasRef("refs_template")) 2449 NewFileFromTemplate(message); 2450 break; 2451 2452 case kNewFolder: 2453 NewFolder(message); 2454 break; 2455 2456 case kUnmountVolume: 2457 UnmountSelectedVolumes(); 2458 break; 2459 2460 case kEmptyTrash: 2461 FSEmptyTrash(); 2462 break; 2463 2464 case kGetInfo: 2465 OpenInfoWindows(); 2466 break; 2467 2468 case kIdentifyEntry: 2469 { 2470 bool force; 2471 if (message->FindBool("force", &force) != B_OK) 2472 force = false; 2473 2474 IdentifySelection(force); 2475 break; 2476 } 2477 2478 case kEditItem: 2479 { 2480 if (ActivePose()) 2481 break; 2482 2483 BPose* pose = fSelectionList->FirstItem(); 2484 if (pose != NULL) { 2485 BPoint where(0, 2486 CurrentPoseList()->IndexOf(pose) * fListElemHeight); 2487 pose->EditFirstWidget(where, this); 2488 } 2489 break; 2490 } 2491 2492 case kOpenParentDir: 2493 OpenParent(); 2494 break; 2495 2496 case kCopyAttributes: 2497 if (be_clipboard->Lock()) { 2498 be_clipboard->Clear(); 2499 BMessage* data = be_clipboard->Data(); 2500 if (data != NULL) { 2501 // copy attributes to the clipboard 2502 BMessage state; 2503 SaveState(state); 2504 2505 BMallocIO stream; 2506 ssize_t size; 2507 if (state.Flatten(&stream, &size) == B_OK) { 2508 data->AddData("application/tracker-columns", 2509 B_MIME_TYPE, stream.Buffer(), size); 2510 be_clipboard->Commit(); 2511 } 2512 } 2513 be_clipboard->Unlock(); 2514 } 2515 break; 2516 2517 case kPasteAttributes: 2518 if (be_clipboard->Lock()) { 2519 BMessage* data = be_clipboard->Data(); 2520 if (data != NULL) { 2521 // find the attributes in the clipboard 2522 const void* buffer; 2523 ssize_t size; 2524 if (data->FindData("application/tracker-columns", 2525 B_MIME_TYPE, &buffer, &size) == B_OK) { 2526 BMessage state; 2527 if (state.Unflatten((const char*)buffer) == B_OK) { 2528 // remove all current columns (one always stays) 2529 BColumn* old; 2530 while ((old = ColumnAt(0)) != NULL) { 2531 if (!RemoveColumn(old, false)) 2532 break; 2533 } 2534 2535 // add new columns 2536 for (int32 index = 0; ; index++) { 2537 BColumn* column 2538 = BColumn::InstantiateFromMessage(state, 2539 index); 2540 if (column == NULL) 2541 break; 2542 2543 AddColumn(column); 2544 } 2545 2546 // remove the last old one 2547 RemoveColumn(old, false); 2548 2549 // set sorting mode 2550 BViewState* viewState 2551 = BViewState::InstantiateFromMessage(state); 2552 if (viewState != NULL) { 2553 SetPrimarySort(viewState->PrimarySort()); 2554 SetSecondarySort(viewState->SecondarySort()); 2555 SetReverseSort(viewState->ReverseSort()); 2556 2557 SortPoses(); 2558 Invalidate(); 2559 } 2560 } 2561 } 2562 } 2563 be_clipboard->Unlock(); 2564 } 2565 break; 2566 2567 case kArrangeBy: 2568 { 2569 uint32 attrHash; 2570 if (message->FindInt32("attr_hash", (int32*)&attrHash) == B_OK) { 2571 if (ColumnFor(attrHash) == NULL) 2572 HandleAttrMenuItemSelected(message); 2573 2574 if (PrimarySort() == attrHash) 2575 attrHash = 0; 2576 2577 SetPrimarySort(attrHash); 2578 SetSecondarySort(0); 2579 Cleanup(true); 2580 } 2581 break; 2582 } 2583 2584 case kArrangeReverseOrder: 2585 SetReverseSort(!fViewState->ReverseSort()); 2586 Cleanup(true); 2587 break; 2588 2589 case kAttributeItem: 2590 HandleAttrMenuItemSelected(message); 2591 break; 2592 2593 case kAddPrinter: 2594 be_app->PostMessage(message); 2595 break; 2596 2597 case kMakeActivePrinter: 2598 SetDefaultPrinter(); 2599 break; 2600 2601 #if DEBUG 2602 case kTestIconCache: 2603 RunIconCacheTests(); 2604 break; 2605 2606 case 'dbug': 2607 { 2608 int32 count = fSelectionList->CountItems(); 2609 for (int32 index = 0; index < count; index++) 2610 fSelectionList->ItemAt(index)->PrintToStream(); 2611 2612 break; 2613 } 2614 #ifdef CHECK_OPEN_MODEL_LEAKS 2615 case 'dpfl': 2616 DumpOpenModels(false); 2617 break; 2618 2619 case 'dpfL': 2620 DumpOpenModels(true); 2621 break; 2622 #endif 2623 #endif 2624 2625 case kCheckTypeahead: 2626 { 2627 bigtime_t doubleClickSpeed; 2628 get_click_speed(&doubleClickSpeed); 2629 if (system_time() - fLastKeyTime > (doubleClickSpeed * 2)) { 2630 fCountView->SetTypeAhead(""); 2631 delete fKeyRunner; 2632 fKeyRunner = NULL; 2633 } 2634 break; 2635 } 2636 2637 case B_OBSERVER_NOTICE_CHANGE: 2638 { 2639 int32 observerWhat; 2640 if (message->FindInt32("be:observe_change_what", &observerWhat) 2641 == B_OK) { 2642 switch (observerWhat) { 2643 case kDateFormatChanged: 2644 UpdateDateColumns(message); 2645 break; 2646 2647 case kVolumesOnDesktopChanged: 2648 AdaptToVolumeChange(message); 2649 break; 2650 2651 case kDesktopIntegrationChanged: 2652 AdaptToDesktopIntegrationChange(message); 2653 break; 2654 2655 case kShowSelectionWhenInactiveChanged: 2656 { 2657 // Updating the settings here will propagate 2658 // setting changed from Tracker to all open 2659 // file panels as well 2660 bool showSelection; 2661 if (message->FindBool("ShowSelectionWhenInactive", 2662 &showSelection) == B_OK) { 2663 fShowSelectionWhenInactive = showSelection; 2664 TrackerSettings().SetShowSelectionWhenInactive( 2665 fShowSelectionWhenInactive); 2666 } 2667 Invalidate(); 2668 break; 2669 } 2670 2671 case kTransparentSelectionChanged: 2672 { 2673 bool transparentSelection; 2674 if (message->FindBool("TransparentSelection", 2675 &transparentSelection) == B_OK) { 2676 fTransparentSelection = transparentSelection; 2677 TrackerSettings().SetTransparentSelection( 2678 fTransparentSelection); 2679 } 2680 break; 2681 } 2682 2683 case kSortFolderNamesFirstChanged: 2684 if (ViewMode() == kListMode) { 2685 TrackerSettings settings; 2686 bool sortFolderNamesFirst; 2687 if (message->FindBool("SortFolderNamesFirst", 2688 &sortFolderNamesFirst) == B_OK) { 2689 settings.SetSortFolderNamesFirst( 2690 sortFolderNamesFirst); 2691 } 2692 NameAttributeText::SetSortFolderNamesFirst( 2693 settings.SortFolderNamesFirst()); 2694 RealNameAttributeText::SetSortFolderNamesFirst( 2695 settings.SortFolderNamesFirst()); 2696 SortPoses(); 2697 Invalidate(); 2698 } 2699 break; 2700 2701 case kHideDotFilesChanged: 2702 { 2703 TrackerSettings settings; 2704 bool hideDotFiles; 2705 if (message->FindBool("HideDotFiles", 2706 &hideDotFiles) == B_OK) { 2707 settings.SetHideDotFiles(hideDotFiles); 2708 } 2709 2710 Refresh(); 2711 break; 2712 } 2713 2714 case kTypeAheadFilteringChanged: 2715 { 2716 TrackerSettings settings; 2717 bool typeAheadFiltering; 2718 if (message->FindBool("TypeAheadFiltering", 2719 &typeAheadFiltering) == B_OK) { 2720 settings.SetTypeAheadFiltering(typeAheadFiltering); 2721 } 2722 2723 if (fFiltering && !typeAheadFiltering) 2724 StopFiltering(); 2725 break; 2726 } 2727 } 2728 } 2729 break; 2730 } 2731 2732 default: 2733 _inherited::MessageReceived(message); 2734 break; 2735 } 2736 } 2737 2738 2739 bool 2740 BPoseView::RemoveColumn(BColumn* columnToRemove, bool runAlert) 2741 { 2742 // make sure last column is not removed 2743 if (CountColumns() == 1) { 2744 if (runAlert) { 2745 BAlert* alert = new BAlert("", 2746 B_TRANSLATE("You must have at least one attribute showing."), 2747 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 2748 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2749 alert->Go(); 2750 } 2751 2752 return false; 2753 } 2754 2755 // column exists so remove it from list 2756 int32 columnIndex = IndexOfColumn(columnToRemove); 2757 float offset = columnToRemove->Offset(); 2758 2759 int32 count = fPoseList->CountItems(); 2760 for (int32 index = 0; index < count; index++) 2761 fPoseList->ItemAt(index)->RemoveWidget(this, columnToRemove); 2762 2763 fColumnList->RemoveItem(columnToRemove, false); 2764 fTitleView->RemoveTitle(columnToRemove); 2765 2766 float attrWidth = columnToRemove->Width(); 2767 delete columnToRemove; 2768 2769 count = CountColumns(); 2770 for (int32 index = columnIndex; index < count; index++) { 2771 BColumn* column = ColumnAt(index); 2772 column->SetOffset(column->Offset() 2773 - (attrWidth + kTitleColumnExtraMargin)); 2774 } 2775 2776 BRect rect(Bounds()); 2777 rect.left = offset; 2778 Invalidate(rect); 2779 2780 ContainerWindow()->MarkAttributeMenu(); 2781 2782 if (IsWatchingDateFormatChange()) { 2783 int32 columnCount = CountColumns(); 2784 bool anyDateAttributesLeft = false; 2785 2786 for (int32 i = 0; i < columnCount; i++) { 2787 BColumn* column = ColumnAt(i); 2788 if (column->AttrType() == B_TIME_TYPE) 2789 anyDateAttributesLeft = true; 2790 2791 if (anyDateAttributesLeft) 2792 break; 2793 } 2794 2795 if (!anyDateAttributesLeft) 2796 StopWatchDateFormatChange(); 2797 } 2798 2799 fStateNeedsSaving = true; 2800 2801 if (fFiltering) { 2802 // the column we removed might just be the one that was used to filter 2803 int32 count = fFilteredPoseList->CountItems(); 2804 for (int32 i = count - 1; i >= 0; i--) { 2805 BPose* pose = fFilteredPoseList->ItemAt(i); 2806 if (!FilterPose(pose)) 2807 RemoveFilteredPose(pose, i); 2808 } 2809 } 2810 2811 return true; 2812 } 2813 2814 2815 bool 2816 BPoseView::AddColumn(BColumn* newColumn, const BColumn* after) 2817 { 2818 if (after == NULL) 2819 after = LastColumn(); 2820 2821 // add new column after last column 2822 float offset; 2823 int32 afterColumnIndex; 2824 if (after != NULL) { 2825 offset = after->Offset() + after->Width() + kTitleColumnExtraMargin; 2826 afterColumnIndex = IndexOfColumn(after); 2827 } else { 2828 offset = StartOffset(); 2829 afterColumnIndex = CountColumns() - 1; 2830 } 2831 2832 // add the new column 2833 fColumnList->AddItem(newColumn, afterColumnIndex + 1); 2834 fTitleView->AddTitle(newColumn); 2835 2836 BRect rect(Bounds()); 2837 2838 // add widget for all visible poses 2839 PoseList* poseList = CurrentPoseList(); 2840 int32 count = poseList->CountItems(); 2841 int32 startIndex = (int32)(rect.top / fListElemHeight); 2842 BPoint loc(0, startIndex * fListElemHeight); 2843 2844 for (int32 index = startIndex; index < count; index++) { 2845 BPose* pose = poseList->ItemAt(index); 2846 if (pose->WidgetFor(newColumn->AttrHash()) == NULL) 2847 pose->AddWidget(this, newColumn); 2848 2849 loc.y += fListElemHeight; 2850 if (loc.y > rect.bottom) 2851 break; 2852 } 2853 2854 // rearrange column titles to fit new column 2855 newColumn->SetOffset(offset); 2856 float attrWidth = newColumn->Width(); 2857 2858 count = CountColumns(); 2859 for (int32 index = afterColumnIndex + 2; index < count; index++) { 2860 BColumn* column = ColumnAt(index); 2861 ASSERT(newColumn != column); 2862 column->SetOffset(column->Offset() + (attrWidth 2863 + kTitleColumnExtraMargin)); 2864 } 2865 2866 rect.left = offset; 2867 Invalidate(rect); 2868 ContainerWindow()->MarkAttributeMenu(); 2869 2870 // Check if this is a time attribute and if so, 2871 // start watching for changed in time/date format: 2872 if (!IsWatchingDateFormatChange() && newColumn->AttrType() == B_TIME_TYPE) 2873 StartWatchDateFormatChange(); 2874 2875 fStateNeedsSaving = true; 2876 2877 if (fFiltering) { 2878 // the column we added might just add new poses to be showed 2879 fFilteredPoseList->MakeEmpty(); 2880 fFiltering = false; 2881 StartFiltering(); 2882 } 2883 2884 return true; 2885 } 2886 2887 2888 void 2889 BPoseView::HandleAttrMenuItemSelected(BMessage* message) 2890 { 2891 // see if source was a menu item 2892 BMenuItem* item; 2893 if (message->FindPointer("source", (void**)&item) != B_OK) 2894 item = NULL; 2895 2896 // find out which column was selected 2897 uint32 attrHash; 2898 if (message->FindInt32("attr_hash", (int32*)&attrHash) != B_OK) 2899 return; 2900 2901 BColumn* column = ColumnFor(attrHash); 2902 if (column != NULL) { 2903 RemoveColumn(column, true); 2904 return; 2905 } else { 2906 // collect info about selected attribute 2907 const char* attrName; 2908 if (message->FindString("attr_name", &attrName) != B_OK) 2909 return; 2910 2911 uint32 attrType; 2912 if (message->FindInt32("attr_type", (int32*)&attrType) != B_OK) 2913 return; 2914 2915 float attrWidth; 2916 if (message->FindFloat("attr_width", &attrWidth) != B_OK) 2917 return; 2918 2919 alignment attrAlign; 2920 if (message->FindInt32("attr_align", (int32*)&attrAlign) != B_OK) 2921 return; 2922 2923 bool isEditable; 2924 if (message->FindBool("attr_editable", &isEditable) != B_OK) 2925 return; 2926 2927 bool isStatfield; 2928 if (message->FindBool("attr_statfield", &isStatfield) != B_OK) 2929 return; 2930 2931 const char* displayAs; 2932 message->FindString("attr_display_as", &displayAs); 2933 2934 column = new BColumn(item->Label(), 0, attrWidth, attrAlign, 2935 attrName, attrType, displayAs, isStatfield, isEditable); 2936 AddColumn(column); 2937 if (item->Menu()->Supermenu() == NULL) 2938 delete item->Menu(); 2939 } 2940 } 2941 2942 2943 const int32 kSanePoseLocation = 50000; 2944 2945 2946 void 2947 BPoseView::ReadPoseInfo(Model* model, PoseInfo* poseInfo) 2948 { 2949 BModelOpener opener(model); 2950 if (model->Node() == NULL) 2951 return; 2952 2953 ReadAttrResult result = kReadAttrFailed; 2954 BEntry entry; 2955 model->GetEntry(&entry); 2956 bool isTrash = model->IsTrash() && IsDesktopView(); 2957 2958 // special case the "root" disks icon 2959 // as well as the trash on desktop 2960 if (model->IsRoot() || isTrash) { 2961 BDirectory dir; 2962 if (FSGetDeskDir(&dir) == B_OK) { 2963 const char* poseInfoAttr = isTrash 2964 ? kAttrTrashPoseInfo 2965 : kAttrDisksPoseInfo; 2966 const char* poseInfoAttrForeign = isTrash 2967 ? kAttrTrashPoseInfoForeign 2968 : kAttrDisksPoseInfoForeign; 2969 result = ReadAttr(&dir, poseInfoAttr, poseInfoAttrForeign, 2970 B_RAW_TYPE, 0, poseInfo, sizeof(*poseInfo), 2971 &PoseInfo::EndianSwap); 2972 } 2973 } else { 2974 ASSERT(model->IsNodeOpen()); 2975 time_t now = time(NULL); 2976 2977 for (int32 count = 10; count >= 0; count--) { 2978 if (model->Node() == NULL) 2979 break; 2980 2981 result = ReadAttr(model->Node(), kAttrPoseInfo, 2982 kAttrPoseInfoForeign, B_RAW_TYPE, 0, poseInfo, 2983 sizeof(*poseInfo), &PoseInfo::EndianSwap); 2984 2985 if (result != kReadAttrFailed) { 2986 // got it, bail 2987 break; 2988 } 2989 2990 // if we're in one of the icon modes and it's a newly created item 2991 // then we're going to retry a few times to see if we can get some 2992 // pose info to properly place the icon 2993 if (ViewMode() == kListMode) 2994 break; 2995 2996 const StatStruct* stat = model->StatBuf(); 2997 if (stat->st_crtime < now - 5 || stat->st_crtime > now) 2998 break; 2999 3000 //PRINT(("retrying to read pose info for %s, %d\n", 3001 // model->Name(), count)); 3002 3003 snooze(10000); 3004 } 3005 } 3006 3007 if (result == kReadAttrFailed) { 3008 poseInfo->fInitedDirectory = -1LL; 3009 poseInfo->fInvisible = false; 3010 } else if (TargetModel() == NULL 3011 || (poseInfo->fInitedDirectory != model->EntryRef()->directory 3012 && (poseInfo->fInitedDirectory 3013 != TargetModel()->NodeRef()->node))) { 3014 // info was read properly but it's not for this directory 3015 poseInfo->fInitedDirectory = -1LL; 3016 } else if (poseInfo->fLocation.x < -kSanePoseLocation 3017 || poseInfo->fLocation.x > kSanePoseLocation 3018 || poseInfo->fLocation.y < -kSanePoseLocation 3019 || poseInfo->fLocation.y > kSanePoseLocation) { 3020 // location values not realistic, probably screwed up, force reset 3021 poseInfo->fInitedDirectory = -1LL; 3022 } 3023 } 3024 3025 3026 ExtendedPoseInfo* 3027 BPoseView::ReadExtendedPoseInfo(Model* model) 3028 { 3029 BModelOpener opener(model); 3030 if (model->Node() == NULL) 3031 return NULL; 3032 3033 ReadAttrResult result = kReadAttrFailed; 3034 3035 const char* extendedPoseInfoAttrName; 3036 const char* extendedPoseInfoAttrForeignName; 3037 3038 // special case the "root" disks icon 3039 if (model->IsRoot()) { 3040 BDirectory dir; 3041 if (FSGetDeskDir(&dir) == B_OK) { 3042 extendedPoseInfoAttrName = kAttrExtendedDisksPoseInfo; 3043 extendedPoseInfoAttrForeignName = kAttrExtendedDisksPoseInfoForegin; 3044 } else 3045 return NULL; 3046 } else { 3047 extendedPoseInfoAttrName = kAttrExtendedPoseInfo; 3048 extendedPoseInfoAttrForeignName = kAttrExtendedPoseInfoForegin; 3049 } 3050 3051 type_code type; 3052 size_t size; 3053 result = GetAttrInfo(model->Node(), extendedPoseInfoAttrName, 3054 extendedPoseInfoAttrForeignName, &type, &size); 3055 3056 if (result == kReadAttrFailed) 3057 return NULL; 3058 3059 char* buffer = new char[ExtendedPoseInfo::SizeWithHeadroom(size)]; 3060 ExtendedPoseInfo* poseInfo = reinterpret_cast<ExtendedPoseInfo*>(buffer); 3061 result = ReadAttr(model->Node(), extendedPoseInfoAttrName, 3062 extendedPoseInfoAttrForeignName, 3063 B_RAW_TYPE, 0, buffer, size, &ExtendedPoseInfo::EndianSwap); 3064 3065 // check that read worked, and data is sane 3066 if (result == kReadAttrFailed 3067 || size > poseInfo->SizeWithHeadroom() 3068 || size < poseInfo->Size()) { 3069 delete[] buffer; 3070 return NULL; 3071 } 3072 3073 return poseInfo; 3074 } 3075 3076 3077 void 3078 BPoseView::SetViewMode(uint32 newMode) 3079 { 3080 uint32 oldMode = ViewMode(); 3081 uint32 lastIconSize = fViewState->LastIconSize(); 3082 3083 if (newMode == oldMode 3084 && (newMode != kIconMode || lastIconSize == fViewState->IconSize())) { 3085 return; 3086 } 3087 3088 ASSERT(!IsFilePanel()); 3089 3090 uint32 lastIconMode = fViewState->LastIconMode(); 3091 if (newMode != kListMode) { 3092 fViewState->SetLastIconMode(newMode); 3093 if (oldMode == kIconMode) 3094 fViewState->SetLastIconSize(fViewState->IconSize()); 3095 } 3096 3097 fViewState->SetViewMode(newMode); 3098 3099 // Try to lock the center of the pose view when scaling icons, but not 3100 // if we are the desktop. 3101 BPoint scaleOffset(0, 0); 3102 bool iconSizeChanged = newMode == kIconMode && oldMode == kIconMode; 3103 if (!IsDesktopWindow() && iconSizeChanged) { 3104 // definitely changing the icon size, so we will need to scroll 3105 BRect bounds(Bounds()); 3106 BPoint center(bounds.LeftTop()); 3107 center.x += bounds.Width() / 2.0; 3108 center.y += bounds.Height() / 2.0; 3109 // convert the center into "unscaled icon placement" space 3110 float oldScale = lastIconSize / 32.0; 3111 BPoint unscaledCenter(center.x / oldScale, center.y / oldScale); 3112 // get the new center in "scaled icon placement" place 3113 float newScale = fViewState->IconSize() / 32.0; 3114 BPoint newCenter(unscaledCenter.x * newScale, 3115 unscaledCenter.y * newScale); 3116 scaleOffset = newCenter - center; 3117 } 3118 3119 // toggle view layout between listmode and non-listmode, if necessary 3120 BContainerWindow* window = ContainerWindow(); 3121 if (oldMode == kListMode) { 3122 if (fFiltering) 3123 ClearFilter(); 3124 3125 if (window != NULL) 3126 window->HideAttributeMenu(); 3127 3128 fTitleView->Hide(); 3129 } else if (newMode == kListMode) { 3130 if (window != NULL) 3131 window->ShowAttributeMenu(); 3132 3133 fTitleView->Show(); 3134 } 3135 3136 CommitActivePose(); 3137 SetIconPoseHeight(); 3138 GetLayoutInfo(newMode, &fGrid, &fOffset); 3139 3140 // see if we need to map icons into new mode 3141 bool mapIcons = false; 3142 if (fOkToMapIcons) { 3143 mapIcons = (newMode != kListMode) && (newMode != lastIconMode 3144 || fViewState->IconSize() != lastIconSize); 3145 } 3146 3147 // check if we need to re-place poses when they are out of view 3148 bool checkLocations = IsDesktopWindow() && iconSizeChanged; 3149 3150 BPoint oldOffset; 3151 BPoint oldGrid; 3152 if (mapIcons) 3153 GetLayoutInfo(lastIconMode, &oldGrid, &oldOffset); 3154 3155 BRect bounds(Bounds()); 3156 PoseList newPoseList(30); 3157 3158 if (newMode != kListMode) { 3159 int32 count = fPoseList->CountItems(); 3160 for (int32 index = 0; index < count; index++) { 3161 BPose* pose = fPoseList->ItemAt(index); 3162 if (pose->HasLocation() == false) { 3163 newPoseList.AddItem(pose); 3164 } else if (checkLocations && !IsValidLocation(pose)) { 3165 // this icon has a location, but needs to be remapped, because 3166 // it is going out of view for example 3167 RemoveFromVSList(pose); 3168 newPoseList.AddItem(pose); 3169 } else if (iconSizeChanged) { 3170 // The pose location is still changed in view coordinates, 3171 // so it needs to be changed anyways! 3172 pose->SetSaveLocation(); 3173 } else if (mapIcons) { 3174 MapToNewIconMode(pose, oldGrid, oldOffset); 3175 } 3176 } 3177 } 3178 3179 // invalidate before anything else to avoid flickering, especially when 3180 // scrolling is also performed (invalidating before scrolling will cause 3181 // app_server to scroll silently, ie not visibly) 3182 Invalidate(); 3183 3184 // update origin in case of a list <-> icon mode transition 3185 BPoint newOrigin; 3186 if (newMode == kListMode) 3187 newOrigin = fViewState->ListOrigin(); 3188 else 3189 newOrigin = fViewState->IconOrigin() + scaleOffset; 3190 3191 PinPointToValidRange(newOrigin); 3192 3193 DisableScrollBars(); 3194 ScrollTo(newOrigin); 3195 3196 // reset hint and arrange poses which DO NOT have a location yet 3197 ResetPosePlacementHint(); 3198 int32 count = newPoseList.CountItems(); 3199 for (int32 index = 0; index < count; index++) { 3200 BPose* pose = newPoseList.ItemAt(index); 3201 PlacePose(pose, bounds); 3202 AddToVSList(pose); 3203 } 3204 3205 SortPoses(); 3206 if (newMode != kListMode) 3207 RecalcExtent(); 3208 3209 UpdateScrollRange(); 3210 SetScrollBarsTo(newOrigin); 3211 EnableScrollBars(); 3212 ContainerWindow()->ViewModeChanged(oldMode, newMode); 3213 } 3214 3215 3216 void 3217 BPoseView::MapToNewIconMode(BPose* pose, BPoint oldGrid, BPoint oldOffset) 3218 { 3219 BPoint delta; 3220 BPoint poseLoc; 3221 3222 poseLoc = PinToGrid(pose->Location(this), oldGrid, oldOffset); 3223 delta = pose->Location(this) - poseLoc; 3224 poseLoc -= oldOffset; 3225 3226 if (poseLoc.x >= 0) 3227 poseLoc.x = floorf(poseLoc.x / oldGrid.x) * fGrid.x; 3228 else 3229 poseLoc.x = ceilf(poseLoc.x / oldGrid.x) * fGrid.x; 3230 3231 if (poseLoc.y >= 0) 3232 poseLoc.y = floorf(poseLoc.y / oldGrid.y) * fGrid.y; 3233 else 3234 poseLoc.y = ceilf(poseLoc.y / oldGrid.y) * fGrid.y; 3235 3236 if ((delta.x != 0) || (delta.y != 0)) { 3237 if (delta.x >= 0) 3238 delta.x = fGrid.x * floorf(delta.x / oldGrid.x); 3239 else 3240 delta.x = fGrid.x * ceilf(delta.x / oldGrid.x); 3241 3242 if (delta.y >= 0) 3243 delta.y = fGrid.y * floorf(delta.y / oldGrid.y); 3244 else 3245 delta.y = fGrid.y * ceilf(delta.y / oldGrid.y); 3246 3247 poseLoc += delta; 3248 } 3249 3250 poseLoc += fOffset; 3251 pose->SetLocation(poseLoc, this); 3252 pose->SetSaveLocation(); 3253 } 3254 3255 3256 void 3257 BPoseView::SetPosesClipboardMode(uint32 clipboardMode) 3258 { 3259 if (ViewMode() == kListMode) { 3260 PoseList* poseList = CurrentPoseList(); 3261 int32 count = poseList->CountItems(); 3262 3263 BPoint loc(0,0); 3264 for (int32 index = 0; index < count; index++) { 3265 BPose* pose = poseList->ItemAt(index); 3266 if (pose->ClipboardMode() != clipboardMode) { 3267 pose->SetClipboardMode(clipboardMode); 3268 Invalidate(pose->CalcRect(loc, this, false)); 3269 } 3270 loc.y += fListElemHeight; 3271 } 3272 } else { 3273 int32 count = fPoseList->CountItems(); 3274 for (int32 index = 0; index < count; index++) { 3275 BPose* pose = fPoseList->ItemAt(index); 3276 if (pose->ClipboardMode() != clipboardMode) { 3277 pose->SetClipboardMode(clipboardMode); 3278 BRect poseRect(pose->CalcRect(this)); 3279 Invalidate(poseRect); 3280 } 3281 } 3282 } 3283 } 3284 3285 3286 void 3287 BPoseView::UpdatePosesClipboardModeFromClipboard(BMessage* clipboardReport) 3288 { 3289 CommitActivePose(); 3290 fSelectionPivotPose = NULL; 3291 fRealPivotPose = NULL; 3292 bool fullInvalidateNeeded = false; 3293 3294 node_ref node; 3295 clipboardReport->FindInt32("device", &node.device); 3296 clipboardReport->FindInt64("directory", &node.node); 3297 3298 bool clearClipboard = false; 3299 clipboardReport->FindBool("clearClipboard", &clearClipboard); 3300 3301 if (clearClipboard && fHasPosesInClipboard) { 3302 // clear all poses 3303 int32 count = fPoseList->CountItems(); 3304 for (int32 index = 0; index < count; index++) { 3305 BPose* pose = fPoseList->ItemAt(index); 3306 pose->Select(false); 3307 pose->SetClipboardMode(0); 3308 } 3309 SetHasPosesInClipboard(false); 3310 fullInvalidateNeeded = true; 3311 fHasPosesInClipboard = false; 3312 } 3313 3314 BRect bounds(Bounds()); 3315 BPoint loc(0, 0); 3316 bool hasPosesInClipboard = false; 3317 int32 foundNodeIndex = 0; 3318 3319 TClipboardNodeRef* clipNode = NULL; 3320 ssize_t size; 3321 for (int32 index = 0; clipboardReport->FindData("tcnode", T_CLIPBOARD_NODE, 3322 index, (const void**)&clipNode, &size) == B_OK; index++) { 3323 BPose* pose = fPoseList->FindPose(&clipNode->node, &foundNodeIndex); 3324 if (pose == NULL) 3325 continue; 3326 3327 if (clipNode->moveMode != pose->ClipboardMode() || pose->IsSelected()) { 3328 pose->SetClipboardMode(clipNode->moveMode); 3329 pose->Select(false); 3330 3331 if (!fullInvalidateNeeded) { 3332 if (ViewMode() == kListMode) { 3333 if (fFiltering) { 3334 pose = fFilteredPoseList->FindPose(&clipNode->node, 3335 &foundNodeIndex); 3336 } 3337 3338 if (pose != NULL) { 3339 loc.y = foundNodeIndex * fListElemHeight; 3340 if (loc.y <= bounds.bottom && loc.y >= bounds.top) 3341 Invalidate(pose->CalcRect(loc, this, false)); 3342 } 3343 } else { 3344 BRect poseRect(pose->CalcRect(this)); 3345 if (bounds.Contains(poseRect.LeftTop()) 3346 || bounds.Contains(poseRect.LeftBottom()) 3347 || bounds.Contains(poseRect.RightBottom()) 3348 || bounds.Contains(poseRect.RightTop())) { 3349 Invalidate(poseRect); 3350 } 3351 } 3352 } 3353 if (clipNode->moveMode) 3354 hasPosesInClipboard = true; 3355 } 3356 } 3357 3358 fSelectionList->MakeEmpty(); 3359 fMimeTypesInSelectionCache.MakeEmpty(); 3360 3361 SetHasPosesInClipboard(hasPosesInClipboard || fHasPosesInClipboard); 3362 3363 if (fullInvalidateNeeded) 3364 Invalidate(); 3365 } 3366 3367 3368 void 3369 BPoseView::PlaceFolder(const entry_ref* ref, const BMessage* message) 3370 { 3371 BNode node(ref); 3372 BPoint location; 3373 bool setPosition = false; 3374 3375 if (message->FindPoint("be:invoke_origin", &location) == B_OK) { 3376 // new folder created from popup, place on click point 3377 setPosition = true; 3378 location = ConvertFromScreen(location); 3379 } else if (ViewMode() != kListMode) { 3380 // new folder created by keyboard shortcut 3381 uint32 buttons; 3382 GetMouse(&location, &buttons); 3383 BPoint globalLocation(location); 3384 ConvertToScreen(&globalLocation); 3385 // check if mouse over window 3386 if (Window()->Frame().Contains(globalLocation)) 3387 // create folder under mouse 3388 setPosition = true; 3389 } 3390 3391 if (setPosition) { 3392 Model* targetModel = TargetModel(); 3393 if (targetModel != NULL && targetModel->NodeRef() != NULL) { 3394 FSSetPoseLocation(targetModel->NodeRef()->node, &node, 3395 location); 3396 } 3397 } 3398 } 3399 3400 3401 void 3402 BPoseView::NewFileFromTemplate(const BMessage* message) 3403 { 3404 Model* targetModel = TargetModel(); 3405 ThrowOnAssert(targetModel != NULL); 3406 3407 entry_ref destEntryRef; 3408 node_ref destNodeRef; 3409 3410 BDirectory destDir(targetModel->NodeRef()); 3411 if (destDir.InitCheck() != B_OK) 3412 return; 3413 3414 // TODO: Localise this 3415 char fileName[B_FILE_NAME_LENGTH] = "New "; 3416 strlcat(fileName, message->FindString("name"), sizeof(fileName)); 3417 FSMakeOriginalName(fileName, &destDir, " copy"); 3418 3419 entry_ref srcRef; 3420 message->FindRef("refs_template", &srcRef); 3421 3422 BDirectory dir(&srcRef); 3423 3424 if (dir.InitCheck() == B_OK) { 3425 // special handling of directories 3426 if (FSCreateNewFolderIn(targetModel->NodeRef(), &destEntryRef, 3427 &destNodeRef) == B_OK) { 3428 BEntry destEntry(&destEntryRef); 3429 destEntry.Rename(fileName); 3430 } 3431 } else { 3432 BFile srcFile(&srcRef, B_READ_ONLY); 3433 BFile destFile(&destDir, fileName, B_READ_WRITE | B_CREATE_FILE); 3434 3435 // copy the data from the template file 3436 char* buffer = new char[1024]; 3437 ssize_t result; 3438 do { 3439 result = srcFile.Read(buffer, 1024); 3440 3441 if (result > 0) { 3442 ssize_t written = destFile.Write(buffer, (size_t)result); 3443 if (written != result) 3444 result = written < B_OK ? written : B_ERROR; 3445 } 3446 } while (result > 0); 3447 delete[] buffer; 3448 } 3449 3450 // todo: create an UndoItem 3451 3452 // copy the attributes from the template file 3453 BNode srcNode(&srcRef); 3454 BNode destNode(&destDir, fileName); 3455 FSCopyAttributesAndStats(&srcNode, &destNode, false); 3456 3457 BEntry entry(&destDir, fileName); 3458 entry.GetRef(&destEntryRef); 3459 3460 // try to place new item at click point or under mouse if possible 3461 PlaceFolder(&destEntryRef, message); 3462 3463 // start renaming the entry 3464 int32 index; 3465 BPose* pose = EntryCreated(targetModel->NodeRef(), &destNodeRef, 3466 destEntryRef.name, &index); 3467 3468 if (pose != NULL) { 3469 WatchNewNode(pose->TargetModel()->NodeRef()); 3470 UpdateScrollRange(); 3471 CommitActivePose(); 3472 SelectPose(pose, index); 3473 pose->EditFirstWidget(BPoint(0, index * fListElemHeight), this); 3474 } 3475 } 3476 3477 3478 void 3479 BPoseView::NewFolder(const BMessage* message) 3480 { 3481 Model* targetModel = TargetModel(); 3482 ThrowOnAssert(targetModel != NULL); 3483 3484 entry_ref ref; 3485 node_ref nodeRef; 3486 3487 if (FSCreateNewFolderIn(targetModel->NodeRef(), &ref, &nodeRef) == B_OK) { 3488 // try to place new folder at click point or under mouse if possible 3489 3490 PlaceFolder(&ref, message); 3491 3492 int32 index; 3493 BPose* pose = EntryCreated(targetModel->NodeRef(), &nodeRef, ref.name, 3494 &index); 3495 3496 if (fFiltering) { 3497 if (fFilteredPoseList->FindPose(&nodeRef, &index) == NULL) { 3498 float scrollBy = 0; 3499 BRect bounds = Bounds(); 3500 AddPoseToList(fFilteredPoseList, true, true, pose, bounds, 3501 scrollBy, true, &index); 3502 } 3503 } 3504 3505 if (pose != NULL) { 3506 UpdateScrollRange(); 3507 CommitActivePose(); 3508 SelectPose(pose, index); 3509 pose->EditFirstWidget(BPoint(0, index * fListElemHeight), this); 3510 } 3511 } 3512 } 3513 3514 3515 void 3516 BPoseView::Cleanup(bool doAll) 3517 { 3518 if (ViewMode() == kListMode) 3519 return; 3520 3521 BContainerWindow* window = ContainerWindow(); 3522 if (window == NULL) 3523 return; 3524 3525 // replace all icons from the top 3526 if (doAll) { 3527 // sort by sort field 3528 SortPoses(); 3529 3530 DisableScrollBars(); 3531 ClearExtent(); 3532 ClearSelection(); 3533 ScrollTo(B_ORIGIN); 3534 UpdateScrollRange(); 3535 SetScrollBarsTo(B_ORIGIN); 3536 ResetPosePlacementHint(); 3537 3538 BRect viewBounds(Bounds()); 3539 3540 // relocate all poses in list (reset vs list) 3541 fVSPoseList->MakeEmpty(); 3542 int32 count = fPoseList->CountItems(); 3543 for (int32 index = 0; index < count; index++) { 3544 BPose* pose = fPoseList->ItemAt(index); 3545 PlacePose(pose, viewBounds); 3546 AddToVSList(pose); 3547 } 3548 3549 RecalcExtent(); 3550 3551 // scroll icons into view so that leftmost icon is "fOffset" from left 3552 UpdateScrollRange(); 3553 EnableScrollBars(); 3554 3555 if (HScrollBar()) { 3556 float min; 3557 float max; 3558 HScrollBar()->GetRange(&min, &max); 3559 HScrollBar()->SetValue(min); 3560 } 3561 3562 UpdateScrollRange(); 3563 Invalidate(viewBounds); 3564 } else { 3565 // clean up items to nearest locations 3566 BRect viewBounds(Bounds()); 3567 int32 count = fPoseList->CountItems(); 3568 for (int32 index = 0; index < count; index++) { 3569 BPose* pose = fPoseList->ItemAt(index); 3570 BPoint location(pose->Location(this)); 3571 BPoint newLocation(PinToGrid(location, fGrid, fOffset)); 3572 3573 bool intersectsDesktopElements = !IsValidLocation(pose); 3574 3575 // do we need to move pose to a grid location? 3576 if (newLocation != location || intersectsDesktopElements) { 3577 // remove pose from VSlist so it doesn't "bump" into itself 3578 RemoveFromVSList(pose); 3579 3580 // try new grid location 3581 BRect oldBounds(pose->CalcRect(this)); 3582 BRect poseBounds(oldBounds); 3583 pose->MoveTo(newLocation, this); 3584 if (SlotOccupied(oldBounds, viewBounds) 3585 || intersectsDesktopElements) { 3586 ResetPosePlacementHint(); 3587 PlacePose(pose, viewBounds); 3588 poseBounds = pose->CalcRect(this); 3589 } 3590 3591 AddToVSList(pose); 3592 AddToExtent(poseBounds); 3593 3594 if (viewBounds.Intersects(poseBounds)) 3595 Invalidate(poseBounds); 3596 if (viewBounds.Intersects(oldBounds)) 3597 Invalidate(oldBounds); 3598 } 3599 } 3600 } 3601 } 3602 3603 3604 void 3605 BPoseView::PlacePose(BPose* pose, BRect &viewBounds) 3606 { 3607 // move pose to probable location 3608 pose->SetLocation(fHintLocation, this); 3609 BRect rect(pose->CalcRect(this)); 3610 BPoint deltaFromBounds(fHintLocation - rect.LeftTop()); 3611 3612 // make pose rect a little bigger to ensure space between poses 3613 rect.InsetBy(-3, 0); 3614 3615 bool checkValidLocation = IsDesktopWindow(); 3616 3617 // find an empty slot to put pose into 3618 while (SlotOccupied(rect, viewBounds) 3619 // check good location on the desktop 3620 || (checkValidLocation && !IsValidLocation(rect))) { 3621 NextSlot(pose, rect, viewBounds); 3622 // we've scanned the entire desktop without finding an available 3623 // position, give up and simply place it towards the top left. 3624 if (checkValidLocation && !rect.Intersects(viewBounds)) { 3625 fHintLocation = PinToGrid(BPoint(0.0, 0.0), fGrid, fOffset); 3626 pose->SetLocation(fHintLocation, this); 3627 rect = pose->CalcRect(this); 3628 break; 3629 } 3630 } 3631 3632 rect.InsetBy(3, 0); 3633 3634 fHintLocation = pose->Location(this) + BPoint(fGrid.x, 0); 3635 3636 pose->SetLocation(rect.LeftTop() + deltaFromBounds, this); 3637 pose->SetSaveLocation(); 3638 } 3639 3640 3641 bool 3642 BPoseView::IsValidLocation(const BPose* pose) 3643 { 3644 if (!IsDesktopWindow()) 3645 return true; 3646 3647 BRect rect(pose->CalcRect(this)); 3648 rect.InsetBy(-3, 0); 3649 return IsValidLocation(rect); 3650 } 3651 3652 3653 bool 3654 BPoseView::IsValidLocation(const BRect& rect) 3655 { 3656 if (!IsDesktopWindow()) 3657 return true; 3658 3659 // on the desktop, don't allow icons outside of the view bounds 3660 if (!Bounds().Contains(rect)) 3661 return false; 3662 3663 // also check the deskbar frame 3664 BRect deskbarFrame; 3665 if (GetDeskbarFrame(&deskbarFrame) == B_OK) { 3666 deskbarFrame.InsetBy(-10, -10); 3667 if (deskbarFrame.Intersects(rect)) 3668 return false; 3669 } 3670 3671 // check replicants 3672 for (int32 i = 0; BView* child = ChildAt(i); i++) { 3673 BRect childFrame = child->Frame(); 3674 childFrame.InsetBy(-5, -5); 3675 if (childFrame.Intersects(rect)) 3676 return false; 3677 } 3678 3679 // location is ok 3680 return true; 3681 } 3682 3683 3684 status_t 3685 BPoseView::GetDeskbarFrame(BRect* frame) 3686 { 3687 // only really check the Deskbar frame every half a second, 3688 // use a cached value otherwise 3689 status_t result = B_OK; 3690 bigtime_t now = system_time(); 3691 if (fLastDeskbarFrameCheckTime + 500000 < now) { 3692 // it's time to check the Deskbar frame again 3693 result = get_deskbar_frame(&fDeskbarFrame); 3694 fLastDeskbarFrameCheckTime = now; 3695 } 3696 *frame = fDeskbarFrame; 3697 3698 return result; 3699 } 3700 3701 3702 void 3703 BPoseView::CheckAutoPlacedPoses() 3704 { 3705 if (ViewMode() == kListMode) 3706 return; 3707 3708 BRect viewBounds(Bounds()); 3709 3710 int32 count = fPoseList->CountItems(); 3711 for (int32 index = 0; index < count; index++) { 3712 BPose* pose = fPoseList->ItemAt(index); 3713 if (pose->WasAutoPlaced()) { 3714 RemoveFromVSList(pose); 3715 fHintLocation = pose->Location(this); 3716 BRect oldBounds(pose->CalcRect(this)); 3717 PlacePose(pose, viewBounds); 3718 3719 BRect newBounds(pose->CalcRect(this)); 3720 AddToVSList(pose); 3721 pose->SetAutoPlaced(false); 3722 AddToExtent(newBounds); 3723 3724 Invalidate(oldBounds); 3725 Invalidate(newBounds); 3726 } 3727 } 3728 } 3729 3730 3731 void 3732 BPoseView::CheckPoseVisibility(BRect* newFrame) 3733 { 3734 bool desktop = IsDesktopWindow() && newFrame != 0; 3735 3736 BRect deskFrame; 3737 if (desktop) { 3738 ASSERT(newFrame); 3739 deskFrame = *newFrame; 3740 } 3741 3742 ASSERT(ViewMode() != kListMode); 3743 3744 BRect bounds(Bounds()); 3745 bounds.InsetBy(20, 20); 3746 3747 int32 count = fPoseList->CountItems(); 3748 for (int32 index = 0; index < count; index++) { 3749 BPose* pose = fPoseList->ItemAt(index); 3750 BPoint newLocation(pose->Location(this)); 3751 bool locationNeedsUpdating = false; 3752 3753 if (desktop) { 3754 // we just switched screen resolution, pick up the right 3755 // icon locations for the new resolution 3756 Model* model = pose->TargetModel(); 3757 ExtendedPoseInfo* info = ReadExtendedPoseInfo(model); 3758 if (info && info->HasLocationForFrame(deskFrame)) { 3759 BPoint locationForFrame = info->LocationForFrame(deskFrame); 3760 if (locationForFrame != newLocation) { 3761 // found one and it is different from the current 3762 newLocation = locationForFrame; 3763 locationNeedsUpdating = true; 3764 Invalidate(pose->CalcRect(this)); 3765 // make sure the old icon gets erased 3766 RemoveFromVSList(pose); 3767 pose->SetLocation(newLocation, this); 3768 // set the new location 3769 } 3770 } 3771 delete[] (char*)info; 3772 // TODO: fix up this mess 3773 } 3774 3775 BRect rect(pose->CalcRect(this)); 3776 if (!rect.Intersects(bounds)) { 3777 // pose doesn't fit on screen 3778 if (!locationNeedsUpdating) { 3779 // didn't already invalidate and remove in the desktop case 3780 Invalidate(rect); 3781 RemoveFromVSList(pose); 3782 } 3783 BPoint loc(pose->Location(this)); 3784 loc.ConstrainTo(bounds); 3785 // place it onscreen 3786 3787 pose->SetLocation(loc, this); 3788 // set the new location 3789 locationNeedsUpdating = true; 3790 } 3791 3792 if (locationNeedsUpdating) { 3793 // pose got reposition by one or both of the above 3794 pose->SetSaveLocation(); 3795 AddToVSList(pose); 3796 // add it at the new location 3797 Invalidate(pose->CalcRect(this)); 3798 // make sure the new pose location updates properly 3799 } 3800 } 3801 } 3802 3803 3804 bool 3805 BPoseView::SlotOccupied(BRect poseRect, BRect viewBounds) const 3806 { 3807 if (fVSPoseList->IsEmpty()) 3808 return false; 3809 3810 // ## be sure to keep this code in sync with calls to NextSlot 3811 // ## in terms of the comparison of fHintLocation and PinToGrid 3812 if (poseRect.right >= viewBounds.right) { 3813 BPoint point(viewBounds.left + fOffset.x, 0); 3814 point = PinToGrid(point, fGrid, fOffset); 3815 if (fHintLocation.x != point.x) 3816 return true; 3817 } 3818 3819 // search only nearby poses (vertically) 3820 int32 index = FirstIndexAtOrBelow((int32)(poseRect.top - IconPoseHeight())); 3821 int32 numPoses = fVSPoseList->CountItems(); 3822 3823 while (index < numPoses && fVSPoseList->ItemAt(index)->Location(this).y 3824 < poseRect.bottom) { 3825 BRect rect(fVSPoseList->ItemAt(index)->CalcRect(this)); 3826 if (poseRect.Intersects(rect)) 3827 return true; 3828 3829 index++; 3830 } 3831 3832 return false; 3833 } 3834 3835 3836 void 3837 BPoseView::NextSlot(BPose* pose, BRect &poseRect, BRect viewBounds) 3838 { 3839 // move to next slot 3840 poseRect.OffsetBy(fGrid.x, 0); 3841 3842 // if we reached the end of row go down to next row 3843 if (poseRect.right > viewBounds.right) { 3844 fHintLocation.y += fGrid.y; 3845 fHintLocation.x = viewBounds.left + fOffset.x; 3846 fHintLocation = PinToGrid(fHintLocation, fGrid, fOffset); 3847 pose->SetLocation(fHintLocation, this); 3848 poseRect = pose->CalcRect(this); 3849 poseRect.InsetBy(-3, 0); 3850 } 3851 } 3852 3853 3854 int32 3855 BPoseView::FirstIndexAtOrBelow(int32 y, bool constrainIndex) const 3856 { 3857 // This method performs a binary search on the vertically sorted pose list 3858 // and returns either the index of the first pose at a given y location or 3859 // the proper index to insert a new pose into the list. 3860 3861 int32 index = 0; 3862 int32 l = 0; 3863 int32 r = fVSPoseList->CountItems() - 1; 3864 3865 while (l <= r) { 3866 index = (l + r) >> 1; 3867 int32 result 3868 = (int32)(y - fVSPoseList->ItemAt(index)->Location(this).y); 3869 3870 if (result < 0) 3871 r = index - 1; 3872 else if (result > 0) 3873 l = index + 1; 3874 else { 3875 // compare turned out equal, find first pose 3876 while (index > 0 3877 && y == fVSPoseList->ItemAt(index - 1)->Location(this).y) 3878 index--; 3879 return index; 3880 } 3881 } 3882 3883 // didn't find pose AT location y - bump index to proper insert point 3884 while (index < fVSPoseList->CountItems() 3885 && fVSPoseList->ItemAt(index)->Location(this).y <= y) { 3886 index++; 3887 } 3888 3889 // if flag is true then constrain index to legal value since this 3890 // method returns the proper insertion point which could be outside 3891 // the current bounds of the list 3892 if (constrainIndex && index >= fVSPoseList->CountItems()) 3893 index = fVSPoseList->CountItems() - 1; 3894 3895 return index; 3896 } 3897 3898 3899 void 3900 BPoseView::AddToVSList(BPose* pose) 3901 { 3902 int32 index = FirstIndexAtOrBelow((int32)pose->Location(this).y, false); 3903 fVSPoseList->AddItem(pose, index); 3904 } 3905 3906 3907 int32 3908 BPoseView::RemoveFromVSList(const BPose* pose) 3909 { 3910 //int32 index = FirstIndexAtOrBelow((int32)pose->Location(this).y); 3911 // This optimisation is buggy and the index returned can be greater 3912 // than the actual index of the pose we search, thus missing it 3913 // and failing to remove it. This having severe implications 3914 // everywhere in the code as it is asserted that it must be always 3915 // in sync with fPoseList. See ticket #4322. 3916 int32 index = 0; 3917 3918 int32 count = fVSPoseList->CountItems(); 3919 for (; index < count; index++) { 3920 BPose* matchingPose = fVSPoseList->ItemAt(index); 3921 ASSERT(matchingPose); 3922 if (!matchingPose) 3923 return -1; 3924 3925 if (pose == matchingPose) { 3926 fVSPoseList->RemoveItemAt(index); 3927 return index; 3928 } 3929 } 3930 3931 return -1; 3932 } 3933 3934 3935 BPoint 3936 BPoseView::PinToGrid(BPoint point, BPoint grid, BPoint offset) const 3937 { 3938 if (grid.x == 0 || grid.y == 0) 3939 return point; 3940 3941 point -= offset; 3942 BPoint gridLoc(point); 3943 3944 if (point.x >= 0) 3945 gridLoc.x = floorf((point.x / grid.x) + 0.5f) * grid.x; 3946 else 3947 gridLoc.x = ceilf((point.x / grid.x) - 0.5f) * grid.x; 3948 3949 if (point.y >= 0) 3950 gridLoc.y = floorf((point.y / grid.y) + 0.5f) * grid.y; 3951 else 3952 gridLoc.y = ceilf((point.y / grid.y) - 0.5f) * grid.y; 3953 3954 gridLoc += offset; 3955 3956 return gridLoc; 3957 } 3958 3959 3960 void 3961 BPoseView::ResetPosePlacementHint() 3962 { 3963 fHintLocation = PinToGrid(BPoint(LeftTop().x + fOffset.x, 3964 LeftTop().y + fOffset.y), fGrid, fOffset); 3965 } 3966 3967 3968 void 3969 BPoseView::SelectPoses(int32 start, int32 end) 3970 { 3971 // clear selection list 3972 fSelectionList->MakeEmpty(); 3973 fMimeTypesInSelectionCache.MakeEmpty(); 3974 fSelectionPivotPose = NULL; 3975 fRealPivotPose = NULL; 3976 3977 bool iconMode = ViewMode() != kListMode; 3978 BPoint loc(0, start * fListElemHeight); 3979 BRect bounds(Bounds()); 3980 3981 PoseList* poseList = CurrentPoseList(); 3982 int32 count = poseList->CountItems(); 3983 for (int32 index = start; index < end && index < count; index++) { 3984 BPose* pose = poseList->ItemAt(index); 3985 fSelectionList->AddItem(pose); 3986 if (index == start) 3987 fSelectionPivotPose = pose; 3988 if (!pose->IsSelected()) { 3989 pose->Select(true); 3990 BRect poseRect; 3991 if (iconMode) 3992 poseRect = pose->CalcRect(this); 3993 else 3994 poseRect = pose->CalcRect(loc, this, false); 3995 3996 if (bounds.Intersects(poseRect)) { 3997 Invalidate(poseRect); 3998 } 3999 } 4000 4001 loc.y += fListElemHeight; 4002 } 4003 } 4004 4005 4006 void 4007 BPoseView::MoveOrChangePoseSelection(int32 to) 4008 { 4009 PoseList* poseList = CurrentPoseList(); 4010 BPose* first = fSelectionList->FirstItem(); 4011 4012 if (first != NULL && fMultipleSelection 4013 && (modifiers() & B_SHIFT_KEY) != 0) { 4014 // Extend selection 4015 BPose* target = poseList->ItemAt(to); 4016 BPose* last = fSelectionList->LastItem(); 4017 int32 firstIndex = poseList->IndexOf(first); 4018 int32 lastIndex = poseList->IndexOf(last); 4019 4020 int32 from = to < firstIndex ? firstIndex : lastIndex; 4021 int32 step = from < to ? 1 : -1; 4022 4023 // TODO: shrink selection depending on anchor 4024 bool select = true; 4025 4026 for (int32 index = from; step > 0 ? index <= to : index >= to; 4027 index += step) { 4028 BPose* pose = poseList->ItemAt(index); 4029 if (pose != NULL && pose->IsSelected() != select) 4030 AddRemovePoseFromSelection(pose, index, select); 4031 } 4032 if (target != NULL) 4033 ScrollIntoView(target, to); 4034 } else { 4035 SelectPose(poseList->ItemAt(to), to); 4036 } 4037 } 4038 4039 4040 void 4041 BPoseView::ScrollIntoView(BPose* pose, int32 index) 4042 { 4043 ScrollIntoView(CalcPoseRect(pose, index, true)); 4044 } 4045 4046 4047 void 4048 BPoseView::ScrollIntoView(BRect poseRect) 4049 { 4050 if (IsDesktopWindow()) 4051 return; 4052 4053 if (ViewMode() == kListMode) { 4054 // if we're in list view then we only care that the entire 4055 // pose is visible vertically, not horizontally 4056 poseRect.left = 0; 4057 poseRect.right = poseRect.left + 1; 4058 } 4059 4060 if (!Bounds().Contains(poseRect)) 4061 SetScrollBarsTo(poseRect.LeftTop()); 4062 } 4063 4064 4065 void 4066 BPoseView::SelectPose(BPose* pose, int32 index, bool scrollIntoView) 4067 { 4068 if (pose == NULL || fSelectionList->CountItems() > 1 || !pose->IsSelected()) 4069 ClearSelection(); 4070 4071 AddPoseToSelection(pose, index, scrollIntoView); 4072 4073 if (pose != NULL) 4074 fSelectionPivotPose = pose; 4075 } 4076 4077 4078 void 4079 BPoseView::AddPoseToSelection(BPose* pose, int32 index, bool scrollIntoView) 4080 { 4081 // TODO: need to check if pose is member of selection list 4082 if (pose != NULL && !pose->IsSelected()) { 4083 pose->Select(true); 4084 fSelectionList->AddItem(pose); 4085 4086 BRect poseRect = CalcPoseRect(pose, index); 4087 Invalidate(poseRect); 4088 4089 if (scrollIntoView) 4090 ScrollIntoView(poseRect); 4091 4092 if (fSelectionChangedHook) 4093 ContainerWindow()->SelectionChanged(); 4094 } 4095 } 4096 4097 4098 void 4099 BPoseView::RemovePoseFromSelection(BPose* pose) 4100 { 4101 if (fSelectionPivotPose == pose) 4102 fSelectionPivotPose = NULL; 4103 4104 if (fRealPivotPose == pose) 4105 fRealPivotPose = NULL; 4106 4107 if (!fSelectionList->RemoveItem(pose)) { 4108 // wasn't selected to begin with 4109 return; 4110 } 4111 4112 pose->Select(false); 4113 if (ViewMode() == kListMode) { 4114 // TODO: need a simple call to CalcRect that works both in listView and 4115 // icon view modes without the need for an index/pos 4116 PoseList* poseList = CurrentPoseList(); 4117 int32 count = poseList->CountItems(); 4118 BPoint loc(0, 0); 4119 for (int32 index = 0; index < count; index++) { 4120 if (pose == poseList->ItemAt(index)) { 4121 Invalidate(pose->CalcRect(loc, this)); 4122 break; 4123 } 4124 loc.y += fListElemHeight; 4125 } 4126 } else 4127 Invalidate(pose->CalcRect(this)); 4128 4129 if (fSelectionChangedHook) 4130 ContainerWindow()->SelectionChanged(); 4131 } 4132 4133 4134 bool 4135 BPoseView::EachItemInDraggedSelection(const BMessage* message, 4136 bool (*func)(BPose*, BPoseView*, void*), BPoseView* poseView, 4137 void* passThru) 4138 { 4139 BContainerWindow* srcWindow; 4140 if (message->FindPointer("src_window", (void**)&srcWindow) != B_OK) 4141 return false; 4142 4143 AutoLock<BWindow> lock(srcWindow); 4144 if (!lock) 4145 return false; 4146 4147 PoseList* selectionList = srcWindow->PoseView()->SelectionList(); 4148 int32 count = selectionList->CountItems(); 4149 4150 for (int32 index = 0; index < count; index++) { 4151 BPose* pose = selectionList->ItemAt(index); 4152 if (func(pose, poseView, passThru)) 4153 // early iteration termination 4154 return true; 4155 } 4156 4157 return false; 4158 } 4159 4160 4161 static bool 4162 ContainsOne(BString* string, const char* matchString) 4163 { 4164 return strcmp(string->String(), matchString) == 0; 4165 } 4166 4167 4168 bool 4169 BPoseView::FindDragNDropAction(const BMessage* dragMessage, bool &canCopy, 4170 bool &canMove, bool &canLink, bool &canErase) 4171 { 4172 canCopy = false; 4173 canMove = false; 4174 canErase = false; 4175 canLink = false; 4176 if (!dragMessage->HasInt32("be:actions")) 4177 return false; 4178 4179 int32 action; 4180 for (int32 index = 0; 4181 dragMessage->FindInt32("be:actions", index, &action) == B_OK; 4182 index++) { 4183 switch (action) { 4184 case B_MOVE_TARGET: 4185 canMove = true; 4186 break; 4187 4188 case B_COPY_TARGET: 4189 canCopy = true; 4190 break; 4191 4192 case B_TRASH_TARGET: 4193 canErase = true; 4194 break; 4195 4196 case B_LINK_TARGET: 4197 canLink = true; 4198 break; 4199 } 4200 } 4201 4202 return canCopy || canMove || canErase || canLink; 4203 } 4204 4205 4206 bool 4207 BPoseView::CanTrashForeignDrag(const Model* targetModel) 4208 { 4209 return targetModel->IsTrash(); 4210 } 4211 4212 4213 bool 4214 BPoseView::CanCopyOrMoveForeignDrag(const Model* targetModel, 4215 const BMessage* dragMessage) 4216 { 4217 if (!targetModel->IsDirectory()) 4218 return false; 4219 4220 // in order to handle a clipping file, the drag initiator must be able 4221 // do deal with B_FILE_MIME_TYPE 4222 for (int32 index = 0; ; index++) { 4223 const char* type; 4224 if (dragMessage->FindString("be:types", index, &type) != B_OK) 4225 break; 4226 4227 if (strcasecmp(type, B_FILE_MIME_TYPE) == 0) 4228 return true; 4229 } 4230 4231 return false; 4232 } 4233 4234 4235 bool 4236 BPoseView::CanHandleDragSelection(const Model* target, 4237 const BMessage* dragMessage, bool ignoreTypes) 4238 { 4239 if (ignoreTypes) 4240 return target->IsDropTarget(); 4241 4242 ASSERT(dragMessage != NULL); 4243 4244 BContainerWindow* srcWindow; 4245 status_t result = dragMessage->FindPointer("src_window", (void**)&srcWindow); 4246 if (result != B_OK || srcWindow == NULL) { 4247 // handle a foreign drag 4248 bool canCopy; 4249 bool canMove; 4250 bool canErase; 4251 bool canLink; 4252 FindDragNDropAction(dragMessage, canCopy, canMove, canLink, canErase); 4253 if (canErase && CanTrashForeignDrag(target)) 4254 return true; 4255 4256 if (canCopy || canMove) { 4257 if (CanCopyOrMoveForeignDrag(target, dragMessage)) 4258 return true; 4259 4260 // TODO: collect all mime types here and pass into 4261 // target->IsDropTargetForList(mimeTypeList); 4262 } 4263 4264 // handle an old style entry_refs only darg message 4265 if (dragMessage->HasRef("refs") && target->IsDirectory()) 4266 return true; 4267 4268 // handle simple text clipping drag&drop message 4269 if (dragMessage->HasData(kPlainTextMimeType, B_MIME_TYPE) 4270 && target->IsDirectory()) { 4271 return true; 4272 } 4273 4274 // handle simple bitmap clipping drag&drop message 4275 if (target->IsDirectory() 4276 && (dragMessage->HasData(kBitmapMimeType, B_MESSAGE_TYPE) 4277 || dragMessage->HasData(kLargeIconType, B_MESSAGE_TYPE) 4278 || dragMessage->HasData(kMiniIconType, B_MESSAGE_TYPE))) { 4279 return true; 4280 } 4281 4282 // TODO: check for a drag message full of refs, feed a list of their 4283 // types to target->IsDropTargetForList(mimeTypeList); 4284 return false; 4285 } 4286 4287 ASSERT(srcWindow != NULL); 4288 4289 AutoLock<BWindow> lock(srcWindow); 4290 if (!lock) 4291 return false; 4292 4293 BObjectList<BString>* mimeTypeList 4294 = srcWindow->PoseView()->MimeTypesInSelection(); 4295 if (mimeTypeList->IsEmpty()) { 4296 PoseList* selectionList = srcWindow->PoseView()->SelectionList(); 4297 if (!selectionList->IsEmpty()) { 4298 // no cached data yet, build the cache 4299 int32 count = selectionList->CountItems(); 4300 4301 for (int32 index = 0; index < count; index++) { 4302 // get the mime type of the model, following a possible symlink 4303 BEntry entry(selectionList->ItemAt( 4304 index)->TargetModel()->EntryRef(), true); 4305 if (entry.InitCheck() != B_OK) 4306 continue; 4307 4308 BFile file(&entry, O_RDONLY); 4309 BNodeInfo mime(&file); 4310 4311 if (mime.InitCheck() != B_OK) 4312 continue; 4313 4314 char mimeType[B_MIME_TYPE_LENGTH]; 4315 mime.GetType(mimeType); 4316 4317 // add unique type string 4318 if (!WhileEachListItem(mimeTypeList, ContainsOne, 4319 (const char*)mimeType)) { 4320 BString* newMimeString = new BString(mimeType); 4321 mimeTypeList->AddItem(newMimeString); 4322 } 4323 } 4324 } 4325 } 4326 4327 return target->IsDropTargetForList(mimeTypeList); 4328 } 4329 4330 4331 void 4332 BPoseView::TrySettingPoseLocation(BNode* node, BPoint point) 4333 { 4334 if (ViewMode() == kListMode) 4335 return; 4336 4337 if ((modifiers() & B_COMMAND_KEY) != 0) { 4338 // align to grid if needed 4339 point = PinToGrid(point, fGrid, fOffset); 4340 } 4341 4342 Model* targetModel = TargetModel(); 4343 ASSERT(targetModel != NULL); 4344 4345 if (targetModel != NULL && targetModel->NodeRef() != NULL 4346 && FSSetPoseLocation(targetModel->NodeRef()->node, node, point) 4347 == B_OK) { 4348 // get rid of opposite endianness attribute 4349 node->RemoveAttr(kAttrPoseInfoForeign); 4350 } 4351 } 4352 4353 4354 status_t 4355 BPoseView::CreateClippingFile(BPoseView* poseView, BFile &result, 4356 char* resultingName, BDirectory* directory, BMessage* message, 4357 const char* fallbackName, bool setLocation, BPoint dropPoint) 4358 { 4359 // build a file name 4360 // try picking it up from the message 4361 const char* suggestedName; 4362 if (message && message->FindString("be:clip_name", &suggestedName) == B_OK) 4363 strncpy(resultingName, suggestedName, B_FILE_NAME_LENGTH - 1); 4364 else 4365 strcpy(resultingName, fallbackName); 4366 4367 FSMakeOriginalName(resultingName, directory, ""); 4368 4369 // create a clipping file 4370 status_t error = directory->CreateFile(resultingName, &result, true); 4371 if (error != B_OK) 4372 return error; 4373 4374 if (setLocation && poseView != NULL) 4375 poseView->TrySettingPoseLocation(&result, dropPoint); 4376 4377 return B_OK; 4378 } 4379 4380 4381 static int32 4382 RunMimeTypeDestinationMenu(const char* actionText, 4383 const BObjectList<BString>* types, 4384 const BObjectList<BString>* specificItems, BPoint where) 4385 { 4386 int32 count; 4387 if (types != NULL) 4388 count = types->CountItems(); 4389 else 4390 count = specificItems->CountItems(); 4391 4392 if (count == 0) 4393 return 0; 4394 4395 BPopUpMenu* menu = new BPopUpMenu("create clipping"); 4396 menu->SetFont(be_plain_font); 4397 4398 for (int32 index = 0; index < count; index++) { 4399 const char* embedTypeAs = NULL; 4400 char buffer[256]; 4401 if (types) { 4402 types->ItemAt(index)->String(); 4403 BMimeType mimeType(embedTypeAs); 4404 4405 if (mimeType.GetShortDescription(buffer) == B_OK) 4406 embedTypeAs = buffer; 4407 } 4408 4409 BString description; 4410 if (specificItems->ItemAt(index)->Length()) { 4411 description << (const BString &)(*specificItems->ItemAt(index)); 4412 4413 if (embedTypeAs) 4414 description << " (" << embedTypeAs << ")"; 4415 4416 } else if (types) 4417 description = embedTypeAs; 4418 4419 BString labelText; 4420 if (actionText) { 4421 int32 length = 1024 - 1 - (int32)strlen(actionText); 4422 if (length > 0) { 4423 description.Truncate(length); 4424 labelText.SetTo(actionText); 4425 labelText.ReplaceFirst("%s", description.String()); 4426 } else 4427 labelText.SetTo(B_TRANSLATE("label too long")); 4428 } else 4429 labelText = description; 4430 4431 menu->AddItem(new BMenuItem(labelText.String(), 0)); 4432 } 4433 4434 menu->AddSeparatorItem(); 4435 menu->AddItem(new BMenuItem(B_TRANSLATE("Cancel"), 0)); 4436 4437 int32 result = -1; 4438 BMenuItem* resultingItem = menu->Go(where, false, true); 4439 if (resultingItem) { 4440 int32 index = menu->IndexOf(resultingItem); 4441 if (index < count) 4442 result = index; 4443 } 4444 4445 delete menu; 4446 4447 return result; 4448 } 4449 4450 4451 bool 4452 BPoseView::HandleMessageDropped(BMessage* message) 4453 { 4454 ASSERT(message->WasDropped()); 4455 4456 // reset system cursor in case it was altered by drag and drop 4457 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); 4458 fCursorCheck = false; 4459 4460 if (!fDropEnabled) 4461 return false; 4462 4463 BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window()); 4464 if (window != NULL && message->HasData("RGBColor", 'RGBC')) { 4465 // do not handle roColor-style drops here, pass them on to the desktop 4466 BMessenger((BHandler*)window).SendMessage(message); 4467 4468 return true; 4469 } 4470 4471 if (fDropTarget && !DragSelectionContains(fDropTarget, message)) 4472 HiliteDropTarget(false); 4473 4474 fDropTarget = NULL; 4475 4476 ASSERT(TargetModel() != NULL); 4477 BPoint offset; 4478 BPoint dropPoint(message->DropPoint(&offset)); 4479 ConvertFromScreen(&dropPoint); 4480 4481 // tenatively figure out the pose we dropped the file onto 4482 int32 index; 4483 BPose* targetPose = FindPose(dropPoint, &index); 4484 Model tmpTarget; 4485 Model* targetModel = NULL; 4486 if (targetPose != NULL) { 4487 targetModel = targetPose->TargetModel(); 4488 if (targetModel->IsSymLink() 4489 && tmpTarget.SetTo(targetPose->TargetModel()->EntryRef(), 4490 true, true) == B_OK) { 4491 targetModel = &tmpTarget; 4492 } 4493 } 4494 4495 return HandleDropCommon(message, targetModel, targetPose, this, dropPoint); 4496 } 4497 4498 4499 bool 4500 BPoseView::HandleDropCommon(BMessage* message, Model* targetModel, 4501 BPose* targetPose, BView* view, BPoint dropPoint) 4502 { 4503 uint32 buttons = (uint32)message->FindInt32("buttons"); 4504 4505 BContainerWindow* containerWindow = NULL; 4506 BPoseView* poseView = dynamic_cast<BPoseView*>(view); 4507 if (poseView != NULL) 4508 containerWindow = poseView->ContainerWindow(); 4509 4510 // look for srcWindow to determine whether drag was initiated in tracker 4511 BContainerWindow* srcWindow = NULL; 4512 status_t result = message->FindPointer("src_window", (void**)&srcWindow); 4513 if (result != B_OK || srcWindow == NULL) { 4514 // drag was from another app 4515 4516 if (targetModel == NULL && poseView != NULL) 4517 targetModel = poseView->TargetModel(); 4518 4519 // figure out if we dropped a file onto a directory and set 4520 // the targetDirectory to it, else set it to this pose view 4521 BDirectory targetDirectory; 4522 if (targetModel != NULL && targetModel->IsDirectory()) 4523 targetDirectory.SetTo(targetModel->EntryRef()); 4524 4525 if (targetModel != NULL && targetModel->IsRoot()) { 4526 // don't drop anything into the root disk 4527 return false; 4528 } 4529 4530 bool canCopy; 4531 bool canMove; 4532 bool canErase; 4533 bool canLink; 4534 if (FindDragNDropAction(message, canCopy, canMove, canLink, canErase)) { 4535 // new D&D protocol 4536 // what action can the drag initiator do? 4537 if (canErase && CanTrashForeignDrag(targetModel)) { 4538 BMessage reply(B_TRASH_TARGET); 4539 message->SendReply(&reply); 4540 return true; 4541 } 4542 4543 if ((canCopy || canMove) 4544 && CanCopyOrMoveForeignDrag(targetModel, message)) { 4545 // handle the promise style drag&drop 4546 4547 // fish for specification of specialized menu items 4548 BObjectList<BString> actionSpecifiers(10, true); 4549 for (int32 index = 0; ; index++) { 4550 const char* string; 4551 if (message->FindString("be:actionspecifier", index, 4552 &string) != B_OK) { 4553 break; 4554 } 4555 4556 ASSERT(string != NULL); 4557 actionSpecifiers.AddItem(new BString(string)); 4558 } 4559 4560 // build the list of types the drag originator offers 4561 BObjectList<BString> types(10, true); 4562 BObjectList<BString> typeNames(10, true); 4563 for (int32 index = 0; ; index++) { 4564 const char* string; 4565 if (message->FindString("be:filetypes", index, &string) 4566 != B_OK) { 4567 break; 4568 } 4569 4570 ASSERT(string != NULL); 4571 types.AddItem(new BString(string)); 4572 4573 const char* typeName = ""; 4574 message->FindString("be:type_descriptions", index, 4575 &typeName); 4576 typeNames.AddItem(new BString(typeName)); 4577 } 4578 4579 int32 specificTypeIndex = -1; 4580 int32 specificActionIndex = -1; 4581 4582 // if control down, run a popup menu 4583 if (canCopy 4584 && SecondaryMouseButtonDown(modifiers(), buttons)) { 4585 if (actionSpecifiers.CountItems() > 0) { 4586 specificActionIndex = RunMimeTypeDestinationMenu(NULL, 4587 NULL, &actionSpecifiers, 4588 view->ConvertToScreen(dropPoint)); 4589 4590 if (specificActionIndex == -1) 4591 return false; 4592 } else if (types.CountItems() > 0) { 4593 specificTypeIndex = RunMimeTypeDestinationMenu( 4594 B_TRANSLATE("Create %s clipping"), 4595 &types, &typeNames, 4596 view->ConvertToScreen(dropPoint)); 4597 4598 if (specificTypeIndex == -1) 4599 return false; 4600 } 4601 } 4602 4603 char name[B_FILE_NAME_LENGTH]; 4604 BFile file; 4605 if (CreateClippingFile(poseView, file, name, &targetDirectory, 4606 message, B_TRANSLATE("Untitled clipping"), 4607 targetPose == NULL, dropPoint) != B_OK) { 4608 return false; 4609 } 4610 4611 // here is a file for the drag initiator, it is up to it now 4612 // to stuff it with the goods 4613 4614 // build the reply message 4615 BMessage reply(canCopy ? B_COPY_TARGET : B_MOVE_TARGET); 4616 reply.AddString("be:types", B_FILE_MIME_TYPE); 4617 if (specificTypeIndex != -1) { 4618 // we had the user pick a specific type from a menu, use it 4619 reply.AddString("be:filetypes", 4620 types.ItemAt(specificTypeIndex)->String()); 4621 4622 if (typeNames.ItemAt(specificTypeIndex)->Length()) { 4623 reply.AddString("be:type_descriptions", 4624 typeNames.ItemAt(specificTypeIndex)->String()); 4625 } 4626 } 4627 4628 if (specificActionIndex != -1) { 4629 // we had the user pick a specific type from a menu, use it 4630 reply.AddString("be:actionspecifier", 4631 actionSpecifiers.ItemAt(specificActionIndex)->String()); 4632 } 4633 4634 reply.AddRef("directory", targetModel->EntryRef()); 4635 reply.AddString("name", name); 4636 4637 // Attach any data the originator may have tagged on 4638 BMessage data; 4639 if (message->FindMessage("be:originator-data", &data) == B_OK) 4640 reply.AddMessage("be:originator-data", &data); 4641 4642 // copy over all the file types the drag initiator claimed to 4643 // support 4644 for (int32 index = 0; ; index++) { 4645 const char* type; 4646 if (message->FindString("be:filetypes", index, &type) 4647 != B_OK) { 4648 break; 4649 } 4650 reply.AddString("be:filetypes", type); 4651 } 4652 4653 message->SendReply(&reply); 4654 return true; 4655 } 4656 } 4657 4658 if (message->HasRef("refs")) { 4659 // TODO: decide here on copy, move or create symlink 4660 // look for specific command or bring up popup 4661 // Unify this with local drag&drop 4662 4663 if (!targetModel->IsDirectory()) { 4664 // bail if we are not a directory 4665 return false; 4666 } 4667 4668 bool canRelativeLink = false; 4669 if (!canCopy && !canMove && !canLink && containerWindow) { 4670 if (SecondaryMouseButtonDown(modifiers(), buttons)) { 4671 switch (containerWindow->ShowDropContextMenu(dropPoint)) { 4672 case kCreateRelativeLink: 4673 canRelativeLink = true; 4674 break; 4675 4676 case kCreateLink: 4677 canLink = true; 4678 break; 4679 4680 case kMoveSelectionTo: 4681 canMove = true; 4682 break; 4683 4684 case kCopySelectionTo: 4685 canCopy = true; 4686 break; 4687 4688 case kCancelButton: 4689 default: 4690 // user canceled context menu 4691 return true; 4692 } 4693 } else 4694 canCopy = true; 4695 } 4696 4697 uint32 moveMode; 4698 if (canCopy) 4699 moveMode = kCopySelectionTo; 4700 else if (canMove) 4701 moveMode = kMoveSelectionTo; 4702 else if (canLink) 4703 moveMode = kCreateLink; 4704 else if (canRelativeLink) 4705 moveMode = kCreateRelativeLink; 4706 else { 4707 TRESPASS(); 4708 return true; 4709 } 4710 4711 // handle refs by performing a copy 4712 BObjectList<entry_ref>* entryList 4713 = new BObjectList<entry_ref>(10, true); 4714 4715 for (int32 index = 0; ; index++) { 4716 // copy all enclosed refs into a list 4717 entry_ref ref; 4718 if (message->FindRef("refs", index, &ref) != B_OK) 4719 break; 4720 entryList->AddItem(new entry_ref(ref)); 4721 } 4722 4723 int32 count = entryList->CountItems(); 4724 if (count != 0) { 4725 BList* pointList = 0; 4726 if (poseView != NULL && targetPose != NULL) { 4727 // calculate a pointList to make the icons land 4728 // were we dropped them 4729 pointList = new BList(count); 4730 // force the the icons to lay out in 5 columns 4731 for (int32 index = 0; count; index++) { 4732 for (int32 j = 0; count && j < 4; j++, count--) { 4733 BPoint point( 4734 dropPoint + BPoint(j * poseView->fGrid.x, 4735 index * poseView->fGrid.y)); 4736 pointList->AddItem( 4737 new BPoint(poseView->PinToGrid(point, 4738 poseView->fGrid, poseView->fOffset))); 4739 } 4740 } 4741 } 4742 4743 // perform asynchronous copy 4744 FSMoveToFolder(entryList, new BEntry(targetModel->EntryRef()), 4745 moveMode, pointList); 4746 4747 return true; 4748 } 4749 4750 // nothing to copy, list doesn't get consumed 4751 delete entryList; 4752 return true; 4753 } 4754 if (message->HasData(kPlainTextMimeType, B_MIME_TYPE)) { 4755 // text dropped, make into a clipping file 4756 if (!targetModel->IsDirectory()) { 4757 // bail if we are not a directory 4758 return false; 4759 } 4760 4761 // find the text 4762 ssize_t textLength; 4763 const char* text; 4764 if (message->FindData(kPlainTextMimeType, B_MIME_TYPE, 4765 (const void**)&text, &textLength) != B_OK) { 4766 return false; 4767 } 4768 4769 char name[B_FILE_NAME_LENGTH]; 4770 BFile file; 4771 if (CreateClippingFile(poseView, file, name, &targetDirectory, 4772 message, B_TRANSLATE("Untitled clipping"), !targetPose, 4773 dropPoint) != B_OK) { 4774 return false; 4775 } 4776 4777 // write out the file 4778 if (file.Seek(0, SEEK_SET) == B_ERROR 4779 || file.Write(text, (size_t)textLength) < 0 4780 || file.SetSize(textLength) != B_OK) { 4781 // failed to write file, remove file and bail 4782 file.Unset(); 4783 BEntry entry(&targetDirectory, name); 4784 entry.Remove(); 4785 PRINT(("error writing text into file %s\n", name)); 4786 } 4787 4788 // pick up TextView styles if available and save them with the file 4789 const text_run_array* textRuns = NULL; 4790 ssize_t dataSize = 0; 4791 if (message->FindData("application/x-vnd.Be-text_run_array", 4792 B_MIME_TYPE, (const void**)&textRuns, &dataSize) == B_OK 4793 && textRuns && dataSize) { 4794 // save styles the same way StyledEdit does 4795 int32 tmpSize = dataSize; 4796 void* data = BTextView::FlattenRunArray(textRuns, &tmpSize); 4797 file.WriteAttr("styles", B_RAW_TYPE, 0, data, (size_t)tmpSize); 4798 free(data); 4799 } 4800 4801 // mark as a clipping file 4802 int32 tmp; 4803 file.WriteAttr(kAttrClippingFile, B_RAW_TYPE, 0, &tmp, 4804 sizeof(int32)); 4805 4806 // set the file type 4807 BNodeInfo info(&file); 4808 info.SetType(kPlainTextMimeType); 4809 4810 return true; 4811 } 4812 4813 if (message->HasData(kBitmapMimeType, B_MESSAGE_TYPE) 4814 || message->HasData(kLargeIconType, B_MESSAGE_TYPE) 4815 || message->HasData(kMiniIconType, B_MESSAGE_TYPE)) { 4816 // bitmap, make into a clipping file 4817 if (!targetModel->IsDirectory()) { 4818 // bail if we are not a directory 4819 return false; 4820 } 4821 4822 BMessage embeddedBitmap; 4823 if (message->FindMessage(kBitmapMimeType, &embeddedBitmap) 4824 != B_OK 4825 && message->FindMessage(kLargeIconType, &embeddedBitmap) 4826 != B_OK 4827 && message->FindMessage(kMiniIconType, &embeddedBitmap) 4828 != B_OK) { 4829 return false; 4830 } 4831 4832 char name[B_FILE_NAME_LENGTH]; 4833 4834 BFile file; 4835 if (CreateClippingFile(poseView, file, name, &targetDirectory, 4836 message, B_TRANSLATE("Untitled bitmap"), targetPose == NULL, 4837 dropPoint) != B_OK) { 4838 return false; 4839 } 4840 4841 int32 size = embeddedBitmap.FlattenedSize(); 4842 if (size > 1024 * 1024) { 4843 // bail if too large 4844 return false; 4845 } 4846 4847 char* buffer = new char [size]; 4848 embeddedBitmap.Flatten(buffer, size); 4849 4850 // write out the file 4851 if (file.Seek(0, SEEK_SET) == B_ERROR 4852 || file.Write(buffer, (size_t)size) < 0 4853 || file.SetSize(size) != B_OK) { 4854 // failed to write file, remove file and bail 4855 file.Unset(); 4856 BEntry entry(&targetDirectory, name); 4857 entry.Remove(); 4858 PRINT(("error writing bitmap into file %s\n", name)); 4859 } 4860 4861 // mark as a clipping file 4862 int32 tmp; 4863 file.WriteAttr(kAttrClippingFile, B_RAW_TYPE, 0, &tmp, 4864 sizeof(int32)); 4865 4866 // set the file type 4867 BNodeInfo info(&file); 4868 info.SetType(kBitmapMimeType); 4869 4870 return true; 4871 } 4872 4873 return false; 4874 } 4875 4876 ASSERT(srcWindow != NULL); 4877 4878 if (srcWindow == containerWindow) { 4879 // drag started in this window 4880 containerWindow->Activate(); 4881 containerWindow->UpdateIfNeeded(); 4882 poseView->ResetPosePlacementHint(); 4883 4884 if (DragSelectionContains(targetPose, message)) { 4885 // drop on self 4886 targetModel = NULL; 4887 } 4888 } 4889 4890 bool wasHandled = false; 4891 bool ignoreTypes = (modifiers() & B_CONTROL_KEY) != 0; 4892 4893 if (targetModel != NULL && containerWindow != NULL) { 4894 // TODO: pick files to drop/launch on a case by case basis 4895 if (targetModel->IsDirectory()) { 4896 MoveSelectionInto(targetModel, srcWindow, containerWindow, 4897 buttons, dropPoint, false); 4898 wasHandled = true; 4899 } else if (CanHandleDragSelection(targetModel, message, ignoreTypes)) { 4900 LaunchAppWithSelection(targetModel, message, !ignoreTypes); 4901 wasHandled = true; 4902 } 4903 } 4904 4905 if (poseView != NULL && !wasHandled) { 4906 BPoint clickPoint = message->FindPoint("click_pt"); 4907 // TODO: removed check for root here need to do that, possibly at a 4908 // different level 4909 poseView->MoveSelectionTo(dropPoint, clickPoint, srcWindow); 4910 } 4911 4912 if (poseView != NULL && poseView->fEnsurePosesVisible) 4913 poseView->CheckPoseVisibility(); 4914 4915 return true; 4916 } 4917 4918 4919 struct LaunchParams { 4920 Model* app; 4921 bool checkTypes; 4922 BMessage* refsMessage; 4923 }; 4924 4925 4926 static bool 4927 AddOneToLaunchMessage(BPose* pose, BPoseView*, void* castToParams) 4928 { 4929 LaunchParams* params = (LaunchParams*)castToParams; 4930 ThrowOnAssert(params != NULL); 4931 ThrowOnAssert(pose != NULL); 4932 ThrowOnAssert(pose->TargetModel() != NULL); 4933 4934 if (params->app->IsDropTarget(params->checkTypes 4935 ? pose->TargetModel() : NULL, true)) { 4936 params->refsMessage->AddRef("refs", pose->TargetModel()->EntryRef()); 4937 } 4938 4939 return false; 4940 } 4941 4942 4943 void 4944 BPoseView::LaunchAppWithSelection(Model* appModel, const BMessage* dragMessage, 4945 bool checkTypes) 4946 { 4947 // launch items from the current selection with <appModel>; only pass 4948 // the same files that we previously decided can be handled by <appModel> 4949 BMessage refs(B_REFS_RECEIVED); 4950 LaunchParams params; 4951 params.app = appModel; 4952 params.checkTypes = checkTypes; 4953 params.refsMessage = &refs; 4954 4955 // add Tracker token so that refs received recipients can script us 4956 BContainerWindow* srcWindow; 4957 if (dragMessage->FindPointer("src_window", (void**)&srcWindow) == B_OK 4958 && srcWindow != NULL) { 4959 params.refsMessage->AddMessenger("TrackerViewToken", 4960 BMessenger(srcWindow->PoseView())); 4961 } 4962 4963 EachItemInDraggedSelection(dragMessage, AddOneToLaunchMessage, 0, ¶ms); 4964 if (params.refsMessage->HasRef("refs")) 4965 TrackerLaunch(appModel->EntryRef(), params.refsMessage, true); 4966 } 4967 4968 4969 bool 4970 BPoseView::DragSelectionContains(const BPose* target, 4971 const BMessage* dragMessage) 4972 { 4973 return EachItemInDraggedSelection(dragMessage, OneMatches, 0, 4974 (void*)target); 4975 } 4976 4977 4978 void 4979 BPoseView::MoveSelectionInto(Model* destFolder, BContainerWindow* srcWindow, 4980 bool forceCopy, bool forceMove, bool createLink, bool relativeLink) 4981 { 4982 uint32 buttons; 4983 BPoint loc; 4984 GetMouse(&loc, &buttons); 4985 MoveSelectionInto(destFolder, srcWindow, 4986 dynamic_cast<BContainerWindow*>(Window()), buttons, loc, forceCopy, 4987 forceMove, createLink, relativeLink); 4988 } 4989 4990 4991 void 4992 BPoseView::MoveSelectionInto(Model* destFolder, BContainerWindow* srcWindow, 4993 BContainerWindow* destWindow, uint32 buttons, BPoint loc, bool forceCopy, 4994 bool forceMove, bool createLink, bool relativeLink, BPoint clickPoint, 4995 bool dropOnGrid) 4996 { 4997 AutoLock<BWindow> lock(srcWindow); 4998 if (!lock) 4999 return; 5000 5001 ASSERT(srcWindow->PoseView()->TargetModel() != NULL); 5002 5003 if (srcWindow->PoseView()->SelectionList()->CountItems() == 0) 5004 return; 5005 5006 bool createRelativeLink = relativeLink; 5007 if (SecondaryMouseButtonDown(modifiers(), buttons) 5008 && destWindow != NULL) { 5009 switch (destWindow->ShowDropContextMenu(loc)) { 5010 case kCreateRelativeLink: 5011 createRelativeLink = true; 5012 break; 5013 5014 case kCreateLink: 5015 createLink = true; 5016 break; 5017 5018 case kMoveSelectionTo: 5019 forceMove = true; 5020 break; 5021 5022 case kCopySelectionTo: 5023 forceCopy = true; 5024 break; 5025 5026 case kCancelButton: 5027 default: 5028 // user canceled context menu 5029 return; 5030 } 5031 } 5032 5033 // make sure source and destination folders are different 5034 if (!createLink && !createRelativeLink 5035 && (*srcWindow->PoseView()->TargetModel()->NodeRef() 5036 == *destFolder->NodeRef())) { 5037 BPoseView* targetView = srcWindow->PoseView(); 5038 if (forceCopy) { 5039 targetView->DuplicateSelection(&clickPoint, &loc); 5040 return; 5041 } 5042 5043 if (targetView->ViewMode() == kListMode) { 5044 // can't move in list view 5045 return; 5046 } 5047 5048 BPoint delta = loc - clickPoint; 5049 int32 count = targetView->fSelectionList->CountItems(); 5050 for (int32 index = 0; index < count; index++) { 5051 BPose* pose = targetView->fSelectionList->ItemAt(index); 5052 5053 // remove pose from VSlist before changing location 5054 // so that we "find" the correct pose to remove 5055 // need to do this because bsearch uses top of pose 5056 // to locate pose to remove 5057 targetView->RemoveFromVSList(pose); 5058 BPoint location (pose->Location(targetView) + delta); 5059 BRect oldBounds(pose->CalcRect(targetView)); 5060 if (dropOnGrid) { 5061 location = targetView->PinToGrid(location, targetView->fGrid, 5062 targetView->fOffset); 5063 } 5064 5065 // TODO: don't drop poses under desktop elements 5066 // ie: replicants, deskbar 5067 pose->MoveTo(location, targetView); 5068 5069 targetView->RemoveFromExtent(oldBounds); 5070 targetView->AddToExtent(pose->CalcRect(targetView)); 5071 5072 // remove and reinsert pose to keep VSlist sorted 5073 targetView->AddToVSList(pose); 5074 } 5075 5076 return; 5077 } 5078 5079 BEntry* destEntry = new BEntry(destFolder->EntryRef()); 5080 bool destIsTrash = destFolder->IsTrash(); 5081 5082 // perform asynchronous copy/move 5083 forceCopy = forceCopy || (modifiers() & B_OPTION_KEY) != 0; 5084 5085 bool okToMove = true; 5086 5087 if (destFolder->IsRoot()) { 5088 BAlert* alert = new BAlert("", 5089 B_TRANSLATE("You must drop items on one of the disk icons " 5090 "in the \"Disks\" window."), B_TRANSLATE("Cancel"), NULL, NULL, 5091 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 5092 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 5093 alert->Go(); 5094 okToMove = false; 5095 } 5096 5097 // can't copy items into the trash 5098 if (forceCopy && destIsTrash) { 5099 BAlert* alert = new BAlert("", 5100 B_TRANSLATE("Sorry, you can't copy items to the Trash."), 5101 B_TRANSLATE("Cancel"), NULL, NULL, B_WIDTH_AS_USUAL, 5102 B_WARNING_ALERT); 5103 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 5104 alert->Go(); 5105 okToMove = false; 5106 } 5107 5108 // can't create symlinks into the trash 5109 if (createLink && destIsTrash) { 5110 BAlert* alert = new BAlert("", 5111 B_TRANSLATE("Sorry, you can't create links in the Trash."), 5112 B_TRANSLATE("Cancel"), NULL, NULL, B_WIDTH_AS_USUAL, 5113 B_WARNING_ALERT); 5114 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 5115 alert->Go(); 5116 okToMove = false; 5117 } 5118 5119 // prompt user if drag was from a query 5120 if (srcWindow->TargetModel()->IsQuery() 5121 && !forceCopy && !destIsTrash && !createLink) { 5122 srcWindow->UpdateIfNeeded(); 5123 BAlert* alert = new BAlert("", 5124 B_TRANSLATE("Are you sure you want to move or copy the selected " 5125 "item(s) to this folder?"), B_TRANSLATE("Cancel"), 5126 B_TRANSLATE("Move"), NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 5127 alert->SetShortcut(0, B_ESCAPE); 5128 okToMove = alert->Go() == 1; 5129 } 5130 5131 if (okToMove) { 5132 PoseList* selectionList = srcWindow->PoseView()->SelectionList(); 5133 BList* pointList = destWindow->PoseView()->GetDropPointList(clickPoint, 5134 loc, selectionList, srcWindow->PoseView()->ViewMode() == kListMode, 5135 dropOnGrid); 5136 int32 selectionSize = selectionList->CountItems(); 5137 BObjectList<entry_ref>* srcList 5138 = new BObjectList<entry_ref>(selectionSize, true); 5139 5140 if (srcWindow->TargetModel()->IsVirtualDirectory()) { 5141 // resolve symlink and add the resulting entry_ref to the list 5142 for (int32 i = 0; i < selectionSize; i++) { 5143 Model* model = selectionList->ItemAt(i)->ResolvedModel(); 5144 if (model != NULL) 5145 srcList->AddItem(new entry_ref(*(model->EntryRef()))); 5146 } 5147 } else 5148 CopySelectionListToEntryRefList(selectionList, srcList); 5149 5150 uint32 moveMode; 5151 if (forceCopy) 5152 moveMode = kCopySelectionTo; 5153 else if (forceMove) 5154 moveMode = kMoveSelectionTo; 5155 else if (createRelativeLink) 5156 moveMode = kCreateRelativeLink; 5157 else if (createLink) 5158 moveMode = kCreateLink; 5159 else if (!CheckDevicesEqual(srcList->ItemAt(0), destFolder)) 5160 moveMode = kCopySelectionTo; 5161 else 5162 moveMode = kMoveSelectionTo; 5163 5164 FSMoveToFolder(srcList, destEntry, moveMode, pointList); 5165 return; 5166 } 5167 5168 delete destEntry; 5169 } 5170 5171 5172 void 5173 BPoseView::MoveSelectionTo(BPoint dropPoint, BPoint clickPoint, 5174 BContainerWindow* srcWindow) 5175 { 5176 // Moves selection from srcWindow into this window, copying if necessary. 5177 5178 BContainerWindow* window = ContainerWindow(); 5179 if (window == NULL) 5180 return; 5181 5182 ASSERT(window->PoseView() != NULL); 5183 ASSERT(TargetModel() != NULL); 5184 5185 // make sure this window is a legal drop target 5186 if (srcWindow != window && !TargetModel()->IsDropTarget()) 5187 return; 5188 5189 uint32 buttons = (uint32)window->CurrentMessage()->FindInt32("buttons"); 5190 bool pinToGrid = (modifiers() & B_COMMAND_KEY) != 0; 5191 MoveSelectionInto(TargetModel(), srcWindow, window, buttons, dropPoint, 5192 false, false, false, false, clickPoint, pinToGrid); 5193 } 5194 5195 5196 inline void 5197 UpdateWasBrokenSymlinkBinder(BPose* pose, Model* model, int32 index, 5198 BPoseView* poseView, BObjectList<Model>* fBrokenLinks) 5199 { 5200 if (!model->IsSymLink()) 5201 return; 5202 5203 BPoint loc(0, index * poseView->ListElemHeight()); 5204 pose->UpdateWasBrokenSymlink(loc, poseView); 5205 if (model->LinkTo() != NULL) 5206 fBrokenLinks->RemoveItem(model); 5207 } 5208 5209 5210 void 5211 BPoseView::TryUpdatingBrokenLinks() 5212 { 5213 AutoLock<BWindow> lock(Window()); 5214 if (!lock) 5215 return; 5216 5217 BObjectList<Model>* brokenLinksCopy = new BObjectList<Model>(*fBrokenLinks); 5218 5219 // try fixing broken symlinks, and detecting broken ones. 5220 EachPoseAndModel(fPoseList, &UpdateWasBrokenSymlinkBinder, this, 5221 fBrokenLinks); 5222 5223 for (int i = brokenLinksCopy->CountItems() - 1; i >= 0; i--) { 5224 if (!fBrokenLinks->HasItem(brokenLinksCopy->ItemAt(i))) 5225 StopWatchingParentsOf(brokenLinksCopy->ItemAt(i)->EntryRef()); 5226 } 5227 5228 delete brokenLinksCopy; 5229 } 5230 5231 5232 void 5233 BPoseView::PoseHandleDeviceUnmounted(BPose* pose, Model* model, int32 index, 5234 BPoseView* poseView, dev_t device) 5235 { 5236 if (model->NodeRef()->device == device) 5237 poseView->DeletePose(model->NodeRef()); 5238 else if (model->IsSymLink() && model->LinkTo() != NULL 5239 && model->LinkTo()->NodeRef()->device == device) { 5240 poseView->DeleteSymLinkPoseTarget(model->LinkTo()->NodeRef(), 5241 pose, index); 5242 } 5243 } 5244 5245 5246 static void 5247 OneMetaMimeChanged(BPose* pose, Model* model, int32 index, 5248 BPoseView* poseView, const char* type) 5249 { 5250 ASSERT(model != NULL); 5251 5252 if (model->IconFrom() != kNode 5253 && model->IconFrom() != kUnknownSource 5254 && model->IconFrom() != kUnknownNotFromNode 5255 // TODO: add supertype compare 5256 && strcasecmp(model->MimeType(), type) == 0) { 5257 // metamime change very likely affected the documents icon 5258 BPoint poseLoc(0, index * poseView->ListElemHeight()); 5259 pose->UpdateIcon(poseLoc, poseView); 5260 } 5261 } 5262 5263 5264 void 5265 BPoseView::MetaMimeChanged(const char* type, const char* preferredApp) 5266 { 5267 IconCache::sIconCache->IconChanged(type, preferredApp); 5268 // wait for other windows to do the same before we start 5269 // updating poses which causes icon recaching 5270 // TODO: this is a design problem that should be solved differently 5271 snooze(10000); 5272 Window()->UpdateIfNeeded(); 5273 5274 EachPoseAndResolvedModel(fPoseList, &OneMetaMimeChanged, this, type); 5275 } 5276 5277 5278 class MetaMimeChangedAccumulator : public AccumulatingFunctionObject { 5279 // pools up matching metamime change notices, executing them as a single 5280 // update 5281 public: 5282 MetaMimeChangedAccumulator(void (BPoseView::*func)(const char* type, 5283 const char* preferredApp), 5284 BContainerWindow* window, const char* type, const char* preferredApp) 5285 : 5286 fCallOnThis(window), 5287 fFunc(func), 5288 fType(type), 5289 fPreferredApp(preferredApp) 5290 { 5291 } 5292 5293 virtual bool CanAccumulate(const AccumulatingFunctionObject* functor) const 5294 { 5295 const MetaMimeChangedAccumulator* accumulator 5296 = dynamic_cast<const MetaMimeChangedAccumulator*>(functor); 5297 if (accumulator == NULL) 5298 return false; 5299 5300 return accumulator && accumulator->fType == fType 5301 && accumulator->fPreferredApp == fPreferredApp; 5302 } 5303 5304 virtual void Accumulate(AccumulatingFunctionObject* DEBUG_ONLY(functor)) 5305 { 5306 ASSERT(CanAccumulate(functor)); 5307 // do nothing, no further accumulating needed 5308 } 5309 5310 protected: 5311 virtual void operator()() 5312 { 5313 AutoLock<BWindow> lock(fCallOnThis); 5314 if (!lock) 5315 return; 5316 5317 (fCallOnThis->PoseView()->*fFunc)(fType.String(), 5318 fPreferredApp.String()); 5319 } 5320 5321 virtual ulong Size() const 5322 { 5323 return sizeof (*this); 5324 } 5325 5326 private: 5327 BContainerWindow* fCallOnThis; 5328 void (BPoseView::*fFunc)(const char* type, const char* preferredApp); 5329 BString fType; 5330 BString fPreferredApp; 5331 }; 5332 5333 5334 bool 5335 BPoseView::NoticeMetaMimeChanged(const BMessage* message) 5336 { 5337 int32 change; 5338 if (message->FindInt32("be:which", &change) != B_OK) 5339 return true; 5340 5341 bool iconChanged = (change & B_ICON_CHANGED) != 0; 5342 bool iconForTypeChanged = (change & B_ICON_FOR_TYPE_CHANGED) != 0; 5343 bool preferredAppChanged = (change & B_APP_HINT_CHANGED) 5344 || (change & B_PREFERRED_APP_CHANGED); 5345 5346 const char* type = NULL; 5347 const char* preferredApp = NULL; 5348 5349 if (iconChanged || preferredAppChanged) 5350 message->FindString("be:type", &type); 5351 5352 if (iconForTypeChanged) { 5353 message->FindString("be:extra_type", &type); 5354 message->FindString("be:type", &preferredApp); 5355 } 5356 5357 if (iconChanged || preferredAppChanged || iconForTypeChanged) { 5358 TaskLoop* taskLoop = ContainerWindow()->DelayedTaskLoop(); 5359 ASSERT(taskLoop != NULL); 5360 taskLoop->AccumulatedRunLater(new MetaMimeChangedAccumulator( 5361 &BPoseView::MetaMimeChanged, ContainerWindow(), type, preferredApp), 5362 200000, 5000000); 5363 } 5364 5365 return true; 5366 } 5367 5368 5369 bool 5370 BPoseView::FSNotification(const BMessage* message) 5371 { 5372 node_ref itemNode; 5373 dev_t device; 5374 Model* targetModel = TargetModel(); 5375 5376 switch (message->FindInt32("opcode")) { 5377 case B_ENTRY_CREATED: 5378 { 5379 ASSERT(targetModel != NULL); 5380 5381 message->FindInt32("device", &itemNode.device); 5382 node_ref dirNode; 5383 dirNode.device = itemNode.device; 5384 message->FindInt64("directory", (int64*)&dirNode.node); 5385 message->FindInt64("node", (int64*)&itemNode.node); 5386 5387 int32 count = fBrokenLinks->CountItems(); 5388 bool createPose = true; 5389 // Query windows can get notices on different dirNodes 5390 // The Disks window can too 5391 // So can the Desktop, as long as the integrate flag is on 5392 TrackerSettings settings; 5393 if (targetModel != NULL && dirNode != *targetModel->NodeRef() 5394 && !targetModel->IsQuery() 5395 && !targetModel->IsVirtualDirectory() 5396 && !targetModel->IsRoot() 5397 && (!settings.ShowDisksIcon() || !IsDesktopView())) { 5398 if (count == 0) 5399 break; 5400 createPose = false; 5401 } 5402 5403 const char* name; 5404 if (message->FindString("name", &name) != B_OK) { 5405 #if DEBUG 5406 SERIAL_PRINT(("no name in entry creation message\n")); 5407 #endif 5408 break; 5409 } 5410 if (count != 0) { 5411 // basically, let's say we have a broken link : 5412 // ./a_link -> ./some_folder/another_folder/a_target 5413 // and that both some_folder and another_folder didn't 5414 // exist yet. We are looking if the just created folder 5415 // is 'some_folder' and watch it, expecting the creation of 5416 // 'another_folder' later and then report the link as fixed. 5417 Model* model = new Model(&dirNode, &itemNode, name); 5418 if (model->IsDirectory()) { 5419 BString createdPath(BPath(model->EntryRef()).Path()); 5420 BDirectory currentDir(targetModel->EntryRef()); 5421 BPath createdDir(model->EntryRef()); 5422 for (int32 i = 0; i < count; i++) { 5423 BSymLink link(fBrokenLinks->ItemAt(i)->EntryRef()); 5424 BPath path; 5425 link.MakeLinkedPath(¤tDir, &path); 5426 BString pathStr(path.Path()); 5427 pathStr.Append("/"); 5428 if (pathStr.Compare(createdPath, 5429 createdPath.Length()) == 0) { 5430 if (pathStr[createdPath.Length()] != '/') 5431 break; 5432 StopWatchingParentsOf(fBrokenLinks->ItemAt(i) 5433 ->EntryRef()); 5434 watch_node(&itemNode, B_WATCH_DIRECTORY, this); 5435 break; 5436 } 5437 } 5438 } 5439 delete model; 5440 } 5441 if (createPose) 5442 EntryCreated(&dirNode, &itemNode, name); 5443 5444 TryUpdatingBrokenLinks(); 5445 break; 5446 } 5447 5448 case B_ENTRY_MOVED: 5449 return EntryMoved(message); 5450 break; 5451 5452 case B_ENTRY_REMOVED: 5453 message->FindInt32("device", &itemNode.device); 5454 message->FindInt64("node", (int64*)&itemNode.node); 5455 5456 // our window itself may be deleted 5457 // we must check to see if this comes as a query 5458 // notification or a node monitor notification because 5459 // if it's a query notification then we're just being told we 5460 // no longer match the query, so we don't want to close the window 5461 // but it's a node monitor notification then that means our query 5462 // file has been deleted so we close the window 5463 5464 if (message->what == B_NODE_MONITOR && targetModel != NULL 5465 && *(targetModel->NodeRef()) == itemNode) { 5466 if (!targetModel->IsRoot()) { 5467 // it is impossible to watch for ENTRY_REMOVED in 5468 // "/" because the notification is ambiguous - the vnode 5469 // is that of the volume but the device is of the parent 5470 // not the same as the device of the volume that way we 5471 // may get aliasing for volumes with vnodes of 1 5472 // (currently the case for iso9660) 5473 DisableSaveLocation(); 5474 Window()->Close(); 5475 } 5476 } else { 5477 int32 index; 5478 BPose* pose = fPoseList->FindPose(&itemNode, &index); 5479 if (pose == NULL) { 5480 // couldn't find pose, first check if the node might be 5481 // target of a symlink pose; 5482 // 5483 // What happens when a node and a symlink to it are in the 5484 // same window? 5485 // They get monitored twice, we get two notifications; the 5486 // first one will get caught by the first FindPose, the 5487 // second one by the DeepFindPose 5488 // 5489 pose = fPoseList->DeepFindPose(&itemNode, &index); 5490 if (pose != NULL) { 5491 DeleteSymLinkPoseTarget(&itemNode, pose, index); 5492 break; 5493 } 5494 } 5495 5496 DeletePose(&itemNode); 5497 TryUpdatingBrokenLinks(); 5498 } 5499 break; 5500 5501 case B_DEVICE_MOUNTED: 5502 { 5503 if (message->FindInt32("new device", &device) != B_OK) 5504 break; 5505 5506 if (targetModel != NULL && targetModel->IsRoot()) { 5507 BVolume volume(device); 5508 if (volume.InitCheck() == B_OK) 5509 CreateVolumePose(&volume, false); 5510 } else if (ContainerWindow()->IsTrash()) { 5511 // add trash items from newly mounted volume 5512 5513 BDirectory trashDir; 5514 BEntry entry; 5515 BVolume volume(device); 5516 if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK 5517 && trashDir.GetEntry(&entry) == B_OK) { 5518 Model model(&entry); 5519 if (model.InitCheck() == B_OK) 5520 AddPoses(&model); 5521 } 5522 } 5523 TaskLoop* taskLoop = ContainerWindow()->DelayedTaskLoop(); 5524 ASSERT(taskLoop); 5525 taskLoop->RunLater(NewMemberFunctionObject( 5526 &BPoseView::TryUpdatingBrokenLinks, this), 500000); 5527 // delay of 500000: wait for volumes to properly finish mounting 5528 // without this in the Model::FinishSettingUpType a symlink 5529 // to a volume would get initialized as a symlink to a directory 5530 // because IsRootDirectory looks like returns false. Either 5531 // there is a race condition or I was doing something wrong. 5532 break; 5533 } 5534 5535 case B_DEVICE_UNMOUNTED: 5536 if (message->FindInt32("device", &device) == B_OK) { 5537 if (targetModel != NULL 5538 && targetModel->NodeRef()->device == device) { 5539 // close the window from a volume that is gone 5540 DisableSaveLocation(); 5541 Window()->Close(); 5542 } else if (targetModel != NULL) { 5543 EachPoseAndModel(fPoseList, &PoseHandleDeviceUnmounted, 5544 this, device); 5545 } 5546 } 5547 break; 5548 5549 case B_STAT_CHANGED: 5550 case B_ATTR_CHANGED: 5551 return AttributeChanged(message); 5552 } 5553 5554 return true; 5555 } 5556 5557 5558 bool 5559 BPoseView::CreateSymlinkPoseTarget(Model* symlink) 5560 { 5561 Model* newResolvedModel = NULL; 5562 Model* result = symlink->LinkTo(); 5563 5564 if (result == NULL) { 5565 BEntry entry(symlink->EntryRef(), true); 5566 if (entry.InitCheck() == B_OK) { 5567 node_ref nref; 5568 entry_ref eref; 5569 entry.GetNodeRef(&nref); 5570 entry.GetRef(&eref); 5571 if (eref.directory != TargetModel()->NodeRef()->node) 5572 WatchNewNode(&nref, B_WATCH_STAT | B_WATCH_ATTR | B_WATCH_NAME 5573 | B_WATCH_INTERIM_STAT, this); 5574 newResolvedModel = new Model(&entry, true); 5575 } else { 5576 fBrokenLinks->AddItem(symlink); 5577 WatchParentOf(symlink->EntryRef()); 5578 return true; 5579 } 5580 result = newResolvedModel; 5581 } 5582 symlink->SetLinkTo(result); 5583 5584 return true; 5585 } 5586 5587 5588 BPose* 5589 BPoseView::EntryCreated(const node_ref* dirNode, const node_ref* itemNode, 5590 const char* name, int32* indexPtr) 5591 { 5592 // reject notification if pose already exists 5593 if (fPoseList->FindPose(itemNode) || FindZombie(itemNode)) 5594 return NULL; 5595 5596 BPoseView::WatchNewNode(itemNode); 5597 // have to node monitor ahead of time because Model will 5598 // cache up the file type and preferred app 5599 Model* model = new Model(dirNode, itemNode, name, true); 5600 5601 if (model->InitCheck() != B_OK) { 5602 // if we have trouble setting up model then we stuff it into 5603 // a zombie list in a half-alive state until we can properly awaken it 5604 PRINT(("2 adding model %s to zombie list, error %s\n", model->Name(), 5605 strerror(model->InitCheck()))); 5606 fZombieList->AddItem(model); 5607 return NULL; 5608 } 5609 5610 PoseInfo poseInfo; 5611 ReadPoseInfo(model, &poseInfo); 5612 5613 if (!PoseVisible(model, &poseInfo)) { 5614 watch_node(model->NodeRef(), B_STOP_WATCHING, this); 5615 delete model; 5616 return NULL; 5617 } 5618 5619 // model is a symlink, cache up the symlink target or scrap 5620 // everything if target is invisible 5621 if (model->IsSymLink() && !CreateSymlinkPoseTarget(model)) { 5622 watch_node(model->NodeRef(), B_STOP_WATCHING, this); 5623 delete model; 5624 return NULL; 5625 } 5626 5627 return CreatePose(model, &poseInfo, true, indexPtr); 5628 } 5629 5630 5631 bool 5632 BPoseView::EntryMoved(const BMessage* message) 5633 { 5634 ino_t oldDir; 5635 node_ref dirNode; 5636 node_ref itemNode; 5637 5638 message->FindInt32("device", &dirNode.device); 5639 itemNode.device = dirNode.device; 5640 message->FindInt64("to directory", (int64*)&dirNode.node); 5641 message->FindInt64("node", (int64*)&itemNode.node); 5642 message->FindInt64("from directory", (int64*)&oldDir); 5643 5644 const char* name; 5645 if (message->FindString("name", &name) != B_OK) 5646 return true; 5647 5648 // handle special case of notifying a name change for a volume 5649 // - the notification is not enough, because the volume's device 5650 // is different than that of the root directory; we have to do a 5651 // lookup using the new volume name and get the volume device from there 5652 StatStruct st; 5653 // get the inode of the root and check if we got a notification on it 5654 if (stat("/", &st) >= 0 5655 && st.st_dev == dirNode.device 5656 && st.st_ino == dirNode.node) { 5657 BString buffer; 5658 buffer << "/" << name; 5659 if (stat(buffer.String(), &st) >= 0) { 5660 // point the dirNode to the actual volume 5661 itemNode.node = st.st_ino; 5662 itemNode.device = st.st_dev; 5663 } 5664 } 5665 5666 Model* targetModel = TargetModel(); 5667 ThrowOnAssert(targetModel != NULL); 5668 5669 node_ref thisDirNode; 5670 if (ContainerWindow()->IsTrash()) { 5671 BDirectory trashDir; 5672 if (FSGetTrashDir(&trashDir, itemNode.device) != B_OK) 5673 return true; 5674 5675 trashDir.GetNodeRef(&thisDirNode); 5676 } else 5677 thisDirNode = *targetModel->NodeRef(); 5678 5679 // see if we need to update window title (and folder itself) 5680 if (thisDirNode == itemNode) { 5681 targetModel->UpdateEntryRef(&dirNode, name); 5682 assert_cast<BContainerWindow*>(Window())->UpdateTitle(); 5683 } 5684 5685 if (oldDir == dirNode.node || targetModel->IsQuery() 5686 || targetModel->IsVirtualDirectory()) { 5687 // rename or move of entry in this directory (or query) 5688 5689 int32 index; 5690 BPose* pose = fPoseList->FindPose(&itemNode, &index); 5691 int32 poseListIndex = index; 5692 bool visible = true; 5693 if (fFiltering) 5694 visible = fFilteredPoseList->FindPose(&itemNode, &index) != NULL; 5695 5696 if (pose != NULL) { 5697 Model* poseModel = pose->TargetModel(); 5698 ASSERT(poseModel != NULL); 5699 poseModel->UpdateEntryRef(&dirNode, name); 5700 // for queries we check for move to trash and remove item if so 5701 if (targetModel->IsQuery()) { 5702 PoseInfo poseInfo; 5703 ReadPoseInfo(poseModel, &poseInfo); 5704 if (!ShouldShowPose(poseModel, &poseInfo)) 5705 return DeletePose(&itemNode, pose, index); 5706 return true; 5707 } 5708 5709 BPoint loc(0, index * fListElemHeight); 5710 // if we get a rename then we need to assume that we might 5711 // have missed some other attr changed notifications so we 5712 // recheck all widgets 5713 if (poseModel->OpenNode() == B_OK) { 5714 pose->UpdateAllWidgets(index, loc, this); 5715 poseModel->CloseNode(); 5716 _CheckPoseSortOrder(fPoseList, pose, poseListIndex); 5717 if (fFiltering) { 5718 if (!visible && FilterPose(pose)) { 5719 BRect bounds = Bounds(); 5720 float scrollBy = 0; 5721 AddPoseToList(fFilteredPoseList, true, true, pose, 5722 bounds, scrollBy, true); 5723 } else if (visible && !FilterPose(pose)) 5724 RemoveFilteredPose(pose, index); 5725 else if (visible) 5726 _CheckPoseSortOrder(fFilteredPoseList, pose, index); 5727 } 5728 } 5729 } else { 5730 // also must watch for renames on zombies 5731 Model* zombie = FindZombie(&itemNode, &index); 5732 if (zombie) { 5733 PRINT(("converting model %s from a zombie\n", zombie->Name())); 5734 zombie->UpdateEntryRef(&dirNode, name); 5735 pose = ConvertZombieToPose(zombie, index); 5736 } else 5737 return false; 5738 } 5739 if (pose != NULL) 5740 pendingNodeMonitorCache.PoseCreatedOrMoved(this, pose); 5741 } else if (oldDir == thisDirNode.node) 5742 DeletePose(&itemNode); 5743 else if (dirNode.node == thisDirNode.node) 5744 EntryCreated(&dirNode, &itemNode, name); 5745 5746 TryUpdatingBrokenLinks(); 5747 5748 return true; 5749 } 5750 5751 5752 void 5753 BPoseView::WatchParentOf(const entry_ref* ref) 5754 { 5755 BPath currentDir(ref); 5756 currentDir.GetParent(¤tDir); 5757 BSymLink symlink(ref); 5758 BPath path; 5759 5760 symlink.MakeLinkedPath(currentDir.Path(), &path); 5761 status_t status = path.GetParent(&path); 5762 5763 while (status == B_BAD_VALUE) 5764 status = path.GetParent(&path); 5765 5766 if (status == B_ENTRY_NOT_FOUND) 5767 return; 5768 5769 node_ref nref; 5770 BNode(path.Path()).GetNodeRef(&nref); 5771 5772 if (nref != *TargetModel()->NodeRef()) 5773 watch_node(&nref, B_WATCH_DIRECTORY, this); 5774 } 5775 5776 5777 void 5778 BPoseView::StopWatchingParentsOf(const entry_ref* ref) 5779 { 5780 BPath path; 5781 BSymLink symlink(ref); 5782 BPath currentDir(ref); 5783 currentDir.GetParent(¤tDir); 5784 symlink.MakeLinkedPath(currentDir.Path(), &path); 5785 5786 if (path.InitCheck() != B_OK) 5787 return; 5788 5789 BObjectList<Model>* brokenLinksCopy = new BObjectList<Model>(*fBrokenLinks); 5790 int32 count = brokenLinksCopy->CountItems(); 5791 5792 while (path.GetParent(&path) == B_OK) { 5793 if (strcmp(path.Path(), "/") == 0) 5794 break; 5795 5796 BNode dir(path.Path()); 5797 node_ref dirNode; 5798 dir.GetNodeRef(&dirNode); 5799 5800 // don't stop watching yourself. 5801 if (dirNode == *TargetModel()->NodeRef()) 5802 continue; 5803 5804 // make sure we don't have another broken links that still requires 5805 // to watch this directory 5806 bool keep = false; 5807 for (int32 i = count - 1; i >= 0; i--) { 5808 BSymLink link(brokenLinksCopy->ItemAt(i)->EntryRef()); 5809 BPath absolutePath; 5810 link.MakeLinkedPath(currentDir.Path(), &absolutePath); 5811 if (BString(absolutePath.Path()).Compare(path.Path(), 5812 strlen(path.Path())) == 0) { 5813 // a broken link still needs to watch this folder, but 5814 // don't let that same link also watch a deeper parent. 5815 brokenLinksCopy->RemoveItemAt(i); 5816 count--; 5817 keep = true; 5818 } 5819 } 5820 if (!keep) 5821 watch_node(&dirNode, B_STOP_WATCHING, this); 5822 } 5823 delete brokenLinksCopy; 5824 } 5825 5826 5827 bool 5828 BPoseView::AttributeChanged(const BMessage* message) 5829 { 5830 node_ref itemNode; 5831 message->FindInt32("device", &itemNode.device); 5832 message->FindInt64("node", (int64*)&itemNode.node); 5833 5834 const char* attrName; 5835 if (message->FindString("attr", &attrName) != B_OK) 5836 attrName = NULL; 5837 5838 Model* targetModel = TargetModel(); 5839 if (targetModel != NULL && *targetModel->NodeRef() == itemNode 5840 && targetModel->IsNodeOpen() 5841 && targetModel->AttrChanged(attrName)) { 5842 // the icon of our target has changed, update drag icon 5843 // TODO: make this simpler (i.e. store the icon with the window) 5844 BView* view = Window()->FindView("MenuBar"); 5845 if (view != NULL) { 5846 view = view->FindView("ThisContainer"); 5847 if (view != NULL) { 5848 IconCache::sIconCache->IconChanged(targetModel); 5849 view->Invalidate(); 5850 } 5851 } 5852 } 5853 5854 int32 index; 5855 attr_info info; 5856 PoseList* posesFound = fPoseList->FindAllPoses(&itemNode); 5857 int32 posesCount = posesFound->CountItems(); 5858 for (int i = 0; i < posesCount; i++) { 5859 BPose* pose = posesFound->ItemAt(i); 5860 Model* poseModel = pose->TargetModel(); 5861 if (poseModel->IsSymLink() && *(poseModel->NodeRef()) != itemNode) { 5862 // change happened on symlink's target 5863 poseModel = poseModel->ResolveIfLink(); 5864 } 5865 ASSERT(poseModel != NULL); 5866 5867 status_t result = B_OK; 5868 for (int32 count = 0; count < 100; count++) { 5869 // if node is busy, wait a little, it may be in the 5870 // middle of mimeset and we wan't to pick up the changes 5871 result = poseModel->OpenNode(); 5872 if (result == B_OK || result != B_BUSY) 5873 break; 5874 5875 PRINT(("poseModel %s busy, retrying in a bit\n", 5876 poseModel->Name())); 5877 snooze(10000); 5878 } 5879 if (result != B_OK) { 5880 PRINT(("Cache Error %s\n", strerror(result))); 5881 continue; 5882 } 5883 5884 bool visible = fPoseList->FindPose(poseModel->NodeRef(), 5885 &index) != NULL; 5886 int32 poseListIndex = index; 5887 5888 if (fFiltering) { 5889 visible = fFilteredPoseList->FindPose( 5890 poseModel->NodeRef(), &index) != NULL; 5891 } 5892 5893 BPoint loc(0, index * fListElemHeight); 5894 if (attrName != NULL && poseModel->Node() != NULL) { 5895 memset(&info, 0, sizeof(attr_info)); 5896 // the call below might fail if the attribute has been removed 5897 poseModel->Node()->GetAttrInfo(attrName, &info); 5898 pose->UpdateWidgetAndModel(poseModel, attrName, info.type, index, 5899 loc, this, visible); 5900 if (strcmp(attrName, kAttrMIMEType) == 0) 5901 RefreshMimeTypeList(); 5902 } else { 5903 pose->UpdateWidgetAndModel(poseModel, 0, 0, index, loc, this, 5904 visible); 5905 } 5906 poseModel->CloseNode(); 5907 if (fFiltering) { 5908 if (!visible && FilterPose(pose)) { 5909 visible = true; 5910 float scrollBy = 0; 5911 BRect bounds = Bounds(); 5912 AddPoseToList(fFilteredPoseList, true, true, pose, bounds, 5913 scrollBy, true); 5914 continue; 5915 } else if (visible && !FilterPose(pose)) { 5916 RemoveFilteredPose(pose, index); 5917 continue; 5918 } 5919 } 5920 5921 if (attrName != NULL) { 5922 // note: the following code is wrong, because this sort of hashing 5923 // may overlap and we get aliasing 5924 uint32 attrHash = AttrHashString(attrName, info.type); 5925 if (attrHash == PrimarySort() || attrHash == SecondarySort()) { 5926 _CheckPoseSortOrder(fPoseList, pose, poseListIndex); 5927 if (fFiltering && visible) 5928 _CheckPoseSortOrder(fFilteredPoseList, pose, index); 5929 } 5930 } else { 5931 int32 fields; 5932 if (message->FindInt32("fields", &fields) != B_OK) 5933 continue; 5934 5935 for (int i = sizeof(sAttrColumnMap) / sizeof(attr_column_relation); 5936 i--;) { 5937 if (sAttrColumnMap[i].attrHash == PrimarySort() 5938 || sAttrColumnMap[i].attrHash == SecondarySort()) { 5939 if ((fields & sAttrColumnMap[i].fieldMask) != 0) { 5940 _CheckPoseSortOrder(fPoseList, pose, poseListIndex); 5941 if (fFiltering && visible) 5942 _CheckPoseSortOrder(fFilteredPoseList, pose, index); 5943 break; 5944 } 5945 } 5946 } 5947 } 5948 } 5949 delete posesFound; 5950 if (posesCount == 0) { 5951 // we received an attr changed notification for a zombie model, it means 5952 // that although we couldn't open the node the first time, it seems 5953 // to be fine now since we're receiving notifications about it, it might 5954 // be a good time to convert it to a non-zombie state. cf. test in #4130 5955 Model* zombie = FindZombie(&itemNode, &index); 5956 if (zombie != NULL) { 5957 PRINT(("converting model %s from a zombie\n", zombie->Name())); 5958 return ConvertZombieToPose(zombie, index) != NULL; 5959 } else { 5960 PRINT(("model has no pose but is not a zombie either!\n")); 5961 return false; 5962 } 5963 } 5964 5965 return true; 5966 } 5967 5968 5969 void 5970 BPoseView::UpdateIcon(BPose* pose) 5971 { 5972 BPoint location; 5973 if (ViewMode() == kListMode) { 5974 // need to find the index of the pose in the pose list 5975 bool found = false; 5976 PoseList* poseList = CurrentPoseList(); 5977 int32 count = poseList->CountItems(); 5978 for (int32 index = 0; index < count; index++) { 5979 if (poseList->ItemAt(index) == pose) { 5980 location.Set(0, index * fListElemHeight); 5981 found = true; 5982 break; 5983 } 5984 } 5985 5986 if (!found) 5987 return; 5988 } 5989 5990 pose->UpdateIcon(location, this); 5991 } 5992 5993 5994 BPose* 5995 BPoseView::ConvertZombieToPose(Model* zombie, int32 index) 5996 { 5997 if (zombie->UpdateStatAndOpenNode() != B_OK) 5998 return NULL; 5999 6000 fZombieList->RemoveItemAt(index); 6001 6002 PoseInfo poseInfo; 6003 ReadPoseInfo(zombie, &poseInfo); 6004 6005 if (ShouldShowPose(zombie, &poseInfo)) { 6006 // TODO: handle symlinks here 6007 return CreatePose(zombie, &poseInfo); 6008 } 6009 6010 delete zombie; 6011 6012 return NULL; 6013 } 6014 6015 6016 BList* 6017 BPoseView::GetDropPointList(BPoint dropStart, BPoint dropEnd, const PoseList* poses, 6018 bool sourceInListMode, bool dropOnGrid) const 6019 { 6020 if (ViewMode() == kListMode) 6021 return NULL; 6022 6023 int32 count = poses->CountItems(); 6024 BList* pointList = new BList(count); 6025 for (int32 index = 0; index < count; index++) { 6026 BPose* pose = poses->ItemAt(index); 6027 BPoint poseLoc; 6028 if (sourceInListMode) 6029 poseLoc = dropEnd + BPoint(0, index * (IconPoseHeight() + 3)); 6030 else 6031 poseLoc = dropEnd + (pose->Location(this) - dropStart); 6032 6033 if (dropOnGrid) 6034 poseLoc = PinToGrid(poseLoc, fGrid, fOffset); 6035 6036 pointList->AddItem(new BPoint(poseLoc)); 6037 } 6038 6039 return pointList; 6040 } 6041 6042 6043 void 6044 BPoseView::DuplicateSelection(BPoint* dropStart, BPoint* dropEnd) 6045 { 6046 // If there is a volume or trash folder, remove them from the list 6047 // because they cannot get copied 6048 int32 selectionSize = fSelectionList->CountItems(); 6049 for (int32 index = 0; index < selectionSize; index++) { 6050 BPose* pose = (BPose*)fSelectionList->ItemAt(index); 6051 Model* poseModel = pose->TargetModel(); 6052 6053 // can't duplicate a volume or the trash 6054 if (poseModel->IsTrash() || poseModel->IsVolume()) { 6055 fSelectionList->RemoveItemAt(index); 6056 index--; 6057 selectionSize--; 6058 if (fSelectionPivotPose == pose) 6059 fSelectionPivotPose = NULL; 6060 6061 if (fRealPivotPose == pose) 6062 fRealPivotPose = NULL; 6063 6064 continue; 6065 } 6066 } 6067 6068 // create entry_ref list from selection 6069 if (!fSelectionList->IsEmpty()) { 6070 BObjectList<entry_ref>* srcList = new BObjectList<entry_ref>( 6071 fSelectionList->CountItems(), true); 6072 CopySelectionListToEntryRefList(fSelectionList, srcList); 6073 6074 BList* dropPoints; 6075 if (dropStart) { 6076 dropPoints = GetDropPointList(*dropStart, *dropEnd, fSelectionList, 6077 ViewMode() == kListMode, (modifiers() & B_COMMAND_KEY) != 0); 6078 } else 6079 dropPoints = NULL; 6080 6081 // perform asynchronous duplicate 6082 FSDuplicate(srcList, dropPoints); 6083 } 6084 } 6085 6086 6087 void 6088 BPoseView::SelectPoseAtLocation(BPoint point) 6089 { 6090 int32 index; 6091 BPose* pose = FindPose(point, &index); 6092 if (pose != NULL) 6093 SelectPose(pose, index); 6094 } 6095 6096 6097 void 6098 BPoseView::MoveListToTrash(BObjectList<entry_ref>* list, bool selectNext, 6099 bool deleteDirectly) 6100 { 6101 if (!list->CountItems()) 6102 return; 6103 6104 BObjectList<FunctionObject>* taskList = 6105 new BObjectList<FunctionObject>(2, true); 6106 // new owning list of tasks 6107 6108 // first move selection to trash, 6109 if (deleteDirectly) { 6110 taskList->AddItem(NewFunctionObject(FSDeleteRefList, list, 6111 false, true)); 6112 } else { 6113 taskList->AddItem(NewFunctionObject(FSMoveToTrash, list, 6114 (BList*)NULL, false)); 6115 } 6116 6117 if (selectNext && ViewMode() == kListMode) { 6118 // next, if in list view mode try selecting the next item after 6119 BPose* pose = fSelectionList->ItemAt(0); 6120 6121 // find a point in the pose 6122 BPoint pointInPose(kListOffset + 5, 5); 6123 int32 index = IndexOfPose(pose); 6124 pointInPose.y += fListElemHeight * index; 6125 6126 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 6127 if (tracker != NULL) { 6128 ThrowOnAssert(TargetModel() != NULL); 6129 6130 // add a function object to the list of tasks to run 6131 // that will select the next item after the one we just 6132 // deleted 6133 taskList->AddItem(NewMemberFunctionObject( 6134 &TTracker::SelectPoseAtLocationSoon, tracker, 6135 *TargetModel()->NodeRef(), pointInPose)); 6136 } 6137 } 6138 // execute the two tasks in order 6139 ThreadSequence::Launch(taskList, true); 6140 } 6141 6142 6143 inline void 6144 CopyOneTrashedRefAsEntry(const entry_ref* ref, BObjectList<entry_ref>* trashList, 6145 BObjectList<entry_ref>* noTrashList, std::map<int32, bool>* deviceHasTrash) 6146 { 6147 std::map<int32, bool> &deviceHasTrashTmp = *deviceHasTrash; 6148 // work around stupid binding problems with EachListItem 6149 6150 BDirectory entryDir(ref); 6151 bool isVolume = entryDir.IsRootDirectory(); 6152 // volumes will get unmounted 6153 6154 // see if pose's device has a trash 6155 int32 device = ref->device; 6156 BDirectory trashDir; 6157 6158 // cache up the result in a map so that we don't have to keep calling 6159 // FSGetTrashDir over and over 6160 if (!isVolume 6161 && deviceHasTrashTmp.find(device) == deviceHasTrashTmp.end()) { 6162 deviceHasTrashTmp[device] = FSGetTrashDir(&trashDir, device) == B_OK; 6163 } 6164 6165 if (isVolume || deviceHasTrashTmp[device]) 6166 trashList->AddItem(new entry_ref(*ref)); 6167 else 6168 noTrashList->AddItem(new entry_ref(*ref)); 6169 } 6170 6171 6172 static void 6173 CopyPoseOneAsEntry(BPose* pose, BObjectList<entry_ref>* trashList, 6174 BObjectList<entry_ref>* noTrashList, std::map<int32, bool>* deviceHasTrash) 6175 { 6176 CopyOneTrashedRefAsEntry(pose->TargetModel()->EntryRef(), trashList, 6177 noTrashList, deviceHasTrash); 6178 } 6179 6180 6181 static bool 6182 CheckVolumeReadOnly(const entry_ref* ref) 6183 { 6184 BVolume volume (ref->device); 6185 if (volume.IsReadOnly()) { 6186 BAlert* alert = new BAlert ("", 6187 B_TRANSLATE("Files cannot be moved or deleted from a read-only " 6188 "volume."), B_TRANSLATE("Cancel"), NULL, NULL, B_WIDTH_AS_USUAL, 6189 B_STOP_ALERT); 6190 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 6191 alert->Go(); 6192 return false; 6193 } 6194 6195 return true; 6196 } 6197 6198 6199 void 6200 BPoseView::MoveSelectionOrEntryToTrash(const entry_ref* ref, bool selectNext) 6201 { 6202 BObjectList<entry_ref>* entriesToTrash = new 6203 BObjectList<entry_ref>(fSelectionList->CountItems()); 6204 BObjectList<entry_ref>* entriesToDeleteOnTheSpot = new 6205 BObjectList<entry_ref>(20, true); 6206 std::map<int32, bool> deviceHasTrash; 6207 6208 if (ref != NULL) { 6209 if (!CheckVolumeReadOnly(ref)) { 6210 delete entriesToTrash; 6211 delete entriesToDeleteOnTheSpot; 6212 return; 6213 } 6214 CopyOneTrashedRefAsEntry(ref, entriesToTrash, entriesToDeleteOnTheSpot, 6215 &deviceHasTrash); 6216 } else { 6217 if (!CheckVolumeReadOnly( 6218 fSelectionList->ItemAt(0)->TargetModel()->EntryRef())) { 6219 delete entriesToTrash; 6220 delete entriesToDeleteOnTheSpot; 6221 return; 6222 } 6223 EachListItem(fSelectionList, CopyPoseOneAsEntry, entriesToTrash, 6224 entriesToDeleteOnTheSpot, &deviceHasTrash); 6225 } 6226 6227 if (entriesToDeleteOnTheSpot->CountItems()) { 6228 BString alertText; 6229 if (ref != NULL) { 6230 alertText.SetTo(B_TRANSLATE("The selected item cannot be moved to " 6231 "the Trash. Would you like to delete it instead? " 6232 "(This operation cannot be reverted.)")); 6233 } else { 6234 alertText.SetTo(B_TRANSLATE("Some of the selected items cannot be " 6235 "moved to the Trash. Would you like to delete them instead? " 6236 "(This operation cannot be reverted.)")); 6237 } 6238 6239 BAlert* alert = new BAlert("", alertText.String(), 6240 B_TRANSLATE("Cancel"), B_TRANSLATE("Delete")); 6241 alert->SetShortcut(0, B_ESCAPE); 6242 if (alert->Go() == 0) 6243 return; 6244 } 6245 6246 MoveListToTrash(entriesToTrash, selectNext, false); 6247 MoveListToTrash(entriesToDeleteOnTheSpot, selectNext, true); 6248 } 6249 6250 6251 void 6252 BPoseView::MoveSelectionToTrash(bool selectNext) 6253 { 6254 if (fSelectionList->IsEmpty()) 6255 return; 6256 6257 // create entry_ref list from selection 6258 // separate items that can be trashed from ones that cannot 6259 6260 MoveSelectionOrEntryToTrash(0, selectNext); 6261 } 6262 6263 6264 void 6265 BPoseView::MoveEntryToTrash(const entry_ref* ref, bool selectNext) 6266 { 6267 MoveSelectionOrEntryToTrash(ref, selectNext); 6268 } 6269 6270 6271 void 6272 BPoseView::DeleteSelection(bool selectNext, bool askUser) 6273 { 6274 int32 count = fSelectionList->CountItems(); 6275 if (count <= 0) 6276 return; 6277 6278 if (!CheckVolumeReadOnly( 6279 fSelectionList->ItemAt(0)->TargetModel()->EntryRef())) { 6280 return; 6281 } 6282 6283 BObjectList<entry_ref>* entriesToDelete 6284 = new BObjectList<entry_ref>(count, true); 6285 6286 for (int32 index = 0; index < count; index++) { 6287 entriesToDelete->AddItem(new entry_ref( 6288 *fSelectionList->ItemAt(index)->TargetModel()->EntryRef())); 6289 } 6290 6291 Delete(entriesToDelete, selectNext, askUser); 6292 } 6293 6294 6295 void 6296 BPoseView::RestoreSelectionFromTrash(bool selectNext) 6297 { 6298 int32 count = fSelectionList -> CountItems(); 6299 if (count <= 0) 6300 return; 6301 6302 BObjectList<entry_ref>* entriesToRestore 6303 = new BObjectList<entry_ref>(count, true); 6304 6305 for (int32 index = 0; index < count; index++) { 6306 entriesToRestore->AddItem(new entry_ref( 6307 *fSelectionList->ItemAt(index)->TargetModel()->EntryRef())); 6308 } 6309 6310 RestoreItemsFromTrash(entriesToRestore, selectNext); 6311 } 6312 6313 6314 void 6315 BPoseView::Delete(const entry_ref &ref, bool selectNext, bool askUser) 6316 { 6317 BObjectList<entry_ref>* entriesToDelete 6318 = new BObjectList<entry_ref>(1, true); 6319 entriesToDelete->AddItem(new entry_ref(ref)); 6320 6321 Delete(entriesToDelete, selectNext, askUser); 6322 } 6323 6324 6325 void 6326 BPoseView::Delete(BObjectList<entry_ref>* list, bool selectNext, bool askUser) 6327 { 6328 if (list->CountItems() == 0) { 6329 delete list; 6330 return; 6331 } 6332 6333 BObjectList<FunctionObject>* taskList = 6334 new BObjectList<FunctionObject>(2, true); 6335 6336 // first move selection to trash, 6337 taskList->AddItem(NewFunctionObject(FSDeleteRefList, list, false, askUser)); 6338 6339 if (selectNext && ViewMode() == kListMode) { 6340 // next, if in list view mode try selecting the next item after 6341 BPose* pose = fSelectionList->ItemAt(0); 6342 6343 // find a point in the pose 6344 BPoint pointInPose(kListOffset + 5, 5); 6345 int32 index = IndexOfPose(pose); 6346 pointInPose.y += fListElemHeight * index; 6347 6348 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 6349 if (tracker != NULL) { 6350 ThrowOnAssert(TargetModel() != NULL); 6351 6352 // add a function object to the list of tasks to run 6353 // that will select the next item after the one we just 6354 // deleted 6355 Model* targetModel = TargetModel(); 6356 ASSERT(targetModel != NULL); 6357 taskList->AddItem(NewMemberFunctionObject( 6358 &TTracker::SelectPoseAtLocationSoon, tracker, 6359 *targetModel->NodeRef(), pointInPose)); 6360 } 6361 } 6362 6363 // execute the two tasks in order 6364 ThreadSequence::Launch(taskList, true); 6365 } 6366 6367 6368 void 6369 BPoseView::RestoreItemsFromTrash(BObjectList<entry_ref>* list, bool selectNext) 6370 { 6371 if (list->CountItems() == 0) { 6372 delete list; 6373 return; 6374 } 6375 6376 BObjectList<FunctionObject>* taskList = 6377 new BObjectList<FunctionObject>(2, true); 6378 6379 // first restoree selection 6380 taskList->AddItem(NewFunctionObject(FSRestoreRefList, list, false)); 6381 6382 if (selectNext && ViewMode() == kListMode) { 6383 // next, if in list view mode try selecting the next item after 6384 BPose* pose = fSelectionList->ItemAt(0); 6385 6386 // find a point in the pose 6387 BPoint pointInPose(kListOffset + 5, 5); 6388 int32 index = IndexOfPose(pose); 6389 pointInPose.y += fListElemHeight * index; 6390 6391 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 6392 if (tracker != NULL) { 6393 ThrowOnAssert(TargetModel() != NULL); 6394 6395 // add a function object to the list of tasks to run 6396 // that will select the next item after the one we just 6397 // restored 6398 Model* targetModel = TargetModel(); 6399 ASSERT(targetModel != NULL); 6400 taskList->AddItem(NewMemberFunctionObject( 6401 &TTracker::SelectPoseAtLocationSoon, tracker, 6402 *targetModel->NodeRef(), pointInPose)); 6403 } 6404 } 6405 6406 // execute the two tasks in order 6407 ThreadSequence::Launch(taskList, true); 6408 } 6409 6410 6411 void 6412 BPoseView::SelectAll() 6413 { 6414 BRect bounds(Bounds()); 6415 6416 // clear selection list 6417 fSelectionList->MakeEmpty(); 6418 fMimeTypesInSelectionCache.MakeEmpty(); 6419 fSelectionPivotPose = NULL; 6420 fRealPivotPose = NULL; 6421 6422 int32 startIndex = 0; 6423 BPoint loc(0, fListElemHeight * startIndex); 6424 6425 bool iconMode = ViewMode() != kListMode; 6426 6427 PoseList* poseList = CurrentPoseList(); 6428 int32 count = poseList->CountItems(); 6429 for (int32 index = startIndex; index < count; index++) { 6430 BPose* pose = poseList->ItemAt(index); 6431 fSelectionList->AddItem(pose); 6432 if (index == startIndex) 6433 fSelectionPivotPose = pose; 6434 6435 if (!pose->IsSelected()) { 6436 pose->Select(true); 6437 6438 BRect poseRect; 6439 if (iconMode) 6440 poseRect = pose->CalcRect(this); 6441 else 6442 poseRect = pose->CalcRect(loc, this); 6443 6444 if (bounds.Intersects(poseRect)) { 6445 pose->Draw(poseRect, bounds, this, false); 6446 Flush(); 6447 } 6448 } 6449 6450 loc.y += fListElemHeight; 6451 } 6452 6453 if (fSelectionChangedHook) 6454 ContainerWindow()->SelectionChanged(); 6455 } 6456 6457 6458 void 6459 BPoseView::InvertSelection() 6460 { 6461 // Since this function shares most code with 6462 // SelectAll(), we could make SelectAll() empty the selection, 6463 // then call InvertSelection() 6464 6465 BRect bounds(Bounds()); 6466 6467 int32 startIndex = 0; 6468 BPoint loc(0, fListElemHeight * startIndex); 6469 6470 fMimeTypesInSelectionCache.MakeEmpty(); 6471 fSelectionPivotPose = NULL; 6472 fRealPivotPose = NULL; 6473 6474 bool iconMode = ViewMode() != kListMode; 6475 6476 PoseList* poseList = CurrentPoseList(); 6477 int32 count = poseList->CountItems(); 6478 for (int32 index = startIndex; index < count; index++) { 6479 BPose* pose = poseList->ItemAt(index); 6480 6481 if (pose->IsSelected()) { 6482 fSelectionList->RemoveItem(pose); 6483 pose->Select(false); 6484 } else { 6485 if (index == startIndex) 6486 fSelectionPivotPose = pose; 6487 6488 fSelectionList->AddItem(pose); 6489 pose->Select(true); 6490 } 6491 6492 BRect poseRect; 6493 if (iconMode) 6494 poseRect = pose->CalcRect(this); 6495 else 6496 poseRect = pose->CalcRect(loc, this); 6497 6498 if (bounds.Intersects(poseRect)) 6499 Invalidate(); 6500 6501 loc.y += fListElemHeight; 6502 } 6503 6504 if (fSelectionChangedHook) 6505 ContainerWindow()->SelectionChanged(); 6506 } 6507 6508 6509 int32 6510 BPoseView::SelectMatchingEntries(const BMessage* message) 6511 { 6512 int32 matchCount = 0; 6513 SetMultipleSelection(true); 6514 6515 ClearSelection(); 6516 6517 TrackerStringExpressionType expressionType; 6518 BString expression; 6519 const char* expressionPointer; 6520 bool invertSelection; 6521 bool ignoreCase; 6522 6523 message->FindInt32("ExpressionType", (int32*)&expressionType); 6524 message->FindString("Expression", &expressionPointer); 6525 message->FindBool("InvertSelection", &invertSelection); 6526 message->FindBool("IgnoreCase", &ignoreCase); 6527 6528 expression = expressionPointer; 6529 6530 PoseList* poseList = CurrentPoseList(); 6531 int32 count = poseList->CountItems(); 6532 TrackerString name; 6533 6534 RegExp regExpression; 6535 6536 // Make sure we don't have any errors in the expression 6537 // before we match the names: 6538 if (expressionType == kRegexpMatch) { 6539 regExpression.SetTo(expression); 6540 6541 if (regExpression.InitCheck() != B_OK) { 6542 BString message( 6543 B_TRANSLATE("Error in regular expression:\n\n'%errstring'")); 6544 message.ReplaceFirst("%errstring", regExpression.ErrorString()); 6545 BAlert* alert = new BAlert("", message.String(), B_TRANSLATE("OK"), 6546 NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT); 6547 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 6548 alert->Go(); 6549 return 0; 6550 } 6551 } 6552 6553 // There is room for optimizations here: If regexp-type match, the Matches() 6554 // function compiles the expression for every entry. One could use 6555 // TrackerString::CompileRegExp and reuse the expression. However, then we 6556 // have to take care of the case sensitivity ourselves. 6557 for (int32 index = 0; index < count; index++) { 6558 BPose* pose = poseList->ItemAt(index); 6559 name = pose->TargetModel()->Name(); 6560 if (name.Matches(expression.String(), !ignoreCase, expressionType) 6561 ^ invertSelection) { 6562 matchCount++; 6563 AddPoseToSelection(pose, index); 6564 } 6565 } 6566 6567 Window()->Activate(); 6568 // Make sure the window is activated for 6569 // subsequent manipulations. Esp. needed 6570 // for the Desktop window. 6571 6572 return matchCount; 6573 } 6574 6575 6576 void 6577 BPoseView::ShowSelectionWindow() 6578 { 6579 Window()->PostMessage(kShowSelectionWindow); 6580 } 6581 6582 6583 void 6584 BPoseView::KeyDown(const char* bytes, int32 count) 6585 { 6586 char key = bytes[0]; 6587 6588 switch (key) { 6589 case B_LEFT_ARROW: 6590 case B_RIGHT_ARROW: 6591 case B_UP_ARROW: 6592 case B_DOWN_ARROW: 6593 { 6594 int32 index; 6595 BPose* pose = FindNearbyPose(key, &index); 6596 if (pose == NULL) 6597 break; 6598 6599 if (fMultipleSelection && (modifiers() & B_SHIFT_KEY) != 0) { 6600 if (pose->IsSelected()) { 6601 RemovePoseFromSelection(fSelectionList->LastItem()); 6602 fSelectionPivotPose = pose; 6603 ScrollIntoView(pose, index); 6604 } else 6605 AddPoseToSelection(pose, index, true); 6606 } else 6607 SelectPose(pose, index); 6608 6609 break; 6610 } 6611 6612 case B_RETURN: 6613 if (fFiltering && fSelectionList->CountItems() == 0) 6614 SelectPose(fFilteredPoseList->FirstItem(), 0); 6615 6616 OpenSelection(); 6617 6618 if (fFiltering && (modifiers() & B_SHIFT_KEY) != 0) 6619 StopFiltering(); 6620 6621 break; 6622 6623 case B_HOME: 6624 // select the first entry (if in listview mode), and 6625 // scroll to the top of the view 6626 if (ViewMode() == kListMode) 6627 MoveOrChangePoseSelection(0); 6628 else 6629 ScrollView(B_HOME); 6630 break; 6631 6632 case B_END: 6633 // select the last entry (if in listview mode), and 6634 // scroll to the bottom of the view 6635 if (ViewMode() == kListMode) 6636 MoveOrChangePoseSelection(CurrentPoseList()->CountItems() - 1); 6637 else 6638 ScrollView(B_END); 6639 break; 6640 6641 case B_PAGE_UP: 6642 if (ViewMode() == kListMode) { 6643 // Select first visible pose 6644 int32 firstIndex = CurrentPoseList()->IndexOf( 6645 fSelectionList->FirstItem()); 6646 int32 index; 6647 BPose* first = FirstVisiblePose(&index); 6648 if (first != NULL) { 6649 if (index == firstIndex) { 6650 ScrollView(B_PAGE_UP); 6651 first = FirstVisiblePose(&index); 6652 } 6653 MoveOrChangePoseSelection(index); 6654 } 6655 } else 6656 ScrollView(B_PAGE_UP); 6657 break; 6658 6659 case B_PAGE_DOWN: 6660 if (ViewMode() == kListMode) { 6661 // Select last visible pose 6662 int32 lastIndex = CurrentPoseList()->IndexOf( 6663 fSelectionList->LastItem()); 6664 int32 index; 6665 BPose* last = LastVisiblePose(&index); 6666 if (last != NULL) { 6667 if (index == lastIndex) { 6668 ScrollView(B_PAGE_DOWN); 6669 last = LastVisiblePose(&index); 6670 } 6671 MoveOrChangePoseSelection(index); 6672 } 6673 } else 6674 ScrollView(B_PAGE_DOWN); 6675 break; 6676 6677 case B_TAB: 6678 if (IsFilePanel()) 6679 _inherited::KeyDown(bytes, count); 6680 else { 6681 if (ViewMode() == kListMode 6682 && TrackerSettings().TypeAheadFiltering()) { 6683 break; 6684 } 6685 6686 if (fSelectionList->IsEmpty()) 6687 sMatchString.Truncate(0); 6688 else { 6689 BPose* pose = fSelectionList->FirstItem(); 6690 sMatchString.SetTo(pose->TargetModel()->Name()); 6691 } 6692 6693 bool reverse 6694 = (Window()->CurrentMessage()->FindInt32("modifiers") 6695 & B_SHIFT_KEY) != 0; 6696 int32 index; 6697 BPose* pose = FindNextMatch(&index, reverse); 6698 if (pose == NULL) { 6699 // wrap around 6700 if (reverse) 6701 sMatchString.SetTo(0x7f, 1); 6702 else 6703 sMatchString.Truncate(0); 6704 6705 pose = FindNextMatch(&index, reverse); 6706 } 6707 6708 SelectPose(pose, index); 6709 } 6710 break; 6711 6712 case B_DELETE: 6713 { 6714 ExcludeTrashFromSelection(); 6715 if (TargetModel() == NULL) { 6716 // Happens if called from within OpenWith window, for example 6717 break; 6718 } 6719 // Make sure user can't trash something already in the trash. 6720 if (TargetModel()->IsTrash()) { 6721 // Delete without asking from the trash 6722 DeleteSelection(true, false); 6723 } else { 6724 TrackerSettings settings; 6725 6726 if ((modifiers() & B_SHIFT_KEY) != 0 6727 || settings.DontMoveFilesToTrash()) { 6728 DeleteSelection(true, settings.AskBeforeDeleteFile()); 6729 } else 6730 MoveSelectionToTrash(); 6731 } 6732 break; 6733 } 6734 6735 case B_BACKSPACE: 6736 { 6737 if (fFiltering) { 6738 BString* lastString = fFilterStrings.LastItem(); 6739 if (lastString->Length() == 0) { 6740 int32 stringCount = fFilterStrings.CountItems(); 6741 if (stringCount > 1) 6742 delete fFilterStrings.RemoveItemAt(stringCount - 1); 6743 else 6744 break; 6745 } else 6746 lastString->TruncateChars(lastString->CountChars() - 1); 6747 6748 fCountView->RemoveFilterCharacter(); 6749 FilterChanged(); 6750 break; 6751 } 6752 6753 if (sMatchString.Length() == 0) 6754 break; 6755 6756 // remove last char from the typeahead buffer 6757 sMatchString.TruncateChars(sMatchString.CountChars() - 1); 6758 6759 fLastKeyTime = system_time(); 6760 6761 fCountView->SetTypeAhead(sMatchString.String()); 6762 6763 // select our new string 6764 int32 index; 6765 BPose* pose = FindBestMatch(&index); 6766 if (pose == NULL) 6767 break; 6768 6769 SelectPose(pose, index); 6770 break; 6771 } 6772 6773 case B_FUNCTION_KEY: 6774 { 6775 BMessage* message = Window()->CurrentMessage(); 6776 if (message != NULL) { 6777 int32 key; 6778 if (message->FindInt32("key", &key) == B_OK && key == B_F2_KEY) 6779 Window()->PostMessage(kEditItem, this); 6780 } 6781 break; 6782 } 6783 6784 case B_INSERT: 6785 break; 6786 6787 default: 6788 { 6789 // handle typeahead selection / filtering 6790 6791 if (ViewMode() == kListMode 6792 && TrackerSettings().TypeAheadFiltering()) { 6793 if (key == ' ' && modifiers() & B_SHIFT_KEY) { 6794 if (fFilterStrings.LastItem()->Length() == 0) 6795 break; 6796 6797 fFilterStrings.AddItem(new BString()); 6798 fCountView->AddFilterCharacter("|"); 6799 break; 6800 } 6801 6802 fFilterStrings.LastItem()->AppendChars(bytes, 1); 6803 fCountView->AddFilterCharacter(bytes); 6804 FilterChanged(); 6805 break; 6806 } 6807 6808 bigtime_t doubleClickSpeed; 6809 get_click_speed(&doubleClickSpeed); 6810 6811 // start watching 6812 if (fKeyRunner == NULL) { 6813 fKeyRunner = new BMessageRunner(this, 6814 new BMessage(kCheckTypeahead), doubleClickSpeed); 6815 if (fKeyRunner->InitCheck() != B_OK) 6816 return; 6817 } 6818 6819 // figure out the time at which the keypress happened 6820 bigtime_t eventTime; 6821 BMessage* message = Window()->CurrentMessage(); 6822 if (message == NULL 6823 || message->FindInt64("when", &eventTime) < B_OK) { 6824 eventTime = system_time(); 6825 } 6826 6827 // add char to existing matchString or start new match string 6828 if (eventTime - fLastKeyTime < (doubleClickSpeed * 2)) 6829 sMatchString.AppendChars(bytes, 1); 6830 else 6831 sMatchString.SetToChars(bytes, 1); 6832 6833 fLastKeyTime = eventTime; 6834 6835 fCountView->SetTypeAhead(sMatchString.String()); 6836 6837 int32 index; 6838 BPose* pose = FindBestMatch(&index); 6839 if (pose == NULL) 6840 break; 6841 6842 SelectPose(pose, index); 6843 break; 6844 } 6845 } 6846 } 6847 6848 6849 BPose* 6850 BPoseView::FindNextMatch(int32* matchingIndex, bool reverse) 6851 { 6852 char bestSoFar[B_FILE_NAME_LENGTH] = { 0 }; 6853 BPose* poseToSelect = NULL; 6854 6855 // loop through all poses to find match 6856 int32 count = fPoseList->CountItems(); 6857 for (int32 index = 0; index < count; index++) { 6858 BPose* pose = fPoseList->ItemAt(index); 6859 6860 if (reverse) { 6861 if (sMatchString.ICompare(pose->TargetModel()->Name()) > 0) { 6862 if (strcasecmp(pose->TargetModel()->Name(), bestSoFar) >= 0 6863 || !bestSoFar[0]) { 6864 strlcpy(bestSoFar, pose->TargetModel()->Name(), 6865 sizeof(bestSoFar)); 6866 poseToSelect = pose; 6867 *matchingIndex = index; 6868 } 6869 } 6870 } else if (sMatchString.ICompare(pose->TargetModel()->Name()) < 0) { 6871 if (strcasecmp(pose->TargetModel()->Name(), bestSoFar) <= 0 6872 || !bestSoFar[0]) { 6873 strlcpy(bestSoFar, pose->TargetModel()->Name(), 6874 sizeof(bestSoFar)); 6875 poseToSelect = pose; 6876 *matchingIndex = index; 6877 } 6878 } 6879 } 6880 6881 return poseToSelect; 6882 } 6883 6884 6885 BPose* 6886 BPoseView::FindBestMatch(int32* index) 6887 { 6888 BPose* poseToSelect = NULL; 6889 float bestScore = -1; 6890 int32 count = fPoseList->CountItems(); 6891 6892 // loop through all poses to find match 6893 for (int32 j = 0; j < CountColumns(); j++) { 6894 BColumn* column = ColumnAt(j); 6895 6896 for (int32 i = 0; i < count; i++) { 6897 BPose* pose = fPoseList->ItemAt(i); 6898 float score = -1; 6899 6900 if (ViewMode() == kListMode) { 6901 ModelNodeLazyOpener modelOpener(pose->TargetModel()); 6902 BTextWidget* widget = pose->WidgetFor(column, this, 6903 modelOpener); 6904 const char* text = NULL; 6905 if (widget != NULL) 6906 text = widget->Text(this); 6907 6908 if (text != NULL) 6909 score = ComputeTypeAheadScore(text, sMatchString.String()); 6910 } else { 6911 score = ComputeTypeAheadScore(pose->TargetModel()->Name(), 6912 sMatchString.String()); 6913 } 6914 6915 if (score > bestScore) { 6916 poseToSelect = pose; 6917 bestScore = score; 6918 *index = i; 6919 } 6920 if (score == kExactMatchScore) 6921 break; 6922 } 6923 6924 // TODO: we might want to change this to make it always work 6925 // over all columns, but this would require some more changes 6926 // to how Tracker represents data (for example we could filter 6927 // the results out). 6928 if (bestScore > 0 || ViewMode() != kListMode) 6929 break; 6930 } 6931 6932 return poseToSelect; 6933 } 6934 6935 6936 static bool 6937 LinesIntersect(float s1, float e1, float s2, float e2) 6938 { 6939 return std::max(s1, s2) < std::min(e1, e2); 6940 } 6941 6942 6943 BPose* 6944 BPoseView::FindNearbyPose(char arrowKey, int32* poseIndex) 6945 { 6946 int32 resultingIndex = -1; 6947 BPose* poseToSelect = NULL; 6948 BPose* selectedPose = fSelectionList->LastItem(); 6949 6950 if (ViewMode() == kListMode) { 6951 PoseList* poseList = CurrentPoseList(); 6952 6953 switch (arrowKey) { 6954 case B_UP_ARROW: 6955 case B_LEFT_ARROW: 6956 if (selectedPose) { 6957 resultingIndex = poseList->IndexOf(selectedPose) - 1; 6958 poseToSelect = poseList->ItemAt(resultingIndex); 6959 if (poseToSelect == NULL && arrowKey == B_LEFT_ARROW) { 6960 resultingIndex = poseList->CountItems() - 1; 6961 poseToSelect = poseList->LastItem(); 6962 } 6963 } else { 6964 resultingIndex = poseList->CountItems() - 1; 6965 poseToSelect = poseList->LastItem(); 6966 } 6967 break; 6968 6969 case B_DOWN_ARROW: 6970 case B_RIGHT_ARROW: 6971 if (selectedPose) { 6972 resultingIndex = poseList->IndexOf(selectedPose) + 1; 6973 poseToSelect = poseList->ItemAt(resultingIndex); 6974 if (poseToSelect == NULL && arrowKey == B_RIGHT_ARROW) { 6975 resultingIndex = 0; 6976 poseToSelect = poseList->FirstItem(); 6977 } 6978 } else { 6979 resultingIndex = 0; 6980 poseToSelect = poseList->FirstItem(); 6981 } 6982 break; 6983 } 6984 *poseIndex = resultingIndex; 6985 6986 return poseToSelect; 6987 } 6988 6989 // must be in one of the icon modes 6990 6991 // handle case where there is no current selection 6992 if (fSelectionList->IsEmpty()) { 6993 // find the upper-left pose (I know it's ugly!) 6994 poseToSelect = fVSPoseList->FirstItem(); 6995 for (int32 index = 0; ;index++) { 6996 BPose* pose = fVSPoseList->ItemAt(++index); 6997 if (pose == NULL) 6998 break; 6999 7000 if (poseToSelect != NULL) { 7001 BRect selectedBounds; 7002 selectedBounds = poseToSelect->CalcRect(this); 7003 BRect poseRect(pose->CalcRect(this)); 7004 7005 if (poseRect.top > selectedBounds.top) 7006 break; 7007 7008 if (poseRect.left < selectedBounds.left) 7009 poseToSelect = pose; 7010 } 7011 } 7012 7013 return poseToSelect; 7014 } 7015 7016 BRect selectionRect; 7017 if (selectedPose != NULL) 7018 selectionRect = selectedPose->CalcRect(this); 7019 7020 BRect bestRect; 7021 7022 // we're not in list mode so scan visually for pose to select 7023 int32 count = fPoseList->CountItems(); 7024 for (int32 index = 0; index < count; index++) { 7025 BPose* pose = fPoseList->ItemAt(index); 7026 BRect poseRect(pose->CalcRect(this)); 7027 7028 switch (arrowKey) { 7029 case B_LEFT_ARROW: 7030 if (LinesIntersect(poseRect.top, poseRect.bottom, 7031 selectionRect.top, selectionRect.bottom) 7032 && poseRect.left < selectionRect.left 7033 && (poseRect.left > bestRect.left 7034 || !bestRect.IsValid())) { 7035 bestRect = poseRect; 7036 poseToSelect = pose; 7037 } 7038 break; 7039 7040 case B_RIGHT_ARROW: 7041 if (LinesIntersect(poseRect.top, poseRect.bottom, 7042 selectionRect.top, selectionRect.bottom) 7043 && poseRect.right > selectionRect.right 7044 && (poseRect.right < bestRect.right 7045 || !bestRect.IsValid())) { 7046 bestRect = poseRect; 7047 poseToSelect = pose; 7048 } 7049 break; 7050 7051 case B_UP_ARROW: 7052 if (LinesIntersect(poseRect.left, poseRect.right, 7053 selectionRect.left, selectionRect.right) 7054 && poseRect.top < selectionRect.top 7055 && (poseRect.top > bestRect.top 7056 || !bestRect.IsValid())) { 7057 bestRect = poseRect; 7058 poseToSelect = pose; 7059 } 7060 break; 7061 7062 case B_DOWN_ARROW: 7063 if (LinesIntersect(poseRect.left, poseRect.right, 7064 selectionRect.left, selectionRect.right) 7065 && poseRect.bottom > selectionRect.bottom 7066 && (poseRect.bottom < bestRect.bottom 7067 || !bestRect.IsValid())) { 7068 bestRect = poseRect; 7069 poseToSelect = pose; 7070 } 7071 break; 7072 } 7073 } 7074 7075 if (poseToSelect != NULL) 7076 return poseToSelect; 7077 7078 return selectedPose; 7079 } 7080 7081 7082 void 7083 BPoseView::ShowContextMenu(BPoint where) 7084 { 7085 BContainerWindow* window = ContainerWindow(); 7086 if (window == NULL) 7087 return; 7088 7089 // handle pose selection 7090 int32 index; 7091 BPose* pose = FindPose(where, &index); 7092 if (pose != NULL) { 7093 if (!pose->IsSelected()) { 7094 ClearSelection(); 7095 pose->Select(true); 7096 fSelectionList->AddItem(pose); 7097 DrawPose(pose, index, false); 7098 } 7099 } else 7100 ClearSelection(); 7101 7102 window->Activate(); 7103 window->UpdateIfNeeded(); 7104 window->ShowContextMenu(where, 7105 pose ? pose->TargetModel()->EntryRef() : 0, this); 7106 7107 if (fSelectionChangedHook) 7108 window->SelectionChanged(); 7109 } 7110 7111 7112 void 7113 BPoseView::_BeginSelectionRect(const BPoint& point, bool shouldExtend) 7114 { 7115 // set initial empty selection rectangle 7116 fSelectionRectInfo.rect = BRect(point, point - BPoint(1, 1)); 7117 7118 if (!fTransparentSelection) { 7119 SetDrawingMode(B_OP_INVERT); 7120 StrokeRect(fSelectionRectInfo.rect, B_MIXED_COLORS); 7121 SetDrawingMode(B_OP_OVER); 7122 } 7123 7124 fSelectionRectInfo.lastRect = fSelectionRectInfo.rect; 7125 fSelectionRectInfo.selection = new BList; 7126 fSelectionRectInfo.startPoint = point; 7127 fSelectionRectInfo.lastPoint = point; 7128 fSelectionRectInfo.isDragging = true; 7129 7130 if (fAutoScrollState == kAutoScrollOff) { 7131 fAutoScrollState = kAutoScrollOn; 7132 Window()->SetPulseRate(20000); 7133 } 7134 } 7135 7136 7137 static void 7138 AddIfPoseSelected(BPose* pose, PoseList* list) 7139 { 7140 if (pose->IsSelected()) 7141 list->AddItem(pose); 7142 } 7143 7144 7145 void 7146 BPoseView::_UpdateSelectionRect(const BPoint& point) 7147 { 7148 if (point != fSelectionRectInfo.lastPoint) { 7149 fSelectionRectInfo.lastPoint = point; 7150 7151 // erase last rect 7152 if (!fTransparentSelection) { 7153 SetDrawingMode(B_OP_INVERT); 7154 StrokeRect(fSelectionRectInfo.rect, B_MIXED_COLORS); 7155 SetDrawingMode(B_OP_OVER); 7156 } 7157 7158 fSelectionRectInfo.rect.top = std::min(point.y, 7159 fSelectionRectInfo.startPoint.y); 7160 fSelectionRectInfo.rect.left = std::min(point.x, 7161 fSelectionRectInfo.startPoint.x); 7162 fSelectionRectInfo.rect.bottom = std::max(point.y, 7163 fSelectionRectInfo.startPoint.y); 7164 fSelectionRectInfo.rect.right = std::max(point.x, 7165 fSelectionRectInfo.startPoint.x); 7166 7167 fIsDrawingSelectionRect = true; 7168 7169 // use current selection rectangle to scan poses 7170 if (ViewMode() == kListMode) { 7171 SelectPosesListMode(fSelectionRectInfo.rect, 7172 &fSelectionRectInfo.selection); 7173 } else { 7174 SelectPosesIconMode(fSelectionRectInfo.rect, 7175 &fSelectionRectInfo.selection); 7176 } 7177 7178 Window()->UpdateIfNeeded(); 7179 7180 // draw new rect 7181 if (!fTransparentSelection) { 7182 SetDrawingMode(B_OP_INVERT); 7183 StrokeRect(fSelectionRectInfo.rect, B_MIXED_COLORS); 7184 SetDrawingMode(B_OP_OVER); 7185 } else { 7186 BRegion updateRegion1; 7187 BRegion updateRegion2; 7188 7189 bool sameWidth = fSelectionRectInfo.rect.Width() 7190 == fSelectionRectInfo.lastRect.Width(); 7191 bool sameHeight = fSelectionRectInfo.rect.Height() 7192 == fSelectionRectInfo.lastRect.Height(); 7193 7194 updateRegion1.Include(fSelectionRectInfo.rect); 7195 updateRegion1.Exclude(fSelectionRectInfo.lastRect.InsetByCopy( 7196 sameWidth ? 0 : 1, sameHeight ? 0 : 1)); 7197 updateRegion2.Include(fSelectionRectInfo.lastRect); 7198 updateRegion2.Exclude(fSelectionRectInfo.rect.InsetByCopy( 7199 sameWidth ? 0 : 1, sameHeight ? 0 : 1)); 7200 updateRegion1.Include(&updateRegion2); 7201 BRect unionRect = fSelectionRectInfo.rect 7202 & fSelectionRectInfo.lastRect; 7203 updateRegion1.Exclude(unionRect 7204 & BRect(-2000, fSelectionRectInfo.startPoint.y, 2000, 7205 fSelectionRectInfo.startPoint.y)); 7206 updateRegion1.Exclude(unionRect 7207 & BRect(fSelectionRectInfo.startPoint.x, -2000, 7208 fSelectionRectInfo.startPoint.x, 2000)); 7209 7210 fSelectionRectInfo.lastRect = fSelectionRectInfo.rect; 7211 7212 Invalidate(&updateRegion1); 7213 Window()->UpdateIfNeeded(); 7214 } 7215 Flush(); 7216 } 7217 } 7218 7219 7220 void 7221 BPoseView::_EndSelectionRect() 7222 { 7223 delete fSelectionRectInfo.selection; 7224 fSelectionRectInfo.selection = NULL; 7225 7226 fSelectionRectInfo.isDragging = false; 7227 fIsDrawingSelectionRect = false; 7228 // TODO: remove BPose dependency? 7229 7230 // do final erase of selection rect 7231 if (!fTransparentSelection) { 7232 SetDrawingMode(B_OP_INVERT); 7233 StrokeRect(fSelectionRectInfo.rect, B_MIXED_COLORS); 7234 SetDrawingMode(B_OP_COPY); 7235 fSelectionRectInfo.rect.Set(0, 0, -1, -1); 7236 } else { 7237 Invalidate(fSelectionRectInfo.rect); 7238 fSelectionRectInfo.rect.Set(0, 0, -1, -1); 7239 Window()->UpdateIfNeeded(); 7240 } 7241 7242 // we now need to update the pose view's selection list by clearing it 7243 // and then polling each pose for selection state and rebuilding list 7244 fSelectionList->MakeEmpty(); 7245 fMimeTypesInSelectionCache.MakeEmpty(); 7246 7247 EachListItem(fPoseList, AddIfPoseSelected, fSelectionList); 7248 7249 // and now make sure that the pivot point is in sync 7250 if (fSelectionPivotPose && !fSelectionList->HasItem(fSelectionPivotPose)) 7251 fSelectionPivotPose = NULL; 7252 if (fRealPivotPose && !fSelectionList->HasItem(fRealPivotPose)) 7253 fRealPivotPose = NULL; 7254 } 7255 7256 7257 void 7258 BPoseView::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage) 7259 { 7260 if (fSelectionRectInfo.isDragging) 7261 _UpdateSelectionRect(where); 7262 7263 if (!fDropEnabled || dragMessage == NULL) 7264 return; 7265 7266 BContainerWindow* window = ContainerWindow(); 7267 if (window == NULL) 7268 return; 7269 7270 if (!window->Dragging()) 7271 window->DragStart(dragMessage); 7272 7273 switch (transit) { 7274 case B_INSIDE_VIEW: 7275 case B_ENTERED_VIEW: 7276 UpdateDropTarget(where, dragMessage, window->ContextMenu()); 7277 if (fAutoScrollState == kAutoScrollOff) { 7278 // turn on auto scrolling if it's not yet on 7279 fAutoScrollState = kWaitForTransition; 7280 window->SetPulseRate(100000); 7281 } 7282 break; 7283 7284 case B_EXITED_VIEW: 7285 DragStop(); 7286 // reset cursor in case we set it to the copy cursor 7287 // in UpdateDropTarget 7288 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); 7289 fCursorCheck = false; 7290 // TODO: autoscroll here 7291 if (!window->ContextMenu()) { 7292 HiliteDropTarget(false); 7293 fDropTarget = NULL; 7294 } 7295 break; 7296 } 7297 } 7298 7299 7300 void 7301 BPoseView::MouseDragged(const BMessage* message) 7302 { 7303 if (fTextWidgetToCheck != NULL) 7304 fTextWidgetToCheck->CancelWait(); 7305 7306 fTrackRightMouseUp = false; 7307 7308 BPoint where; 7309 uint32 buttons = 0; 7310 if (message->FindPoint("be:view_where", &where) != B_OK 7311 || message->FindInt32("buttons", (int32*)&buttons) != B_OK) { 7312 return; 7313 } 7314 7315 bool extendSelection = (modifiers() & B_COMMAND_KEY) != 0 7316 && fMultipleSelection; 7317 7318 int32 index; 7319 BPose* pose = FindPose(where, &index); 7320 if (pose != NULL) 7321 DragSelectedPoses(pose, where); 7322 else if (buttons == B_PRIMARY_MOUSE_BUTTON) 7323 _BeginSelectionRect(where, extendSelection); 7324 } 7325 7326 7327 void 7328 BPoseView::MouseLongDown(const BMessage* message) 7329 { 7330 fTrackRightMouseUp = false; 7331 7332 BPoint where; 7333 if (message->FindPoint("where", &where) != B_OK) 7334 return; 7335 7336 ShowContextMenu(where); 7337 } 7338 7339 7340 void 7341 BPoseView::MouseIdle(const BMessage* message) 7342 { 7343 BPoint where; 7344 uint32 buttons = 0; 7345 GetMouse(&where, &buttons); 7346 // We could retrieve 'where' from the incoming 7347 // message but we need the buttons state anyway 7348 // and B_MOUSE_IDLE message doesn't pass it 7349 BContainerWindow* window = ContainerWindow(); 7350 7351 if (buttons == 0 || window == NULL || !window->Dragging()) 7352 return; 7353 7354 if (fDropTarget != NULL) { 7355 FrameForPose(fDropTarget, true, &fStartFrame); 7356 ShowContextMenu(where); 7357 } else 7358 window->Activate(); 7359 } 7360 7361 7362 void 7363 BPoseView::MouseDown(BPoint where) 7364 { 7365 // handle disposing of drag data lazily 7366 DragStop(); 7367 BContainerWindow* window = ContainerWindow(); 7368 if (window == NULL) 7369 return; 7370 7371 if (IsDesktopWindow()) { 7372 BScreen screen(Window()); 7373 rgb_color color = screen.DesktopColor(); 7374 SetLowColor(color); 7375 SetViewColor(color); 7376 } 7377 7378 MakeFocus(); 7379 7380 uint32 buttons = (uint32)window->CurrentMessage()->FindInt32("buttons"); 7381 uint32 modifierKeys = modifiers(); 7382 bool secondaryMouseButtonDown 7383 = SecondaryMouseButtonDown(modifierKeys, buttons); 7384 fTrackRightMouseUp = secondaryMouseButtonDown; 7385 bool extendSelection = (modifierKeys & B_COMMAND_KEY) != 0 7386 && fMultipleSelection; 7387 7388 CommitActivePose(); 7389 7390 int32 index; 7391 BPose* pose = FindPose(where, &index); 7392 if (pose != NULL) { 7393 if (!pose->IsSelected() || !secondaryMouseButtonDown) 7394 AddRemoveSelectionRange(where, extendSelection, pose); 7395 7396 if (fTextWidgetToCheck != NULL 7397 && (pose != fLastClickedPose || secondaryMouseButtonDown)) { 7398 fTextWidgetToCheck->CancelWait(); 7399 } 7400 7401 if (!extendSelection && WasDoubleClick(pose, where, buttons) 7402 && buttons == B_PRIMARY_MOUSE_BUTTON 7403 && fLastClickButtons == B_PRIMARY_MOUSE_BUTTON 7404 && (modifierKeys & B_CONTROL_KEY) == 0) { 7405 // special handling for path field double-clicks 7406 if (!WasClickInPath(pose, index, where)) 7407 OpenSelection(pose, &index); 7408 } 7409 } else { 7410 // click was not in any pose 7411 fLastClickedPose = NULL; 7412 if (fTextWidgetToCheck != NULL) 7413 fTextWidgetToCheck->CancelWait(); 7414 7415 window->Activate(); 7416 window->UpdateIfNeeded(); 7417 7418 // only clear selection if we are not extending it 7419 if (!extendSelection || !fSelectionRectEnabled || !fMultipleSelection) 7420 ClearSelection(); 7421 7422 // show desktop context menu 7423 if (SecondaryMouseButtonDown(modifierKeys, buttons)) 7424 ShowContextMenu(where); 7425 } 7426 7427 if (fSelectionChangedHook) 7428 window->SelectionChanged(); 7429 } 7430 7431 7432 void 7433 BPoseView::SetTextWidgetToCheck(BTextWidget* widget, BTextWidget* old) 7434 { 7435 if (old == NULL || fTextWidgetToCheck == old) 7436 fTextWidgetToCheck = widget; 7437 } 7438 7439 7440 void 7441 BPoseView::MouseUp(BPoint where) 7442 { 7443 if (fSelectionRectInfo.isDragging) 7444 _EndSelectionRect(); 7445 7446 int32 index; 7447 BPose* pose = FindPose(where, &index); 7448 uint32 lastButtons = Window()->CurrentMessage()->FindInt32("last_buttons"); 7449 if (pose != NULL && fLastClickedPose != NULL && fAllowPoseEditing 7450 && !fTrackRightMouseUp) { 7451 // This handy field has been added by the tracking filter. 7452 // we need lastButtons for right button mouse-up tracking, 7453 // because there's currently no way to know wich buttons were 7454 // released in BView::MouseUp (unlike BView::KeyUp) 7455 pose->MouseUp(BPoint(0, index * fListElemHeight), this, where, index); 7456 } 7457 7458 // Showing the pose context menu is done on mouse up (or long click) 7459 // to make right button dragging possible 7460 if (pose != NULL && fTrackRightMouseUp 7461 && (SecondaryMouseButtonDown(modifiers(), lastButtons))) { 7462 if (!pose->IsSelected()) { 7463 ClearSelection(); 7464 pose->Select(true); 7465 fSelectionList->AddItem(pose); 7466 DrawPose(pose, index, false); 7467 } 7468 ShowContextMenu(where); 7469 } 7470 fTrackRightMouseUp = false; 7471 } 7472 7473 7474 bool 7475 BPoseView::WasClickInPath(const BPose* pose, int32 index, 7476 BPoint mouseLocation) const 7477 { 7478 if (pose == NULL || (ViewMode() != kListMode)) 7479 return false; 7480 7481 BPoint loc(0, index * fListElemHeight); 7482 BTextWidget* widget; 7483 if (!pose->PointInPose(loc, this, mouseLocation, &widget) || !widget) 7484 return false; 7485 7486 // note: the following code is wrong, because this sort of hashing 7487 // may overlap and we get aliasing 7488 if (widget->AttrHash() != AttrHashString(kAttrPath, B_STRING_TYPE)) 7489 return false; 7490 7491 BEntry entry(widget->Text(this)); 7492 if (entry.InitCheck() != B_OK) 7493 return false; 7494 7495 entry_ref ref; 7496 if (entry.GetRef(&ref) == B_OK) { 7497 BMessage message(B_REFS_RECEIVED); 7498 message.AddRef("refs", &ref); 7499 be_app->PostMessage(&message); 7500 return true; 7501 } 7502 7503 return false; 7504 } 7505 7506 7507 bool 7508 BPoseView::WasDoubleClick(const BPose* pose, BPoint point, int32 buttons) 7509 { 7510 // check proximity 7511 BPoint delta = point - fLastClickPoint; 7512 int32 clicks = Window()->CurrentMessage()->FindInt32("clicks"); 7513 7514 if (clicks == 2 7515 && fabs(delta.x) < kDoubleClickTresh 7516 && fabs(delta.y) < kDoubleClickTresh 7517 && pose == fLastClickedPose) { 7518 fLastClickPoint.Set(INT32_MAX, INT32_MAX); 7519 fLastClickedPose = NULL; 7520 if (fTextWidgetToCheck != NULL) 7521 fTextWidgetToCheck->CancelWait(); 7522 7523 return buttons == fLastClickButtons; 7524 } 7525 7526 fLastClickPoint = point; 7527 fLastClickedPose = pose; 7528 fLastClickButtons = buttons; 7529 7530 return false; 7531 } 7532 7533 7534 static void 7535 AddPoseRefToMessage(Model* model, BMessage* message) 7536 { 7537 // Make sure that every file added to the message has its 7538 // MIME type set. 7539 BNode node(model->EntryRef()); 7540 if (node.InitCheck() == B_OK) { 7541 BNodeInfo info(&node); 7542 char type[B_MIME_TYPE_LENGTH]; 7543 type[0] = '\0'; 7544 if (info.GetType(type) != B_OK) { 7545 BPath path(model->EntryRef()); 7546 if (path.InitCheck() == B_OK) 7547 update_mime_info(path.Path(), false, false, false); 7548 } 7549 } 7550 message->AddRef("refs", model->EntryRef()); 7551 } 7552 7553 7554 void 7555 BPoseView::DragSelectedPoses(const BPose* pose, BPoint clickPoint) 7556 { 7557 if (!fDragEnabled) 7558 return; 7559 7560 ASSERT(pose); 7561 7562 // make sure pose is selected, it could have been deselected as part of 7563 // a click during selection extention 7564 if (!pose->IsSelected()) 7565 return; 7566 7567 // setup tracking rect by unioning all selected pose rects 7568 BMessage message(B_SIMPLE_DATA); 7569 message.AddPointer("src_window", Window()); 7570 message.AddPoint("click_pt", clickPoint); 7571 7572 // add Tracker token so that refs received recipients can script us 7573 message.AddMessenger("TrackerViewToken", BMessenger(this)); 7574 7575 // cannot use EachPoseAndModel here, because that iterates the selected 7576 // poses in reverse order 7577 for (int32 index = 0; index < fSelectionList->CountItems(); index++) { 7578 AddPoseRefToMessage(fSelectionList->ItemAt(index)->TargetModel(), 7579 &message); 7580 } 7581 7582 // make sure button is still down 7583 uint32 buttons; 7584 BPoint tempLoc; 7585 GetMouse(&tempLoc, &buttons); 7586 if (buttons != 0) { 7587 int32 index = CurrentPoseList()->IndexOf(pose); 7588 message.AddInt32("buttons", (int32)buttons); 7589 BRect dragRect(GetDragRect(index)); 7590 BBitmap* dragBitmap = NULL; 7591 BPoint offset; 7592 #ifdef DRAG_FRAME 7593 if (dragRect.Width() < kTransparentDragThreshold.x 7594 && dragRect.Height() < kTransparentDragThreshold.y) { 7595 dragBitmap = MakeDragBitmap(dragRect, clickPoint, index, offset); 7596 } 7597 #else 7598 // The bitmap is now always created (if DRAG_FRAME is not defined) 7599 dragBitmap = MakeDragBitmap(dragRect, clickPoint, index, offset); 7600 #endif 7601 if (dragBitmap != NULL) { 7602 DragMessage(&message, dragBitmap, B_OP_ALPHA, offset); 7603 // this DragMessage supports alpha blending 7604 } else 7605 DragMessage(&message, dragRect); 7606 7607 // turn on auto scrolling 7608 fAutoScrollState = kWaitForTransition; 7609 Window()->SetPulseRate(100000); 7610 } 7611 } 7612 7613 7614 BBitmap* 7615 BPoseView::MakeDragBitmap(BRect dragRect, BPoint clickedPoint, 7616 int32 clickedPoseIndex, BPoint &offset) 7617 { 7618 BRect inner(clickedPoint.x - kTransparentDragThreshold.x / 2, 7619 clickedPoint.y - kTransparentDragThreshold.y / 2, 7620 clickedPoint.x + kTransparentDragThreshold.x / 2, 7621 clickedPoint.y + kTransparentDragThreshold.y / 2); 7622 7623 // (BRect & BRect) doesn't work correctly if the rectangles don't intersect 7624 // this catches a bug that is produced somewhere before this function is 7625 // called 7626 if (!inner.Intersects(dragRect)) 7627 return NULL; 7628 7629 inner = inner & dragRect; 7630 7631 // If the selection is bigger than the specified limit, the 7632 // contents will fade out when they come near the borders 7633 bool fadeTop = false; 7634 bool fadeBottom = false; 7635 bool fadeLeft = false; 7636 bool fadeRight = false; 7637 bool fade = false; 7638 if (inner.left > dragRect.left) { 7639 inner.left = std::max(inner.left - 32, dragRect.left); 7640 fade = fadeLeft = true; 7641 } 7642 if (inner.right < dragRect.right) { 7643 inner.right = std::min(inner.right + 32, dragRect.right); 7644 fade = fadeRight = true; 7645 } 7646 if (inner.top > dragRect.top) { 7647 inner.top = std::max(inner.top - 32, dragRect.top); 7648 fade = fadeTop = true; 7649 } 7650 if (inner.bottom < dragRect.bottom) { 7651 inner.bottom = std::min(inner.bottom + 32, dragRect.bottom); 7652 fade = fadeBottom = true; 7653 } 7654 7655 // set the offset for the dragged bitmap (for the BView::DragMessage() call) 7656 offset = clickedPoint - BPoint(2, 1) - inner.LeftTop(); 7657 7658 BRect rect(inner); 7659 rect.OffsetTo(B_ORIGIN); 7660 7661 BBitmap* bitmap = new BBitmap(rect, B_RGBA32, true); 7662 bitmap->Lock(); 7663 BView* view = new BView(bitmap->Bounds(), "", B_FOLLOW_NONE, 0); 7664 bitmap->AddChild(view); 7665 7666 view->SetOrigin(0, 0); 7667 7668 BRect clipRect(view->Bounds()); 7669 BRegion newClip; 7670 newClip.Set(clipRect); 7671 view->ConstrainClippingRegion(&newClip); 7672 7673 memset(bitmap->Bits(), 0, bitmap->BitsLength()); 7674 7675 view->SetDrawingMode(B_OP_ALPHA); 7676 view->SetHighColor(0, 0, 0, uint8(fade ? 164 : 128)); 7677 // set the level of transparency by value 7678 view->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE); 7679 7680 BRect bounds(Bounds()); 7681 7682 PoseList* poseList = CurrentPoseList(); 7683 if (ViewMode() == kListMode) { 7684 int32 count = poseList->CountItems(); 7685 int32 startIndex = (int32)(bounds.top / fListElemHeight); 7686 BPoint loc(0, startIndex * fListElemHeight); 7687 7688 for (int32 index = startIndex; index < count; index++) { 7689 BPose* pose = poseList->ItemAt(index); 7690 if (pose->IsSelected()) { 7691 BRect poseRect(pose->CalcRect(loc, this, true)); 7692 if (poseRect.Intersects(inner)) { 7693 BPoint offsetBy(-inner.LeftTop().x, -inner.LeftTop().y); 7694 pose->Draw(poseRect, poseRect, this, view, true, offsetBy, 7695 false); 7696 } 7697 } 7698 loc.y += fListElemHeight; 7699 if (loc.y > bounds.bottom) 7700 break; 7701 } 7702 } else { 7703 // add rects for visible poses only (uses VSList!!) 7704 int32 startIndex 7705 = FirstIndexAtOrBelow((int32)(bounds.top - IconPoseHeight())); 7706 int32 count = fVSPoseList->CountItems(); 7707 7708 for (int32 index = startIndex; index < count; index++) { 7709 BPose* pose = fVSPoseList->ItemAt(index); 7710 if (pose != NULL && pose->IsSelected()) { 7711 BRect poseRect(pose->CalcRect(this)); 7712 if (!poseRect.Intersects(inner)) 7713 continue; 7714 7715 BPoint offsetBy(-inner.LeftTop().x, -inner.LeftTop().y); 7716 pose->Draw(poseRect, poseRect, this, view, true, offsetBy, 7717 false); 7718 } 7719 } 7720 } 7721 7722 view->Sync(); 7723 7724 // fade out the contents if necessary 7725 if (fade) { 7726 uint32* bits = (uint32*)bitmap->Bits(); 7727 int32 width = bitmap->BytesPerRow() / 4; 7728 7729 if (fadeLeft) 7730 FadeRGBA32Horizontal(bits, width, int32(rect.bottom), 0, 64); 7731 7732 if (fadeRight) { 7733 FadeRGBA32Horizontal(bits, width, int32(rect.bottom), 7734 int32(rect.right), int32(rect.right) - 64); 7735 } 7736 7737 if (fadeTop) 7738 FadeRGBA32Vertical(bits, width, int32(rect.bottom), 0, 64); 7739 7740 if (fadeBottom) { 7741 FadeRGBA32Vertical(bits, width, int32(rect.bottom), 7742 int32(rect.bottom), int32(rect.bottom) - 64); 7743 } 7744 } 7745 7746 bitmap->Unlock(); 7747 return bitmap; 7748 } 7749 7750 7751 BRect 7752 BPoseView::GetDragRect(int32 clickedPoseIndex) 7753 { 7754 BRect result; 7755 BRect bounds(Bounds()); 7756 7757 PoseList* poseList = CurrentPoseList(); 7758 BPose* pose = poseList->ItemAt(clickedPoseIndex); 7759 if (ViewMode() == kListMode) { 7760 // get starting rect of clicked pose 7761 result = CalcPoseRectList(pose, clickedPoseIndex, true); 7762 7763 // add rects for visible poses only 7764 int32 count = poseList->CountItems(); 7765 int32 startIndex = (int32)(bounds.top / fListElemHeight); 7766 BPoint loc(0, startIndex * fListElemHeight); 7767 7768 for (int32 index = startIndex; index < count; index++) { 7769 pose = poseList->ItemAt(index); 7770 if (pose->IsSelected()) 7771 result = result | pose->CalcRect(loc, this, true); 7772 7773 loc.y += fListElemHeight; 7774 if (loc.y > bounds.bottom) 7775 break; 7776 } 7777 } else { 7778 // get starting rect of clicked pose 7779 result = pose->CalcRect(this); 7780 7781 // add rects for visible poses only (uses VSList!!) 7782 int32 count = fVSPoseList->CountItems(); 7783 for (int32 index = FirstIndexAtOrBelow( 7784 (int32)(bounds.top - IconPoseHeight())); 7785 index < count; index++) { 7786 BPose* pose = fVSPoseList->ItemAt(index); 7787 if (pose != NULL) { 7788 if (pose->IsSelected()) 7789 result = result | pose->CalcRect(this); 7790 7791 if (pose->Location(this).y > bounds.bottom) 7792 break; 7793 } 7794 } 7795 } 7796 7797 return result; 7798 } 7799 7800 7801 // TODO: SelectPosesListMode and SelectPosesIconMode are terrible and share 7802 // most code 7803 void 7804 BPoseView::SelectPosesListMode(BRect selectionRect, BList** oldList) 7805 { 7806 ASSERT(ViewMode() == kListMode); 7807 7808 // collect all the poses which are enclosed inside the selection rect 7809 BList* newList = new BList; 7810 BRect bounds(Bounds()); 7811 SetDrawingMode(B_OP_COPY); 7812 // TODO: I _think_ there is no more synchronous drawing here, 7813 // so this should be save to remove 7814 7815 int32 startIndex = (int32)(selectionRect.top / fListElemHeight); 7816 if (startIndex < 0) 7817 startIndex = 0; 7818 7819 BPoint loc(0, startIndex * fListElemHeight); 7820 7821 PoseList* poseList = CurrentPoseList(); 7822 int32 count = poseList->CountItems(); 7823 for (int32 index = startIndex; index < count; index++) { 7824 BPose* pose = poseList->ItemAt(index); 7825 BRect poseRect(pose->CalcRect(loc, this)); 7826 7827 if (selectionRect.Intersects(poseRect)) { 7828 bool selected = pose->IsSelected(); 7829 pose->Select(!fSelectionList->HasItem(pose)); 7830 newList->AddItem((void*)(addr_t)index); 7831 // this sucks, need to clean up using a vector class instead 7832 // of BList 7833 7834 if ((selected != pose->IsSelected()) 7835 && poseRect.Intersects(bounds)) { 7836 Invalidate(poseRect); 7837 } 7838 7839 // First Pose selected gets to be the pivot. 7840 if ((fSelectionPivotPose == NULL) && (selected == false)) 7841 fSelectionPivotPose = pose; 7842 } 7843 7844 loc.y += fListElemHeight; 7845 if (loc.y > selectionRect.bottom) 7846 break; 7847 } 7848 7849 // take the old set of enclosed poses and invert selection state 7850 // on those which are no longer enclosed 7851 count = (*oldList)->CountItems(); 7852 for (int32 index = 0; index < count; index++) { 7853 int32 oldIndex = (addr_t)(*oldList)->ItemAt(index); 7854 7855 if (!newList->HasItem((void*)(addr_t)oldIndex)) { 7856 BPose* pose = poseList->ItemAt(oldIndex); 7857 pose->Select(!pose->IsSelected()); 7858 loc.Set(0, oldIndex * fListElemHeight); 7859 BRect poseRect(pose->CalcRect(loc, this)); 7860 7861 if (poseRect.Intersects(bounds)) 7862 Invalidate(poseRect); 7863 } 7864 } 7865 7866 delete* oldList; 7867 *oldList = newList; 7868 } 7869 7870 7871 void 7872 BPoseView::SelectPosesIconMode(BRect selectionRect, BList** oldList) 7873 { 7874 ASSERT(ViewMode() != kListMode); 7875 7876 // collect all the poses which are enclosed inside the selection rect 7877 BList* newList = new BList; 7878 BRect bounds(Bounds()); 7879 SetDrawingMode(B_OP_COPY); 7880 7881 int32 startIndex = FirstIndexAtOrBelow( 7882 (int32)(selectionRect.top - IconPoseHeight()), true); 7883 if (startIndex < 0) 7884 startIndex = 0; 7885 7886 int32 count = fPoseList->CountItems(); 7887 for (int32 index = startIndex; index < count; index++) { 7888 BPose* pose = fVSPoseList->ItemAt(index); 7889 if (pose != NULL) { 7890 BRect poseRect(pose->CalcRect(this)); 7891 7892 if (selectionRect.Intersects(poseRect)) { 7893 bool selected = pose->IsSelected(); 7894 pose->Select(!fSelectionList->HasItem(pose)); 7895 newList->AddItem((void*)(addr_t)index); 7896 7897 if ((selected != pose->IsSelected()) 7898 && poseRect.Intersects(bounds)) { 7899 Invalidate(poseRect); 7900 } 7901 7902 // first Pose selected gets to be the pivot 7903 if ((fSelectionPivotPose == NULL) && (selected == false)) 7904 fSelectionPivotPose = pose; 7905 } 7906 7907 if (pose->Location(this).y > selectionRect.bottom) 7908 break; 7909 } 7910 } 7911 7912 // take the old set of enclosed poses and invert selection state 7913 // on those which are no longer enclosed 7914 count = (*oldList)->CountItems(); 7915 for (int32 index = 0; index < count; index++) { 7916 int32 oldIndex = (addr_t)(*oldList)->ItemAt(index); 7917 7918 if (!newList->HasItem((void*)(addr_t)oldIndex)) { 7919 BPose* pose = fVSPoseList->ItemAt(oldIndex); 7920 pose->Select(!pose->IsSelected()); 7921 BRect poseRect(pose->CalcRect(this)); 7922 7923 if (poseRect.Intersects(bounds)) 7924 Invalidate(poseRect); 7925 } 7926 } 7927 7928 delete* oldList; 7929 *oldList = newList; 7930 } 7931 7932 7933 void 7934 BPoseView::AddRemoveSelectionRange(BPoint where, bool extendSelection, 7935 BPose* pose) 7936 { 7937 ASSERT(pose != NULL); 7938 7939 if (pose == fSelectionPivotPose && !extendSelection) 7940 return; 7941 7942 if (fMultipleSelection && (modifiers() & B_SHIFT_KEY) != 0 && fSelectionPivotPose) { 7943 // multi pose extend/shrink current selection 7944 bool select = !pose->IsSelected() || !extendSelection; 7945 // This weird bit of logic causes the selection to always 7946 // center around the pivot point, unless you choose to hold 7947 // down COMMAND, which will unselect between the pivot and 7948 // the most recently selected Pose. 7949 7950 if (!extendSelection) { 7951 // Remember fSelectionPivotPose because ClearSelection() NULLs it 7952 // and we need it to be preserved. 7953 const BPose* savedPivotPose = fSelectionPivotPose; 7954 ClearSelection(); 7955 fSelectionPivotPose = savedPivotPose; 7956 } 7957 7958 if (ViewMode() == kListMode) { 7959 PoseList* poseList = CurrentPoseList(); 7960 int32 currentSelectedIndex = poseList->IndexOf(pose); 7961 int32 lastSelectedIndex = poseList->IndexOf(fSelectionPivotPose); 7962 7963 int32 startRange; 7964 int32 endRange; 7965 7966 if (lastSelectedIndex < currentSelectedIndex) { 7967 startRange = lastSelectedIndex; 7968 endRange = currentSelectedIndex; 7969 } else { 7970 startRange = currentSelectedIndex; 7971 endRange = lastSelectedIndex; 7972 } 7973 7974 for (int32 i = startRange; i <= endRange; i++) 7975 AddRemovePoseFromSelection(poseList->ItemAt(i), i, select); 7976 } else { 7977 BRect selection(where, fSelectionPivotPose->Location(this)); 7978 7979 // Things will get odd if we don't 'fix' the selection rect. 7980 if (selection.left > selection.right) 7981 std::swap(selection.left, selection.right); 7982 7983 if (selection.top > selection.bottom) 7984 std::swap(selection.top, selection.bottom); 7985 7986 // If the selection rect is not at least 1 pixel high/wide, things 7987 // are also not going to work out. 7988 if (selection.IntegerWidth() < 1) 7989 selection.right = selection.left + 1.0f; 7990 7991 if (selection.IntegerHeight() < 1) 7992 selection.bottom = selection.top + 1.0f; 7993 7994 ASSERT(selection.IsValid()); 7995 7996 int32 count = fPoseList->CountItems(); 7997 for (int32 index = count - 1; index >= 0; index--) { 7998 BPose* currPose = fPoseList->ItemAt(index); 7999 // TODO: works only in non-list mode? 8000 if (selection.Intersects(currPose->CalcRect(this))) 8001 AddRemovePoseFromSelection(currPose, index, select); 8002 } 8003 } 8004 } else { 8005 int32 index = CurrentPoseList()->IndexOf(pose); 8006 if (!extendSelection) { 8007 if (!pose->IsSelected()) { 8008 // create new selection 8009 ClearSelection(); 8010 AddRemovePoseFromSelection(pose, index, true); 8011 fSelectionPivotPose = pose; 8012 } 8013 } else { 8014 fMimeTypesInSelectionCache.MakeEmpty(); 8015 AddRemovePoseFromSelection(pose, index, !pose->IsSelected()); 8016 } 8017 } 8018 8019 // If the list is empty, there cannot be a pivot pose, 8020 // however if the list is not empty there must be a pivot 8021 // pose. 8022 if (fSelectionList->IsEmpty()) { 8023 fSelectionPivotPose = NULL; 8024 fRealPivotPose = NULL; 8025 } else if (fSelectionPivotPose == NULL) { 8026 fSelectionPivotPose = pose; 8027 fRealPivotPose = pose; 8028 } 8029 } 8030 8031 8032 void 8033 BPoseView::DeleteSymLinkPoseTarget(const node_ref* itemNode, BPose* pose, 8034 int32 index) 8035 { 8036 ASSERT(pose->TargetModel()->IsSymLink()); 8037 watch_node(itemNode, B_STOP_WATCHING, this); 8038 8039 // watch the parent of the symlink, so that we know when the symlink 8040 // can be considered fixed. 8041 WatchParentOf(pose->TargetModel()->EntryRef()); 8042 8043 BPoint loc(0, index * fListElemHeight); 8044 pose->TargetModel()->SetLinkTo(NULL); 8045 pose->UpdateBrokenSymLink(loc, this); 8046 } 8047 8048 8049 bool 8050 BPoseView::DeletePose(const node_ref* itemNode, BPose* pose, int32 index) 8051 { 8052 watch_node(itemNode, B_STOP_WATCHING, this); 8053 8054 if (pose == NULL) 8055 pose = fPoseList->FindPose(itemNode, &index); 8056 8057 if (pose != NULL) { 8058 fInsertedNodes.erase(fInsertedNodes.find(*itemNode)); 8059 if (pose->TargetModel()->IsSymLink()) { 8060 fBrokenLinks->RemoveItem(pose->TargetModel()); 8061 StopWatchingParentsOf(pose->TargetModel()->EntryRef()); 8062 Model* target = pose->TargetModel()->LinkTo(); 8063 if (target) 8064 watch_node(target->NodeRef(), B_STOP_WATCHING, this); 8065 } 8066 8067 ASSERT(TargetModel()); 8068 8069 if (pose == fDropTarget) 8070 fDropTarget = NULL; 8071 8072 if (pose == ActivePose()) 8073 CommitActivePose(); 8074 8075 Window()->UpdateIfNeeded(); 8076 8077 // remove it from list no matter what since it might be in list 8078 // but not "selected" since selection is hidden 8079 fSelectionList->RemoveItem(pose); 8080 if (fSelectionPivotPose == pose) 8081 fSelectionPivotPose = NULL; 8082 if (fRealPivotPose == pose) 8083 fRealPivotPose = NULL; 8084 8085 if (pose->IsSelected() && fSelectionChangedHook) 8086 ContainerWindow()->SelectionChanged(); 8087 8088 fPoseList->RemoveItemAt(index); 8089 8090 bool visible = true; 8091 if (fFiltering) { 8092 if (fFilteredPoseList->FindPose(itemNode, &index) != NULL) 8093 fFilteredPoseList->RemoveItemAt(index); 8094 else 8095 visible = false; 8096 } 8097 8098 fMimeTypeListIsDirty = true; 8099 8100 if (pose->HasLocation()) 8101 RemoveFromVSList(pose); 8102 8103 if (visible) { 8104 BRect invalidRect; 8105 if (ViewMode() == kListMode) 8106 invalidRect = CalcPoseRectList(pose, index); 8107 else 8108 invalidRect = pose->CalcRect(this); 8109 8110 if (ViewMode() == kListMode) 8111 CloseGapInList(&invalidRect); 8112 else 8113 RemoveFromExtent(invalidRect); 8114 8115 Invalidate(invalidRect); 8116 UpdateCount(); 8117 UpdateScrollRange(); 8118 ResetPosePlacementHint(); 8119 8120 if (ViewMode() == kListMode) { 8121 BRect bounds(Bounds()); 8122 int32 index = (int32)(bounds.bottom / fListElemHeight); 8123 BPose* pose = CurrentPoseList()->ItemAt(index); 8124 8125 if (pose == NULL && bounds.top > 0) { 8126 // scroll up a little 8127 BView::ScrollTo(bounds.left, 8128 std::max(bounds.top - fListElemHeight, 0.0f)); 8129 } 8130 } 8131 } 8132 8133 delete pose; 8134 } else { 8135 // we might be getting a delete for an item in the zombie list 8136 Model* zombie = FindZombie(itemNode, &index); 8137 if (zombie) { 8138 PRINT(("deleting zombie model %s\n", zombie->Name())); 8139 fZombieList->RemoveItemAt(index); 8140 delete zombie; 8141 } else 8142 return false; 8143 } 8144 8145 return true; 8146 } 8147 8148 8149 Model* 8150 BPoseView::FindZombie(const node_ref* itemNode, int32* resultingIndex) 8151 { 8152 int32 count = fZombieList->CountItems(); 8153 for (int32 index = 0; index < count; index++) { 8154 Model* zombie = fZombieList->ItemAt(index); 8155 if (*zombie->NodeRef() == *itemNode) { 8156 if (resultingIndex) 8157 *resultingIndex = index; 8158 return zombie; 8159 } 8160 } 8161 8162 return NULL; 8163 } 8164 8165 8166 // return pose at location h,v (search list starting from bottom so 8167 // drawing and hit detection reflect the same pose ordering) 8168 BPose* 8169 BPoseView::FindPose(BPoint point, int32* poseIndex) const 8170 { 8171 if (ViewMode() == kListMode) { 8172 int32 index = (int32)(point.y / fListElemHeight); 8173 if (poseIndex != NULL) 8174 *poseIndex = index; 8175 8176 BPoint loc(0, index * fListElemHeight); 8177 BPose* pose = CurrentPoseList()->ItemAt(index); 8178 if (pose != NULL && pose->PointInPose(loc, this, point)) 8179 return pose; 8180 } else { 8181 int32 count = fPoseList->CountItems(); 8182 for (int32 index = count - 1; index >= 0; index--) { 8183 BPose* pose = fPoseList->ItemAt(index); 8184 if (pose->PointInPose(this, point)) { 8185 if (poseIndex) 8186 *poseIndex = index; 8187 8188 return pose; 8189 } 8190 } 8191 } 8192 8193 return NULL; 8194 } 8195 8196 8197 BPose* 8198 BPoseView::FirstVisiblePose(int32* _index) const 8199 { 8200 ASSERT(ViewMode() == kListMode); 8201 return FindPose(BPoint(kListOffset, 8202 Bounds().top + fListElemHeight - 1), _index); 8203 } 8204 8205 8206 BPose* 8207 BPoseView::LastVisiblePose(int32* _index) const 8208 { 8209 ASSERT(ViewMode() == kListMode); 8210 BPose* pose = FindPose(BPoint(kListOffset, Bounds().top + Frame().Height() 8211 - fListElemHeight + 2), _index); 8212 if (pose == NULL) { 8213 // Just get the last one 8214 pose = CurrentPoseList()->LastItem(); 8215 if (_index != NULL) 8216 *_index = CurrentPoseList()->CountItems() - 1; 8217 } 8218 return pose; 8219 } 8220 8221 8222 void 8223 BPoseView::OpenSelection(BPose* clickedPose, int32* index) 8224 { 8225 BPose* singleWindowBrowsePose = clickedPose; 8226 TrackerSettings settings; 8227 8228 // get first selected pose in selection if none was clicked 8229 if (settings.SingleWindowBrowse() 8230 && !singleWindowBrowsePose 8231 && fSelectionList->CountItems() == 1 8232 && !IsFilePanel()) { 8233 singleWindowBrowsePose = fSelectionList->ItemAt(0); 8234 } 8235 8236 // check if we can use the single window mode 8237 if (settings.SingleWindowBrowse() 8238 && !IsDesktopWindow() 8239 && !IsFilePanel() 8240 && (modifiers() & B_OPTION_KEY) == 0 8241 && TargetModel()->IsDirectory() 8242 && singleWindowBrowsePose 8243 && singleWindowBrowsePose->ResolvedModel() 8244 && singleWindowBrowsePose->ResolvedModel()->IsDirectory()) { 8245 // Switch to new directory 8246 BMessage msg(kSwitchDirectory); 8247 msg.AddRef("refs", singleWindowBrowsePose->ResolvedModel()->EntryRef()); 8248 Window()->PostMessage(&msg); 8249 } else { 8250 // otherwise use standard method 8251 OpenSelectionCommon(clickedPose, index, false); 8252 } 8253 8254 } 8255 8256 8257 void 8258 BPoseView::OpenSelectionUsing(BPose* clickedPose, int32* index) 8259 { 8260 OpenSelectionCommon(clickedPose, index, true); 8261 } 8262 8263 8264 void 8265 BPoseView::OpenSelectionCommon(BPose* clickedPose, int32* poseIndex, 8266 bool openWith) 8267 { 8268 int32 count = fSelectionList->CountItems(); 8269 if (count == 0) 8270 return; 8271 8272 BMessage message(B_REFS_RECEIVED); 8273 8274 for (int32 index = 0; index < count; index++) { 8275 BPose* pose = fSelectionList->ItemAt(index); 8276 message.AddRef("refs", pose->TargetModel()->EntryRef()); 8277 8278 // close parent window if option down and we're not the desktop 8279 // and we're not in single window mode 8280 if (dynamic_cast<TTracker*>(be_app) == NULL 8281 || (modifiers() & B_OPTION_KEY) == 0 8282 || IsFilePanel() 8283 || IsDesktopWindow() 8284 || TrackerSettings().SingleWindowBrowse()) { 8285 continue; 8286 } 8287 8288 ASSERT(TargetModel()); 8289 message.AddData("nodeRefsToClose", B_RAW_TYPE, TargetModel()->NodeRef(), 8290 sizeof (node_ref)); 8291 } 8292 8293 if (openWith) 8294 message.AddInt32("launchUsingSelector", 0); 8295 8296 // add a messenger to the launch message that will be used to 8297 // dispatch scripting calls from apps to the PoseView 8298 message.AddMessenger("TrackerViewToken", BMessenger(this)); 8299 8300 if (fSelectionHandler) 8301 fSelectionHandler->PostMessage(&message); 8302 8303 if (clickedPose) { 8304 ASSERT(poseIndex); 8305 if (ViewMode() == kListMode) 8306 DrawOpenAnimation(CalcPoseRectList(clickedPose, *poseIndex, true)); 8307 else 8308 DrawOpenAnimation(clickedPose->CalcRect(this)); 8309 } 8310 } 8311 8312 8313 void 8314 BPoseView::DrawOpenAnimation(BRect rect) 8315 { 8316 SetDrawingMode(B_OP_INVERT); 8317 8318 BRect box1(rect); 8319 box1.InsetBy(rect.Width() / 2 - 2, rect.Height() / 2 - 2); 8320 BRect box2(box1); 8321 8322 for (int32 index = 0; index < 7; index++) { 8323 box2 = box1; 8324 box2.InsetBy(-2, -2); 8325 StrokeRect(box1, B_MIXED_COLORS); 8326 Sync(); 8327 StrokeRect(box2, B_MIXED_COLORS); 8328 Sync(); 8329 snooze(10000); 8330 StrokeRect(box1, B_MIXED_COLORS); 8331 StrokeRect(box2, B_MIXED_COLORS); 8332 Sync(); 8333 box1 = box2; 8334 } 8335 8336 SetDrawingMode(B_OP_OVER); 8337 } 8338 8339 8340 void 8341 BPoseView::UnmountSelectedVolumes() 8342 { 8343 BVolume boot; 8344 BVolumeRoster().GetBootVolume(&boot); 8345 8346 int32 select_count = fSelectionList->CountItems(); 8347 for (int32 index = 0; index < select_count; index++) { 8348 BPose* pose = fSelectionList->ItemAt(index); 8349 if (pose == NULL) 8350 continue; 8351 8352 Model* model = pose->TargetModel(); 8353 if (model->IsVolume()) { 8354 BVolume volume(model->NodeRef()->device); 8355 if (volume != boot) { 8356 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 8357 if (tracker != NULL) 8358 tracker->SaveAllPoseLocations(); 8359 8360 BMessage message(kUnmountVolume); 8361 message.AddInt32("device_id", volume.Device()); 8362 be_app->PostMessage(&message); 8363 } 8364 } 8365 } 8366 } 8367 8368 8369 void 8370 BPoseView::ClearPoses() 8371 { 8372 CommitActivePose(); 8373 SavePoseLocations(); 8374 ClearFilter(); 8375 8376 // clear all pose lists 8377 fPoseList->MakeEmpty(); 8378 fMimeTypeListIsDirty = true; 8379 fVSPoseList->MakeEmpty(); 8380 fZombieList->MakeEmpty(); 8381 fSelectionList->MakeEmpty(); 8382 fSelectionPivotPose = NULL; 8383 fRealPivotPose = NULL; 8384 fMimeTypesInSelectionCache.MakeEmpty(); 8385 fBrokenLinks->MakeEmpty(); 8386 8387 DisableScrollBars(); 8388 ScrollTo(BPoint(0, 0)); 8389 UpdateScrollRange(); 8390 SetScrollBarsTo(BPoint(0, 0)); 8391 EnableScrollBars(); 8392 ResetPosePlacementHint(); 8393 ClearExtent(); 8394 8395 if (fSelectionChangedHook) 8396 ContainerWindow()->SelectionChanged(); 8397 } 8398 8399 8400 void 8401 BPoseView::SwitchDir(const entry_ref* newDirRef, AttributeStreamNode* node) 8402 { 8403 ASSERT(TargetModel()); 8404 if (*newDirRef == *TargetModel()->EntryRef()) 8405 // no change 8406 return; 8407 8408 Model* model = new Model(newDirRef, true); 8409 if (model->InitCheck() != B_OK || !model->IsDirectory()) { 8410 delete model; 8411 return; 8412 } 8413 8414 CommitActivePose(); 8415 8416 // before clearing and adding new poses, we reset "blessed" async 8417 // thread id to prevent old add_poses thread from adding any more icons 8418 // the new add_poses thread will then set fAddPosesThread to its ID and it 8419 // will be allowed to add icons 8420 fAddPosesThreads.clear(); 8421 fInsertedNodes.clear(); 8422 8423 delete fModel; 8424 fModel = model; 8425 8426 // check if model is a trash dir, if so 8427 // update ContainerWindow's fIsTrash, etc. 8428 // variables to indicate new state 8429 ContainerWindow()->UpdateIfTrash(model); 8430 8431 StopWatching(); 8432 ClearPoses(); 8433 8434 // Restore state, might fail if the state has never been saved for this node 8435 uint32 oldMode = ViewMode(); 8436 bool viewStateRestored = false; 8437 if (node != NULL) { 8438 BViewState* previousState = fViewState; 8439 RestoreState(node); 8440 viewStateRestored = (fViewState != previousState); 8441 } 8442 8443 // Make sure fTitleView is rebuilt, as fColumnList might have changed 8444 fTitleView->Reset(); 8445 8446 if (viewStateRestored) { 8447 if (ViewMode() == kListMode && oldMode != kListMode) { 8448 if (ContainerWindow()) 8449 ContainerWindow()->ShowAttributeMenu(); 8450 8451 fTitleView->Show(); 8452 } else if (ViewMode() != kListMode && oldMode == kListMode) { 8453 fTitleView->Hide(); 8454 8455 if (ContainerWindow()) 8456 ContainerWindow()->HideAttributeMenu(); 8457 } else if (ViewMode() == kListMode && oldMode == kListMode) 8458 fTitleView->Invalidate(); 8459 8460 BPoint origin; 8461 if (ViewMode() == kListMode) 8462 origin = fViewState->ListOrigin(); 8463 else 8464 origin = fViewState->IconOrigin(); 8465 8466 PinPointToValidRange(origin); 8467 8468 SetIconPoseHeight(); 8469 GetLayoutInfo(ViewMode(), &fGrid, &fOffset); 8470 ResetPosePlacementHint(); 8471 8472 DisableScrollBars(); 8473 ScrollTo(origin); 8474 UpdateScrollRange(); 8475 SetScrollBarsTo(origin); 8476 EnableScrollBars(); 8477 } else { 8478 ResetOrigin(); 8479 ResetPosePlacementHint(); 8480 } 8481 8482 StartWatching(); 8483 8484 // be sure this happens after origin is set and window is sized 8485 // properly for proper icon caching! 8486 8487 if (ContainerWindow()->IsTrash()) 8488 AddTrashPoses(); 8489 else 8490 AddPoses(TargetModel()); 8491 TargetModel()->CloseNode(); 8492 8493 Invalidate(); 8494 8495 fLastKeyTime = 0; 8496 } 8497 8498 8499 void 8500 BPoseView::Refresh() 8501 { 8502 BEntry entry; 8503 8504 ASSERT(TargetModel()); 8505 if (TargetModel()->OpenNode() != B_OK) 8506 return; 8507 8508 StopWatching(); 8509 fInsertedNodes.clear(); 8510 ClearPoses(); 8511 StartWatching(); 8512 8513 // be sure this happens after origin is set and window is sized 8514 // properly for proper icon caching! 8515 AddPoses(TargetModel()); 8516 TargetModel()->CloseNode(); 8517 8518 if (fRefFilter != NULL) { 8519 fFiltering = false; 8520 StartFiltering(); 8521 } 8522 8523 Invalidate(); 8524 ResetOrigin(); 8525 ResetPosePlacementHint(); 8526 } 8527 8528 8529 void 8530 BPoseView::ResetOrigin() 8531 { 8532 DisableScrollBars(); 8533 ScrollTo(B_ORIGIN); 8534 UpdateScrollRange(); 8535 SetScrollBarsTo(B_ORIGIN); 8536 EnableScrollBars(); 8537 } 8538 8539 8540 void 8541 BPoseView::EditQueries() 8542 { 8543 // edit selected queries 8544 SendSelectionAsRefs(kEditQuery, true); 8545 } 8546 8547 8548 void 8549 BPoseView::SendSelectionAsRefs(uint32 what, bool onlyQueries) 8550 { 8551 // fix this by having a proper selection iterator 8552 8553 int32 numItems = fSelectionList->CountItems(); 8554 if (!numItems) 8555 return; 8556 8557 bool haveRef = false; 8558 BMessage message; 8559 message.what = what; 8560 8561 for (int32 index = 0; index < numItems; index++) { 8562 BPose* pose = fSelectionList->ItemAt(index); 8563 if (onlyQueries) { 8564 // to check if pose is a query, follow any symlink first 8565 BEntry resolvedEntry(pose->TargetModel()->EntryRef(), true); 8566 if (resolvedEntry.InitCheck() != B_OK) 8567 continue; 8568 8569 Model model(&resolvedEntry); 8570 if (!model.IsQuery() && !model.IsQueryTemplate()) 8571 continue; 8572 } 8573 haveRef = true; 8574 message.AddRef("refs", pose->TargetModel()->EntryRef()); 8575 } 8576 if (!haveRef) 8577 return; 8578 8579 if (onlyQueries) 8580 // this is used to make query templates come up in a special edit window 8581 message.AddBool("editQueryOnPose", onlyQueries); 8582 8583 BMessenger(kTrackerSignature).SendMessage(&message); 8584 } 8585 8586 8587 void 8588 BPoseView::OpenInfoWindows() 8589 { 8590 BMessenger tracker(kTrackerSignature); 8591 if (!tracker.IsValid()) { 8592 BAlert* alert = new BAlert("", 8593 B_TRANSLATE("The Tracker must be running to see Info windows."), 8594 B_TRANSLATE("Cancel"), NULL, NULL, B_WIDTH_AS_USUAL, 8595 B_WARNING_ALERT); 8596 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 8597 alert->Go(); 8598 return; 8599 } 8600 SendSelectionAsRefs(kGetInfo); 8601 } 8602 8603 8604 void 8605 BPoseView::SetDefaultPrinter() 8606 { 8607 BMessenger trackerMessenger(kTrackerSignature); 8608 if (!trackerMessenger.IsValid()) { 8609 BAlert* alert = new BAlert("", 8610 B_TRANSLATE("The Tracker must be running to see set the default " 8611 "printer."), B_TRANSLATE("Cancel"), NULL, NULL, B_WIDTH_AS_USUAL, 8612 B_WARNING_ALERT); 8613 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 8614 alert->Go(); 8615 return; 8616 } 8617 SendSelectionAsRefs(kMakeActivePrinter); 8618 } 8619 8620 8621 void 8622 BPoseView::OpenParent() 8623 { 8624 if (!TargetModel() || TargetModel()->IsRoot() || IsDesktopWindow()) 8625 return; 8626 8627 BEntry entry(TargetModel()->EntryRef()); 8628 entry_ref ref; 8629 8630 if (FSGetParentVirtualDirectoryAware(entry, entry) != B_OK 8631 || entry.GetRef(&ref) != B_OK) 8632 return; 8633 8634 BEntry root("/"); 8635 if (!TrackerSettings().SingleWindowBrowse() 8636 && !TrackerSettings().ShowNavigator() 8637 && !TrackerSettings().ShowDisksIcon() && entry == root 8638 && (modifiers() & B_CONTROL_KEY) == 0) 8639 return; 8640 8641 Model parentModel(&ref); 8642 8643 BMessage message(B_REFS_RECEIVED); 8644 message.AddRef("refs", &ref); 8645 8646 if (dynamic_cast<TTracker*>(be_app)) { 8647 // add information about the child, so that we can select it 8648 // in the parent view 8649 message.AddData("nodeRefToSelect", B_RAW_TYPE, TargetModel()->NodeRef(), 8650 sizeof (node_ref)); 8651 8652 if ((modifiers() & B_OPTION_KEY) != 0 && !IsFilePanel()) { 8653 // if option down, add instructions to close the parent 8654 message.AddData("nodeRefsToClose", B_RAW_TYPE, 8655 TargetModel()->NodeRef(), sizeof (node_ref)); 8656 } 8657 } 8658 8659 if (TrackerSettings().SingleWindowBrowse()) { 8660 BMessage msg(kSwitchDirectory); 8661 msg.AddRef("refs", &ref); 8662 Window()->PostMessage(&msg); 8663 } else 8664 be_app->PostMessage(&message); 8665 } 8666 8667 8668 void 8669 BPoseView::IdentifySelection(bool force) 8670 { 8671 int32 count = fSelectionList->CountItems(); 8672 for (int32 index = 0; index < count; index++) { 8673 BPose* pose = fSelectionList->ItemAt(index); 8674 BEntry entry(pose->TargetModel()->ResolveIfLink()->EntryRef()); 8675 if (entry.InitCheck() == B_OK) { 8676 BPath path; 8677 if (entry.GetPath(&path) == B_OK) 8678 update_mime_info(path.Path(), true, false, force ? 2 : 1); 8679 } 8680 } 8681 } 8682 8683 8684 void 8685 BPoseView::ClearSelection() 8686 { 8687 CommitActivePose(); 8688 fSelectionPivotPose = NULL; 8689 fRealPivotPose = NULL; 8690 8691 if (fSelectionList->CountItems() > 0) { 8692 // scan all visible poses first 8693 BRect bounds(Bounds()); 8694 8695 if (ViewMode() == kListMode) { 8696 int32 startIndex = (int32)(bounds.top / fListElemHeight); 8697 BPoint loc(0, startIndex * fListElemHeight); 8698 8699 PoseList* poseList = CurrentPoseList(); 8700 int32 count = poseList->CountItems(); 8701 for (int32 index = startIndex; index < count; index++) { 8702 BPose* pose = poseList->ItemAt(index); 8703 if (pose->IsSelected()) { 8704 pose->Select(false); 8705 Invalidate(pose->CalcRect(loc, this, false)); 8706 } 8707 8708 loc.y += fListElemHeight; 8709 if (loc.y > bounds.bottom) 8710 break; 8711 } 8712 } else { 8713 int32 startIndex = FirstIndexAtOrBelow( 8714 (int32)(bounds.top - IconPoseHeight()), true); 8715 int32 count = fVSPoseList->CountItems(); 8716 for (int32 index = startIndex; index < count; index++) { 8717 BPose* pose = fVSPoseList->ItemAt(index); 8718 if (pose != NULL) { 8719 if (pose->IsSelected()) { 8720 pose->Select(false); 8721 Invalidate(pose->CalcRect(this)); 8722 } 8723 8724 if (pose->Location(this).y > bounds.bottom) 8725 break; 8726 } 8727 } 8728 } 8729 8730 // clear selection state in all poses 8731 int32 count = fSelectionList->CountItems(); 8732 for (int32 index = 0; index < count; index++) 8733 fSelectionList->ItemAt(index)->Select(false); 8734 8735 fSelectionList->MakeEmpty(); 8736 } 8737 8738 fMimeTypesInSelectionCache.MakeEmpty(); 8739 } 8740 8741 8742 void 8743 BPoseView::ShowSelection(bool show) 8744 { 8745 if (fSelectionVisible == show) 8746 return; 8747 8748 fSelectionVisible = show; 8749 8750 if (fSelectionList->CountItems() <= 0) 8751 return; 8752 8753 // scan all visible poses first 8754 BRect bounds(Bounds()); 8755 8756 if (ViewMode() == kListMode) { 8757 int32 startIndex = (int32)(bounds.top / fListElemHeight); 8758 BPoint loc(0, startIndex * fListElemHeight); 8759 8760 PoseList* poseList = CurrentPoseList(); 8761 int32 count = poseList->CountItems(); 8762 for (int32 index = startIndex; index < count; index++) { 8763 BPose* pose = poseList->ItemAt(index); 8764 if (fSelectionList->HasItem(pose)) 8765 if (pose->IsSelected() != show 8766 || fShowSelectionWhenInactive) { 8767 if (!fShowSelectionWhenInactive) 8768 pose->Select(show); 8769 8770 pose->Draw(BRect(pose->CalcRect(loc, this, false)), 8771 bounds, this, false); 8772 } 8773 8774 loc.y += fListElemHeight; 8775 if (loc.y > bounds.bottom) 8776 break; 8777 } 8778 } else { 8779 int32 startIndex = FirstIndexAtOrBelow( 8780 (int32)(bounds.top - IconPoseHeight()), true); 8781 int32 count = fVSPoseList->CountItems(); 8782 for (int32 index = startIndex; index < count; index++) { 8783 BPose* pose = fVSPoseList->ItemAt(index); 8784 if (pose != NULL) { 8785 if (fSelectionList->HasItem(pose)) 8786 if (pose->IsSelected() != show 8787 || fShowSelectionWhenInactive) { 8788 if (!fShowSelectionWhenInactive) 8789 pose->Select(show); 8790 8791 Invalidate(pose->CalcRect(this)); 8792 } 8793 8794 if (pose->Location(this).y > bounds.bottom) 8795 break; 8796 } 8797 } 8798 } 8799 8800 // now set all other poses 8801 int32 count = fSelectionList->CountItems(); 8802 for (int32 index = 0; index < count; index++) { 8803 BPose* pose = fSelectionList->ItemAt(index); 8804 if (pose->IsSelected() != show && !fShowSelectionWhenInactive) 8805 pose->Select(show); 8806 } 8807 8808 // finally update fRealPivotPose/fSelectionPivotPose 8809 if (!show) { 8810 fRealPivotPose = fSelectionPivotPose; 8811 fSelectionPivotPose = NULL; 8812 } else { 8813 if (fRealPivotPose) 8814 fSelectionPivotPose = fRealPivotPose; 8815 8816 fRealPivotPose = NULL; 8817 } 8818 } 8819 8820 8821 void 8822 BPoseView::AddRemovePoseFromSelection(BPose* pose, int32 index, bool select) 8823 { 8824 // Do not allow double selection/deselection. 8825 if (select == pose->IsSelected()) 8826 return; 8827 8828 pose->Select(select); 8829 8830 // update display 8831 if (ViewMode() == kListMode) { 8832 Invalidate(pose->CalcRect(BPoint(0, index * fListElemHeight), 8833 this, false)); 8834 } else 8835 Invalidate(pose->CalcRect(this)); 8836 8837 if (select) 8838 fSelectionList->AddItem(pose); 8839 else { 8840 fSelectionList->RemoveItem(pose, false); 8841 if (fSelectionPivotPose == pose) 8842 fSelectionPivotPose = NULL; 8843 8844 if (fRealPivotPose == pose) 8845 fRealPivotPose = NULL; 8846 } 8847 } 8848 8849 8850 void 8851 BPoseView::RemoveFromExtent(const BRect &rect) 8852 { 8853 ASSERT(ViewMode() != kListMode); 8854 8855 if (rect.left <= fExtent.left || rect.right >= fExtent.right 8856 || rect.top <= fExtent.top || rect.bottom >= fExtent.bottom) 8857 RecalcExtent(); 8858 } 8859 8860 8861 void 8862 BPoseView::RecalcExtent() 8863 { 8864 ASSERT(ViewMode() != kListMode); 8865 8866 ClearExtent(); 8867 int32 count = fPoseList->CountItems(); 8868 for (int32 index = 0; index < count; index++) 8869 AddToExtent(fPoseList->ItemAt(index)->CalcRect(this)); 8870 } 8871 8872 8873 BRect 8874 BPoseView::Extent() const 8875 { 8876 BRect rect; 8877 8878 if (ViewMode() == kListMode) { 8879 BColumn* column = fColumnList->LastItem(); 8880 if (column != NULL) { 8881 rect.left = rect.top = 0; 8882 rect.right = column->Offset() + column->Width() 8883 + kTitleColumnRightExtraMargin - kRoomForLine / 2.0f; 8884 rect.bottom = fListElemHeight * CurrentPoseList()->CountItems(); 8885 } else 8886 rect.Set(LeftTop().x, LeftTop().y, LeftTop().x, LeftTop().y); 8887 } else { 8888 rect = fExtent; 8889 rect.left -= fOffset.x; 8890 rect.top -= fOffset.y; 8891 rect.right += fOffset.x; 8892 rect.bottom += fOffset.y; 8893 if (!rect.IsValid()) 8894 rect.Set(LeftTop().x, LeftTop().y, LeftTop().x, LeftTop().y); 8895 } 8896 8897 return rect; 8898 } 8899 8900 8901 void 8902 BPoseView::SetScrollBarsTo(BPoint point) 8903 { 8904 if (fHScrollBar && fVScrollBar) { 8905 fHScrollBar->SetValue(point.x); 8906 fVScrollBar->SetValue(point.y); 8907 } else { 8908 // TODO: I don't know what this was supposed to work around 8909 // (ie why it wasn't calling ScrollTo(point) simply). Although 8910 // it cannot have been tested, since it was broken before, I am 8911 // still leaving this, since I know there can be a subtle change in 8912 // behaviour (BView<->BScrollBar feedback effects) when scrolling 8913 // both directions at once versus separately. 8914 BPoint origin = LeftTop(); 8915 ScrollTo(BPoint(origin.x, point.y)); 8916 ScrollTo(point); 8917 } 8918 } 8919 8920 8921 void 8922 BPoseView::PinPointToValidRange(BPoint& origin) 8923 { 8924 // !NaN and valid range 8925 // the following checks are not broken even they look like they are 8926 if (!(origin.x >= 0) && !(origin.x <= 0)) 8927 origin.x = 0; 8928 else if (origin.x < -40000.0 || origin.x > 40000.0) 8929 origin.x = 0; 8930 8931 if (!(origin.y >= 0) && !(origin.y <= 0)) 8932 origin.y = 0; 8933 else if (origin.y < -40000.0 || origin.y > 40000.0) 8934 origin.y = 0; 8935 } 8936 8937 8938 void 8939 BPoseView::UpdateScrollRange() 8940 { 8941 // TODO: some calls to UpdateScrollRange don't do the right thing because 8942 // Extent doesn't return the right value (too early in PoseView lifetime??) 8943 // 8944 // This happened most with file panels, when opening a parent - added 8945 // an extra call to UpdateScrollRange in SelectChildInParent to work 8946 // around this 8947 8948 AutoLock<BWindow> lock(Window()); 8949 if (!lock) 8950 return; 8951 8952 BRect bounds(Bounds()); 8953 8954 BPoint origin(LeftTop()); 8955 BRect extent(Extent()); 8956 8957 lock.Unlock(); 8958 8959 BPoint minVal(std::min(extent.left, origin.x), 8960 std::min(extent.top, origin.y)); 8961 8962 BPoint maxVal((extent.right - bounds.right) + origin.x, 8963 (extent.bottom - bounds.bottom) + origin.y); 8964 8965 maxVal.x = std::max(maxVal.x, origin.x); 8966 maxVal.y = std::max(maxVal.y, origin.y); 8967 8968 if (fHScrollBar) { 8969 float scrollMin; 8970 float scrollMax; 8971 fHScrollBar->GetRange(&scrollMin, &scrollMax); 8972 if (minVal.x != scrollMin || maxVal.x != scrollMax) { 8973 fHScrollBar->SetRange(minVal.x, maxVal.x); 8974 fHScrollBar->SetSteps(kSmallStep, bounds.Width()); 8975 } 8976 } 8977 8978 if (fVScrollBar) { 8979 float scrollMin; 8980 float scrollMax; 8981 fVScrollBar->GetRange(&scrollMin, &scrollMax); 8982 8983 if (minVal.y != scrollMin || maxVal.y != scrollMax) { 8984 fVScrollBar->SetRange(minVal.y, maxVal.y); 8985 fVScrollBar->SetSteps(kSmallStep, bounds.Height()); 8986 } 8987 } 8988 8989 // set proportions for bars 8990 BRect totalExtent(extent | bounds); 8991 8992 if (fHScrollBar && totalExtent.Width() != 0.0) { 8993 float proportion = bounds.Width() / totalExtent.Width(); 8994 if (fHScrollBar->Proportion() != proportion) 8995 fHScrollBar->SetProportion(proportion); 8996 } 8997 8998 if (fVScrollBar && totalExtent.Height() != 0.0) { 8999 float proportion = bounds.Height() / totalExtent.Height(); 9000 if (fVScrollBar->Proportion() != proportion) 9001 fVScrollBar->SetProportion(proportion); 9002 } 9003 } 9004 9005 9006 void 9007 BPoseView::DrawPose(BPose* pose, int32 index, bool fullDraw) 9008 { 9009 BRect rect = CalcPoseRect(pose, index, fullDraw); 9010 9011 if (TrackerSettings().ShowVolumeSpaceBar() 9012 && pose->TargetModel()->IsVolume()) { 9013 Invalidate(rect); 9014 } else 9015 pose->Draw(rect, rect, this, fullDraw); 9016 } 9017 9018 9019 rgb_color 9020 BPoseView::DeskTextColor() const 9021 { 9022 rgb_color color = ViewColor(); 9023 float thresh = color.red + (color.green * 1.25f) + (color.blue * 0.45f); 9024 9025 if (thresh >= 360) { 9026 color.red = 0; 9027 color.green = 0; 9028 color.blue = 0; 9029 } else { 9030 color.red = 255; 9031 color.green = 255; 9032 color.blue = 255; 9033 } 9034 9035 return color; 9036 } 9037 9038 9039 rgb_color 9040 BPoseView::DeskTextBackColor() const 9041 { 9042 // returns black or white color depending on the desktop background 9043 int32 thresh = 0; 9044 rgb_color color = LowColor(); 9045 9046 if (color.red > 150) 9047 thresh++; 9048 9049 if (color.green > 150) 9050 thresh++; 9051 9052 if (color.blue > 150) 9053 thresh++; 9054 9055 if (thresh > 1) { 9056 color.red = 255; 9057 color.green = 255; 9058 color.blue = 255; 9059 } else { 9060 color.red = 0; 9061 color.green = 0; 9062 color.blue = 0; 9063 } 9064 9065 return color; 9066 } 9067 9068 9069 void 9070 BPoseView::Draw(BRect updateRect) 9071 { 9072 if (IsDesktopWindow()) { 9073 BScreen screen(Window()); 9074 rgb_color color = screen.DesktopColor(); 9075 SetLowColor(color); 9076 SetViewColor(color); 9077 } 9078 DrawViewCommon(updateRect); 9079 9080 if ((Flags() & B_DRAW_ON_CHILDREN) == 0) 9081 DrawAfterChildren(updateRect); 9082 } 9083 9084 9085 void 9086 BPoseView::DrawAfterChildren(BRect updateRect) 9087 { 9088 if (fTransparentSelection && fSelectionRectInfo.rect.IsValid()) { 9089 SetDrawingMode(B_OP_ALPHA); 9090 rgb_color color = ui_color(B_NAVIGATION_BASE_COLOR); 9091 color.alpha = 128; 9092 SetHighColor(color); 9093 if (fSelectionRectInfo.rect.Width() == 0 9094 || fSelectionRectInfo.rect.Height() == 0) { 9095 StrokeLine(fSelectionRectInfo.rect.LeftTop(), 9096 fSelectionRectInfo.rect.RightBottom()); 9097 } else { 9098 StrokeRect(fSelectionRectInfo.rect); 9099 BRect interior = fSelectionRectInfo.rect; 9100 interior.InsetBy(1, 1); 9101 if (interior.IsValid()) { 9102 color = ui_color(B_CONTROL_HIGHLIGHT_COLOR); 9103 color.alpha = 90; 9104 SetHighColor(color); 9105 FillRect(interior); 9106 } 9107 } 9108 SetDrawingMode(B_OP_OVER); 9109 } 9110 } 9111 9112 9113 void 9114 BPoseView::SynchronousUpdate(BRect updateRect, bool clip) 9115 { 9116 if (clip) { 9117 BRegion updateRegion; 9118 updateRegion.Set(updateRect); 9119 ConstrainClippingRegion(&updateRegion); 9120 } 9121 9122 Invalidate(updateRect); 9123 Window()->UpdateIfNeeded(); 9124 9125 if (clip) 9126 ConstrainClippingRegion(NULL); 9127 } 9128 9129 9130 void 9131 BPoseView::DrawViewCommon(const BRect& updateRect) 9132 { 9133 if (ViewMode() == kListMode) { 9134 PoseList* poseList = CurrentPoseList(); 9135 int32 count = poseList->CountItems(); 9136 int32 startIndex 9137 = (int32)((updateRect.top - fListElemHeight) / fListElemHeight); 9138 9139 if (startIndex < 0) 9140 startIndex = 0; 9141 9142 BPoint loc(0, startIndex * fListElemHeight); 9143 9144 for (int32 index = startIndex; index < count; index++) { 9145 BPose* pose = poseList->ItemAt(index); 9146 BRect poseRect(pose->CalcRect(loc, this, true)); 9147 pose->Draw(poseRect, updateRect, this, true); 9148 loc.y += fListElemHeight; 9149 if (loc.y >= updateRect.bottom) 9150 break; 9151 } 9152 } else { 9153 int32 count = fPoseList->CountItems(); 9154 for (int32 index = 0; index < count; index++) { 9155 BPose* pose = fPoseList->ItemAt(index); 9156 BRect poseRect(pose->CalcRect(this)); 9157 if (updateRect.Intersects(poseRect)) 9158 pose->Draw(poseRect, updateRect, this, true); 9159 } 9160 } 9161 } 9162 9163 9164 void 9165 BPoseView::ColumnRedraw(BRect updateRect) 9166 { 9167 // used for dynamic column resizing using an offscreen draw buffer 9168 ASSERT(ViewMode() == kListMode); 9169 9170 if (IsDesktopWindow()) { 9171 BScreen screen(Window()); 9172 rgb_color d = screen.DesktopColor(); 9173 SetLowColor(d); 9174 SetViewColor(d); 9175 } 9176 9177 int32 startIndex 9178 = (int32)((updateRect.top - fListElemHeight) / fListElemHeight); 9179 if (startIndex < 0) 9180 startIndex = 0; 9181 9182 PoseList* poseList = CurrentPoseList(); 9183 int32 count = poseList->CountItems(); 9184 if (!count) 9185 return; 9186 9187 BPoint loc(0, startIndex * fListElemHeight); 9188 BRect srcRect = poseList->ItemAt(0)->CalcRect(BPoint(0, 0), this, false); 9189 srcRect.right += 1024; // need this to erase correctly 9190 sOffscreen->BeginUsing(srcRect); 9191 BView* offscreenView = sOffscreen->View(); 9192 9193 BRegion updateRegion; 9194 updateRegion.Set(updateRect); 9195 ConstrainClippingRegion(&updateRegion); 9196 9197 for (int32 index = startIndex; index < count; index++) { 9198 BPose* pose = poseList->ItemAt(index); 9199 9200 offscreenView->SetDrawingMode(B_OP_COPY); 9201 offscreenView->SetLowColor(LowColor()); 9202 offscreenView->FillRect(offscreenView->Bounds(), B_SOLID_LOW); 9203 9204 BRect dstRect = srcRect; 9205 dstRect.OffsetTo(loc); 9206 9207 BPoint offsetBy(0, -(index * ListElemHeight())); 9208 pose->Draw(dstRect, updateRect, this, offscreenView, true, 9209 offsetBy, pose->IsSelected()); 9210 9211 offscreenView->Sync(); 9212 SetDrawingMode(B_OP_COPY); 9213 DrawBitmap(sOffscreen->Bitmap(), srcRect, dstRect); 9214 loc.y += fListElemHeight; 9215 if (loc.y > updateRect.bottom) 9216 break; 9217 } 9218 sOffscreen->DoneUsing(); 9219 ConstrainClippingRegion(0); 9220 } 9221 9222 9223 void 9224 BPoseView::CloseGapInList(BRect* invalidRect) 9225 { 9226 (*invalidRect).bottom = Extent().bottom + fListElemHeight; 9227 BRect bounds(Bounds()); 9228 9229 if (bounds.Intersects(*invalidRect)) { 9230 BRect destRect(*invalidRect); 9231 destRect = destRect & bounds; 9232 destRect.bottom -= fListElemHeight; 9233 9234 BRect srcRect(destRect); 9235 srcRect.OffsetBy(0, fListElemHeight); 9236 9237 if (srcRect.Intersects(bounds) || destRect.Intersects(bounds)) 9238 CopyBits(srcRect, destRect); 9239 9240 *invalidRect = srcRect; 9241 (*invalidRect).top = destRect.bottom; 9242 } 9243 } 9244 9245 9246 void 9247 BPoseView::CheckPoseSortOrder(BPose* pose, int32 oldIndex) 9248 { 9249 _CheckPoseSortOrder(CurrentPoseList(), pose, oldIndex); 9250 } 9251 9252 9253 void 9254 BPoseView::_CheckPoseSortOrder(PoseList* poseList, BPose* pose, int32 oldIndex) 9255 { 9256 if (ViewMode() != kListMode) 9257 return; 9258 9259 Window()->UpdateIfNeeded(); 9260 9261 // take pose out of list for BSearch 9262 poseList->RemoveItemAt(oldIndex); 9263 int32 afterIndex; 9264 int32 orientation = BSearchList(poseList, pose, &afterIndex, oldIndex); 9265 9266 int32 newIndex; 9267 if (orientation == kInsertAtFront) 9268 newIndex = 0; 9269 else 9270 newIndex = afterIndex + 1; 9271 9272 if (newIndex == oldIndex) { 9273 poseList->AddItem(pose, oldIndex); 9274 return; 9275 } 9276 9277 if (fFiltering && poseList != fFilteredPoseList) { 9278 poseList->AddItem(pose, newIndex); 9279 return; 9280 } 9281 9282 BRect invalidRect(CalcPoseRectList(pose, oldIndex)); 9283 CloseGapInList(&invalidRect); 9284 Invalidate(invalidRect); 9285 // need to invalidate for the last item in the list 9286 InsertPoseAfter(pose, &afterIndex, orientation, &invalidRect); 9287 poseList->AddItem(pose, newIndex); 9288 Invalidate(invalidRect); 9289 } 9290 9291 9292 static int 9293 PoseCompareAddWidget(const BPose* p1, const BPose* p2, BPoseView* view) 9294 { 9295 // pose comparison and lazy text widget adding 9296 9297 uint32 sort = view->PrimarySort(); 9298 BColumn* column = view->ColumnFor(sort); 9299 if (column == NULL) 9300 return 0; 9301 9302 BPose* primary; 9303 BPose* secondary; 9304 if (!view->ReverseSort()) { 9305 primary = const_cast<BPose*>(p1); 9306 secondary = const_cast<BPose*>(p2); 9307 } else { 9308 primary = const_cast<BPose*>(p2); 9309 secondary = const_cast<BPose*>(p1); 9310 } 9311 9312 int32 result = 0; 9313 for (int32 count = 0; ; count++) { 9314 BTextWidget* widget1 = primary->WidgetFor(sort); 9315 if (widget1 == NULL) 9316 widget1 = primary->AddWidget(view, column); 9317 9318 BTextWidget* widget2 = secondary->WidgetFor(sort); 9319 if (widget2 == NULL) 9320 widget2 = secondary->AddWidget(view, column); 9321 9322 if (widget1 == NULL || widget2 == NULL) 9323 return result; 9324 9325 result = widget1->Compare(*widget2, view); 9326 9327 if (count != 0) 9328 return result; 9329 9330 // do we need to sort by secondary attribute? 9331 if (result == 0) { 9332 sort = view->SecondarySort(); 9333 if (!sort) 9334 return result; 9335 9336 column = view->ColumnFor(sort); 9337 if (column == NULL) 9338 return result; 9339 } 9340 } 9341 9342 return result; 9343 } 9344 9345 9346 static BPose* 9347 BSearch(PoseList* table, const BPose* key, BPoseView* view, 9348 int (*cmp)(const BPose*, const BPose*, BPoseView*), bool returnClosest) 9349 { 9350 int32 r = table->CountItems(); 9351 BPose* result = 0; 9352 9353 for (int32 l = 1; l <= r;) { 9354 int32 m = (l + r) / 2; 9355 9356 result = table->ItemAt(m - 1); 9357 int32 compareResult = (cmp)(result, key, view); 9358 if (compareResult == 0) 9359 return result; 9360 else if (compareResult < 0) 9361 l = m + 1; 9362 else 9363 r = m - 1; 9364 } 9365 if (returnClosest) 9366 return result; 9367 9368 return NULL; 9369 } 9370 9371 9372 int32 9373 BPoseView::BSearchList(PoseList* poseList, const BPose* pose, 9374 int32* resultingIndex, int32 oldIndex) 9375 { 9376 // check to see if insertion should be at beginning of list 9377 const BPose* firstPose = poseList->FirstItem(); 9378 if (!firstPose) 9379 return kInsertAtFront; 9380 9381 if (PoseCompareAddWidget(pose, firstPose, this) < 0) { 9382 *resultingIndex = 0; 9383 return kInsertAtFront; 9384 } 9385 9386 int32 count = poseList->CountItems(); 9387 9388 // look if old position is still ok, by comparing to siblings 9389 bool valid = oldIndex > 0 && oldIndex < count - 1; 9390 valid = valid && PoseCompareAddWidget(pose, 9391 poseList->ItemAt(oldIndex - 1), this) >= 0; 9392 // the current item is gone, so not oldIndex+1 9393 valid = valid && PoseCompareAddWidget(pose, 9394 poseList->ItemAt(oldIndex), this) <= 0; 9395 9396 if (valid) { 9397 *resultingIndex = oldIndex - 1; 9398 return kInsertAfter; 9399 } 9400 9401 *resultingIndex = count - 1; 9402 9403 const BPose* searchResult = BSearch(poseList, pose, this, 9404 PoseCompareAddWidget); 9405 9406 if (searchResult != NULL) { 9407 // what are we doing here?? 9408 // looks like we are skipping poses with identical search results or 9409 // something 9410 int32 index = poseList->IndexOf(searchResult); 9411 for (; index < count; index++) { 9412 int32 result = PoseCompareAddWidget(pose, poseList->ItemAt(index), 9413 this); 9414 if (result <= 0) { 9415 --index; 9416 break; 9417 } 9418 } 9419 9420 if (index != count) 9421 *resultingIndex = index; 9422 } 9423 9424 return kInsertAfter; 9425 } 9426 9427 9428 void 9429 BPoseView::SetPrimarySort(uint32 attrHash) 9430 { 9431 BColumn* column = ColumnFor(attrHash); 9432 if (column != NULL) { 9433 fViewState->SetPrimarySort(attrHash); 9434 fViewState->SetPrimarySortType(column->AttrType()); 9435 } 9436 } 9437 9438 9439 void 9440 BPoseView::SetSecondarySort(uint32 attrHash) 9441 { 9442 BColumn* column = ColumnFor(attrHash); 9443 if (column != NULL) { 9444 fViewState->SetSecondarySort(attrHash); 9445 fViewState->SetSecondarySortType(column->AttrType()); 9446 } else { 9447 fViewState->SetSecondarySort(0); 9448 fViewState->SetSecondarySortType(0); 9449 } 9450 } 9451 9452 9453 void 9454 BPoseView::SetReverseSort(bool reverse) 9455 { 9456 fViewState->SetReverseSort(reverse); 9457 } 9458 9459 9460 inline int 9461 PoseCompareAddWidgetBinder(const BPose* p1, const BPose* p2, 9462 void* castToPoseView) 9463 { 9464 return PoseCompareAddWidget(p1, p2, (BPoseView*)castToPoseView); 9465 } 9466 9467 9468 struct PoseComparator : public std::binary_function<const BPose*, 9469 const BPose*, bool> 9470 { 9471 PoseComparator(BPoseView* poseView): fPoseView(poseView) { } 9472 9473 bool operator() (const BPose* p1, const BPose* p2) 9474 { 9475 return PoseCompareAddWidget(p1, p2, fPoseView) < 0; 9476 } 9477 9478 BPoseView* fPoseView; 9479 }; 9480 9481 9482 #if xDEBUG 9483 static BPose* 9484 DumpOne(BPose* pose, void*) 9485 { 9486 pose->TargetModel()->PrintToStream(0); 9487 return 0; 9488 } 9489 #endif 9490 9491 9492 void 9493 BPoseView::SortPoses() 9494 { 9495 if (fTextWidgetToCheck != NULL) 9496 fTextWidgetToCheck->CancelWait(); 9497 9498 CommitActivePose(); 9499 // PRINT(("pose list count %d\n", fPoseList->CountItems())); 9500 #if xDEBUG 9501 fPoseList->EachElement(DumpOne, 0); 9502 PRINT(("===================\n")); 9503 #endif 9504 9505 BPose** poses = reinterpret_cast<BPose**>( 9506 PoseList::Private(fPoseList).AsBList()->Items()); 9507 std::stable_sort(poses, &poses[fPoseList->CountItems()], 9508 PoseComparator(this)); 9509 if (fFiltering) { 9510 poses = reinterpret_cast<BPose**>( 9511 PoseList::Private(fFilteredPoseList).AsBList()->Items()); 9512 std::stable_sort(poses, &poses[fFilteredPoseList->CountItems()], 9513 PoseComparator(this)); 9514 } 9515 } 9516 9517 9518 BColumn* 9519 BPoseView::ColumnFor(uint32 attr) const 9520 { 9521 int32 count = fColumnList->CountItems(); 9522 for (int32 index = 0; index < count; index++) { 9523 BColumn* column = ColumnAt(index); 9524 if (column->AttrHash() == attr) 9525 return column; 9526 } 9527 9528 return NULL; 9529 } 9530 9531 9532 bool 9533 BPoseView::ResizeColumnToWidest(BColumn* column) 9534 { 9535 ASSERT(ViewMode() == kListMode); 9536 9537 // returns true if actually resized 9538 9539 float maxWidth = kMinColumnWidth; 9540 9541 PoseList* poseList = CurrentPoseList(); 9542 int32 count = poseList->CountItems(); 9543 for (int32 i = 0; i < count; ++i) { 9544 BTextWidget* widget 9545 = poseList->ItemAt(i)->WidgetFor(column->AttrHash()); 9546 if (widget != NULL) { 9547 float width = widget->PreferredWidth(this); 9548 if (width > maxWidth) 9549 maxWidth = width; 9550 } 9551 } 9552 9553 if (maxWidth > kMinColumnWidth || maxWidth < column->Width()) { 9554 ResizeColumn(column, maxWidth); 9555 return true; 9556 } 9557 9558 return false; 9559 } 9560 9561 9562 BPoint 9563 BPoseView::ResizeColumn(BColumn* column, float newSize, 9564 float* lastLineDrawPos, 9565 void (*drawLineFunc)(BPoseView*, BPoint, BPoint), 9566 void (*undrawLineFunc)(BPoseView*, BPoint, BPoint)) 9567 { 9568 BRect sourceRect(Bounds()); 9569 BPoint result(sourceRect.RightBottom()); 9570 9571 BRect destRect(sourceRect); 9572 // we will use sourceRect and destRect for copyBits 9573 BRect invalidateRect(sourceRect); 9574 // this will serve to clean up after the invalidate 9575 BRect columnDrawRect(sourceRect); 9576 // we will use columnDrawRect to draw the actual resized column 9577 9578 bool shrinking = newSize < column->Width(); 9579 columnDrawRect.left = column->Offset(); 9580 columnDrawRect.right = column->Offset() + kTitleColumnRightExtraMargin 9581 - kRoomForLine + newSize; 9582 sourceRect.left = column->Offset() + kTitleColumnRightExtraMargin 9583 - kRoomForLine + column->Width(); 9584 destRect.left = columnDrawRect.right; 9585 destRect.right = destRect.left + sourceRect.Width(); 9586 invalidateRect.left = destRect.right; 9587 invalidateRect.right = sourceRect.right; 9588 9589 column->SetWidth(newSize); 9590 9591 float offset = StartOffset(); 9592 int32 count = fColumnList->CountItems(); 9593 for (int32 index = 0; index < count; index++) { 9594 column = fColumnList->ItemAt(index); 9595 column->SetOffset(offset); 9596 BColumn* last = column; 9597 offset = last->Offset() + last->Width() + kTitleColumnExtraMargin; 9598 } 9599 9600 if (shrinking) { 9601 ColumnRedraw(columnDrawRect); 9602 // dont have to undraw when shrinking 9603 CopyBits(sourceRect, destRect); 9604 if (drawLineFunc != NULL) { 9605 ASSERT(lastLineDrawPos != NULL); 9606 (drawLineFunc)(this, BPoint(destRect.left + kRoomForLine, 9607 destRect.top), 9608 BPoint(destRect.left + kRoomForLine, destRect.bottom)); 9609 *lastLineDrawPos = destRect.left + kRoomForLine; 9610 } 9611 } else { 9612 CopyBits(sourceRect, destRect); 9613 if (undrawLineFunc != NULL) { 9614 ASSERT(lastLineDrawPos != NULL); 9615 (undrawLineFunc)(this, BPoint(*lastLineDrawPos, sourceRect.top), 9616 BPoint(*lastLineDrawPos, sourceRect.bottom)); 9617 } 9618 if (drawLineFunc != NULL) { 9619 ASSERT(lastLineDrawPos); 9620 (drawLineFunc)(this, BPoint(destRect.left + kRoomForLine, 9621 destRect.top), 9622 BPoint(destRect.left + kRoomForLine, destRect.bottom)); 9623 *lastLineDrawPos = destRect.left + kRoomForLine; 9624 } 9625 ColumnRedraw(columnDrawRect); 9626 } 9627 if (invalidateRect.left < invalidateRect.right) 9628 SynchronousUpdate(invalidateRect, true); 9629 9630 fStateNeedsSaving = true; 9631 9632 return result; 9633 } 9634 9635 9636 void 9637 BPoseView::MoveColumnTo(BColumn* src, BColumn* dest) 9638 { 9639 // find the leftmost boundary of columns we are about to reshuffle 9640 float miny = src->Offset(); 9641 if (miny > dest->Offset()) 9642 miny = dest->Offset(); 9643 9644 // ensure columns are in proper order in list 9645 int32 index = fColumnList->IndexOf(dest); 9646 fColumnList->RemoveItem(src, false); 9647 fColumnList->AddItem(src, index); 9648 9649 float offset = StartOffset(); 9650 int32 count = fColumnList->CountItems(); 9651 for (int32 index = 0; index < count; index++) { 9652 BColumn* column = fColumnList->ItemAt(index); 9653 column->SetOffset(offset); 9654 BColumn* last = column; 9655 offset = last->Offset() + last->Width() + kTitleColumnExtraMargin 9656 - kRoomForLine / 2; 9657 } 9658 9659 // invalidate everything to the right of miny 9660 BRect bounds(Bounds()); 9661 bounds.left = miny; 9662 Invalidate(bounds); 9663 9664 fStateNeedsSaving = true; 9665 } 9666 9667 9668 bool 9669 BPoseView::UpdateDropTarget(BPoint mouseLoc, const BMessage* dragMessage, 9670 bool trackingContextMenu) 9671 { 9672 ASSERT(dragMessage != NULL); 9673 9674 int32 index; 9675 BPose* targetPose = FindPose(mouseLoc, &index); 9676 if (targetPose != NULL && DragSelectionContains(targetPose, dragMessage)) 9677 targetPose = NULL; 9678 9679 if ((fCursorCheck && targetPose == fDropTarget) 9680 || (trackingContextMenu && !targetPose)) { 9681 // no change 9682 return false; 9683 } 9684 9685 fCursorCheck = true; 9686 if (fDropTarget && !DragSelectionContains(fDropTarget, dragMessage)) 9687 HiliteDropTarget(false); 9688 9689 fDropTarget = targetPose; 9690 9691 // dereference if symlink 9692 Model* targetModel = NULL; 9693 if (targetPose != NULL) 9694 targetModel = targetPose->TargetModel(); 9695 9696 Model tmpTarget; 9697 if (targetModel != NULL && targetModel->IsSymLink() 9698 && tmpTarget.SetTo(targetPose->TargetModel()->EntryRef(), true, true) 9699 == B_OK) { 9700 targetModel = &tmpTarget; 9701 } 9702 9703 bool ignoreTypes = (modifiers() & B_CONTROL_KEY) != 0; 9704 if (targetPose != NULL) { 9705 if (targetModel != NULL 9706 && CanHandleDragSelection(targetModel, dragMessage, ignoreTypes)) { 9707 // new target is valid, select it 9708 HiliteDropTarget(true); 9709 } else { 9710 fDropTarget = NULL; 9711 fCursorCheck = false; 9712 } 9713 } 9714 if (targetModel == NULL) 9715 targetModel = TargetModel(); 9716 9717 // if this is an OpenWith window, we'll have no target model 9718 if (targetModel == NULL) 9719 return false; 9720 9721 entry_ref srcRef; 9722 if (targetModel->IsDirectory() && dragMessage->HasRef("refs") 9723 && dragMessage->FindRef("refs", &srcRef) == B_OK) { 9724 Model srcModel(&srcRef); 9725 if (!CheckDevicesEqual(&srcRef, targetModel) 9726 && !srcModel.IsVolume() 9727 && !srcModel.IsRoot()) { 9728 BCursor copyCursor(B_CURSOR_ID_COPY); 9729 SetViewCursor(©Cursor); 9730 return true; 9731 } 9732 } 9733 9734 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); 9735 9736 return true; 9737 } 9738 9739 9740 bool 9741 BPoseView::FrameForPose(BPose* targetPose, bool convert, BRect* poseRect) 9742 { 9743 bool frameIsValid = false; 9744 BRect bounds(Bounds()); 9745 9746 if (ViewMode() == kListMode) { 9747 PoseList* poseList = CurrentPoseList(); 9748 int32 count = poseList->CountItems(); 9749 int32 startIndex = (int32)(bounds.top / fListElemHeight); 9750 9751 BPoint loc(0, startIndex * fListElemHeight); 9752 for (int32 index = startIndex; index < count; index++) { 9753 if (targetPose == poseList->ItemAt(index)) { 9754 *poseRect = fDropTarget->CalcRect(loc, this, false); 9755 frameIsValid = true; 9756 } 9757 9758 loc.y += fListElemHeight; 9759 if (loc.y > bounds.bottom) 9760 frameIsValid = false; 9761 } 9762 } else { 9763 int32 startIndex = FirstIndexAtOrBelow((int32)(bounds.top 9764 - IconPoseHeight()), true); 9765 int32 count = fVSPoseList->CountItems(); 9766 9767 for (int32 index = startIndex; index < count; index++) { 9768 BPose* pose = fVSPoseList->ItemAt(index); 9769 if (pose != NULL) { 9770 if (pose == fDropTarget) { 9771 *poseRect = pose->CalcRect(this); 9772 frameIsValid = true; 9773 break; 9774 } 9775 9776 if (pose->Location(this).y > bounds.bottom) { 9777 frameIsValid = false; 9778 break; 9779 } 9780 } 9781 } 9782 } 9783 9784 if (convert) 9785 ConvertToScreen(poseRect); 9786 9787 return frameIsValid; 9788 } 9789 9790 9791 bool 9792 BPoseView::MenuTrackingHook(BMenu* menu, void*) 9793 { 9794 // return true if the menu should go away 9795 if (!menu->LockLooper()) 9796 return false; 9797 9798 uint32 buttons; 9799 BPoint location; 9800 menu->GetMouse(&location, &buttons); 9801 9802 bool mouseInMenu = true; 9803 // don't test for buttons up here and try to circumvent messaging 9804 // lest you miss an invoke that will happen after the window goes away 9805 9806 BRect bounds(menu->Bounds()); 9807 bounds.InsetBy(-kMenuTrackMargin, -kMenuTrackMargin); 9808 if (bounds.Contains(location)) { 9809 // still in menu 9810 mouseInMenu = false; 9811 } 9812 9813 if (mouseInMenu) { 9814 menu->ConvertToScreen(&location); 9815 int32 count = menu->CountItems(); 9816 for (int32 index = 0 ; index < count; index++) { 9817 // iterate through all of the items in the menu 9818 // if the submenu is showing, see if the mouse is in the submenu 9819 BMenuItem* item = menu->ItemAt(index); 9820 if (item && item->Submenu()) { 9821 BWindow* window = item->Submenu()->Window(); 9822 bool inSubmenu = false; 9823 if (window && window->Lock()) { 9824 if (!window->IsHidden()) { 9825 BRect frame(window->Frame()); 9826 9827 frame.InsetBy(-kMenuTrackMargin, -kMenuTrackMargin); 9828 inSubmenu = frame.Contains(location); 9829 } 9830 window->Unlock(); 9831 if (inSubmenu) { 9832 // only one menu can have its window open bail now 9833 mouseInMenu = false; 9834 break; 9835 } 9836 } 9837 } 9838 } 9839 } 9840 9841 menu->UnlockLooper(); 9842 9843 return mouseInMenu; 9844 } 9845 9846 9847 void 9848 BPoseView::DragStop() 9849 { 9850 fStartFrame.Set(0, 0, 0, 0); 9851 BContainerWindow* window = ContainerWindow(); 9852 if (window != NULL) 9853 window->DragStop(); 9854 } 9855 9856 9857 void 9858 BPoseView::HiliteDropTarget(bool hiliteState) 9859 { 9860 // hilites current drop target while dragging, does not modify 9861 // selection list 9862 if (fDropTarget == NULL) 9863 return; 9864 9865 // note: fAlreadySelectedDropTarget is a trick to avoid to really search 9866 // fSelectionList. Another solution would be to add Hilite/IsHilited just 9867 // like Select/IsSelected in BPose and let it handle this case internally 9868 9869 // can happen when starting a new drag 9870 if (fAlreadySelectedDropTarget != fDropTarget) 9871 fAlreadySelectedDropTarget = NULL; 9872 9873 // don't select, this droptarget was already part of a user selection 9874 if (fDropTarget->IsSelected() && hiliteState) { 9875 fAlreadySelectedDropTarget = fDropTarget; 9876 return; 9877 } 9878 9879 // don't unselect the fAlreadySelectedDropTarget 9880 if ((fAlreadySelectedDropTarget == fDropTarget) && !hiliteState) { 9881 fAlreadySelectedDropTarget = NULL; 9882 return; 9883 } 9884 9885 fDropTarget->Select(hiliteState); 9886 9887 // scan all visible poses 9888 BRect bounds(Bounds()); 9889 9890 if (ViewMode() == kListMode) { 9891 PoseList* poseList = CurrentPoseList(); 9892 int32 count = poseList->CountItems(); 9893 int32 startIndex = (int32)(bounds.top / fListElemHeight); 9894 9895 BPoint loc(0, startIndex * fListElemHeight); 9896 9897 for (int32 index = startIndex; index < count; index++) { 9898 if (fDropTarget == poseList->ItemAt(index)) { 9899 BRect poseRect = fDropTarget->CalcRect(loc, this, false); 9900 fDropTarget->Draw(poseRect, poseRect, this, false); 9901 break; 9902 } 9903 9904 loc.y += fListElemHeight; 9905 if (loc.y > bounds.bottom) 9906 break; 9907 } 9908 } else { 9909 int32 startIndex = FirstIndexAtOrBelow( 9910 (int32)(bounds.top - IconPoseHeight()), true); 9911 int32 count = fVSPoseList->CountItems(); 9912 9913 for (int32 index = startIndex; index < count; index++) { 9914 BPose* pose = fVSPoseList->ItemAt(index); 9915 if (pose != NULL) { 9916 if (pose == fDropTarget) { 9917 BRect poseRect = pose->CalcRect(this); 9918 // TODO: maybe leave just the else part 9919 if (!hiliteState) 9920 // deselecting an icon with widget drawn over background 9921 // have to be a little tricky here - draw just the icon, 9922 // invalidate the widget 9923 pose->DeselectWithoutErasingBackground(poseRect, this); 9924 else 9925 pose->Draw(poseRect, poseRect, this, false); 9926 break; 9927 } 9928 9929 if (pose->Location(this).y > bounds.bottom) 9930 break; 9931 } 9932 } 9933 } 9934 } 9935 9936 9937 bool 9938 BPoseView::CheckAutoScroll(BPoint mouseLoc, bool shouldScroll) 9939 { 9940 if (!fShouldAutoScroll) 9941 return false; 9942 9943 // make sure window is in front before attempting scrolling 9944 BContainerWindow* window = ContainerWindow(); 9945 if (window == NULL) 9946 return false; 9947 9948 BRect bounds(Bounds()); 9949 BRect extent(Extent()); 9950 9951 bool wouldScroll = false; 9952 bool keepGoing; 9953 float scrollIncrement; 9954 9955 BRect border(bounds); 9956 border.bottom = border.top; 9957 border.top -= kBorderHeight; 9958 if (ViewMode() == kListMode) 9959 border.top -= TitleView()->Bounds().Height(); 9960 9961 bool selectionScrolling = fSelectionRectInfo.isDragging; 9962 9963 if (bounds.top > extent.top) { 9964 if (selectionScrolling) { 9965 keepGoing = mouseLoc.y < bounds.top; 9966 if (fabs(bounds.top - mouseLoc.y) > kSlowScrollBucket) 9967 scrollIncrement = fAutoScrollInc / 1.5f; 9968 else 9969 scrollIncrement = fAutoScrollInc / 4; 9970 } else { 9971 keepGoing = border.Contains(mouseLoc); 9972 scrollIncrement = fAutoScrollInc; 9973 } 9974 9975 if (keepGoing) { 9976 wouldScroll = true; 9977 if (shouldScroll) { 9978 if (fVScrollBar != NULL) { 9979 fVScrollBar->SetValue( 9980 fVScrollBar->Value() - scrollIncrement); 9981 } else 9982 ScrollBy(0, -scrollIncrement); 9983 } 9984 } 9985 } 9986 9987 border = bounds; 9988 border.top = border.bottom; 9989 border.bottom += (float)B_H_SCROLL_BAR_HEIGHT; 9990 if (bounds.bottom < extent.bottom) { 9991 if (selectionScrolling) { 9992 keepGoing = mouseLoc.y > bounds.bottom; 9993 if (fabs(bounds.bottom - mouseLoc.y) > kSlowScrollBucket) 9994 scrollIncrement = fAutoScrollInc / 1.5f; 9995 else 9996 scrollIncrement = fAutoScrollInc / 4; 9997 } else { 9998 keepGoing = border.Contains(mouseLoc); 9999 scrollIncrement = fAutoScrollInc; 10000 } 10001 10002 if (keepGoing) { 10003 wouldScroll = true; 10004 if (shouldScroll) { 10005 if (fVScrollBar != NULL) { 10006 fVScrollBar->SetValue( 10007 fVScrollBar->Value() + scrollIncrement); 10008 } else 10009 ScrollBy(0, scrollIncrement); 10010 } 10011 } 10012 } 10013 10014 border = bounds; 10015 border.right = border.left; 10016 border.left -= 6; 10017 if (bounds.left > extent.left) { 10018 if (selectionScrolling) { 10019 keepGoing = mouseLoc.x < bounds.left; 10020 if (fabs(bounds.left - mouseLoc.x) > kSlowScrollBucket) 10021 scrollIncrement = fAutoScrollInc / 1.5f; 10022 else 10023 scrollIncrement = fAutoScrollInc / 4; 10024 } else { 10025 keepGoing = border.Contains(mouseLoc); 10026 scrollIncrement = fAutoScrollInc; 10027 } 10028 10029 if (keepGoing) { 10030 wouldScroll = true; 10031 if (shouldScroll) { 10032 if (fHScrollBar != NULL) { 10033 fHScrollBar->SetValue( 10034 fHScrollBar->Value() - scrollIncrement); 10035 } else 10036 ScrollBy(-scrollIncrement, 0); 10037 } 10038 } 10039 } 10040 10041 border = bounds; 10042 border.left = border.right; 10043 border.right += (float)B_V_SCROLL_BAR_WIDTH; 10044 if (bounds.right < extent.right) { 10045 if (selectionScrolling) { 10046 keepGoing = mouseLoc.x > bounds.right; 10047 if (fabs(bounds.right - mouseLoc.x) > kSlowScrollBucket) 10048 scrollIncrement = fAutoScrollInc / 1.5f; 10049 else 10050 scrollIncrement = fAutoScrollInc / 4; 10051 } else { 10052 keepGoing = border.Contains(mouseLoc); 10053 scrollIncrement = fAutoScrollInc; 10054 } 10055 10056 if (keepGoing) { 10057 wouldScroll = true; 10058 if (shouldScroll) { 10059 if (fHScrollBar != NULL) { 10060 fHScrollBar->SetValue( 10061 fHScrollBar->Value() + scrollIncrement); 10062 } else 10063 ScrollBy(scrollIncrement, 0); 10064 } 10065 } 10066 } 10067 10068 // Force selection rect update to account for the new scrolled coords 10069 // without a mouse move 10070 if (selectionScrolling) 10071 _UpdateSelectionRect(mouseLoc); 10072 10073 return wouldScroll; 10074 } 10075 10076 10077 void 10078 BPoseView::HandleAutoScroll() 10079 { 10080 if (!fShouldAutoScroll) 10081 return; 10082 10083 uint32 buttons; 10084 BPoint mouseLoc; 10085 GetMouse(&mouseLoc, &buttons); 10086 10087 if (buttons == 0) { 10088 fAutoScrollState = kAutoScrollOff; 10089 Window()->SetPulseRate(500000); 10090 return; 10091 } 10092 10093 switch (fAutoScrollState) { 10094 case kWaitForTransition: 10095 if (CheckAutoScroll(mouseLoc, false) == false) 10096 fAutoScrollState = kDelayAutoScroll; 10097 break; 10098 10099 case kDelayAutoScroll: 10100 if (CheckAutoScroll(mouseLoc, false) == true) { 10101 snooze(600000); 10102 GetMouse(&mouseLoc, &buttons); 10103 if (CheckAutoScroll(mouseLoc, false) == true) 10104 fAutoScrollState = kAutoScrollOn; 10105 } 10106 break; 10107 10108 case kAutoScrollOn: 10109 CheckAutoScroll(mouseLoc, true); 10110 break; 10111 } 10112 } 10113 10114 10115 BRect 10116 BPoseView::CalcPoseRect(const BPose* pose, int32 index, 10117 bool firstColumnOnly) const 10118 { 10119 if (ViewMode() == kListMode) 10120 return CalcPoseRectList(pose, index, firstColumnOnly); 10121 else 10122 return CalcPoseRectIcon(pose); 10123 } 10124 10125 10126 BRect 10127 BPoseView::CalcPoseRectIcon(const BPose* pose) const 10128 { 10129 return pose->CalcRect(this); 10130 } 10131 10132 10133 BRect 10134 BPoseView::CalcPoseRectList(const BPose* pose, int32 index, 10135 bool firstColumnOnly) const 10136 { 10137 return pose->CalcRect(BPoint(0, index * fListElemHeight), this, 10138 firstColumnOnly); 10139 } 10140 10141 10142 bool 10143 BPoseView::Represents(const node_ref* node) const 10144 { 10145 return *(fModel->NodeRef()) == *node; 10146 } 10147 10148 10149 bool 10150 BPoseView::Represents(const entry_ref* ref) const 10151 { 10152 return *fModel->EntryRef() == *ref; 10153 } 10154 10155 10156 void 10157 BPoseView::ShowBarberPole() 10158 { 10159 if (fCountView) { 10160 AutoLock<BWindow> lock(Window()); 10161 if (!lock) 10162 return; 10163 fCountView->StartBarberPole(); 10164 } 10165 } 10166 10167 10168 void 10169 BPoseView::HideBarberPole() 10170 { 10171 if (fCountView) { 10172 AutoLock<BWindow> lock(Window()); 10173 if (!lock) 10174 return; 10175 fCountView->EndBarberPole(); 10176 } 10177 } 10178 10179 10180 bool 10181 BPoseView::IsWatchingDateFormatChange() 10182 { 10183 return fIsWatchingDateFormatChange; 10184 } 10185 10186 10187 void 10188 BPoseView::StartWatchDateFormatChange() 10189 { 10190 BMessenger trackerMessenger(kTrackerSignature); 10191 BHandler::StartWatching(trackerMessenger, kDateFormatChanged); 10192 fIsWatchingDateFormatChange = true; 10193 } 10194 10195 10196 void 10197 BPoseView::StopWatchDateFormatChange() 10198 { 10199 if (IsFilePanel()) { 10200 BMessenger trackerMessenger(kTrackerSignature); 10201 BHandler::StopWatching(trackerMessenger, kDateFormatChanged); 10202 } else if (be_app->LockLooper()) { 10203 be_app->StopWatching(this, kDateFormatChanged); 10204 be_app->UnlockLooper(); 10205 } 10206 10207 fIsWatchingDateFormatChange = false; 10208 } 10209 10210 10211 void 10212 BPoseView::UpdateDateColumns(BMessage* message) 10213 { 10214 int32 columnCount = CountColumns(); 10215 BRect columnRect(Bounds()); 10216 10217 for (int32 i = 0; i < columnCount; i++) { 10218 BColumn* col = ColumnAt(i); 10219 if (col && col->AttrType() == B_TIME_TYPE) { 10220 columnRect.left = col->Offset(); 10221 columnRect.right = columnRect.left + col->Width(); 10222 Invalidate(columnRect); 10223 } 10224 } 10225 } 10226 10227 10228 void 10229 BPoseView::AdaptToVolumeChange(BMessage*) 10230 { 10231 } 10232 10233 10234 void 10235 BPoseView::AdaptToDesktopIntegrationChange(BMessage*) 10236 { 10237 } 10238 10239 10240 bool 10241 BPoseView::WidgetTextOutline() const 10242 { 10243 return fWidgetTextOutline; 10244 } 10245 10246 10247 void 10248 BPoseView::SetWidgetTextOutline(bool on) 10249 { 10250 fWidgetTextOutline = on; 10251 } 10252 10253 10254 void 10255 BPoseView::EnsurePoseUnselected(BPose* pose) 10256 { 10257 if (pose == fDropTarget) 10258 fDropTarget = NULL; 10259 10260 if (pose == ActivePose()) 10261 CommitActivePose(); 10262 10263 fSelectionList->RemoveItem(pose); 10264 if (fSelectionPivotPose == pose) 10265 fSelectionPivotPose = NULL; 10266 10267 if (fRealPivotPose == pose) 10268 fRealPivotPose = NULL; 10269 10270 if (pose->IsSelected()) { 10271 pose->Select(false); 10272 if (fSelectionChangedHook) 10273 ContainerWindow()->SelectionChanged(); 10274 } 10275 } 10276 10277 10278 void 10279 BPoseView::RemoveFilteredPose(BPose* pose, int32 index) 10280 { 10281 EnsurePoseUnselected(pose); 10282 fFilteredPoseList->RemoveItemAt(index); 10283 10284 BRect invalidRect = CalcPoseRectList(pose, index); 10285 CloseGapInList(&invalidRect); 10286 10287 Invalidate(invalidRect); 10288 } 10289 10290 10291 void 10292 BPoseView::FilterChanged() 10293 { 10294 if (ViewMode() != kListMode) 10295 return; 10296 10297 int32 stringCount = fFilterStrings.CountItems(); 10298 int32 length = fFilterStrings.LastItem()->CountChars(); 10299 10300 if (!fFiltering && (length > 0 || fRefFilter != NULL)) 10301 StartFiltering(); 10302 else if (fFiltering && stringCount == 1 && length == 0 10303 && fRefFilter == NULL) { 10304 ClearFilter(); 10305 } else { 10306 if (fLastFilterStringCount > stringCount 10307 || (fLastFilterStringCount == stringCount 10308 && fLastFilterStringLength > length) 10309 || fRefFilter != NULL) { 10310 // something was removed, need to start over 10311 fFilteredPoseList->MakeEmpty(); 10312 fFiltering = false; 10313 StartFiltering(); 10314 } else { 10315 int32 count = fFilteredPoseList->CountItems(); 10316 for (int32 i = count - 1; i >= 0; i--) { 10317 BPose* pose = fFilteredPoseList->ItemAt(i); 10318 if (!FilterPose(pose)) 10319 RemoveFilteredPose(pose, i); 10320 } 10321 } 10322 } 10323 10324 fLastFilterStringCount = stringCount; 10325 fLastFilterStringLength = length; 10326 UpdateAfterFilterChange(); 10327 } 10328 10329 10330 void 10331 BPoseView::UpdateAfterFilterChange() 10332 { 10333 UpdateCount(); 10334 10335 BPose* pose = fFilteredPoseList->LastItem(); 10336 if (pose == NULL) 10337 BView::ScrollTo(0, 0); 10338 else { 10339 BRect bounds = Bounds(); 10340 float height = fFilteredPoseList->CountItems() * fListElemHeight; 10341 if (bounds.top > 0 && bounds.bottom > height) 10342 BView::ScrollTo(0, std::max(height - bounds.Height(), 0.0f)); 10343 } 10344 10345 UpdateScrollRange(); 10346 } 10347 10348 10349 bool 10350 BPoseView::FilterPose(BPose* pose) 10351 { 10352 if (!fFiltering || pose == NULL) 10353 return false; 10354 10355 if (fRefFilter != NULL) { 10356 PoseInfo poseInfo; 10357 ReadPoseInfo(pose->TargetModel(), &poseInfo); 10358 pose->TargetModel()->OpenNode(); 10359 if (!ShouldShowPose(pose->TargetModel(), &poseInfo)) 10360 return false; 10361 } 10362 10363 int32 stringCount = fFilterStrings.CountItems(); 10364 int32 matchesLeft = stringCount; 10365 10366 bool found[stringCount]; 10367 memset(found, 0, sizeof(found)); 10368 10369 ModelNodeLazyOpener modelOpener(pose->TargetModel()); 10370 for (int32 i = 0; i < CountColumns(); i++) { 10371 BTextWidget* widget = pose->WidgetFor(ColumnAt(i), this, modelOpener); 10372 const char* text = NULL; 10373 if (widget == NULL) 10374 continue; 10375 10376 text = widget->Text(this); 10377 if (text == NULL) 10378 continue; 10379 10380 for (int32 j = 0; j < stringCount; j++) { 10381 if (found[j]) 10382 continue; 10383 10384 if (strcasestr(text, fFilterStrings.ItemAt(j)->String()) != NULL) { 10385 if (--matchesLeft == 0) 10386 return true; 10387 10388 found[j] = true; 10389 } 10390 } 10391 } 10392 10393 return false; 10394 } 10395 10396 10397 void 10398 BPoseView::StartFiltering() 10399 { 10400 if (fFiltering) 10401 return; 10402 10403 fFiltering = true; 10404 int32 count = fPoseList->CountItems(); 10405 for (int32 i = 0; i < count; i++) { 10406 BPose* pose = fPoseList->ItemAt(i); 10407 if (FilterPose(pose)) 10408 fFilteredPoseList->AddItem(pose); 10409 else 10410 EnsurePoseUnselected(pose); 10411 } 10412 10413 Invalidate(); 10414 } 10415 10416 10417 bool 10418 BPoseView::IsFiltering() const 10419 { 10420 return fFiltering; 10421 } 10422 10423 10424 void 10425 BPoseView::StopFiltering() 10426 { 10427 ClearFilter(); 10428 UpdateAfterFilterChange(); 10429 } 10430 10431 10432 void 10433 BPoseView::ClearFilter() 10434 { 10435 if (!fFiltering) 10436 return; 10437 10438 fCountView->CancelFilter(); 10439 10440 int32 stringCount = fFilterStrings.CountItems(); 10441 for (int32 i = stringCount - 1; i > 0; i--) 10442 delete fFilterStrings.RemoveItemAt(i); 10443 10444 fFilterStrings.LastItem()->Truncate(0); 10445 fLastFilterStringCount = 1; 10446 fLastFilterStringLength = 0; 10447 10448 if (fRefFilter == NULL) 10449 fFiltering = false; 10450 10451 fFilteredPoseList->MakeEmpty(); 10452 10453 Invalidate(); 10454 } 10455 10456 10457 void 10458 BPoseView::ExcludeTrashFromSelection() 10459 { 10460 int32 count = fSelectionList->CountItems(); 10461 for (int index = 0; index < count; index++) { 10462 BPose* pose = fSelectionList->ItemAt(index); 10463 if (CanTrashForeignDrag(pose->TargetModel())) { 10464 RemovePoseFromSelection(pose); 10465 break; 10466 } 10467 } 10468 } 10469 10470 10471 // #pragma mark - TScrollBar 10472 10473 10474 TScrollBar::TScrollBar(const char* name, BView* target, float min, float max) 10475 : 10476 BScrollBar(name, target, min, max, B_HORIZONTAL), 10477 fTitleView(NULL) 10478 { 10479 } 10480 10481 10482 void 10483 TScrollBar::ValueChanged(float value) 10484 { 10485 if (fTitleView) { 10486 BPoint origin = fTitleView->LeftTop(); 10487 fTitleView->ScrollTo(BPoint(value, origin.y)); 10488 } 10489 10490 _inherited::ValueChanged(value); 10491 } 10492 10493 10494 TPoseViewFilter::TPoseViewFilter(BPoseView* pose) 10495 : 10496 BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE), 10497 fPoseView(pose) 10498 { 10499 } 10500 10501 10502 TPoseViewFilter::~TPoseViewFilter() 10503 { 10504 } 10505 10506 10507 filter_result 10508 TPoseViewFilter::Filter(BMessage* message, BHandler**) 10509 { 10510 filter_result result = B_DISPATCH_MESSAGE; 10511 10512 switch (message->what) { 10513 case B_ARCHIVED_OBJECT: 10514 bool handled = fPoseView->HandleMessageDropped(message); 10515 if (handled) 10516 result = B_SKIP_MESSAGE; 10517 break; 10518 } 10519 10520 return result; 10521 } 10522 10523 10524 // #pragma mark - static member initializations 10525 10526 float BPoseView::sFontHeight = -1; 10527 font_height BPoseView::sFontInfo = { 0, 0, 0 }; 10528 BFont BPoseView::sCurrentFont; 10529 OffscreenBitmap* BPoseView::sOffscreen = new OffscreenBitmap; 10530 BString BPoseView::sMatchString = ""; 10531