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