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