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