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 fOkToMapIcons(false), 267 fEnsurePosesVisible(false), 268 fShouldAutoScroll(true), 269 fIsDesktopWindow(false), 270 fIsWatchingDateFormatChange(false), 271 fHasPosesInClipboard(false), 272 fCursorCheck(false), 273 fFiltering(false), 274 fFilterStrings(4, true), 275 fLastFilterStringCount(1), 276 fLastFilterStringLength(0), 277 fLastKeyTime(0), 278 fLastDeskbarFrameCheckTime(LONGLONG_MIN), 279 fDeskbarFrame(0, 0, -1, -1), 280 fTextWidgetToCheck(NULL), 281 fActiveTextWidget(NULL), 282 fCachedIconSizeFrom(0) 283 { 284 fListElemHeight = ceilf(be_plain_font->Size() * 1.65f); 285 fListOffset = ceilf(be_control_look->DefaultLabelSpacing() * 3.3f); 286 287 fViewState->SetViewMode(viewMode); 288 fShowSelectionWhenInactive 289 = TrackerSettings().ShowSelectionWhenInactive(); 290 fTransparentSelection = TrackerSettings().TransparentSelection(); 291 fFilterStrings.AddItem(new BString()); 292 } 293 294 295 BPoseView::~BPoseView() 296 { 297 delete fPoseList; 298 delete fFilteredPoseList; 299 delete fVSPoseList; 300 delete fColumnList; 301 delete fSelectionList; 302 delete fMimeTypeList; 303 delete fZombieList; 304 delete fViewState; 305 delete fModel; 306 delete fKeyRunner; 307 delete fBrokenLinks; 308 309 IconCache::sIconCache->Deleting(this); 310 } 311 312 313 void 314 BPoseView::Init(AttributeStreamNode* node) 315 { 316 RestoreState(node); 317 InitCommon(); 318 } 319 320 321 void 322 BPoseView::Init(const BMessage &message) 323 { 324 RestoreState(message); 325 InitCommon(); 326 } 327 328 329 void 330 BPoseView::InitCommon() 331 { 332 BContainerWindow* window = ContainerWindow(); 333 334 // Create the TitleView and CountView 335 fTitleView = new BTitleView(this); 336 if (ViewMode() != kListMode) 337 fTitleView->Hide(); 338 if (fHScrollBar != NULL) 339 fHScrollBar->SetTitleView(fTitleView); 340 341 fCountView = new BCountView(this); 342 343 BPoint origin; 344 if (ViewMode() == kListMode) 345 origin = fViewState->ListOrigin(); 346 else 347 origin = fViewState->IconOrigin(); 348 349 PinPointToValidRange(origin); 350 351 // init things related to laying out items 352 SetIconPoseHeight(); 353 GetLayoutInfo(ViewMode(), &fGrid, &fOffset); 354 ResetPosePlacementHint(); 355 356 DisableScrollBars(); 357 ScrollTo(origin); 358 UpdateScrollRange(); 359 SetScrollBarsTo(origin); 360 EnableScrollBars(); 361 362 StartWatching(); 363 // turn on volume node monitor, metamime monitor, etc. 364 365 // populate the window 366 if (window != NULL && window->IsTrash()) 367 AddTrashPoses(); 368 else 369 AddPoses(TargetModel()); 370 } 371 372 373 static int 374 CompareColumns(const BColumn* c1, const BColumn* c2) 375 { 376 if (c1->Offset() > c2->Offset()) 377 return 1; 378 else if (c1->Offset() < c2->Offset()) 379 return -1; 380 381 return 0; 382 } 383 384 385 void 386 BPoseView::RestoreColumnState(AttributeStreamNode* node) 387 { 388 fColumnList->MakeEmpty(); 389 if (fTitleView != NULL) 390 fTitleView->Reset(); 391 392 if (node != NULL) { 393 const char* columnsAttr; 394 const char* columnsAttrForeign; 395 if (TargetModel() && TargetModel()->IsRoot()) { 396 columnsAttr = kAttrDisksColumns; 397 columnsAttrForeign = kAttrDisksColumnsForeign; 398 } else { 399 columnsAttr = kAttrColumns; 400 columnsAttrForeign = kAttrColumnsForeign; 401 } 402 403 bool wrongEndianness = false; 404 const char* name = columnsAttr; 405 size_t size = (size_t)node->Contains(name, B_RAW_TYPE); 406 if (size == 0) { 407 name = columnsAttrForeign; 408 wrongEndianness = true; 409 size = (size_t)node->Contains(name, B_RAW_TYPE); 410 } 411 412 if (size > 0 && size < 10000) { 413 // check for invalid sizes here to protect against 414 // munged attributes 415 char* buffer = new char[size]; 416 off_t result = node->Read(name, 0, B_RAW_TYPE, size, buffer); 417 if (result) { 418 BMallocIO stream; 419 stream.WriteAt(0, buffer, size); 420 stream.Seek(0, SEEK_SET); 421 422 // Clear old column list if neccessary 423 424 // Put items in the list in order so they can be checked 425 // for overlaps below. 426 BObjectList<BColumn> tempSortedList; 427 for (;;) { 428 BColumn* column = BColumn::InstantiateFromStream(&stream, 429 wrongEndianness); 430 if (column == NULL) 431 break; 432 tempSortedList.AddItem(column); 433 } 434 AddColumnList(&tempSortedList); 435 } 436 delete[] buffer; 437 } 438 } 439 440 SetUpDefaultColumnsIfNeeded(); 441 if (!ColumnFor(PrimarySort())) { 442 fViewState->SetPrimarySort(FirstColumn()->AttrHash()); 443 fViewState->SetPrimarySortType(FirstColumn()->AttrType()); 444 } 445 446 if (PrimarySort() == SecondarySort()) 447 fViewState->SetSecondarySort(0); 448 } 449 450 451 void 452 BPoseView::RestoreColumnState(const BMessage &message) 453 { 454 fColumnList->MakeEmpty(); 455 if (fTitleView != NULL) 456 fTitleView->Reset(); 457 458 BObjectList<BColumn> tempSortedList; 459 for (int32 index = 0; ; index++) { 460 BColumn* column = BColumn::InstantiateFromMessage(message, index); 461 if (column == NULL) 462 break; 463 464 tempSortedList.AddItem(column); 465 } 466 467 AddColumnList(&tempSortedList); 468 469 SetUpDefaultColumnsIfNeeded(); 470 if (!ColumnFor(PrimarySort())) { 471 fViewState->SetPrimarySort(FirstColumn()->AttrHash()); 472 fViewState->SetPrimarySortType(FirstColumn()->AttrType()); 473 } 474 475 if (PrimarySort() == SecondarySort()) 476 fViewState->SetSecondarySort(0); 477 } 478 479 480 void 481 BPoseView::AddColumnList(BObjectList<BColumn>* list) 482 { 483 list->SortItems(&CompareColumns); 484 485 float nextLeftEdge = StartOffset(); 486 for (int32 columIndex = 0; columIndex < list->CountItems(); columIndex++) { 487 BColumn* column = list->ItemAt(columIndex); 488 489 // Always realign columns, since the title-view snaps on resize anyway. 490 column->SetOffset(nextLeftEdge); 491 492 nextLeftEdge = column->Offset() + column->Width() 493 - kRoomForLine / 2.0f + kTitleColumnExtraMargin; 494 fColumnList->AddItem(column); 495 496 if (!IsWatchingDateFormatChange() 497 && column->AttrType() == B_TIME_TYPE) { 498 StartWatchDateFormatChange(); 499 } 500 } 501 502 if (fTitleView != NULL) 503 fTitleView->Reset(); 504 } 505 506 507 void 508 BPoseView::RestoreState(AttributeStreamNode* node) 509 { 510 RestoreColumnState(node); 511 512 if (node != NULL) { 513 const char* viewStateAttr; 514 const char* viewStateAttrForeign; 515 516 if (TargetModel() && TargetModel()->IsRoot()) { 517 viewStateAttr = kAttrDisksViewState; 518 viewStateAttrForeign = kAttrDisksViewStateForeign; 519 } else { 520 viewStateAttr = ViewStateAttributeName(); 521 viewStateAttrForeign = ForeignViewStateAttributeName(); 522 } 523 524 bool wrongEndianness = false; 525 const char* name = viewStateAttr; 526 size_t size = (size_t)node->Contains(name, B_RAW_TYPE); 527 if (!size) { 528 name = viewStateAttrForeign; 529 wrongEndianness = true; 530 size = (size_t)node->Contains(name, B_RAW_TYPE); 531 } 532 533 if (size > 0 && size < 10000) { 534 // check for invalid sizes here to protect against 535 // munged attributes 536 char* buffer = new char[size]; 537 off_t result = node->Read(name, 0, B_RAW_TYPE, size, buffer); 538 if (result) { 539 BMallocIO stream; 540 stream.WriteAt(0, buffer, size); 541 stream.Seek(0, SEEK_SET); 542 BViewState* viewstate 543 = BViewState::InstantiateFromStream(&stream, 544 wrongEndianness); 545 if (viewstate) { 546 delete fViewState; 547 fViewState = viewstate; 548 } 549 } 550 delete[] buffer; 551 } 552 } 553 554 if (IsDesktopWindow() && ViewMode() == kListMode) { 555 // recover if desktop window view state set wrong 556 fViewState->SetViewMode(kIconMode); 557 } 558 } 559 560 561 void 562 BPoseView::RestoreState(const BMessage &message) 563 { 564 RestoreColumnState(message); 565 566 BViewState* viewstate = BViewState::InstantiateFromMessage(message); 567 if (viewstate != NULL) { 568 delete fViewState; 569 fViewState = viewstate; 570 } 571 572 if (IsDesktopWindow() && ViewMode() == kListMode) { 573 // recover if desktop window view state set wrong 574 fViewState->SetViewMode(kIconMode); 575 } 576 } 577 578 579 namespace BPrivate { 580 581 bool 582 ClearViewOriginOne(const char* DEBUG_ONLY(name), uint32 type, off_t size, 583 void* viewStateArchive, void*) 584 { 585 ASSERT(strcmp(name, kAttrViewState) == 0); 586 587 if (viewStateArchive == NULL) 588 return false; 589 590 if (type != B_RAW_TYPE) 591 return false; 592 593 BMallocIO stream; 594 stream.WriteAt(0, viewStateArchive, (size_t)size); 595 stream.Seek(0, SEEK_SET); 596 BViewState* viewstate = BViewState::InstantiateFromStream(&stream, false); 597 if (!viewstate) 598 return false; 599 600 // this is why we are here - zero out 601 viewstate->SetListOrigin(BPoint(0, 0)); 602 viewstate->SetIconOrigin(BPoint(0, 0)); 603 604 stream.Seek(0, SEEK_SET); 605 viewstate->ArchiveToStream(&stream); 606 stream.ReadAt(0, viewStateArchive, (size_t)size); 607 608 return true; 609 } 610 611 } // namespace BPrivate 612 613 614 void 615 BPoseView::SetUpDefaultColumnsIfNeeded() 616 { 617 // in case there were errors getting some columns 618 if (CountColumns() != 0) 619 return; 620 621 AddColumn(new BColumn(B_TRANSLATE("Name"), 145, 622 B_ALIGN_LEFT, kAttrStatName, B_STRING_TYPE, true, true)); 623 AddColumn(new BColumn(B_TRANSLATE("Size"), 80, 624 B_ALIGN_RIGHT, kAttrStatSize, B_OFF_T_TYPE, true, false)); 625 AddColumn(new BColumn(B_TRANSLATE("Modified"), 150, 626 B_ALIGN_LEFT, kAttrStatModified, B_TIME_TYPE, true, false)); 627 628 if (!IsWatchingDateFormatChange()) 629 StartWatchDateFormatChange(); 630 } 631 632 633 const char* 634 BPoseView::ViewStateAttributeName() const 635 { 636 return IsDesktopView() ? kAttrDesktopViewState : kAttrViewState; 637 } 638 639 640 const char* 641 BPoseView::ForeignViewStateAttributeName() const 642 { 643 return IsDesktopView() ? kAttrDesktopViewStateForeign 644 : kAttrViewStateForeign; 645 } 646 647 648 void 649 BPoseView::SaveColumnState(AttributeStreamNode* node) 650 { 651 BMallocIO stream; 652 for (int32 index = 0; ; index++) { 653 const BColumn* column = ColumnAt(index); 654 if (column == NULL) 655 break; 656 column->ArchiveToStream(&stream); 657 } 658 659 const char* columnsAttr; 660 const char* columnsAttrForeign; 661 if (TargetModel() && TargetModel()->IsRoot()) { 662 columnsAttr = kAttrDisksColumns; 663 columnsAttrForeign = kAttrDisksColumnsForeign; 664 } else { 665 columnsAttr = kAttrColumns; 666 columnsAttrForeign = kAttrColumnsForeign; 667 } 668 669 node->Write(columnsAttr, columnsAttrForeign, B_RAW_TYPE, 670 stream.Position(), stream.Buffer()); 671 } 672 673 674 void 675 BPoseView::SaveColumnState(BMessage& message) const 676 { 677 for (int32 index = 0; ; index++) { 678 const BColumn* column = ColumnAt(index); 679 if (column == NULL) 680 break; 681 682 column->ArchiveToMessage(message); 683 } 684 } 685 686 687 void 688 BPoseView::SaveState(AttributeStreamNode* node) 689 { 690 SaveColumnState(node); 691 692 // save view state into object 693 BMallocIO stream; 694 695 stream.Seek(0, SEEK_SET); 696 fViewState->ArchiveToStream(&stream); 697 698 const char* viewStateAttr; 699 const char* viewStateAttrForeign; 700 if (TargetModel() != NULL && TargetModel()->IsRoot()) { 701 viewStateAttr = kAttrDisksViewState; 702 viewStateAttrForeign = kAttrDisksViewStateForeign; 703 } else { 704 viewStateAttr = ViewStateAttributeName(); 705 viewStateAttrForeign = ForeignViewStateAttributeName(); 706 } 707 708 node->Write(viewStateAttr, viewStateAttrForeign, B_RAW_TYPE, 709 stream.Position(), stream.Buffer()); 710 711 fStateNeedsSaving = false; 712 } 713 714 715 void 716 BPoseView::SaveState(BMessage& message) const 717 { 718 SaveColumnState(message); 719 fViewState->ArchiveToMessage(message); 720 } 721 722 723 float 724 BPoseView::StringWidth(const char* str) const 725 { 726 return BPrivate::gWidthBuffer->StringWidth(str, 0, (int32)strlen(str), 727 be_plain_font); 728 } 729 730 731 float 732 BPoseView::StringWidth(const char* str, int32 len) const 733 { 734 ASSERT(strlen(str) == (uint32)len); 735 736 return BPrivate::gWidthBuffer->StringWidth(str, 0, len, be_plain_font); 737 } 738 739 740 void 741 BPoseView::SavePoseLocations(BRect* frameIfDesktop) 742 { 743 PoseInfo poseInfo; 744 745 if (!fSavePoseLocations) 746 return; 747 748 ASSERT(Window()->IsLocked()); 749 750 Model* targetModel = TargetModel(); 751 ThrowOnAssert(targetModel != NULL); 752 753 BVolume volume(TargetModel()->NodeRef()->device); 754 if (volume.InitCheck() != B_OK) 755 return; 756 757 if (!targetModel->IsRoot() 758 && (volume.IsReadOnly() || !volume.KnowsAttr())) { 759 // check that we can write out attrs; Root should always work 760 // because it gets saved on the boot disk but the above checks 761 // will fail 762 return; 763 } 764 765 bool isDesktop = IsDesktopWindow() && (frameIfDesktop != NULL); 766 767 int32 count = fPoseList->CountItems(); 768 for (int32 index = 0; index < count; index++) { 769 BPose* pose = fPoseList->ItemAt(index); 770 if (pose->NeedsSaveLocation() && pose->HasLocation()) { 771 Model* model = pose->TargetModel(); 772 poseInfo.fInvisible = false; 773 774 if (model->IsRoot()) 775 poseInfo.fInitedDirectory = targetModel->NodeRef()->node; 776 else 777 poseInfo.fInitedDirectory = model->EntryRef()->directory; 778 779 poseInfo.fLocation = pose->Location(this); 780 781 ExtendedPoseInfo* extendedPoseInfo = NULL; 782 size_t extendedPoseInfoSize = 0; 783 ModelNodeLazyOpener opener(model, true); 784 785 if (isDesktop) { 786 opener.OpenNode(true); 787 // if saving desktop icons, save an extended pose info too 788 extendedPoseInfo = ReadExtendedPoseInfo(model); 789 // read the pre-existing one 790 791 if (!extendedPoseInfo) { 792 // don't have one yet, allocate one 793 size_t size = ExtendedPoseInfo::Size(1); 794 extendedPoseInfo = (ExtendedPoseInfo*) 795 new char [size]; 796 797 memset((void*)extendedPoseInfo, 0, size); 798 extendedPoseInfo->fWorkspaces = 0xffffffff; 799 extendedPoseInfo->fInvisible = false; 800 extendedPoseInfo->fShowFromBootOnly = false; 801 extendedPoseInfo->fNumFrames = 0; 802 } 803 ASSERT(extendedPoseInfo); 804 805 extendedPoseInfo->SetLocationForFrame(pose->Location(this), 806 *frameIfDesktop); 807 extendedPoseInfoSize = extendedPoseInfo->Size(); 808 } 809 810 if (model->InitCheck() != B_OK) { 811 delete[] (char*)extendedPoseInfo; 812 continue; 813 } 814 815 ASSERT(model); 816 ASSERT(model->InitCheck() == B_OK); 817 // special handling for "root" disks icon 818 // and Trash pose on Desktop directory 819 bool isTrash = model->IsTrash() && IsDesktopView(); 820 if (model->IsRoot() || isTrash) { 821 BDirectory deskDir; 822 if (FSGetDeskDir(&deskDir) == B_OK) { 823 const char* poseInfoAttr = isTrash ? kAttrTrashPoseInfo 824 : kAttrDisksPoseInfo; 825 const char* poseInfoAttrForeign = isTrash 826 ? kAttrTrashPoseInfoForeign 827 : kAttrDisksPoseInfoForeign; 828 if (deskDir.WriteAttr(poseInfoAttr, B_RAW_TYPE, 0, 829 &poseInfo, sizeof(poseInfo)) == sizeof(poseInfo)) { 830 // nuke opposite endianness 831 deskDir.RemoveAttr(poseInfoAttrForeign); 832 } 833 834 if (!isTrash && isDesktop 835 && deskDir.WriteAttr(kAttrExtendedDisksPoseInfo, 836 B_RAW_TYPE, 0, extendedPoseInfo, extendedPoseInfoSize) 837 == (ssize_t)extendedPoseInfoSize) { 838 // nuke opposite endianness 839 deskDir.RemoveAttr(kAttrExtendedDisksPoseInfoForegin); 840 } 841 } 842 } else { 843 model->WriteAttrKillForeign(kAttrPoseInfo, 844 kAttrPoseInfoForeign, B_RAW_TYPE, 0, &poseInfo, 845 sizeof(poseInfo)); 846 847 if (isDesktop) { 848 model->WriteAttrKillForeign(kAttrExtendedPoseInfo, 849 kAttrExtendedPoseInfoForegin, 850 B_RAW_TYPE, 0, extendedPoseInfo, 851 extendedPoseInfoSize); 852 } 853 } 854 855 delete[] (char*)extendedPoseInfo; 856 // TODO: fix up this mess 857 } 858 } 859 } 860 861 862 void 863 BPoseView::StartWatching() 864 { 865 // watch volumes 866 TTracker::WatchNode(NULL, B_WATCH_MOUNT, this); 867 868 Model* targetModel = TargetModel(); 869 if (targetModel != NULL) 870 TTracker::WatchNode(targetModel->NodeRef(), B_WATCH_ATTR, this); 871 872 BMimeType::StartWatching(BMessenger(this)); 873 } 874 875 876 void 877 BPoseView::StopWatching() 878 { 879 stop_watching(this); 880 BMimeType::StopWatching(BMessenger(this)); 881 } 882 883 884 void 885 BPoseView::DetachedFromWindow() 886 { 887 if (fTitleView && !fTitleView->Window()) 888 delete fTitleView; 889 890 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 891 if (tracker != NULL && tracker->Lock()) { 892 tracker->StopWatching(this, kShowSelectionWhenInactiveChanged); 893 tracker->StopWatching(this, kTransparentSelectionChanged); 894 tracker->StopWatching(this, kSortFolderNamesFirstChanged); 895 tracker->StopWatching(this, kHideDotFilesChanged); 896 tracker->StopWatching(this, kTypeAheadFilteringChanged); 897 tracker->Unlock(); 898 } 899 900 std::set<thread_id> addPosesThreads(fAddPosesThreads); 901 fAddPosesThreads.clear(); 902 // The threads check periodically if they are still valid, 903 // and clearing the list makes them all "invalid." 904 std::set<thread_id>::iterator it; 905 for (it = addPosesThreads.begin(); it != addPosesThreads.end(); it++) { 906 UnlockLooper(); 907 wait_for_thread(*it, NULL); 908 LockLooper(); 909 } 910 911 StopWatching(); 912 CommitActivePose(); 913 SavePoseLocations(); 914 915 FSClipboardStopWatch(this); 916 } 917 918 919 void 920 BPoseView::Pulse() 921 { 922 BContainerWindow* window = ContainerWindow(); 923 if (window == NULL) 924 return; 925 926 window->PulseTaskLoop(); 927 // make sure task loop gets pulsed properly, if installed 928 929 // update item count view in window if necessary 930 UpdateCount(); 931 932 if (fAutoScrollState != kAutoScrollOff) 933 HandleAutoScroll(); 934 935 // do we need to update scrollbars? 936 BRect extent = Extent(); 937 if ((fLastExtent != extent) || (fLastLeftTop != LeftTop())) { 938 uint32 buttons; 939 BPoint mouse; 940 GetMouse(&mouse, &buttons); 941 if (buttons == 0) { 942 UpdateScrollRange(); 943 fLastExtent = extent; 944 fLastLeftTop = LeftTop(); 945 } 946 } 947 948 // do we have a TextWidget waiting for expiracy of its double-click 949 // check? 950 if (fTextWidgetToCheck != NULL) 951 fTextWidgetToCheck->CheckExpiration(); 952 } 953 954 955 void 956 BPoseView::ScrollTo(BPoint where) 957 { 958 _inherited::ScrollTo(where); 959 960 // keep the view state in sync 961 if (ViewMode() == kListMode) 962 fViewState->SetListOrigin(LeftTop()); 963 else 964 fViewState->SetIconOrigin(LeftTop()); 965 } 966 967 968 void 969 BPoseView::AttachedToWindow() 970 { 971 fIsDesktopWindow = dynamic_cast<BDeskWindow*>(Window()) != NULL; 972 if (fIsDesktopWindow) 973 AddFilter(new TPoseViewFilter(this)); 974 else { 975 SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR); 976 SetLowUIColor(ViewUIColor()); 977 } 978 979 AddFilter(new ShortcutFilter(B_RETURN, B_OPTION_KEY, kOpenSelection, 980 this)); 981 // add Option-Return as a shortcut filter because AddShortcut 982 // doesn't allow us to have shortcuts without Command yet 983 AddFilter(new ShortcutFilter(B_ESCAPE, 0, B_CANCEL, this)); 984 // Escape key, used to abort an on-going clipboard cut or filtering 985 AddFilter(new ShortcutFilter(B_ESCAPE, B_SHIFT_KEY, 986 kCancelSelectionToClipboard, this)); 987 // Escape + SHIFT will remove current selection from clipboard, 988 // or all poses from current folder if 0 selected 989 990 AddFilter(new LongAndDragTrackingFilter(kMsgMouseLongDown, 991 kMsgMouseDragged)); 992 993 fLastLeftTop = LeftTop(); 994 995 // static - init just once 996 if (sFontHeight == -1) { 997 be_plain_font->GetHeight(&sFontInfo); 998 sFontHeight = sFontInfo.ascent + sFontInfo.descent 999 + sFontInfo.leading; 1000 } 1001 1002 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 1003 if (tracker != NULL && tracker->Lock()) { 1004 tracker->StartWatching(this, kShowSelectionWhenInactiveChanged); 1005 tracker->StartWatching(this, kTransparentSelectionChanged); 1006 tracker->StartWatching(this, kSortFolderNamesFirstChanged); 1007 tracker->StartWatching(this, kHideDotFilesChanged); 1008 tracker->StartWatching(this, kTypeAheadFilteringChanged); 1009 tracker->Unlock(); 1010 } 1011 1012 FSClipboardStartWatch(this); 1013 } 1014 1015 1016 BSize 1017 BPoseView::IconSize() const 1018 { 1019 if (fCachedIconSizeFrom != fViewState->IconSize()) { 1020 fCachedIconSizeFrom = fViewState->IconSize(); 1021 fCachedIconSize = be_control_look->ComposeIconSize(fCachedIconSizeFrom); 1022 } 1023 return fCachedIconSize; 1024 } 1025 1026 1027 void 1028 BPoseView::SetIconPoseHeight() 1029 { 1030 switch (ViewMode()) { 1031 case kIconMode: 1032 // IconSize should already be set in MessageReceived() 1033 fIconPoseHeight = ceilf(IconSizeInt() + sFontHeight + 1); 1034 break; 1035 1036 case kMiniIconMode: 1037 fViewState->SetIconSize(B_MINI_ICON); 1038 fIconPoseHeight = std::max((float)IconSizeInt(), sFontHeight + 1); 1039 break; 1040 1041 case kListMode: 1042 default: 1043 fViewState->SetIconSize(B_MINI_ICON); 1044 fIconPoseHeight = fListElemHeight; 1045 break; 1046 } 1047 } 1048 1049 1050 void 1051 BPoseView::GetLayoutInfo(uint32 mode, BPoint* grid, BPoint* offset) const 1052 { 1053 switch (mode) { 1054 case kMiniIconMode: 1055 grid->Set(IconSizeInt() * 6, ceilf(IconSizeInt() * 1.25f)); 1056 offset->Set(ceilf(IconSizeInt() * 0.6f), ceilf(IconSizeInt() * 0.3f)); 1057 break; 1058 1059 case kIconMode: 1060 { 1061 const float gridOffset = ceilf(IconSizeInt() * 0.875f), 1062 offsetValue = ceilf(IconSizeInt() * 0.625f); 1063 grid->Set(IconSizeInt() + gridOffset, IconSizeInt() + gridOffset); 1064 offset->Set(offsetValue, offsetValue); 1065 break; 1066 } 1067 1068 default: 1069 { 1070 const float labelSpacing = be_control_look->DefaultLabelSpacing(); 1071 grid->Set(0, 0); 1072 offset->Set(labelSpacing - 1, labelSpacing - 1); 1073 break; 1074 } 1075 } 1076 } 1077 1078 1079 void 1080 BPoseView::ScrollView(int32 type) 1081 { 1082 if (fVScrollBar == NULL) 1083 return; 1084 1085 float max, min; 1086 fVScrollBar->GetSteps(&min, &max); 1087 1088 switch (type) { 1089 case B_HOME: 1090 fVScrollBar->SetValue(0); 1091 break; 1092 case B_END: 1093 fVScrollBar->SetValue(max); 1094 break; 1095 case B_PAGE_UP: 1096 fVScrollBar->SetValue(fVScrollBar->Value() - max); 1097 break; 1098 case B_PAGE_DOWN: 1099 fVScrollBar->SetValue(fVScrollBar->Value() + max); 1100 break; 1101 } 1102 } 1103 1104 1105 void 1106 BPoseView::MakeFocus(bool focused) 1107 { 1108 bool invalidate = false; 1109 if (focused != IsFocus()) 1110 invalidate = true; 1111 1112 _inherited::MakeFocus(focused); 1113 1114 if (invalidate) { 1115 BorderedView* view = dynamic_cast<BorderedView*>(Parent()); 1116 if (view != NULL) 1117 view->PoseViewFocused(focused); 1118 } 1119 } 1120 1121 1122 BSize 1123 BPoseView::MinSize() 1124 { 1125 // Between the BTitleView, BCountView, and scrollbars, 1126 // we don't need any extra room. 1127 return BSize(0, 0); 1128 } 1129 1130 1131 void 1132 BPoseView::WindowActivated(bool active) 1133 { 1134 if (!active) 1135 CommitActivePose(); 1136 1137 ShowSelection(active); 1138 1139 if (active && ActivePose() == NULL && !IsFilePanel()) 1140 MakeFocus(); 1141 } 1142 1143 1144 void 1145 BPoseView::SetActivePose(BPose* pose) 1146 { 1147 if (pose != ActivePose()) { 1148 CommitActivePose(); 1149 fActivePose = pose; 1150 } 1151 } 1152 1153 1154 void 1155 BPoseView::CommitActivePose(bool saveChanges) 1156 { 1157 BPose* activePose = ActivePose(); 1158 if (activePose != NULL) { 1159 int32 index = fPoseList->IndexOf(ActivePose()); 1160 if (fFiltering) 1161 index = fFilteredPoseList->IndexOf(ActivePose()); 1162 1163 BPoint loc(0, index * fListElemHeight); 1164 if (ViewMode() != kListMode) 1165 loc = ActivePose()->Location(this); 1166 1167 activePose->Commit(saveChanges, loc, this, index); 1168 BPose* pose = fActivePose; 1169 fActivePose = NULL; 1170 if (fFiltering && !FilterPose(pose)) 1171 RemoveFilteredPose(pose, index); 1172 } 1173 } 1174 1175 1176 EntryListBase* 1177 BPoseView::InitDirentIterator(const entry_ref* ref) 1178 { 1179 // set up a directory iteration 1180 Model sourceModel(ref, false, true); 1181 if (sourceModel.InitCheck() != B_OK) 1182 return NULL; 1183 1184 ASSERT(!sourceModel.IsQuery()); 1185 ASSERT(!sourceModel.IsVirtualDirectory()); 1186 ASSERT(sourceModel.Node() != NULL); 1187 1188 BDirectory* directory = dynamic_cast<BDirectory*>(sourceModel.Node()); 1189 1190 ASSERT(directory != NULL); 1191 1192 if (directory == NULL) { 1193 HideBarberPole(); 1194 return NULL; 1195 } 1196 1197 EntryListBase* entryList = new CachedDirectoryEntryList(*directory); 1198 if (entryList->Rewind() != B_OK) { 1199 delete entryList; 1200 HideBarberPole(); 1201 return NULL; 1202 } 1203 1204 TTracker::WatchNode(sourceModel.NodeRef(), B_WATCH_DIRECTORY 1205 | B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR, this); 1206 1207 return entryList; 1208 } 1209 1210 1211 void 1212 BPoseView::ReturnDirentIterator(EntryListBase* iterator) 1213 { 1214 delete iterator; 1215 } 1216 1217 1218 uint32 1219 BPoseView::WatchNewNodeMask() 1220 { 1221 return B_WATCH_STAT | B_WATCH_INTERIM_STAT | B_WATCH_ATTR; 1222 } 1223 1224 1225 status_t 1226 BPoseView::WatchNewNode(const node_ref* item) 1227 { 1228 return WatchNewNode(item, WatchNewNodeMask(), BMessenger(this)); 1229 } 1230 1231 1232 status_t 1233 BPoseView::WatchNewNode(const node_ref* item, uint32 mask, BMessenger messenger) 1234 { 1235 status_t result = TTracker::WatchNode(item, mask, messenger); 1236 1237 #if DEBUG 1238 if (result != B_OK) 1239 PRINT(("failed to watch node %s\n", strerror(result))); 1240 #endif 1241 1242 return result; 1243 } 1244 1245 1246 struct AddPosesParams { 1247 BMessenger target; 1248 entry_ref ref; 1249 }; 1250 1251 1252 bool 1253 BPoseView::IsValidAddPosesThread(thread_id currentThread) const 1254 { 1255 return fAddPosesThreads.find(currentThread) != fAddPosesThreads.end(); 1256 } 1257 1258 1259 void 1260 BPoseView::AddPoses(Model* model) 1261 { 1262 // if model is zero, PoseView has other means of iterating through all 1263 // the entries that it adds 1264 if (model != NULL) { 1265 TrackerSettings settings; 1266 if (model->IsRoot()) { 1267 AddRootPoses(true, settings.MountSharedVolumesOntoDesktop()); 1268 return; 1269 } else if (IsDesktopView() 1270 && (settings.MountVolumesOntoDesktop() || settings.ShowDisksIcon() 1271 || (IsFilePanel() && settings.DesktopFilePanelRoot()))) 1272 AddRootPoses(true, settings.MountSharedVolumesOntoDesktop()); 1273 } 1274 1275 ShowBarberPole(); 1276 1277 AddPosesParams* params = new AddPosesParams(); 1278 BMessenger tmp(this); 1279 params->target = tmp; 1280 1281 if (model != NULL) 1282 params->ref = *model->EntryRef(); 1283 1284 thread_id addPosesThread = spawn_thread(&BPoseView::AddPosesTask, 1285 "add poses", B_DISPLAY_PRIORITY, params); 1286 1287 if (addPosesThread >= B_OK) { 1288 fAddPosesThreads.insert(addPosesThread); 1289 resume_thread(addPosesThread); 1290 } else 1291 delete params; 1292 } 1293 1294 1295 class AutoLockingMessenger { 1296 // Note: 1297 // this locker requires that you lock/unlock the messenger and associated 1298 // looper only through the autolocker interface, otherwise the hasLock 1299 // flag gets out of sync 1300 // 1301 // Also, this class represents the entire BMessenger, not just it's 1302 // autolocker (unlike MessengerAutoLocker) 1303 public: 1304 AutoLockingMessenger(const BMessenger &target, bool lockLater = false) 1305 : 1306 messenger(target), 1307 hasLock(false) 1308 { 1309 if (!lockLater) 1310 hasLock = messenger.LockTarget(); 1311 } 1312 1313 ~AutoLockingMessenger() 1314 { 1315 if (hasLock) { 1316 BLooper* looper; 1317 messenger.Target(&looper); 1318 ASSERT(looper->IsLocked()); 1319 looper->Unlock(); 1320 } 1321 } 1322 1323 bool Lock() 1324 { 1325 if (!hasLock) 1326 hasLock = messenger.LockTarget(); 1327 1328 return hasLock; 1329 } 1330 1331 bool IsLocked() const 1332 { 1333 return hasLock; 1334 } 1335 1336 void Unlock() 1337 { 1338 if (hasLock) { 1339 BLooper* looper; 1340 messenger.Target(&looper); 1341 ASSERT(looper); 1342 looper->Unlock(); 1343 hasLock = false; 1344 } 1345 } 1346 1347 BLooper* Looper() const 1348 { 1349 BLooper* looper; 1350 messenger.Target(&looper); 1351 return looper; 1352 } 1353 1354 BHandler* Handler() const 1355 { 1356 ASSERT(hasLock); 1357 return messenger.Target(0); 1358 } 1359 1360 BMessenger Target() const 1361 { 1362 return messenger; 1363 } 1364 1365 private: 1366 BMessenger messenger; 1367 bool hasLock; 1368 }; 1369 1370 1371 class failToLock { /* exception in AddPoses */ }; 1372 1373 1374 status_t 1375 BPoseView::AddPosesTask(void* castToParams) 1376 { 1377 // AddPosesTask reads a bunch of models and passes them off to 1378 // the pose placing and drawing routine. 1379 1380 AddPosesParams* params = (AddPosesParams*)castToParams; 1381 BMessenger target(params->target); 1382 entry_ref ref(params->ref); 1383 1384 delete params; 1385 1386 AutoLockingMessenger lock(target); 1387 1388 if (!lock.IsLocked()) 1389 return B_ERROR; 1390 1391 thread_id threadID = find_thread(NULL); 1392 1393 BPoseView* view = dynamic_cast<BPoseView*>(lock.Handler()); 1394 ThrowOnAssert(view != NULL); 1395 1396 BWindow* window = dynamic_cast<BWindow*>(lock.Looper()); 1397 ThrowOnAssert(window != NULL); 1398 1399 // allocate the iterator we will use for adding poses; this 1400 // can be a directory or any other collection of entry_refs, such 1401 // as results of a query; subclasses override this to provide 1402 // other than standard directory iterations 1403 EntryListBase* container = view->InitDirentIterator(&ref); 1404 if (container == NULL) { 1405 view->HideBarberPole(); 1406 return B_ERROR; 1407 } 1408 1409 AddPosesResult* posesResult = new AddPosesResult; 1410 posesResult->fCount = 0; 1411 int32 modelChunkIndex = -1; 1412 bigtime_t nextChunkTime = 0; 1413 uint32 watchMask = view->WatchNewNodeMask(); 1414 1415 bool hideDotFiles = TrackerSettings().HideDotFiles(); 1416 #if DEBUG 1417 for (int32 index = 0; index < kMaxAddPosesChunk; index++) 1418 posesResult->fModels[index] = (Model*)0xdeadbeef; 1419 #endif 1420 1421 try { 1422 for (;;) { 1423 lock.Unlock(); 1424 1425 status_t result = B_OK; 1426 char entBuf[1024]; 1427 dirent* eptr = (dirent*)entBuf; 1428 Model* model = 0; 1429 node_ref dirNode; 1430 node_ref itemNode; 1431 1432 int32 count = container->GetNextDirents(eptr, 1024, 1); 1433 if (count <= 0 && modelChunkIndex == -1) 1434 break; 1435 1436 if (count > 0) { 1437 ASSERT(count == 1); 1438 1439 if ((!hideDotFiles && (!strcmp(eptr->d_name, ".") 1440 || !strcmp(eptr->d_name, ".."))) 1441 || (hideDotFiles && eptr->d_name[0] == '.')) { 1442 continue; 1443 } 1444 1445 dirNode.device = eptr->d_pdev; 1446 dirNode.node = eptr->d_pino; 1447 itemNode.device = eptr->d_dev; 1448 itemNode.node = eptr->d_ino; 1449 1450 BPoseView::WatchNewNode(&itemNode, watchMask, lock.Target()); 1451 // have to node monitor ahead of time because Model will 1452 // cache up the file type and preferred app 1453 // OK to call when poseView is not locked 1454 model = new Model(&dirNode, &itemNode, eptr->d_name, false); 1455 result = model->InitCheck(); 1456 modelChunkIndex++; 1457 posesResult->fModels[modelChunkIndex] = model; 1458 } 1459 1460 // before we access the pose view, lock down the window 1461 1462 if (!lock.Lock()) { 1463 PRINT(("failed to lock\n")); 1464 posesResult->fCount = modelChunkIndex + 1; 1465 throw failToLock(); 1466 } 1467 1468 if (!view->IsValidAddPosesThread(threadID)) { 1469 // this handles the case of a file panel when the directory is 1470 // switched and an old AddPosesTask needs to die. 1471 // we might no longer be the current async thread 1472 // for this view - if not then we're done 1473 view->HideBarberPole(); 1474 1475 view->ReturnDirentIterator(container); 1476 container = NULL; 1477 1478 // for now use the same cleanup as failToLock does 1479 posesResult->fCount = modelChunkIndex + 1; 1480 throw failToLock(); 1481 } 1482 1483 if (count > 0) { 1484 // try to watch the model, no matter what 1485 1486 if (result != B_OK) { 1487 // failed to init pose, model is a zombie, add to zombie 1488 // list 1489 PRINT(("1 adding model %s to zombie list, error %s\n", 1490 model->Name(), strerror(model->InitCheck()))); 1491 view->fZombieList->AddItem(model); 1492 modelChunkIndex--; 1493 continue; 1494 } 1495 1496 view->ReadPoseInfo(model, 1497 &posesResult->fPoseInfos[modelChunkIndex]); 1498 1499 if (!PoseVisible(model, 1500 &posesResult->fPoseInfos[modelChunkIndex])) { 1501 modelChunkIndex--; 1502 continue; 1503 } 1504 1505 if (model->IsSymLink()) 1506 view->CreateSymlinkPoseTarget(model); 1507 } 1508 1509 bigtime_t now = system_time(); 1510 1511 if (count <= 0 || modelChunkIndex >= kMaxAddPosesChunk - 1 1512 || now > nextChunkTime) { 1513 // keep getting models until we get <kMaxAddPosesChunk> of them 1514 // or until 300000 runs out 1515 1516 ASSERT(modelChunkIndex >= 0); 1517 1518 // send of the created poses 1519 1520 posesResult->fCount = modelChunkIndex + 1; 1521 BMessage creationData(kAddNewPoses); 1522 creationData.AddPointer("currentPoses", posesResult); 1523 creationData.AddRef("ref", &ref); 1524 1525 lock.Target().SendMessage(&creationData); 1526 1527 modelChunkIndex = -1; 1528 nextChunkTime = now + 300000; 1529 1530 posesResult = new AddPosesResult; 1531 posesResult->fCount = 0; 1532 1533 snooze(500); 1534 // be nice 1535 } 1536 1537 if (count <= 0) 1538 break; 1539 } 1540 1541 BMessage finishedSending(kAddPosesCompleted); 1542 lock.Target().SendMessage(&finishedSending); 1543 1544 } catch (failToLock) { 1545 // we are here because the window got closed or otherwise failed to 1546 // lock 1547 1548 PRINT(("add_poses cleanup \n")); 1549 // failed to lock window, bail 1550 delete posesResult; 1551 delete container; 1552 1553 return B_ERROR; 1554 } 1555 1556 ASSERT(modelChunkIndex == -1); 1557 1558 delete posesResult; 1559 1560 if (lock.Lock()) { 1561 view->ReturnDirentIterator(container); 1562 view->fAddPosesThreads.erase(threadID); 1563 } else 1564 delete container; 1565 1566 return B_OK; 1567 } 1568 1569 1570 void 1571 BPoseView::AddRootPoses(bool watchIndividually, bool mountShared) 1572 { 1573 BVolumeRoster roster; 1574 roster.Rewind(); 1575 BVolume volume; 1576 1577 if (TrackerSettings().ShowDisksIcon() && !TargetModel()->IsRoot()) { 1578 BEntry entry("/"); 1579 Model model(&entry); 1580 if (model.InitCheck() == B_OK) { 1581 BMessage monitorMsg; 1582 monitorMsg.what = B_NODE_MONITOR; 1583 1584 monitorMsg.AddInt32("opcode", B_ENTRY_CREATED); 1585 1586 monitorMsg.AddInt32("device", model.NodeRef()->device); 1587 monitorMsg.AddInt64("node", model.NodeRef()->node); 1588 monitorMsg.AddInt64("directory", model.EntryRef()->directory); 1589 monitorMsg.AddString("name", model.EntryRef()->name); 1590 if (Window()) 1591 Window()->PostMessage(&monitorMsg, this); 1592 } 1593 } else { 1594 while (roster.GetNextVolume(&volume) == B_OK) { 1595 if (!volume.IsPersistent()) 1596 continue; 1597 1598 if (volume.IsShared() && !mountShared) 1599 continue; 1600 1601 CreateVolumePose(&volume, watchIndividually); 1602 } 1603 } 1604 1605 SortPoses(); 1606 UpdateCount(); 1607 Invalidate(); 1608 } 1609 1610 1611 void 1612 BPoseView::RemoveRootPoses() 1613 { 1614 int32 index; 1615 int32 count = fPoseList->CountItems(); 1616 for (index = 0; index < count;) { 1617 BPose* pose = fPoseList->ItemAt(index); 1618 if (pose != NULL) { 1619 Model* model = pose->TargetModel(); 1620 if (model != NULL) { 1621 if (model->IsVolume()) { 1622 DeletePose(model->NodeRef()); 1623 count--; 1624 } else 1625 index++; 1626 } 1627 } 1628 } 1629 1630 SortPoses(); 1631 UpdateCount(); 1632 Invalidate(); 1633 } 1634 1635 1636 void 1637 BPoseView::AddTrashPoses() 1638 { 1639 // the trash window needs to display a union of all the 1640 // trash folders from all the mounted volumes 1641 BVolumeRoster volRoster; 1642 volRoster.Rewind(); 1643 BVolume volume; 1644 while (volRoster.GetNextVolume(&volume) == B_OK) { 1645 if (!volume.IsPersistent()) 1646 continue; 1647 1648 BDirectory trashDir; 1649 BEntry entry; 1650 if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK 1651 && trashDir.GetEntry(&entry) == B_OK) { 1652 Model model(&entry); 1653 if (model.InitCheck() == B_OK) 1654 AddPoses(&model); 1655 } 1656 } 1657 } 1658 1659 1660 void 1661 BPoseView::AddPosesCompleted() 1662 { 1663 BContainerWindow* containerWindow = ContainerWindow(); 1664 if (containerWindow != NULL) 1665 containerWindow->AddMimeTypesToMenu(); 1666 1667 // if we're not in icon mode then we need to check for poses that 1668 // were "auto" placed to see if they overlap with other icons 1669 if (ViewMode() != kListMode) 1670 CheckAutoPlacedPoses(); 1671 1672 UpdateScrollRange(); 1673 HideBarberPole(); 1674 1675 // make sure that the last item in the list is not placed 1676 // above the top of the view (leaving you with an empty window) 1677 if (ViewMode() == kListMode) { 1678 BRect bounds(Bounds()); 1679 float lastItemTop = (CurrentPoseList()->CountItems() - 1) 1680 * fListElemHeight; 1681 if (bounds.top > lastItemTop) 1682 BView::ScrollTo(bounds.left, std::max(lastItemTop, 0.0f)); 1683 } 1684 } 1685 1686 1687 void 1688 BPoseView::CreateVolumePose(BVolume* volume, bool watchIndividually) 1689 { 1690 if (volume->InitCheck() != B_OK || !volume->IsPersistent()) { 1691 // We never want to create poses for those volumes; the file 1692 // system root, /pipe, /dev, etc. are all non-persistent 1693 return; 1694 } 1695 1696 BDirectory root; 1697 if (volume->GetRootDirectory(&root) != B_OK) 1698 return; 1699 1700 BEntry entry; 1701 root.GetEntry(&entry); 1702 1703 entry_ref ref; 1704 entry.GetRef(&ref); 1705 1706 // If the volume is mounted at a directory of a persistent volume, we don't 1707 // want it on the desktop or in the disks window. 1708 BVolume parentVolume(ref.device); 1709 if (parentVolume.InitCheck() == B_OK && parentVolume.IsPersistent()) 1710 return; 1711 1712 node_ref itemNode; 1713 root.GetNodeRef(&itemNode); 1714 1715 node_ref dirNode; 1716 dirNode.device = ref.device; 1717 dirNode.node = ref.directory; 1718 1719 BPose* pose = EntryCreated(&dirNode, &itemNode, ref.name, 0); 1720 if (pose != NULL && watchIndividually) { 1721 // make sure volume names still get watched, even though 1722 // they are on the desktop which is not their physical parent 1723 pose->TargetModel()->WatchVolumeAndMountPoint(B_WATCH_NAME 1724 | B_WATCH_STAT | B_WATCH_ATTR, this); 1725 } 1726 } 1727 1728 1729 void 1730 BPoseView::CreateTrashPose() 1731 { 1732 BVolume volume; 1733 if (BVolumeRoster().GetBootVolume(&volume) == B_OK) { 1734 BDirectory trash; 1735 BEntry entry; 1736 node_ref ref; 1737 if (FSGetTrashDir(&trash, volume.Device()) == B_OK 1738 && trash.GetEntry(&entry) == B_OK 1739 && entry.GetNodeRef(&ref) == B_OK) { 1740 WatchNewNode(&ref); 1741 Model* model = new Model(&entry); 1742 PoseInfo info; 1743 ReadPoseInfo(model, &info); 1744 CreatePose(model, &info, false, NULL, NULL, true); 1745 } 1746 } 1747 } 1748 1749 1750 BPose* 1751 BPoseView::CreatePose(Model* model, PoseInfo* poseInfo, bool insertionSort, 1752 int32* indexPtr, BRect* boundsPointer, bool forceDraw) 1753 { 1754 BPose* result; 1755 CreatePoses(&model, poseInfo, 1, &result, insertionSort, indexPtr, 1756 boundsPointer, forceDraw); 1757 1758 return result; 1759 } 1760 1761 1762 void 1763 BPoseView::FinishPendingScroll(float &listViewScrollBy, BRect srcRect) 1764 { 1765 if (listViewScrollBy == 0.0) 1766 return; 1767 1768 // copy top contents to bottom and 1769 // redraw from top to top of part that could be copied 1770 1771 if (srcRect.Width() > listViewScrollBy) { 1772 BRect dstRect = srcRect; 1773 srcRect.bottom -= listViewScrollBy; 1774 dstRect.top += listViewScrollBy; 1775 CopyBits(srcRect, dstRect); 1776 listViewScrollBy = 0; 1777 srcRect.bottom = dstRect.top; 1778 } 1779 SynchronousUpdate(srcRect); 1780 } 1781 1782 1783 bool 1784 BPoseView::AddPosesThreadValid(const entry_ref* ref) const 1785 { 1786 return *(TargetModel()->EntryRef()) == *ref || ContainerWindow()->IsTrash(); 1787 } 1788 1789 1790 void 1791 BPoseView::AddPoseToList(PoseList* list, bool visibleList, bool insertionSort, 1792 BPose* pose, BRect &viewBounds, float &listViewScrollBy, bool forceDraw, 1793 int32* indexPtr) 1794 { 1795 int32 poseIndex = list->CountItems(); 1796 1797 BRect poseBounds; 1798 bool havePoseBounds = false; 1799 bool addedItem = false; 1800 bool needToDraw = true; 1801 1802 if (insertionSort && poseIndex > 0) { 1803 int32 orientation = BSearchList(list, pose, &poseIndex, poseIndex); 1804 1805 if (orientation == kInsertAfter) 1806 poseIndex++; 1807 1808 if (visibleList) { 1809 // we only care about the positions if this is a visible list 1810 poseBounds = CalcPoseRectList(pose, poseIndex); 1811 havePoseBounds = true; 1812 1813 // Simple optimization: if the new pose bounds is completely below 1814 // the current view bounds, we do not need to draw. 1815 if (poseBounds.top > viewBounds.bottom) { 1816 needToDraw = false; 1817 } else { 1818 // The new pose may need to be placed where another pose already 1819 // is. This code creates some rects where we either need to 1820 // slide some already drawn poses down, or at least update the 1821 // rect where the new pose is. 1822 BRect srcRect(Extent()); 1823 srcRect.top = poseBounds.top; 1824 srcRect = srcRect & viewBounds; 1825 BRect destRect(srcRect); 1826 destRect.OffsetBy(0, fListElemHeight); 1827 1828 // special case the addition of a pose that scrolls 1829 // the extent into the view for the first time: 1830 if (destRect.bottom > viewBounds.top 1831 && destRect.top > destRect.bottom) { 1832 // make destRect valid 1833 destRect.top = viewBounds.top; 1834 } 1835 1836 // TODO: As long as either srcRect or destRect are valid, this 1837 // will always be true because srcRect is built from viewBounds. 1838 // Many times they are not valid, but most of the time they are, 1839 // and in a folder with a lot of contents this causes a lot of 1840 // unnecessary drawing. Hence the optimization above. This all 1841 // just needs to be rethought completely. Similar code is in 1842 // BPoseView::InsertPoseAfter. 1843 if (srcRect.Intersects(viewBounds) 1844 || destRect.Intersects(viewBounds)) { 1845 // The visual area is affected by the insertion. 1846 // If items have been added above the visual area, 1847 // delay the scrolling. srcRect.bottom holds the 1848 // current Extent(). So if the bottom is still above 1849 // the viewBounds top, it means the view is scrolled 1850 // to show the area below the items that have already 1851 // been added. 1852 if (srcRect.top == viewBounds.top 1853 && srcRect.bottom >= viewBounds.top 1854 && poseIndex != 0) { 1855 // if new pose above current view bounds, cache up 1856 // the draw and do it later 1857 listViewScrollBy += fListElemHeight; 1858 needToDraw = false; 1859 } else { 1860 FinishPendingScroll(listViewScrollBy, viewBounds); 1861 list->AddItem(pose, poseIndex); 1862 1863 fMimeTypeListIsDirty = true; 1864 addedItem = true; 1865 if (srcRect.IsValid()) { 1866 // Slide the already drawn bits down. 1867 CopyBits(srcRect, destRect); 1868 // Shrink the srcRect down to the just the part that 1869 // needs to be redrawn. 1870 srcRect.bottom = destRect.top; 1871 SynchronousUpdate(srcRect); 1872 } else { 1873 // This is probably the bottom of the view or just 1874 // got scrolled into view. 1875 SynchronousUpdate(destRect); 1876 } 1877 needToDraw = false; 1878 } 1879 } 1880 } 1881 } 1882 } 1883 1884 if (!addedItem) { 1885 list->AddItem(pose, poseIndex); 1886 fMimeTypeListIsDirty = true; 1887 } 1888 1889 if (visibleList && needToDraw && forceDraw) { 1890 if (!havePoseBounds) 1891 poseBounds = CalcPoseRectList(pose, poseIndex); 1892 1893 if (viewBounds.Intersects(poseBounds)) 1894 SynchronousUpdate(poseBounds); 1895 } 1896 1897 if (indexPtr) 1898 *indexPtr = poseIndex; 1899 } 1900 1901 1902 void 1903 BPoseView::CreatePoses(Model** models, PoseInfo* poseInfoArray, int32 count, 1904 BPose** resultingPoses, bool insertionSort, int32* lastPoseIndexPointer, 1905 BRect* boundsPointer, bool forceDraw) 1906 { 1907 // were we passed the bounds of the view? 1908 BRect viewBounds; 1909 if (boundsPointer != NULL) 1910 viewBounds = *boundsPointer; 1911 else 1912 viewBounds = Bounds(); 1913 1914 bool clipboardLocked = be_clipboard->Lock(); 1915 1916 int32 poseIndex = 0; 1917 uint32 clipboardMode = 0; 1918 float listViewScrollBy = 0; 1919 for (int32 modelIndex = 0; modelIndex < count; modelIndex++) { 1920 Model* model = models[modelIndex]; 1921 1922 // pose adopts model and deletes it when done 1923 if (fInsertedNodes.Contains(*(model->NodeRef())) 1924 || FindZombie(model->NodeRef())) { 1925 watch_node(model->NodeRef(), B_STOP_WATCHING, this); 1926 delete model; 1927 if (resultingPoses) 1928 resultingPoses[modelIndex] = NULL; 1929 1930 continue; 1931 } else 1932 fInsertedNodes.Add(*(model->NodeRef())); 1933 1934 if ((clipboardMode = FSClipboardFindNodeMode(model, !clipboardLocked, 1935 true)) != 0 && !HasPosesInClipboard()) { 1936 SetHasPosesInClipboard(true); 1937 } 1938 1939 model->OpenNode(); 1940 ASSERT(model->IsNodeOpen()); 1941 PoseInfo* poseInfo = &poseInfoArray[modelIndex]; 1942 BPose* pose = new BPose(model, this, clipboardMode); 1943 1944 if (resultingPoses) 1945 resultingPoses[modelIndex] = pose; 1946 1947 // set location from poseinfo if saved loc was for this dir 1948 if (poseInfo->fInitedDirectory != -1LL) { 1949 PinPointToValidRange(poseInfo->fLocation); 1950 pose->SetLocation(poseInfo->fLocation, this); 1951 AddToVSList(pose); 1952 } 1953 1954 BRect poseBounds; 1955 1956 switch (ViewMode()) { 1957 case kListMode: 1958 { 1959 AddPoseToList(fPoseList, !fFiltering, insertionSort, pose, 1960 viewBounds, listViewScrollBy, forceDraw, &poseIndex); 1961 1962 if (fFiltering && FilterPose(pose)) { 1963 AddPoseToList(fFilteredPoseList, true, insertionSort, pose, 1964 viewBounds, listViewScrollBy, forceDraw, &poseIndex); 1965 } 1966 1967 break; 1968 } 1969 1970 case kIconMode: 1971 case kMiniIconMode: 1972 if (poseInfo->fInitedDirectory == -1LL || fAlwaysAutoPlace) { 1973 if (pose->HasLocation()) 1974 RemoveFromVSList(pose); 1975 1976 PlacePose(pose, viewBounds); 1977 1978 // we set a flag in the pose here to signify that we were 1979 // auto placed - after adding all poses to window, we're 1980 // going to go back and make sure that the auto placed poses 1981 // don't overlap previously positioned icons. If so, we'll 1982 // move them to new positions. 1983 if (!fAlwaysAutoPlace) 1984 pose->SetAutoPlaced(true); 1985 1986 AddToVSList(pose); 1987 } 1988 1989 // add item to list and draw if necessary 1990 fPoseList->AddItem(pose); 1991 fMimeTypeListIsDirty = true; 1992 1993 poseBounds = pose->CalcRect(this); 1994 1995 if (fEnsurePosesVisible && !viewBounds.Intersects(poseBounds)) { 1996 viewBounds.InsetBy(20, 20); 1997 RemoveFromVSList(pose); 1998 BPoint loc(pose->Location(this)); 1999 loc.ConstrainTo(viewBounds); 2000 pose->SetLocation(loc, this); 2001 pose->SetSaveLocation(); 2002 AddToVSList(pose); 2003 poseBounds = pose->CalcRect(this); 2004 viewBounds.InsetBy(-20, -20); 2005 } 2006 2007 if (forceDraw && viewBounds.Intersects(poseBounds)) 2008 Invalidate(poseBounds); 2009 2010 // if this is the first item then we set extent here 2011 if (!fExtent.IsValid()) 2012 fExtent = poseBounds; 2013 else 2014 AddToExtent(poseBounds); 2015 2016 break; 2017 } 2018 if (model->IsSymLink()) 2019 model->ResolveIfLink()->CloseNode(); 2020 2021 model->CloseNode(); 2022 } 2023 2024 if (clipboardLocked) 2025 be_clipboard->Unlock(); 2026 2027 FinishPendingScroll(listViewScrollBy, viewBounds); 2028 2029 if (lastPoseIndexPointer != NULL) 2030 *lastPoseIndexPointer = poseIndex; 2031 } 2032 2033 2034 bool 2035 BPoseView::PoseVisible(const Model* model, const PoseInfo* poseInfo) 2036 { 2037 return !poseInfo->fInvisible; 2038 } 2039 2040 2041 bool 2042 BPoseView::ShouldShowPose(const Model* model, const PoseInfo* poseInfo) 2043 { 2044 if (!PoseVisible(model, poseInfo)) 2045 return false; 2046 2047 // check filter before adding item 2048 if (!fRefFilter) 2049 return true; 2050 2051 struct stat_beos stat; 2052 convert_to_stat_beos(model->StatBuf(), &stat); 2053 2054 return fRefFilter->Filter(model->EntryRef(), model->Node(), &stat, 2055 model->MimeType()); 2056 } 2057 2058 2059 const char* 2060 BPoseView::MimeTypeAt(int32 index) 2061 { 2062 if (fMimeTypeListIsDirty) 2063 RefreshMimeTypeList(); 2064 2065 return fMimeTypeList->ItemAt(index)->String(); 2066 } 2067 2068 2069 int32 2070 BPoseView::CountMimeTypes() 2071 { 2072 if (fMimeTypeListIsDirty) 2073 RefreshMimeTypeList(); 2074 2075 return fMimeTypeList->CountItems(); 2076 } 2077 2078 2079 void 2080 BPoseView::AddMimeType(const char* mimeType) 2081 { 2082 int32 count = fMimeTypeList->CountItems(); 2083 for (int32 index = 0; index < count; index++) { 2084 if (*fMimeTypeList->ItemAt(index) == mimeType) 2085 return; 2086 } 2087 2088 fMimeTypeList->AddItem(new BString(mimeType)); 2089 } 2090 2091 2092 void 2093 BPoseView::RefreshMimeTypeList() 2094 { 2095 fMimeTypeList->MakeEmpty(); 2096 fMimeTypeListIsDirty = false; 2097 2098 for (int32 index = 0;; index++) { 2099 BPose* pose = PoseAtIndex(index); 2100 if (pose == NULL) 2101 break; 2102 2103 Model* targetModel = pose->TargetModel(); 2104 if (targetModel != NULL) 2105 AddMimeType(targetModel->MimeType()); 2106 } 2107 } 2108 2109 2110 void 2111 BPoseView::InsertPoseAfter(BPose* pose, int32* index, int32 orientation, 2112 BRect* invalidRect) 2113 { 2114 if (orientation == kInsertAfter) { 2115 // TODO: get rid of this 2116 (*index)++; 2117 } 2118 2119 BRect bounds(Bounds()); 2120 // copy the good bits in the list 2121 BRect srcRect(Extent()); 2122 srcRect.top = CalcPoseRectList(pose, *index).top; 2123 srcRect = srcRect & bounds; 2124 BRect destRect(srcRect); 2125 destRect.OffsetBy(0, fListElemHeight); 2126 2127 if (srcRect.Intersects(bounds) || destRect.Intersects(bounds)) 2128 CopyBits(srcRect, destRect); 2129 2130 // this is the invalid rectangle 2131 srcRect.bottom = destRect.top; 2132 *invalidRect = srcRect; 2133 } 2134 2135 2136 void 2137 BPoseView::DisableScrollBars() 2138 { 2139 if (fHScrollBar != NULL) 2140 fHScrollBar->SetTarget((BView*)NULL); 2141 2142 if (fVScrollBar != NULL) 2143 fVScrollBar->SetTarget((BView*)NULL); 2144 } 2145 2146 2147 void 2148 BPoseView::EnableScrollBars() 2149 { 2150 if (fHScrollBar != NULL) 2151 fHScrollBar->SetTarget(this); 2152 2153 if (fVScrollBar != NULL) 2154 fVScrollBar->SetTarget(this); 2155 } 2156 2157 2158 void 2159 BPoseView::AddScrollBars() 2160 { 2161 fHScrollBar = new TScrollBar("HScrollBar", this, 0, 100); 2162 fVScrollBar = new BScrollBar("VScrollBar", this, 0, 100, B_VERTICAL); 2163 } 2164 2165 2166 void 2167 BPoseView::UpdateCount() 2168 { 2169 if (fCountView != NULL) 2170 fCountView->CheckCount(); 2171 } 2172 2173 2174 void 2175 BPoseView::MessageReceived(BMessage* message) 2176 { 2177 if (message->WasDropped() && HandleMessageDropped(message)) 2178 return; 2179 2180 if (HandleScriptingMessage(message)) 2181 return; 2182 2183 switch (message->what) { 2184 case kAddNewPoses: 2185 { 2186 AddPosesResult* currentPoses; 2187 entry_ref ref; 2188 if (message->FindPointer("currentPoses", 2189 reinterpret_cast<void**>(¤tPoses)) == B_OK 2190 && message->FindRef("ref", &ref) == B_OK) { 2191 // check if CreatePoses should be called 2192 // (abort if dir has been switched under normal 2193 // circumstances, ignore in several special cases) 2194 if (AddPosesThreadValid(&ref)) { 2195 CreatePoses(currentPoses->fModels, 2196 currentPoses->fPoseInfos, 2197 currentPoses->fCount, NULL, true, 0, 0, true); 2198 currentPoses->ReleaseModels(); 2199 } 2200 delete currentPoses; 2201 } 2202 break; 2203 } 2204 2205 case kAddPosesCompleted: 2206 AddPosesCompleted(); 2207 break; 2208 2209 case kRestoreBackgroundImage: 2210 ContainerWindow()->UpdateBackgroundImage(); 2211 break; 2212 2213 case B_META_MIME_CHANGED: 2214 NoticeMetaMimeChanged(message); 2215 break; 2216 2217 case B_NODE_MONITOR: 2218 case B_PATH_MONITOR: 2219 case B_QUERY_UPDATE: 2220 if (!FSNotification(message)) 2221 pendingNodeMonitorCache.Add(message); 2222 break; 2223 2224 case kIconMode: { 2225 int32 size = -1; 2226 int32 scale; 2227 if (message->FindInt32("size", &size) == B_OK) { 2228 // Nothing else to do in this case. 2229 } else if (message->FindInt32("scale", &scale) == B_OK 2230 && fViewState->ViewMode() == kIconMode) { 2231 if (scale == 0 && (int32)UnscaledIconSizeInt() != 32) { 2232 switch ((int32)UnscaledIconSizeInt()) { 2233 case 40: size = 32; break; 2234 case 48: size = 40; break; 2235 case 64: size = 48; break; 2236 case 96: size = 64; break; 2237 case 128: size = 96; break; 2238 } 2239 } else if (scale == 1 && (int32)UnscaledIconSizeInt() != 128) { 2240 switch ((int32)UnscaledIconSizeInt()) { 2241 case 32: size = 40; break; 2242 case 40: size = 48; break; 2243 case 48: size = 64; break; 2244 case 64: size = 96; break; 2245 case 96: size = 128; break; 2246 } 2247 } 2248 } else { 2249 int32 iconSize = fViewState->LastIconSize(); 2250 if (iconSize < 32 || iconSize > 128) { 2251 // uninitialized last icon size? 2252 iconSize = 32; 2253 } 2254 size = iconSize; 2255 } 2256 if (size <= 0) 2257 break; 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