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