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