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