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