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 1389 delete params; 1390 1391 AutoLockingMessenger lock(target); 1392 1393 if (!lock.IsLocked()) 1394 return B_ERROR; 1395 1396 thread_id threadID = find_thread(NULL); 1397 1398 BPoseView* view = dynamic_cast<BPoseView*>(lock.Handler()); 1399 ThrowOnAssert(view != NULL); 1400 1401 BWindow* window = dynamic_cast<BWindow*>(lock.Looper()); 1402 ThrowOnAssert(window != NULL); 1403 1404 // allocate the iterator we will use for adding poses; this 1405 // can be a directory or any other collection of entry_refs, such 1406 // as results of a query; subclasses override this to provide 1407 // other than standard directory iterations 1408 EntryListBase* container = view->InitDirentIterator(&ref); 1409 if (container == NULL) { 1410 view->HideBarberPole(); 1411 return B_ERROR; 1412 } 1413 1414 AddPosesResult* posesResult = new AddPosesResult; 1415 posesResult->fCount = 0; 1416 int32 modelChunkIndex = -1; 1417 bigtime_t nextChunkTime = 0; 1418 uint32 watchMask = view->WatchNewNodeMask(); 1419 1420 bool hideDotFiles = TrackerSettings().HideDotFiles(); 1421 #if DEBUG 1422 for (int32 index = 0; index < kMaxAddPosesChunk; index++) 1423 posesResult->fModels[index] = (Model*)0xdeadbeef; 1424 #endif 1425 1426 try { 1427 for (;;) { 1428 lock.Unlock(); 1429 1430 status_t result = B_OK; 1431 char entBuf[1024]; 1432 dirent* eptr = (dirent*)entBuf; 1433 Model* model = 0; 1434 node_ref dirNode; 1435 node_ref itemNode; 1436 1437 int32 count = container->GetNextDirents(eptr, 1024, 1); 1438 if (count <= 0 && modelChunkIndex == -1) 1439 break; 1440 1441 if (count > 0) { 1442 ASSERT(count == 1); 1443 1444 if ((!hideDotFiles && (!strcmp(eptr->d_name, ".") 1445 || !strcmp(eptr->d_name, ".."))) 1446 || (hideDotFiles && eptr->d_name[0] == '.')) { 1447 continue; 1448 } 1449 1450 dirNode.device = eptr->d_pdev; 1451 dirNode.node = eptr->d_pino; 1452 itemNode.device = eptr->d_dev; 1453 itemNode.node = eptr->d_ino; 1454 1455 BPoseView::WatchNewNode(&itemNode, watchMask, lock.Target()); 1456 // have to node monitor ahead of time because Model will 1457 // cache up the file type and preferred app 1458 // OK to call when poseView is not locked 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* containerWindow = ContainerWindow(); 1669 if (containerWindow != NULL) 1670 containerWindow->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 ExcludeTrashFromSelection(); 2407 if (ContainerWindow()->IsTrash()) 2408 // if trash delete instantly 2409 DeleteSelection(true, false); 2410 else 2411 DeleteSelection(); 2412 break; 2413 2414 case kMoveToTrash: 2415 { 2416 ExcludeTrashFromSelection(); 2417 TrackerSettings settings; 2418 2419 if ((modifiers() & B_SHIFT_KEY) != 0 2420 || settings.DontMoveFilesToTrash()) { 2421 DeleteSelection(true, settings.AskBeforeDeleteFile()); 2422 } else 2423 MoveSelectionToTrash(); 2424 2425 break; 2426 } 2427 2428 case kCleanupAll: 2429 Cleanup(true); 2430 break; 2431 2432 case kCleanup: 2433 Cleanup(); 2434 break; 2435 2436 case kEditQuery: 2437 EditQueries(); 2438 break; 2439 2440 case kRunAutomounterSettings: 2441 be_app->PostMessage(message); 2442 break; 2443 2444 case kNewEntryFromTemplate: 2445 if (message->HasRef("refs_template")) 2446 NewFileFromTemplate(message); 2447 break; 2448 2449 case kNewFolder: 2450 NewFolder(message); 2451 break; 2452 2453 case kUnmountVolume: 2454 UnmountSelectedVolumes(); 2455 break; 2456 2457 case kEmptyTrash: 2458 FSEmptyTrash(); 2459 break; 2460 2461 case kGetInfo: 2462 OpenInfoWindows(); 2463 break; 2464 2465 case kIdentifyEntry: 2466 { 2467 bool force; 2468 if (message->FindBool("force", &force) != B_OK) 2469 force = false; 2470 2471 IdentifySelection(force); 2472 break; 2473 } 2474 2475 case kEditItem: 2476 { 2477 if (ActivePose()) 2478 break; 2479 2480 BPose* pose = fSelectionList->FirstItem(); 2481 if (pose != NULL) { 2482 BPoint where(0, 2483 CurrentPoseList()->IndexOf(pose) * fListElemHeight); 2484 pose->EditFirstWidget(where, this); 2485 } 2486 break; 2487 } 2488 2489 case kOpenParentDir: 2490 OpenParent(); 2491 break; 2492 2493 case kCopyAttributes: 2494 if (be_clipboard->Lock()) { 2495 be_clipboard->Clear(); 2496 BMessage* data = be_clipboard->Data(); 2497 if (data != NULL) { 2498 // copy attributes to the clipboard 2499 BMessage state; 2500 SaveState(state); 2501 2502 BMallocIO stream; 2503 ssize_t size; 2504 if (state.Flatten(&stream, &size) == B_OK) { 2505 data->AddData("application/tracker-columns", 2506 B_MIME_TYPE, stream.Buffer(), size); 2507 be_clipboard->Commit(); 2508 } 2509 } 2510 be_clipboard->Unlock(); 2511 } 2512 break; 2513 2514 case kPasteAttributes: 2515 if (be_clipboard->Lock()) { 2516 BMessage* data = be_clipboard->Data(); 2517 if (data != NULL) { 2518 // find the attributes in the clipboard 2519 const void* buffer; 2520 ssize_t size; 2521 if (data->FindData("application/tracker-columns", 2522 B_MIME_TYPE, &buffer, &size) == B_OK) { 2523 BMessage state; 2524 if (state.Unflatten((const char*)buffer) == B_OK) { 2525 // remove all current columns (one always stays) 2526 BColumn* old; 2527 while ((old = ColumnAt(0)) != NULL) { 2528 if (!RemoveColumn(old, false)) 2529 break; 2530 } 2531 2532 // add new columns 2533 for (int32 index = 0; ; index++) { 2534 BColumn* column 2535 = BColumn::InstantiateFromMessage(state, 2536 index); 2537 if (column == NULL) 2538 break; 2539 2540 AddColumn(column); 2541 } 2542 2543 // remove the last old one 2544 RemoveColumn(old, false); 2545 2546 // set sorting mode 2547 BViewState* viewState 2548 = BViewState::InstantiateFromMessage(state); 2549 if (viewState != NULL) { 2550 SetPrimarySort(viewState->PrimarySort()); 2551 SetSecondarySort(viewState->SecondarySort()); 2552 SetReverseSort(viewState->ReverseSort()); 2553 2554 SortPoses(); 2555 Invalidate(); 2556 } 2557 } 2558 } 2559 } 2560 be_clipboard->Unlock(); 2561 } 2562 break; 2563 2564 case kArrangeBy: 2565 { 2566 uint32 attrHash; 2567 if (message->FindInt32("attr_hash", (int32*)&attrHash) == B_OK) { 2568 if (ColumnFor(attrHash) == NULL) 2569 HandleAttrMenuItemSelected(message); 2570 2571 if (PrimarySort() == attrHash) 2572 attrHash = 0; 2573 2574 SetPrimarySort(attrHash); 2575 SetSecondarySort(0); 2576 Cleanup(true); 2577 } 2578 break; 2579 } 2580 2581 case kArrangeReverseOrder: 2582 SetReverseSort(!fViewState->ReverseSort()); 2583 Cleanup(true); 2584 break; 2585 2586 case kAttributeItem: 2587 HandleAttrMenuItemSelected(message); 2588 break; 2589 2590 case kAddPrinter: 2591 be_app->PostMessage(message); 2592 break; 2593 2594 case kMakeActivePrinter: 2595 SetDefaultPrinter(); 2596 break; 2597 2598 #if DEBUG 2599 case kTestIconCache: 2600 RunIconCacheTests(); 2601 break; 2602 2603 case 'dbug': 2604 { 2605 int32 selectCount = CountSelected(); 2606 for (int32 index = 0; index < selectCount; index++) 2607 fSelectionList->ItemAt(index)->PrintToStream(); 2608 2609 break; 2610 } 2611 #ifdef CHECK_OPEN_MODEL_LEAKS 2612 case 'dpfl': 2613 DumpOpenModels(false); 2614 break; 2615 2616 case 'dpfL': 2617 DumpOpenModels(true); 2618 break; 2619 #endif 2620 #endif 2621 2622 case kCheckTypeahead: 2623 { 2624 bigtime_t doubleClickSpeed; 2625 get_click_speed(&doubleClickSpeed); 2626 if (system_time() - fLastKeyTime > (doubleClickSpeed * 2)) { 2627 fCountView->SetTypeAhead(""); 2628 delete fKeyRunner; 2629 fKeyRunner = NULL; 2630 } 2631 break; 2632 } 2633 2634 case B_OBSERVER_NOTICE_CHANGE: 2635 { 2636 int32 observerWhat; 2637 if (message->FindInt32("be:observe_change_what", &observerWhat) 2638 == B_OK) { 2639 switch (observerWhat) { 2640 case kDateFormatChanged: 2641 UpdateDateColumns(message); 2642 break; 2643 2644 case kVolumesOnDesktopChanged: 2645 AdaptToVolumeChange(message); 2646 break; 2647 2648 case kDesktopIntegrationChanged: 2649 AdaptToDesktopIntegrationChange(message); 2650 break; 2651 2652 case kShowSelectionWhenInactiveChanged: 2653 { 2654 // Updating the settings here will propagate 2655 // setting changed from Tracker to all open 2656 // file panels as well 2657 bool showSelection; 2658 if (message->FindBool("ShowSelectionWhenInactive", 2659 &showSelection) == B_OK) { 2660 fShowSelectionWhenInactive = showSelection; 2661 TrackerSettings().SetShowSelectionWhenInactive( 2662 fShowSelectionWhenInactive); 2663 } 2664 Invalidate(); 2665 break; 2666 } 2667 2668 case kTransparentSelectionChanged: 2669 { 2670 bool transparentSelection; 2671 if (message->FindBool("TransparentSelection", 2672 &transparentSelection) == B_OK) { 2673 fTransparentSelection = transparentSelection; 2674 TrackerSettings().SetTransparentSelection( 2675 fTransparentSelection); 2676 } 2677 break; 2678 } 2679 2680 case kSortFolderNamesFirstChanged: 2681 if (ViewMode() == kListMode) { 2682 TrackerSettings settings; 2683 bool sortFolderNamesFirst; 2684 if (message->FindBool("SortFolderNamesFirst", 2685 &sortFolderNamesFirst) == B_OK) { 2686 settings.SetSortFolderNamesFirst( 2687 sortFolderNamesFirst); 2688 } 2689 NameAttributeText::SetSortFolderNamesFirst( 2690 settings.SortFolderNamesFirst()); 2691 RealNameAttributeText::SetSortFolderNamesFirst( 2692 settings.SortFolderNamesFirst()); 2693 SortPoses(); 2694 Invalidate(); 2695 } 2696 break; 2697 2698 case kHideDotFilesChanged: 2699 { 2700 TrackerSettings settings; 2701 bool hideDotFiles; 2702 if (message->FindBool("HideDotFiles", 2703 &hideDotFiles) == B_OK) { 2704 settings.SetHideDotFiles(hideDotFiles); 2705 } 2706 2707 Refresh(); 2708 break; 2709 } 2710 2711 case kTypeAheadFilteringChanged: 2712 { 2713 TrackerSettings settings; 2714 bool typeAheadFiltering; 2715 if (message->FindBool("TypeAheadFiltering", 2716 &typeAheadFiltering) == B_OK) { 2717 settings.SetTypeAheadFiltering(typeAheadFiltering); 2718 } 2719 2720 if (fFiltering && !typeAheadFiltering) 2721 StopFiltering(); 2722 break; 2723 } 2724 } 2725 } 2726 break; 2727 } 2728 2729 default: 2730 _inherited::MessageReceived(message); 2731 break; 2732 } 2733 } 2734 2735 2736 bool 2737 BPoseView::RemoveColumn(BColumn* columnToRemove, bool runAlert) 2738 { 2739 // make sure last column is not removed 2740 if (CountColumns() == 1) { 2741 if (runAlert) { 2742 BAlert* alert = new BAlert("", 2743 B_TRANSLATE("You must have at least one attribute showing."), 2744 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 2745 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 2746 alert->Go(); 2747 } 2748 2749 return false; 2750 } 2751 2752 // column exists so remove it from list 2753 int32 columnIndex = IndexOfColumn(columnToRemove); 2754 float offset = columnToRemove->Offset(); 2755 2756 int32 poseCount = fPoseList->CountItems(); 2757 for (int32 index = 0; index < poseCount; index++) 2758 fPoseList->ItemAt(index)->RemoveWidget(this, columnToRemove); 2759 2760 fColumnList->RemoveItem(columnToRemove, false); 2761 fTitleView->RemoveTitle(columnToRemove); 2762 2763 float attrWidth = columnToRemove->Width(); 2764 delete columnToRemove; 2765 2766 int32 count = CountColumns(); 2767 for (int32 index = columnIndex; index < count; index++) { 2768 BColumn* column = ColumnAt(index); 2769 column->SetOffset(column->Offset() 2770 - (attrWidth + kTitleColumnExtraMargin)); 2771 } 2772 2773 BRect rect(Bounds()); 2774 rect.left = offset; 2775 Invalidate(rect); 2776 2777 ContainerWindow()->MarkAttributeMenu(); 2778 2779 if (IsWatchingDateFormatChange()) { 2780 int32 columnCount = CountColumns(); 2781 bool anyDateAttributesLeft = false; 2782 2783 for (int32 i = 0; i < columnCount; i++) { 2784 BColumn* column = ColumnAt(i); 2785 if (column->AttrType() == B_TIME_TYPE) 2786 anyDateAttributesLeft = true; 2787 2788 if (anyDateAttributesLeft) 2789 break; 2790 } 2791 2792 if (!anyDateAttributesLeft) 2793 StopWatchDateFormatChange(); 2794 } 2795 2796 fStateNeedsSaving = true; 2797 2798 if (fFiltering) { 2799 // the column we removed might just be the one that was used to filter 2800 int32 poseCount = fFilteredPoseList->CountItems(); 2801 for (int32 i = poseCount - 1; i >= 0; i--) { 2802 BPose* pose = fFilteredPoseList->ItemAt(i); 2803 if (!FilterPose(pose)) 2804 RemoveFilteredPose(pose, i); 2805 } 2806 } 2807 2808 return true; 2809 } 2810 2811 2812 bool 2813 BPoseView::AddColumn(BColumn* newColumn, const BColumn* after) 2814 { 2815 if (after == NULL && CountColumns() > 0) 2816 after = LastColumn(); 2817 2818 // add new column after last column 2819 float offset; 2820 int32 afterColumnIndex; 2821 if (after != NULL) { 2822 offset = after->Offset() + after->Width() + kTitleColumnExtraMargin; 2823 afterColumnIndex = IndexOfColumn(after); 2824 } else { 2825 offset = StartOffset(); 2826 afterColumnIndex = CountColumns() - 1; 2827 } 2828 2829 // add the new column 2830 fColumnList->AddItem(newColumn, afterColumnIndex + 1); 2831 if (fTitleView != NULL) 2832 fTitleView->AddTitle(newColumn); 2833 2834 BRect rect(Bounds()); 2835 2836 // add widget for all visible poses 2837 PoseList* poseList = CurrentPoseList(); 2838 int32 poseCount = poseList->CountItems(); 2839 int32 startIndex = (int32)(rect.top / fListElemHeight); 2840 BPoint loc(0, startIndex * fListElemHeight); 2841 2842 for (int32 index = startIndex; index < poseCount; index++) { 2843 BPose* pose = poseList->ItemAt(index); 2844 if (pose->WidgetFor(newColumn->AttrHash()) == NULL) 2845 pose->AddWidget(this, newColumn); 2846 2847 loc.y += fListElemHeight; 2848 if (loc.y > rect.bottom) 2849 break; 2850 } 2851 2852 // rearrange column titles to fit new column 2853 newColumn->SetOffset(offset); 2854 float attrWidth = newColumn->Width(); 2855 2856 int32 count = CountColumns(); 2857 for (int32 index = afterColumnIndex + 2; index < count; index++) { 2858 BColumn* column = ColumnAt(index); 2859 ASSERT(newColumn != column); 2860 column->SetOffset(column->Offset() + (attrWidth 2861 + kTitleColumnExtraMargin)); 2862 } 2863 2864 rect.left = offset; 2865 Invalidate(rect); 2866 ContainerWindow()->MarkAttributeMenu(); 2867 2868 // Check if this is a time attribute and if so, 2869 // start watching for changed in time/date format: 2870 if (!IsWatchingDateFormatChange() && newColumn->AttrType() == B_TIME_TYPE) 2871 StartWatchDateFormatChange(); 2872 2873 fStateNeedsSaving = true; 2874 2875 if (fFiltering) { 2876 // the column we added might just add new poses to be showed 2877 fFilteredPoseList->MakeEmpty(); 2878 fFiltering = false; 2879 StartFiltering(); 2880 } 2881 2882 return true; 2883 } 2884 2885 2886 void 2887 BPoseView::HandleAttrMenuItemSelected(BMessage* message) 2888 { 2889 // see if source was a menu item 2890 BMenuItem* item; 2891 if (message->FindPointer("source", (void**)&item) != B_OK) 2892 item = NULL; 2893 2894 // find out which column was selected 2895 uint32 attrHash; 2896 if (message->FindInt32("attr_hash", (int32*)&attrHash) != B_OK) 2897 return; 2898 2899 BColumn* column = ColumnFor(attrHash); 2900 if (column != NULL) { 2901 RemoveColumn(column, true); 2902 return; 2903 } else { 2904 // collect info about selected attribute 2905 const char* attrName; 2906 if (message->FindString("attr_name", &attrName) != B_OK) 2907 return; 2908 2909 uint32 attrType; 2910 if (message->FindInt32("attr_type", (int32*)&attrType) != B_OK) 2911 return; 2912 2913 float attrWidth; 2914 if (message->FindFloat("attr_width", &attrWidth) != B_OK) 2915 return; 2916 2917 alignment attrAlign; 2918 if (message->FindInt32("attr_align", (int32*)&attrAlign) != B_OK) 2919 return; 2920 2921 bool isEditable; 2922 if (message->FindBool("attr_editable", &isEditable) != B_OK) 2923 return; 2924 2925 bool isStatfield; 2926 if (message->FindBool("attr_statfield", &isStatfield) != B_OK) 2927 return; 2928 2929 const char* displayAs; 2930 message->FindString("attr_display_as", &displayAs); 2931 2932 column = new BColumn(item->Label(), attrWidth, attrAlign, 2933 attrName, attrType, displayAs, isStatfield, isEditable); 2934 AddColumn(column); 2935 if (item->Menu()->Supermenu() == NULL) 2936 delete item->Menu(); 2937 } 2938 } 2939 2940 2941 const int32 kSanePoseLocation = 50000; 2942 2943 2944 void 2945 BPoseView::ReadPoseInfo(Model* model, PoseInfo* poseInfo) 2946 { 2947 BModelOpener opener(model); 2948 if (model->Node() == NULL) 2949 return; 2950 2951 ReadAttrResult result = kReadAttrFailed; 2952 BEntry entry; 2953 model->GetEntry(&entry); 2954 bool isTrash = model->IsTrash() && IsDesktopView(); 2955 2956 // special case the "root" disks icon 2957 // as well as the trash on desktop 2958 if (model->IsRoot() || isTrash) { 2959 BDirectory dir; 2960 if (FSGetDeskDir(&dir) == B_OK) { 2961 const char* poseInfoAttr = isTrash 2962 ? kAttrTrashPoseInfo 2963 : kAttrDisksPoseInfo; 2964 const char* poseInfoAttrForeign = isTrash 2965 ? kAttrTrashPoseInfoForeign 2966 : kAttrDisksPoseInfoForeign; 2967 result = ReadAttr(&dir, poseInfoAttr, poseInfoAttrForeign, 2968 B_RAW_TYPE, 0, poseInfo, sizeof(*poseInfo), 2969 &PoseInfo::EndianSwap); 2970 } 2971 } else { 2972 ASSERT(model->IsNodeOpen()); 2973 time_t now = time(NULL); 2974 2975 for (int32 count = 10; count >= 0; count--) { 2976 if (model->Node() == NULL) 2977 break; 2978 2979 result = ReadAttr(model->Node(), kAttrPoseInfo, 2980 kAttrPoseInfoForeign, B_RAW_TYPE, 0, poseInfo, 2981 sizeof(*poseInfo), &PoseInfo::EndianSwap); 2982 2983 if (result != kReadAttrFailed) { 2984 // got it, bail 2985 break; 2986 } 2987 2988 // if we're in one of the icon modes and it's a newly created item 2989 // then we're going to retry a few times to see if we can get some 2990 // pose info to properly place the icon 2991 if (ViewMode() == kListMode) 2992 break; 2993 2994 const StatStruct* stat = model->StatBuf(); 2995 if (stat->st_crtime < now - 5 || stat->st_crtime > now) 2996 break; 2997 2998 //PRINT(("retrying to read pose info for %s, %d\n", 2999 // model->Name(), count)); 3000 3001 snooze(10000); 3002 } 3003 } 3004 3005 if (result == kReadAttrFailed) { 3006 poseInfo->fInitedDirectory = -1LL; 3007 poseInfo->fInvisible = false; 3008 } else if (TargetModel() == NULL 3009 || (poseInfo->fInitedDirectory != model->EntryRef()->directory 3010 && (poseInfo->fInitedDirectory 3011 != TargetModel()->NodeRef()->node))) { 3012 // info was read properly but it's not for this directory 3013 poseInfo->fInitedDirectory = -1LL; 3014 } else if (poseInfo->fLocation.x < -kSanePoseLocation 3015 || poseInfo->fLocation.x > kSanePoseLocation 3016 || poseInfo->fLocation.y < -kSanePoseLocation 3017 || poseInfo->fLocation.y > kSanePoseLocation) { 3018 // location values not realistic, probably screwed up, force reset 3019 poseInfo->fInitedDirectory = -1LL; 3020 } 3021 } 3022 3023 3024 ExtendedPoseInfo* 3025 BPoseView::ReadExtendedPoseInfo(Model* model) 3026 { 3027 BModelOpener opener(model); 3028 if (model->Node() == NULL) 3029 return NULL; 3030 3031 ReadAttrResult result = kReadAttrFailed; 3032 3033 const char* extendedPoseInfoAttrName; 3034 const char* extendedPoseInfoAttrForeignName; 3035 3036 // special case the "root" disks icon 3037 if (model->IsRoot()) { 3038 BDirectory dir; 3039 if (FSGetDeskDir(&dir) == B_OK) { 3040 extendedPoseInfoAttrName = kAttrExtendedDisksPoseInfo; 3041 extendedPoseInfoAttrForeignName = kAttrExtendedDisksPoseInfoForegin; 3042 } else 3043 return NULL; 3044 } else { 3045 extendedPoseInfoAttrName = kAttrExtendedPoseInfo; 3046 extendedPoseInfoAttrForeignName = kAttrExtendedPoseInfoForegin; 3047 } 3048 3049 type_code type; 3050 size_t size; 3051 result = GetAttrInfo(model->Node(), extendedPoseInfoAttrName, 3052 extendedPoseInfoAttrForeignName, &type, &size); 3053 3054 if (result == kReadAttrFailed) 3055 return NULL; 3056 3057 char* buffer = new char[ExtendedPoseInfo::SizeWithHeadroom(size)]; 3058 ExtendedPoseInfo* poseInfo = reinterpret_cast<ExtendedPoseInfo*>(buffer); 3059 result = ReadAttr(model->Node(), extendedPoseInfoAttrName, 3060 extendedPoseInfoAttrForeignName, 3061 B_RAW_TYPE, 0, buffer, size, &ExtendedPoseInfo::EndianSwap); 3062 3063 // check that read worked, and data is sane 3064 if (result == kReadAttrFailed 3065 || size > poseInfo->SizeWithHeadroom() 3066 || size < poseInfo->Size()) { 3067 delete[] buffer; 3068 return NULL; 3069 } 3070 3071 return poseInfo; 3072 } 3073 3074 3075 void 3076 BPoseView::SetViewMode(uint32 newMode) 3077 { 3078 uint32 oldMode = ViewMode(); 3079 uint32 lastIconSize = fViewState->LastIconSize(); 3080 3081 if (newMode == oldMode 3082 && (newMode != kIconMode || lastIconSize == fViewState->IconSize())) { 3083 return; 3084 } 3085 3086 ASSERT(!IsFilePanel()); 3087 3088 uint32 lastIconMode = fViewState->LastIconMode(); 3089 if (newMode != kListMode) { 3090 fViewState->SetLastIconMode(newMode); 3091 if (oldMode == kIconMode) 3092 fViewState->SetLastIconSize(fViewState->IconSize()); 3093 } 3094 3095 fViewState->SetViewMode(newMode); 3096 3097 // Try to lock the center of the pose view when scaling icons, but not 3098 // if we are the desktop. 3099 BPoint scaleOffset(0, 0); 3100 bool iconSizeChanged = newMode == kIconMode && oldMode == kIconMode; 3101 if (!IsDesktopWindow() && iconSizeChanged) { 3102 // definitely changing the icon size, so we will need to scroll 3103 BRect bounds(Bounds()); 3104 BPoint center(bounds.LeftTop()); 3105 center.x += bounds.Width() / 2.0; 3106 center.y += bounds.Height() / 2.0; 3107 // convert the center into "unscaled icon placement" space 3108 float oldScale = lastIconSize / 32.0; 3109 BPoint unscaledCenter(center.x / oldScale, center.y / oldScale); 3110 // get the new center in "scaled icon placement" place 3111 float newScale = fViewState->IconSize() / 32.0f; 3112 BPoint newCenter(unscaledCenter.x * newScale, 3113 unscaledCenter.y * newScale); 3114 scaleOffset = newCenter - center; 3115 } 3116 3117 // toggle view layout between listmode and non-listmode, if necessary 3118 BContainerWindow* window = ContainerWindow(); 3119 if (oldMode == kListMode) { 3120 if (fFiltering) 3121 ClearFilter(); 3122 3123 if (window != NULL) 3124 window->HideAttributeMenu(); 3125 3126 fTitleView->Hide(); 3127 } else if (newMode == kListMode) { 3128 if (window != NULL) 3129 window->ShowAttributeMenu(); 3130 3131 fTitleView->Show(); 3132 } 3133 3134 CommitActivePose(); 3135 SetIconPoseHeight(); 3136 GetLayoutInfo(newMode, &fGrid, &fOffset); 3137 3138 // see if we need to map icons into new mode 3139 bool mapIcons = false; 3140 if (fOkToMapIcons) { 3141 mapIcons = (newMode != kListMode) && (newMode != lastIconMode 3142 || fViewState->IconSize() != lastIconSize); 3143 } 3144 3145 // check if we need to re-place poses when they are out of view 3146 bool checkLocations = IsDesktopWindow() && iconSizeChanged; 3147 3148 BPoint oldOffset; 3149 BPoint oldGrid; 3150 if (mapIcons) 3151 GetLayoutInfo(lastIconMode, &oldGrid, &oldOffset); 3152 3153 BRect bounds(Bounds()); 3154 PoseList newPoseList(30); 3155 3156 if (newMode != kListMode) { 3157 int32 poseCount = fPoseList->CountItems(); 3158 for (int32 index = 0; index < poseCount; index++) { 3159 BPose* pose = fPoseList->ItemAt(index); 3160 if (pose->HasLocation() == false) { 3161 newPoseList.AddItem(pose); 3162 } else if (checkLocations && !IsValidLocation(pose)) { 3163 // this icon has a location, but needs to be remapped, because 3164 // it is going out of view for example 3165 RemoveFromVSList(pose); 3166 newPoseList.AddItem(pose); 3167 } else if (iconSizeChanged) { 3168 // The pose location is still changed in view coordinates, 3169 // so it needs to be changed anyways! 3170 pose->SetSaveLocation(); 3171 } else if (mapIcons) { 3172 MapToNewIconMode(pose, oldGrid, oldOffset); 3173 } 3174 } 3175 } 3176 3177 // invalidate before anything else to avoid flickering, especially when 3178 // scrolling is also performed (invalidating before scrolling will cause 3179 // app_server to scroll silently, ie not visibly) 3180 Invalidate(); 3181 3182 // update origin in case of a list <-> icon mode transition 3183 BPoint newOrigin; 3184 if (newMode == kListMode) 3185 newOrigin = fViewState->ListOrigin(); 3186 else 3187 newOrigin = fViewState->IconOrigin() + scaleOffset; 3188 3189 PinPointToValidRange(newOrigin); 3190 3191 DisableScrollBars(); 3192 ScrollTo(newOrigin); 3193 3194 // reset hint and arrange poses which DO NOT have a location yet 3195 ResetPosePlacementHint(); 3196 int32 poseCount = newPoseList.CountItems(); 3197 for (int32 index = 0; index < poseCount; index++) { 3198 BPose* pose = newPoseList.ItemAt(index); 3199 PlacePose(pose, bounds); 3200 AddToVSList(pose); 3201 } 3202 3203 SortPoses(); 3204 if (newMode != kListMode) 3205 RecalcExtent(); 3206 3207 UpdateScrollRange(); 3208 SetScrollBarsTo(newOrigin); 3209 EnableScrollBars(); 3210 ContainerWindow()->ViewModeChanged(oldMode, newMode); 3211 } 3212 3213 3214 void 3215 BPoseView::MapToNewIconMode(BPose* pose, BPoint oldGrid, BPoint oldOffset) 3216 { 3217 BPoint delta; 3218 BPoint poseLoc; 3219 3220 poseLoc = PinToGrid(pose->Location(this), oldGrid, oldOffset); 3221 delta = pose->Location(this) - poseLoc; 3222 poseLoc -= oldOffset; 3223 3224 if (poseLoc.x >= 0) 3225 poseLoc.x = floorf(poseLoc.x / oldGrid.x) * fGrid.x; 3226 else 3227 poseLoc.x = ceilf(poseLoc.x / oldGrid.x) * fGrid.x; 3228 3229 if (poseLoc.y >= 0) 3230 poseLoc.y = floorf(poseLoc.y / oldGrid.y) * fGrid.y; 3231 else 3232 poseLoc.y = ceilf(poseLoc.y / oldGrid.y) * fGrid.y; 3233 3234 if ((delta.x != 0) || (delta.y != 0)) { 3235 if (delta.x >= 0) 3236 delta.x = fGrid.x * floorf(delta.x / oldGrid.x); 3237 else 3238 delta.x = fGrid.x * ceilf(delta.x / oldGrid.x); 3239 3240 if (delta.y >= 0) 3241 delta.y = fGrid.y * floorf(delta.y / oldGrid.y); 3242 else 3243 delta.y = fGrid.y * ceilf(delta.y / oldGrid.y); 3244 3245 poseLoc += delta; 3246 } 3247 3248 poseLoc += fOffset; 3249 pose->SetLocation(poseLoc, this); 3250 pose->SetSaveLocation(); 3251 } 3252 3253 3254 void 3255 BPoseView::SetPosesClipboardMode(uint32 clipboardMode) 3256 { 3257 if (ViewMode() == kListMode) { 3258 PoseList* poseList = CurrentPoseList(); 3259 int32 poseCount = poseList->CountItems(); 3260 3261 BPoint loc(0,0); 3262 for (int32 index = 0; index < poseCount; index++) { 3263 BPose* pose = poseList->ItemAt(index); 3264 if (pose->ClipboardMode() != clipboardMode) { 3265 pose->SetClipboardMode(clipboardMode); 3266 Invalidate(pose->CalcRect(loc, this, false)); 3267 } 3268 loc.y += fListElemHeight; 3269 } 3270 } else { 3271 int32 poseCount = fPoseList->CountItems(); 3272 for (int32 index = 0; index < poseCount; index++) { 3273 BPose* pose = fPoseList->ItemAt(index); 3274 if (pose->ClipboardMode() != clipboardMode) { 3275 pose->SetClipboardMode(clipboardMode); 3276 BRect poseRect(pose->CalcRect(this)); 3277 Invalidate(poseRect); 3278 } 3279 } 3280 } 3281 } 3282 3283 3284 void 3285 BPoseView::UpdatePosesClipboardModeFromClipboard(BMessage* clipboardReport) 3286 { 3287 CommitActivePose(); 3288 fSelectionPivotPose = NULL; 3289 fRealPivotPose = NULL; 3290 bool fullInvalidateNeeded = false; 3291 3292 node_ref node; 3293 clipboardReport->FindInt32("device", &node.device); 3294 clipboardReport->FindInt64("directory", &node.node); 3295 3296 bool clearClipboard = false; 3297 clipboardReport->FindBool("clearClipboard", &clearClipboard); 3298 3299 if (clearClipboard && fHasPosesInClipboard) { 3300 // clear all poses 3301 int32 poseCount = fPoseList->CountItems(); 3302 for (int32 index = 0; index < poseCount; index++) { 3303 BPose* pose = fPoseList->ItemAt(index); 3304 pose->Select(false); 3305 pose->SetClipboardMode(0); 3306 } 3307 SetHasPosesInClipboard(false); 3308 fullInvalidateNeeded = true; 3309 fHasPosesInClipboard = false; 3310 } 3311 3312 BRect bounds(Bounds()); 3313 BPoint loc(0, 0); 3314 bool hasPosesInClipboard = false; 3315 int32 foundNodeIndex = 0; 3316 3317 TClipboardNodeRef* clipNode = NULL; 3318 ssize_t size; 3319 for (int32 index = 0; clipboardReport->FindData("tcnode", T_CLIPBOARD_NODE, 3320 index, (const void**)&clipNode, &size) == B_OK; index++) { 3321 BPose* pose = fPoseList->FindPose(&clipNode->node, &foundNodeIndex); 3322 if (pose == NULL) 3323 continue; 3324 3325 if (clipNode->moveMode != pose->ClipboardMode() || pose->IsSelected()) { 3326 pose->SetClipboardMode(clipNode->moveMode); 3327 pose->Select(false); 3328 3329 if (!fullInvalidateNeeded) { 3330 if (ViewMode() == kListMode) { 3331 if (fFiltering) { 3332 pose = fFilteredPoseList->FindPose(&clipNode->node, 3333 &foundNodeIndex); 3334 } 3335 3336 if (pose != NULL) { 3337 loc.y = foundNodeIndex * fListElemHeight; 3338 if (loc.y <= bounds.bottom && loc.y >= bounds.top) 3339 Invalidate(pose->CalcRect(loc, this, false)); 3340 } 3341 } else { 3342 BRect poseRect(pose->CalcRect(this)); 3343 if (bounds.Contains(poseRect.LeftTop()) 3344 || bounds.Contains(poseRect.LeftBottom()) 3345 || bounds.Contains(poseRect.RightBottom()) 3346 || bounds.Contains(poseRect.RightTop())) { 3347 Invalidate(poseRect); 3348 } 3349 } 3350 } 3351 if (clipNode->moveMode) 3352 hasPosesInClipboard = true; 3353 } 3354 } 3355 3356 fSelectionList->MakeEmpty(); 3357 fMimeTypesInSelectionCache.MakeEmpty(); 3358 3359 SetHasPosesInClipboard(hasPosesInClipboard || fHasPosesInClipboard); 3360 3361 if (fullInvalidateNeeded) 3362 Invalidate(); 3363 } 3364 3365 3366 void 3367 BPoseView::PlaceFolder(const entry_ref* ref, const BMessage* message) 3368 { 3369 BNode node(ref); 3370 BPoint location; 3371 bool setPosition = false; 3372 3373 if (message->FindPoint("be:invoke_origin", &location) == B_OK) { 3374 // new folder created from popup, place on click point 3375 setPosition = true; 3376 location = ConvertFromScreen(location); 3377 } else if (ViewMode() != kListMode) { 3378 // new folder created by keyboard shortcut 3379 uint32 buttons; 3380 GetMouse(&location, &buttons); 3381 BPoint globalLocation(location); 3382 ConvertToScreen(&globalLocation); 3383 // check if mouse over window 3384 if (Window()->Frame().Contains(globalLocation)) 3385 // create folder under mouse 3386 setPosition = true; 3387 } 3388 3389 if (setPosition) { 3390 Model* targetModel = TargetModel(); 3391 if (targetModel != NULL && targetModel->NodeRef() != NULL) { 3392 FSSetPoseLocation(targetModel->NodeRef()->node, &node, 3393 location); 3394 } 3395 } 3396 } 3397 3398 3399 void 3400 BPoseView::NewFileFromTemplate(const BMessage* message) 3401 { 3402 Model* targetModel = TargetModel(); 3403 ThrowOnAssert(targetModel != NULL); 3404 3405 entry_ref destEntryRef; 3406 node_ref destNodeRef; 3407 3408 BDirectory destDir(targetModel->NodeRef()); 3409 if (destDir.InitCheck() != B_OK) 3410 return; 3411 3412 // TODO: Localise this 3413 char fileName[B_FILE_NAME_LENGTH] = "New "; 3414 strlcat(fileName, message->FindString("name"), sizeof(fileName)); 3415 FSMakeOriginalName(fileName, &destDir, " copy"); 3416 3417 entry_ref srcRef; 3418 message->FindRef("refs_template", &srcRef); 3419 3420 BDirectory dir(&srcRef); 3421 3422 if (dir.InitCheck() == B_OK) { 3423 // special handling of directories 3424 if (FSCreateNewFolderIn(targetModel->NodeRef(), &destEntryRef, 3425 &destNodeRef) == B_OK) { 3426 BEntry destEntry(&destEntryRef); 3427 destEntry.Rename(fileName); 3428 } 3429 } else { 3430 BFile srcFile(&srcRef, B_READ_ONLY); 3431 BFile destFile(&destDir, fileName, B_READ_WRITE | B_CREATE_FILE); 3432 3433 // copy the data from the template file 3434 char* buffer = new char[1024]; 3435 ssize_t result; 3436 do { 3437 result = srcFile.Read(buffer, 1024); 3438 3439 if (result > 0) { 3440 ssize_t written = destFile.Write(buffer, (size_t)result); 3441 if (written != result) 3442 result = written < B_OK ? written : B_ERROR; 3443 } 3444 } while (result > 0); 3445 delete[] buffer; 3446 } 3447 3448 // todo: create an UndoItem 3449 3450 // copy the attributes from the template file 3451 BNode srcNode(&srcRef); 3452 BNode destNode(&destDir, fileName); 3453 FSCopyAttributesAndStats(&srcNode, &destNode, false); 3454 3455 BEntry entry(&destDir, fileName); 3456 entry.GetRef(&destEntryRef); 3457 3458 // try to place new item at click point or under mouse if possible 3459 PlaceFolder(&destEntryRef, message); 3460 3461 // start renaming the entry 3462 int32 index; 3463 BPose* pose = EntryCreated(targetModel->NodeRef(), &destNodeRef, 3464 destEntryRef.name, &index); 3465 3466 if (pose != NULL) { 3467 WatchNewNode(pose->TargetModel()->NodeRef()); 3468 UpdateScrollRange(); 3469 CommitActivePose(); 3470 SelectPose(pose, index); 3471 pose->EditFirstWidget(BPoint(0, index * fListElemHeight), this); 3472 } 3473 } 3474 3475 3476 void 3477 BPoseView::NewFolder(const BMessage* message) 3478 { 3479 Model* targetModel = TargetModel(); 3480 ThrowOnAssert(targetModel != NULL); 3481 3482 entry_ref ref; 3483 node_ref nodeRef; 3484 3485 if (FSCreateNewFolderIn(targetModel->NodeRef(), &ref, &nodeRef) == B_OK) { 3486 // try to place new folder at click point or under mouse if possible 3487 3488 PlaceFolder(&ref, message); 3489 3490 int32 index; 3491 BPose* pose = EntryCreated(targetModel->NodeRef(), &nodeRef, ref.name, 3492 &index); 3493 3494 if (fFiltering) { 3495 if (fFilteredPoseList->FindPose(&nodeRef, &index) == NULL) { 3496 float scrollBy = 0; 3497 BRect bounds = Bounds(); 3498 AddPoseToList(fFilteredPoseList, true, true, pose, bounds, 3499 scrollBy, true, &index); 3500 } 3501 } 3502 3503 if (pose != NULL) { 3504 UpdateScrollRange(); 3505 CommitActivePose(); 3506 SelectPose(pose, index); 3507 pose->EditFirstWidget(BPoint(0, index * fListElemHeight), this); 3508 } 3509 } 3510 } 3511 3512 3513 void 3514 BPoseView::Cleanup(bool doAll) 3515 { 3516 if (ViewMode() == kListMode) 3517 return; 3518 3519 BContainerWindow* window = ContainerWindow(); 3520 if (window == NULL) 3521 return; 3522 3523 // replace all icons from the top 3524 if (doAll) { 3525 // sort by sort field 3526 SortPoses(); 3527 3528 DisableScrollBars(); 3529 ClearExtent(); 3530 ClearSelection(); 3531 ScrollTo(B_ORIGIN); 3532 UpdateScrollRange(); 3533 SetScrollBarsTo(B_ORIGIN); 3534 ResetPosePlacementHint(); 3535 3536 BRect viewBounds(Bounds()); 3537 3538 // relocate all poses in list (reset vs list) 3539 fVSPoseList->MakeEmpty(); 3540 int32 poseCount = fPoseList->CountItems(); 3541 for (int32 index = 0; index < poseCount; index++) { 3542 BPose* pose = fPoseList->ItemAt(index); 3543 PlacePose(pose, viewBounds); 3544 AddToVSList(pose); 3545 } 3546 3547 RecalcExtent(); 3548 3549 // scroll icons into view so that leftmost icon is "fOffset" from left 3550 UpdateScrollRange(); 3551 EnableScrollBars(); 3552 3553 if (HScrollBar()) { 3554 float min; 3555 float max; 3556 HScrollBar()->GetRange(&min, &max); 3557 HScrollBar()->SetValue(min); 3558 } 3559 3560 UpdateScrollRange(); 3561 Invalidate(viewBounds); 3562 } else { 3563 // clean up items to nearest locations 3564 BRect viewBounds(Bounds()); 3565 int32 poseCount = fPoseList->CountItems(); 3566 for (int32 index = 0; index < poseCount; index++) { 3567 BPose* pose = fPoseList->ItemAt(index); 3568 BPoint location(pose->Location(this)); 3569 BPoint newLocation(PinToGrid(location, fGrid, fOffset)); 3570 3571 bool intersectsDesktopElements = !IsValidLocation(pose); 3572 3573 // do we need to move pose to a grid location? 3574 if (newLocation != location || intersectsDesktopElements) { 3575 // remove pose from VSlist so it doesn't "bump" into itself 3576 RemoveFromVSList(pose); 3577 3578 // try new grid location 3579 BRect oldBounds(pose->CalcRect(this)); 3580 BRect poseBounds(oldBounds); 3581 pose->MoveTo(newLocation, this); 3582 if (SlotOccupied(oldBounds, viewBounds) 3583 || intersectsDesktopElements) { 3584 ResetPosePlacementHint(); 3585 PlacePose(pose, viewBounds); 3586 poseBounds = pose->CalcRect(this); 3587 } 3588 3589 AddToVSList(pose); 3590 AddToExtent(poseBounds); 3591 3592 if (viewBounds.Intersects(poseBounds)) 3593 Invalidate(poseBounds); 3594 if (viewBounds.Intersects(oldBounds)) 3595 Invalidate(oldBounds); 3596 } 3597 } 3598 } 3599 } 3600 3601 3602 void 3603 BPoseView::PlacePose(BPose* pose, BRect &viewBounds) 3604 { 3605 // move pose to probable location 3606 pose->SetLocation(fHintLocation, this); 3607 BRect rect(pose->CalcRect(this)); 3608 BPoint deltaFromBounds(fHintLocation - rect.LeftTop()); 3609 3610 // make pose rect a little bigger to ensure space between poses 3611 rect.InsetBy(-3, 0); 3612 3613 bool checkValidLocation = IsDesktopWindow(); 3614 3615 // find an empty slot to put pose into 3616 while (SlotOccupied(rect, viewBounds) 3617 // check good location on the desktop 3618 || (checkValidLocation && !IsValidLocation(rect))) { 3619 NextSlot(pose, rect, viewBounds); 3620 // we've scanned the entire desktop without finding an available 3621 // position, give up and simply place it towards the top left. 3622 if (checkValidLocation && !rect.Intersects(viewBounds)) { 3623 fHintLocation = PinToGrid(BPoint(0.0, 0.0), fGrid, fOffset); 3624 pose->SetLocation(fHintLocation, this); 3625 rect = pose->CalcRect(this); 3626 break; 3627 } 3628 } 3629 3630 rect.InsetBy(3, 0); 3631 3632 fHintLocation = pose->Location(this) + BPoint(fGrid.x, 0); 3633 3634 pose->SetLocation(rect.LeftTop() + deltaFromBounds, this); 3635 pose->SetSaveLocation(); 3636 } 3637 3638 3639 bool 3640 BPoseView::IsValidLocation(const BPose* pose) 3641 { 3642 if (!IsDesktopWindow()) 3643 return true; 3644 3645 BRect rect(pose->CalcRect(this)); 3646 rect.InsetBy(-3, 0); 3647 return IsValidLocation(rect); 3648 } 3649 3650 3651 bool 3652 BPoseView::IsValidLocation(const BRect& rect) 3653 { 3654 if (!IsDesktopWindow()) 3655 return true; 3656 3657 // on the desktop, don't allow icons outside of the view bounds 3658 if (!Bounds().Contains(rect)) 3659 return false; 3660 3661 // also check the deskbar frame 3662 BRect deskbarFrame; 3663 if (GetDeskbarFrame(&deskbarFrame) == B_OK) { 3664 deskbarFrame.InsetBy(-10, -10); 3665 if (deskbarFrame.Intersects(rect)) 3666 return false; 3667 } 3668 3669 // check replicants 3670 for (int32 i = 0; BView* child = ChildAt(i); i++) { 3671 BRect childFrame = child->Frame(); 3672 childFrame.InsetBy(-5, -5); 3673 if (childFrame.Intersects(rect)) 3674 return false; 3675 } 3676 3677 // location is ok 3678 return true; 3679 } 3680 3681 3682 status_t 3683 BPoseView::GetDeskbarFrame(BRect* frame) 3684 { 3685 // only really check the Deskbar frame every half a second, 3686 // use a cached value otherwise 3687 status_t result = B_OK; 3688 bigtime_t now = system_time(); 3689 if (fLastDeskbarFrameCheckTime + 500000 < now) { 3690 // it's time to check the Deskbar frame again 3691 result = get_deskbar_frame(&fDeskbarFrame); 3692 fLastDeskbarFrameCheckTime = now; 3693 } 3694 *frame = fDeskbarFrame; 3695 3696 return result; 3697 } 3698 3699 3700 void 3701 BPoseView::CheckAutoPlacedPoses() 3702 { 3703 if (ViewMode() == kListMode) 3704 return; 3705 3706 BRect viewBounds(Bounds()); 3707 3708 int32 poseCount = fPoseList->CountItems(); 3709 for (int32 index = 0; index < poseCount; index++) { 3710 BPose* pose = fPoseList->ItemAt(index); 3711 if (pose->WasAutoPlaced()) { 3712 RemoveFromVSList(pose); 3713 fHintLocation = pose->Location(this); 3714 BRect oldBounds(pose->CalcRect(this)); 3715 PlacePose(pose, viewBounds); 3716 3717 BRect newBounds(pose->CalcRect(this)); 3718 AddToVSList(pose); 3719 pose->SetAutoPlaced(false); 3720 AddToExtent(newBounds); 3721 3722 Invalidate(oldBounds); 3723 Invalidate(newBounds); 3724 } 3725 } 3726 } 3727 3728 3729 void 3730 BPoseView::CheckPoseVisibility(BRect* newFrame) 3731 { 3732 bool desktop = IsDesktopWindow() && newFrame != 0; 3733 3734 BRect deskFrame; 3735 if (desktop) { 3736 ASSERT(newFrame); 3737 deskFrame = *newFrame; 3738 } 3739 3740 ASSERT(ViewMode() != kListMode); 3741 3742 BRect bounds(Bounds()); 3743 bounds.InsetBy(20, 20); 3744 3745 int32 poseCount = fPoseList->CountItems(); 3746 for (int32 index = 0; index < poseCount; index++) { 3747 BPose* pose = fPoseList->ItemAt(index); 3748 BPoint newLocation(pose->Location(this)); 3749 bool locationNeedsUpdating = false; 3750 3751 if (desktop) { 3752 // we just switched screen resolution, pick up the right 3753 // icon locations for the new resolution 3754 Model* model = pose->TargetModel(); 3755 ExtendedPoseInfo* info = ReadExtendedPoseInfo(model); 3756 if (info && info->HasLocationForFrame(deskFrame)) { 3757 BPoint locationForFrame = info->LocationForFrame(deskFrame); 3758 if (locationForFrame != newLocation) { 3759 // found one and it is different from the current 3760 newLocation = locationForFrame; 3761 locationNeedsUpdating = true; 3762 Invalidate(pose->CalcRect(this)); 3763 // make sure the old icon gets erased 3764 RemoveFromVSList(pose); 3765 pose->SetLocation(newLocation, this); 3766 // set the new location 3767 } 3768 } 3769 delete[] (char*)info; 3770 // TODO: fix up this mess 3771 } 3772 3773 BRect rect(pose->CalcRect(this)); 3774 if (!rect.Intersects(bounds)) { 3775 // pose doesn't fit on screen 3776 if (!locationNeedsUpdating) { 3777 // didn't already invalidate and remove in the desktop case 3778 Invalidate(rect); 3779 RemoveFromVSList(pose); 3780 } 3781 BPoint loc(pose->Location(this)); 3782 loc.ConstrainTo(bounds); 3783 // place it onscreen 3784 3785 pose->SetLocation(loc, this); 3786 // set the new location 3787 locationNeedsUpdating = true; 3788 } 3789 3790 if (locationNeedsUpdating) { 3791 // pose got reposition by one or both of the above 3792 pose->SetSaveLocation(); 3793 AddToVSList(pose); 3794 // add it at the new location 3795 Invalidate(pose->CalcRect(this)); 3796 // make sure the new pose location updates properly 3797 } 3798 } 3799 } 3800 3801 3802 bool 3803 BPoseView::SlotOccupied(BRect poseRect, BRect viewBounds) const 3804 { 3805 if (fVSPoseList->IsEmpty()) 3806 return false; 3807 3808 // ## be sure to keep this code in sync with calls to NextSlot 3809 // ## in terms of the comparison of fHintLocation and PinToGrid 3810 if (poseRect.right >= viewBounds.right) { 3811 BPoint point(viewBounds.left + fOffset.x, 0); 3812 point = PinToGrid(point, fGrid, fOffset); 3813 if (fHintLocation.x != point.x) 3814 return true; 3815 } 3816 3817 // search only nearby poses (vertically) 3818 int32 index = FirstIndexAtOrBelow((int32)(poseRect.top - IconPoseHeight())); 3819 int32 numPoses = fVSPoseList->CountItems(); 3820 3821 while (index < numPoses && fVSPoseList->ItemAt(index)->Location(this).y 3822 < poseRect.bottom) { 3823 BRect rect(fVSPoseList->ItemAt(index)->CalcRect(this)); 3824 if (poseRect.Intersects(rect)) 3825 return true; 3826 3827 index++; 3828 } 3829 3830 return false; 3831 } 3832 3833 3834 void 3835 BPoseView::NextSlot(BPose* pose, BRect &poseRect, BRect viewBounds) 3836 { 3837 // move to next slot 3838 poseRect.OffsetBy(fGrid.x, 0); 3839 3840 // if we reached the end of row go down to next row 3841 if (poseRect.right > viewBounds.right) { 3842 fHintLocation.y += fGrid.y; 3843 fHintLocation.x = viewBounds.left + fOffset.x; 3844 fHintLocation = PinToGrid(fHintLocation, fGrid, fOffset); 3845 pose->SetLocation(fHintLocation, this); 3846 poseRect = pose->CalcRect(this); 3847 poseRect.InsetBy(-3, 0); 3848 } 3849 } 3850 3851 3852 int32 3853 BPoseView::FirstIndexAtOrBelow(int32 y, bool constrainIndex) const 3854 { 3855 // This method performs a binary search on the vertically sorted pose list 3856 // and returns either the index of the first pose at a given y location or 3857 // the proper index to insert a new pose into the list. 3858 3859 int32 index = 0; 3860 int32 l = 0; 3861 int32 r = fVSPoseList->CountItems() - 1; 3862 3863 while (l <= r) { 3864 index = (l + r) >> 1; 3865 int32 result 3866 = (int32)(y - fVSPoseList->ItemAt(index)->Location(this).y); 3867 3868 if (result < 0) 3869 r = index - 1; 3870 else if (result > 0) 3871 l = index + 1; 3872 else { 3873 // compare turned out equal, find first pose 3874 while (index > 0 3875 && y == fVSPoseList->ItemAt(index - 1)->Location(this).y) 3876 index--; 3877 return index; 3878 } 3879 } 3880 3881 // didn't find pose AT location y - bump index to proper insert point 3882 while (index < fVSPoseList->CountItems() 3883 && fVSPoseList->ItemAt(index)->Location(this).y <= y) { 3884 index++; 3885 } 3886 3887 // if flag is true then constrain index to legal value since this 3888 // method returns the proper insertion point which could be outside 3889 // the current bounds of the list 3890 if (constrainIndex && index >= fVSPoseList->CountItems()) 3891 index = fVSPoseList->CountItems() - 1; 3892 3893 return index; 3894 } 3895 3896 3897 void 3898 BPoseView::AddToVSList(BPose* pose) 3899 { 3900 int32 index = FirstIndexAtOrBelow((int32)pose->Location(this).y, false); 3901 fVSPoseList->AddItem(pose, index); 3902 } 3903 3904 3905 int32 3906 BPoseView::RemoveFromVSList(const BPose* pose) 3907 { 3908 //int32 index = FirstIndexAtOrBelow((int32)pose->Location(this).y); 3909 // This optimisation is buggy and the index returned can be greater 3910 // than the actual index of the pose we search, thus missing it 3911 // and failing to remove it. This having severe implications 3912 // everywhere in the code as it is asserted that it must be always 3913 // in sync with fPoseList. See ticket #4322. 3914 int32 index = 0; 3915 3916 int32 poseCount = fVSPoseList->CountItems(); 3917 for (; index < poseCount; index++) { 3918 BPose* matchingPose = fVSPoseList->ItemAt(index); 3919 ASSERT(matchingPose); 3920 if (!matchingPose) 3921 return -1; 3922 3923 if (pose == matchingPose) { 3924 fVSPoseList->RemoveItemAt(index); 3925 return index; 3926 } 3927 } 3928 3929 return -1; 3930 } 3931 3932 3933 BPoint 3934 BPoseView::PinToGrid(BPoint point, BPoint grid, BPoint offset) const 3935 { 3936 if (grid.x == 0 || grid.y == 0) 3937 return point; 3938 3939 point -= offset; 3940 BPoint gridLoc(point); 3941 3942 if (point.x >= 0) 3943 gridLoc.x = floorf((point.x / grid.x) + 0.5f) * grid.x; 3944 else 3945 gridLoc.x = ceilf((point.x / grid.x) - 0.5f) * grid.x; 3946 3947 if (point.y >= 0) 3948 gridLoc.y = floorf((point.y / grid.y) + 0.5f) * grid.y; 3949 else 3950 gridLoc.y = ceilf((point.y / grid.y) - 0.5f) * grid.y; 3951 3952 gridLoc += offset; 3953 3954 return gridLoc; 3955 } 3956 3957 3958 void 3959 BPoseView::ResetPosePlacementHint() 3960 { 3961 fHintLocation = PinToGrid(BPoint(LeftTop().x + fOffset.x, 3962 LeftTop().y + fOffset.y), fGrid, fOffset); 3963 } 3964 3965 3966 void 3967 BPoseView::SelectPoses(int32 start, int32 end) 3968 { 3969 // clear selection list 3970 fSelectionList->MakeEmpty(); 3971 fMimeTypesInSelectionCache.MakeEmpty(); 3972 fSelectionPivotPose = NULL; 3973 fRealPivotPose = NULL; 3974 3975 bool iconMode = ViewMode() != kListMode; 3976 BPoint loc(0, start * fListElemHeight); 3977 BRect bounds(Bounds()); 3978 3979 PoseList* poseList = CurrentPoseList(); 3980 int32 poseCount = poseList->CountItems(); 3981 for (int32 index = start; index < end && index < poseCount; index++) { 3982 BPose* pose = poseList->ItemAt(index); 3983 fSelectionList->AddItem(pose); 3984 if (index == start) 3985 fSelectionPivotPose = pose; 3986 if (!pose->IsSelected()) { 3987 pose->Select(true); 3988 BRect poseRect; 3989 if (iconMode) 3990 poseRect = pose->CalcRect(this); 3991 else 3992 poseRect = pose->CalcRect(loc, this, false); 3993 3994 if (bounds.Intersects(poseRect)) { 3995 Invalidate(poseRect); 3996 } 3997 } 3998 3999 loc.y += fListElemHeight; 4000 } 4001 } 4002 4003 4004 void 4005 BPoseView::MoveOrChangePoseSelection(int32 to) 4006 { 4007 PoseList* poseList = CurrentPoseList(); 4008 BPose* first = fSelectionList->FirstItem(); 4009 4010 if (first != NULL && fMultipleSelection 4011 && (modifiers() & B_SHIFT_KEY) != 0) { 4012 // Extend selection 4013 BPose* target = poseList->ItemAt(to); 4014 BPose* last = fSelectionList->LastItem(); 4015 int32 firstIndex = poseList->IndexOf(first); 4016 int32 lastIndex = poseList->IndexOf(last); 4017 4018 int32 from = to < firstIndex ? firstIndex : lastIndex; 4019 int32 step = from < to ? 1 : -1; 4020 4021 // TODO: shrink selection depending on anchor 4022 bool select = true; 4023 4024 for (int32 index = from; step > 0 ? index <= to : index >= to; 4025 index += step) { 4026 BPose* pose = poseList->ItemAt(index); 4027 if (pose != NULL && pose->IsSelected() != select) 4028 AddRemovePoseFromSelection(pose, index, select); 4029 } 4030 if (target != NULL) 4031 ScrollIntoView(target, to); 4032 } else { 4033 SelectPose(poseList->ItemAt(to), to); 4034 } 4035 } 4036 4037 4038 void 4039 BPoseView::ScrollIntoView(BPose* pose, int32 index) 4040 { 4041 ScrollIntoView(CalcPoseRect(pose, index, true)); 4042 } 4043 4044 4045 void 4046 BPoseView::ScrollIntoView(BRect poseRect) 4047 { 4048 if (IsDesktopWindow()) 4049 return; 4050 4051 BPoint oldPos = Bounds().LeftTop(), newPos = oldPos; 4052 4053 // In list mode, only scroll vertically 4054 if (ViewMode() != kListMode) { 4055 if (poseRect.left < Bounds().left 4056 || poseRect.Width() > Bounds().Width()) 4057 newPos.x += poseRect.left - Bounds().left; 4058 else if (poseRect.right > Bounds().right) 4059 newPos.x += poseRect.right - Bounds().right; 4060 } 4061 4062 if (poseRect.top < Bounds().top 4063 || poseRect.Height() > Bounds().Height()) 4064 newPos.y += poseRect.top - Bounds().top; 4065 else if (poseRect.bottom > Bounds().bottom) 4066 newPos.y += poseRect.bottom - Bounds().bottom; 4067 4068 if (newPos != oldPos) 4069 SetScrollBarsTo(newPos); 4070 } 4071 4072 4073 void 4074 BPoseView::SelectPose(BPose* pose, int32 index, bool scrollIntoView) 4075 { 4076 if (pose == NULL || CountSelected() > 1 || !pose->IsSelected()) 4077 ClearSelection(); 4078 4079 AddPoseToSelection(pose, index, scrollIntoView); 4080 4081 if (pose != NULL) 4082 fSelectionPivotPose = pose; 4083 } 4084 4085 4086 void 4087 BPoseView::AddPoseToSelection(BPose* pose, int32 index, bool scrollIntoView) 4088 { 4089 // TODO: need to check if pose is member of selection list 4090 if (pose != NULL && !pose->IsSelected()) { 4091 pose->Select(true); 4092 fSelectionList->AddItem(pose); 4093 4094 BRect poseRect = CalcPoseRect(pose, index); 4095 Invalidate(poseRect); 4096 4097 if (scrollIntoView) 4098 ScrollIntoView(poseRect); 4099 4100 if (fSelectionChangedHook) 4101 ContainerWindow()->SelectionChanged(); 4102 } 4103 } 4104 4105 4106 void 4107 BPoseView::RemovePoseFromSelection(BPose* pose) 4108 { 4109 if (fSelectionPivotPose == pose) 4110 fSelectionPivotPose = NULL; 4111 4112 if (fRealPivotPose == pose) 4113 fRealPivotPose = NULL; 4114 4115 if (!fSelectionList->RemoveItem(pose)) { 4116 // wasn't selected to begin with 4117 return; 4118 } 4119 4120 pose->Select(false); 4121 if (ViewMode() == kListMode) { 4122 // TODO: need a simple call to CalcRect that works both in listView and 4123 // icon view modes without the need for an index/pos 4124 PoseList* poseList = CurrentPoseList(); 4125 int32 poseCount = poseList->CountItems(); 4126 BPoint loc(0, 0); 4127 for (int32 index = 0; index < poseCount; index++) { 4128 if (pose == poseList->ItemAt(index)) { 4129 Invalidate(pose->CalcRect(loc, this)); 4130 break; 4131 } 4132 loc.y += fListElemHeight; 4133 } 4134 } else 4135 Invalidate(pose->CalcRect(this)); 4136 4137 if (fSelectionChangedHook) 4138 ContainerWindow()->SelectionChanged(); 4139 } 4140 4141 4142 bool 4143 BPoseView::EachItemInDraggedSelection(const BMessage* message, 4144 bool (*func)(BPose*, BPoseView*, void*), BPoseView* poseView, 4145 void* passThru) 4146 { 4147 BContainerWindow* srcWindow; 4148 if (message->FindPointer("src_window", (void**)&srcWindow) != B_OK) 4149 return false; 4150 4151 AutoLock<BWindow> lock(srcWindow); 4152 if (!lock) 4153 return false; 4154 4155 PoseList* selectionList = srcWindow->PoseView()->SelectionList(); 4156 int32 selectCount = selectionList->CountItems(); 4157 4158 for (int32 index = 0; index < selectCount; index++) { 4159 BPose* pose = selectionList->ItemAt(index); 4160 if (func(pose, poseView, passThru)) 4161 // early iteration termination 4162 return true; 4163 } 4164 4165 return false; 4166 } 4167 4168 4169 static bool 4170 ContainsOne(BString* string, const char* matchString) 4171 { 4172 return strcmp(string->String(), matchString) == 0; 4173 } 4174 4175 4176 bool 4177 BPoseView::FindDragNDropAction(const BMessage* dragMessage, bool &canCopy, 4178 bool &canMove, bool &canLink, bool &canErase) 4179 { 4180 canCopy = false; 4181 canMove = false; 4182 canErase = false; 4183 canLink = false; 4184 if (!dragMessage->HasInt32("be:actions")) 4185 return false; 4186 4187 int32 action; 4188 for (int32 index = 0; 4189 dragMessage->FindInt32("be:actions", index, &action) == B_OK; 4190 index++) { 4191 switch (action) { 4192 case B_MOVE_TARGET: 4193 canMove = true; 4194 break; 4195 4196 case B_COPY_TARGET: 4197 canCopy = true; 4198 break; 4199 4200 case B_TRASH_TARGET: 4201 canErase = true; 4202 break; 4203 4204 case B_LINK_TARGET: 4205 canLink = true; 4206 break; 4207 } 4208 } 4209 4210 return canCopy || canMove || canErase || canLink; 4211 } 4212 4213 4214 bool 4215 BPoseView::CanTrashForeignDrag(const Model* targetModel) 4216 { 4217 return targetModel->IsTrash(); 4218 } 4219 4220 4221 bool 4222 BPoseView::CanCopyOrMoveForeignDrag(const Model* targetModel, 4223 const BMessage* dragMessage) 4224 { 4225 if (!targetModel->IsDirectory() && !targetModel->IsVirtualDirectory()) 4226 return false; 4227 4228 // in order to handle a clipping file, the drag initiator must be able 4229 // do deal with B_FILE_MIME_TYPE 4230 for (int32 index = 0; ; index++) { 4231 const char* type; 4232 if (dragMessage->FindString("be:types", index, &type) != B_OK) 4233 break; 4234 4235 if (strcasecmp(type, B_FILE_MIME_TYPE) == 0) 4236 return true; 4237 } 4238 4239 return false; 4240 } 4241 4242 4243 bool 4244 BPoseView::CanHandleDragSelection(const Model* target, 4245 const BMessage* dragMessage, bool ignoreTypes) 4246 { 4247 if (ignoreTypes) 4248 return target->IsDropTarget(); 4249 4250 ASSERT(dragMessage != NULL); 4251 4252 BContainerWindow* srcWindow; 4253 status_t result = dragMessage->FindPointer("src_window", (void**)&srcWindow); 4254 if (result != B_OK || srcWindow == NULL) { 4255 // handle a foreign drag 4256 bool canCopy; 4257 bool canMove; 4258 bool canErase; 4259 bool canLink; 4260 FindDragNDropAction(dragMessage, canCopy, canMove, canLink, canErase); 4261 if (canErase && CanTrashForeignDrag(target)) 4262 return true; 4263 4264 if (canCopy || canMove) { 4265 if (CanCopyOrMoveForeignDrag(target, dragMessage)) 4266 return true; 4267 4268 // TODO: collect all mime types here and pass into 4269 // target->IsDropTargetForList(mimeTypeList); 4270 } 4271 4272 // handle an old style entry_refs only drag message 4273 if (dragMessage->HasRef("refs") 4274 && (target->IsDirectory() || target->IsVirtualDirectory())) { 4275 return true; 4276 } 4277 4278 // handle simple text clipping drag&drop message 4279 if (dragMessage->HasData(kPlainTextMimeType, B_MIME_TYPE) 4280 && target->IsDirectory()) { 4281 return true; 4282 } 4283 4284 // handle simple bitmap clipping drag&drop message 4285 if (target->IsDirectory() 4286 && (dragMessage->HasData(kBitmapMimeType, B_MESSAGE_TYPE) 4287 || dragMessage->HasData(kLargeIconType, B_MESSAGE_TYPE) 4288 || dragMessage->HasData(kMiniIconType, B_MESSAGE_TYPE))) { 4289 return true; 4290 } 4291 4292 // TODO: check for a drag message full of refs, feed a list of their 4293 // types to target->IsDropTargetForList(mimeTypeList); 4294 return false; 4295 } 4296 4297 ASSERT(srcWindow != NULL); 4298 4299 AutoLock<BWindow> lock(srcWindow); 4300 if (!lock) 4301 return false; 4302 4303 BObjectList<BString>* mimeTypeList 4304 = srcWindow->PoseView()->MimeTypesInSelection(); 4305 if (mimeTypeList->IsEmpty()) { 4306 PoseList* selectionList = srcWindow->PoseView()->SelectionList(); 4307 if (!selectionList->IsEmpty()) { 4308 // no cached data yet, build the cache 4309 int32 selectCount = selectionList->CountItems(); 4310 4311 for (int32 index = 0; index < selectCount; index++) { 4312 // get the mime type of the model, following a possible symlink 4313 BEntry entry(selectionList->ItemAt( 4314 index)->TargetModel()->EntryRef(), true); 4315 if (entry.InitCheck() != B_OK) 4316 continue; 4317 4318 BFile file(&entry, O_RDONLY); 4319 BNodeInfo mime(&file); 4320 4321 if (mime.InitCheck() != B_OK) 4322 continue; 4323 4324 char mimeType[B_MIME_TYPE_LENGTH]; 4325 mime.GetType(mimeType); 4326 4327 // add unique type string 4328 if (!WhileEachListItem(mimeTypeList, ContainsOne, 4329 (const char*)mimeType)) { 4330 BString* newMimeString = new BString(mimeType); 4331 mimeTypeList->AddItem(newMimeString); 4332 } 4333 } 4334 } 4335 } 4336 4337 return target->IsDropTargetForList(mimeTypeList); 4338 } 4339 4340 4341 void 4342 BPoseView::TrySettingPoseLocation(BNode* node, BPoint point) 4343 { 4344 if (ViewMode() == kListMode) 4345 return; 4346 4347 if ((modifiers() & B_COMMAND_KEY) != 0) { 4348 // align to grid if needed 4349 point = PinToGrid(point, fGrid, fOffset); 4350 } 4351 4352 Model* targetModel = TargetModel(); 4353 ASSERT(targetModel != NULL); 4354 4355 if (targetModel != NULL && targetModel->NodeRef() != NULL 4356 && FSSetPoseLocation(targetModel->NodeRef()->node, node, point) 4357 == B_OK) { 4358 // get rid of opposite endianness attribute 4359 node->RemoveAttr(kAttrPoseInfoForeign); 4360 } 4361 } 4362 4363 4364 status_t 4365 BPoseView::CreateClippingFile(BPoseView* poseView, BFile &result, 4366 char* resultingName, BDirectory* directory, BMessage* message, 4367 const char* fallbackName, bool setLocation, BPoint dropPoint) 4368 { 4369 // build a file name 4370 // try picking it up from the message 4371 const char* suggestedName; 4372 if (message && message->FindString("be:clip_name", &suggestedName) == B_OK) 4373 strncpy(resultingName, suggestedName, B_FILE_NAME_LENGTH - 1); 4374 else 4375 strcpy(resultingName, fallbackName); 4376 4377 FSMakeOriginalName(resultingName, directory, ""); 4378 4379 // create a clipping file 4380 status_t error = directory->CreateFile(resultingName, &result, true); 4381 if (error != B_OK) 4382 return error; 4383 4384 if (setLocation && poseView != NULL) 4385 poseView->TrySettingPoseLocation(&result, dropPoint); 4386 4387 return B_OK; 4388 } 4389 4390 4391 static int32 4392 RunMimeTypeDestinationMenu(const char* actionText, 4393 const BObjectList<BString>* types, 4394 const BObjectList<BString>* specificItems, BPoint where) 4395 { 4396 int32 count; 4397 if (types != NULL) 4398 count = types->CountItems(); 4399 else 4400 count = specificItems->CountItems(); 4401 4402 if (count == 0) 4403 return 0; 4404 4405 BPopUpMenu* menu = new BPopUpMenu("create clipping"); 4406 menu->SetFont(be_plain_font); 4407 4408 for (int32 index = 0; index < count; index++) { 4409 const char* embedTypeAs = NULL; 4410 char buffer[256]; 4411 if (types) { 4412 types->ItemAt(index)->String(); 4413 BMimeType mimeType(embedTypeAs); 4414 4415 if (mimeType.GetShortDescription(buffer) == B_OK) 4416 embedTypeAs = buffer; 4417 } 4418 4419 BString description; 4420 if (specificItems->ItemAt(index)->Length()) { 4421 description << (const BString &)(*specificItems->ItemAt(index)); 4422 4423 if (embedTypeAs) 4424 description << " (" << embedTypeAs << ")"; 4425 4426 } else if (types) 4427 description = embedTypeAs; 4428 4429 BString labelText; 4430 if (actionText) { 4431 int32 length = 1024 - 1 - (int32)strlen(actionText); 4432 if (length > 0) { 4433 description.Truncate(length); 4434 labelText.SetTo(actionText); 4435 labelText.ReplaceFirst("%s", description.String()); 4436 } else 4437 labelText.SetTo(B_TRANSLATE("label too long")); 4438 } else 4439 labelText = description; 4440 4441 menu->AddItem(new BMenuItem(labelText.String(), 0)); 4442 } 4443 4444 menu->AddSeparatorItem(); 4445 menu->AddItem(new BMenuItem(B_TRANSLATE("Cancel"), 0)); 4446 4447 int32 result = -1; 4448 BMenuItem* resultingItem = menu->Go(where, false, true); 4449 if (resultingItem) { 4450 int32 index = menu->IndexOf(resultingItem); 4451 if (index < count) 4452 result = index; 4453 } 4454 4455 delete menu; 4456 4457 return result; 4458 } 4459 4460 4461 bool 4462 BPoseView::HandleMessageDropped(BMessage* message) 4463 { 4464 ASSERT(message->WasDropped()); 4465 4466 // reset system cursor in case it was altered by drag and drop 4467 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); 4468 fCursorCheck = false; 4469 4470 if (!fDropEnabled) 4471 return false; 4472 4473 BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window()); 4474 if (window != NULL && message->HasData("RGBColor", 'RGBC')) { 4475 // do not handle roColor-style drops here, pass them on to the desktop 4476 BMessenger((BHandler*)window).SendMessage(message); 4477 4478 return true; 4479 } 4480 4481 if (fDropTarget && !DragSelectionContains(fDropTarget, message)) 4482 HiliteDropTarget(false); 4483 4484 fDropTarget = NULL; 4485 4486 ASSERT(TargetModel() != NULL); 4487 BPoint offset; 4488 BPoint dropPoint(message->DropPoint(&offset)); 4489 ConvertFromScreen(&dropPoint); 4490 4491 // tenatively figure out the pose we dropped the file onto 4492 int32 index; 4493 BPose* targetPose = FindPose(dropPoint, &index); 4494 Model tmpTarget; 4495 Model* targetModel = NULL; 4496 if (targetPose != NULL) { 4497 targetModel = targetPose->TargetModel(); 4498 if (targetModel->IsSymLink() 4499 && tmpTarget.SetTo(targetPose->TargetModel()->EntryRef(), 4500 true, true) == B_OK) { 4501 targetModel = &tmpTarget; 4502 } 4503 } 4504 4505 return HandleDropCommon(message, targetModel, targetPose, this, dropPoint); 4506 } 4507 4508 4509 bool 4510 BPoseView::HandleDropCommon(BMessage* message, Model* targetModel, 4511 BPose* targetPose, BView* view, BPoint dropPoint) 4512 { 4513 uint32 buttons = (uint32)message->FindInt32("buttons"); 4514 4515 BContainerWindow* containerWindow = NULL; 4516 BPoseView* poseView = dynamic_cast<BPoseView*>(view); 4517 if (poseView != NULL) 4518 containerWindow = poseView->ContainerWindow(); 4519 4520 // look for srcWindow to determine whether drag was initiated in tracker 4521 BContainerWindow* srcWindow = NULL; 4522 status_t result = message->FindPointer("src_window", (void**)&srcWindow); 4523 if (result != B_OK || srcWindow == NULL) { 4524 // drag was from another app 4525 4526 if (targetModel == NULL && poseView != NULL) 4527 targetModel = poseView->TargetModel(); 4528 4529 // figure out if we dropped a file onto a directory and set 4530 // the targetDirectory to it, else set it to this pose view 4531 BDirectory targetDirectory; 4532 if (targetModel != NULL && targetModel->IsDirectory()) 4533 targetDirectory.SetTo(targetModel->EntryRef()); 4534 4535 if (targetModel != NULL && targetModel->IsRoot()) { 4536 // don't drop anything into the root disk 4537 return false; 4538 } 4539 4540 bool canCopy; 4541 bool canMove; 4542 bool canErase; 4543 bool canLink; 4544 if (FindDragNDropAction(message, canCopy, canMove, canLink, canErase)) { 4545 // new D&D protocol 4546 // what action can the drag initiator do? 4547 if (canErase && CanTrashForeignDrag(targetModel)) { 4548 BMessage reply(B_TRASH_TARGET); 4549 message->SendReply(&reply); 4550 return true; 4551 } 4552 4553 if ((canCopy || canMove) 4554 && CanCopyOrMoveForeignDrag(targetModel, message)) { 4555 // handle the promise style drag&drop 4556 4557 // fish for specification of specialized menu items 4558 BObjectList<BString> actionSpecifiers(10, true); 4559 for (int32 index = 0; ; index++) { 4560 const char* string; 4561 if (message->FindString("be:actionspecifier", index, 4562 &string) != B_OK) { 4563 break; 4564 } 4565 4566 ASSERT(string != NULL); 4567 actionSpecifiers.AddItem(new BString(string)); 4568 } 4569 4570 // build the list of types the drag originator offers 4571 BObjectList<BString> types(10, true); 4572 BObjectList<BString> typeNames(10, true); 4573 for (int32 index = 0; ; index++) { 4574 const char* string; 4575 if (message->FindString("be:filetypes", index, &string) 4576 != B_OK) { 4577 break; 4578 } 4579 4580 ASSERT(string != NULL); 4581 types.AddItem(new BString(string)); 4582 4583 const char* typeName = ""; 4584 message->FindString("be:type_descriptions", index, 4585 &typeName); 4586 typeNames.AddItem(new BString(typeName)); 4587 } 4588 4589 int32 specificTypeIndex = -1; 4590 int32 specificActionIndex = -1; 4591 4592 // if control down, run a popup menu 4593 if (canCopy 4594 && SecondaryMouseButtonDown(modifiers(), buttons)) { 4595 if (actionSpecifiers.CountItems() > 0) { 4596 specificActionIndex = RunMimeTypeDestinationMenu(NULL, 4597 NULL, &actionSpecifiers, 4598 view->ConvertToScreen(dropPoint)); 4599 4600 if (specificActionIndex == -1) 4601 return false; 4602 } else if (types.CountItems() > 0) { 4603 specificTypeIndex = RunMimeTypeDestinationMenu( 4604 B_TRANSLATE("Create %s clipping"), 4605 &types, &typeNames, 4606 view->ConvertToScreen(dropPoint)); 4607 4608 if (specificTypeIndex == -1) 4609 return false; 4610 } 4611 } 4612 4613 char name[B_FILE_NAME_LENGTH]; 4614 BFile file; 4615 if (CreateClippingFile(poseView, file, name, &targetDirectory, 4616 message, B_TRANSLATE("Untitled clipping"), 4617 targetPose == NULL, dropPoint) != B_OK) { 4618 return false; 4619 } 4620 4621 // here is a file for the drag initiator, it is up to it now 4622 // to stuff it with the goods 4623 4624 // build the reply message 4625 BMessage reply(canCopy ? B_COPY_TARGET : B_MOVE_TARGET); 4626 reply.AddString("be:types", B_FILE_MIME_TYPE); 4627 if (specificTypeIndex != -1) { 4628 // we had the user pick a specific type from a menu, use it 4629 reply.AddString("be:filetypes", 4630 types.ItemAt(specificTypeIndex)->String()); 4631 4632 if (typeNames.ItemAt(specificTypeIndex)->Length()) { 4633 reply.AddString("be:type_descriptions", 4634 typeNames.ItemAt(specificTypeIndex)->String()); 4635 } 4636 } 4637 4638 if (specificActionIndex != -1) { 4639 // we had the user pick a specific type from a menu, use it 4640 reply.AddString("be:actionspecifier", 4641 actionSpecifiers.ItemAt(specificActionIndex)->String()); 4642 } 4643 4644 reply.AddRef("directory", targetModel->EntryRef()); 4645 reply.AddString("name", name); 4646 4647 // Attach any data the originator may have tagged on 4648 BMessage data; 4649 if (message->FindMessage("be:originator-data", &data) == B_OK) 4650 reply.AddMessage("be:originator-data", &data); 4651 4652 // copy over all the file types the drag initiator claimed to 4653 // support 4654 for (int32 index = 0; ; index++) { 4655 const char* type; 4656 if (message->FindString("be:filetypes", index, &type) 4657 != B_OK) { 4658 break; 4659 } 4660 reply.AddString("be:filetypes", type); 4661 } 4662 4663 message->SendReply(&reply); 4664 return true; 4665 } 4666 } 4667 4668 if (message->HasRef("refs")) { 4669 // TODO: decide here on copy, move or create symlink 4670 // look for specific command or bring up popup 4671 // Unify this with local drag&drop 4672 4673 if (!targetModel->IsDirectory() 4674 && !targetModel->IsVirtualDirectory()) { 4675 // bail if we are not a directory 4676 return false; 4677 } 4678 4679 bool canRelativeLink = false; 4680 if (!canCopy && !canMove && !canLink && containerWindow) { 4681 if (SecondaryMouseButtonDown(modifiers(), buttons)) { 4682 switch (containerWindow->ShowDropContextMenu(dropPoint, 4683 srcWindow != NULL ? srcWindow->PoseView() : NULL)) { 4684 case kCreateRelativeLink: 4685 canRelativeLink = true; 4686 break; 4687 4688 case kCreateLink: 4689 canLink = true; 4690 break; 4691 4692 case kMoveSelectionTo: 4693 canMove = true; 4694 break; 4695 4696 case kCopySelectionTo: 4697 canCopy = true; 4698 break; 4699 4700 case kCancelButton: 4701 default: 4702 // user canceled context menu 4703 return true; 4704 } 4705 } else 4706 canCopy = true; 4707 } 4708 4709 uint32 moveMode; 4710 if (canCopy) 4711 moveMode = kCopySelectionTo; 4712 else if (canMove) 4713 moveMode = kMoveSelectionTo; 4714 else if (canLink) 4715 moveMode = kCreateLink; 4716 else if (canRelativeLink) 4717 moveMode = kCreateRelativeLink; 4718 else { 4719 TRESPASS(); 4720 return true; 4721 } 4722 4723 // handle refs by performing a copy 4724 BObjectList<entry_ref>* entryList 4725 = new BObjectList<entry_ref>(10, true); 4726 4727 for (int32 index = 0; ; index++) { 4728 // copy all enclosed refs into a list 4729 entry_ref ref; 4730 if (message->FindRef("refs", index, &ref) != B_OK) 4731 break; 4732 entryList->AddItem(new entry_ref(ref)); 4733 } 4734 4735 int32 count = entryList->CountItems(); 4736 if (count != 0) { 4737 BList* pointList = 0; 4738 if (poseView != NULL && targetPose != NULL) { 4739 // calculate a pointList to make the icons land 4740 // were we dropped them 4741 pointList = new BList(count); 4742 // force the the icons to lay out in 5 columns 4743 for (int32 index = 0; count; index++) { 4744 for (int32 j = 0; count && j < 4; j++, count--) { 4745 BPoint point( 4746 dropPoint + BPoint(j * poseView->fGrid.x, 4747 index * poseView->fGrid.y)); 4748 pointList->AddItem( 4749 new BPoint(poseView->PinToGrid(point, 4750 poseView->fGrid, poseView->fOffset))); 4751 } 4752 } 4753 } 4754 4755 // perform asynchronous copy 4756 FSMoveToFolder(entryList, new BEntry(targetModel->EntryRef()), 4757 moveMode, pointList); 4758 4759 return true; 4760 } 4761 4762 // nothing to copy, list doesn't get consumed 4763 delete entryList; 4764 return true; 4765 } 4766 if (message->HasData(kPlainTextMimeType, B_MIME_TYPE)) { 4767 // text dropped, make into a clipping file 4768 if (!targetModel->IsDirectory()) { 4769 // bail if we are not a directory 4770 return false; 4771 } 4772 4773 // find the text 4774 ssize_t textLength; 4775 const char* text; 4776 if (message->FindData(kPlainTextMimeType, B_MIME_TYPE, 4777 (const void**)&text, &textLength) != B_OK) { 4778 return false; 4779 } 4780 4781 char name[B_FILE_NAME_LENGTH]; 4782 BFile file; 4783 if (CreateClippingFile(poseView, file, name, &targetDirectory, 4784 message, B_TRANSLATE("Untitled clipping"), !targetPose, 4785 dropPoint) != B_OK) { 4786 return false; 4787 } 4788 4789 // write out the file 4790 if (file.Seek(0, SEEK_SET) == B_ERROR 4791 || file.Write(text, (size_t)textLength) < 0 4792 || file.SetSize(textLength) != B_OK) { 4793 // failed to write file, remove file and bail 4794 file.Unset(); 4795 BEntry entry(&targetDirectory, name); 4796 entry.Remove(); 4797 PRINT(("error writing text into file %s\n", name)); 4798 } 4799 4800 // pick up TextView styles if available and save them with the file 4801 const text_run_array* textRuns = NULL; 4802 ssize_t dataSize = 0; 4803 if (message->FindData("application/x-vnd.Be-text_run_array", 4804 B_MIME_TYPE, (const void**)&textRuns, &dataSize) == B_OK 4805 && textRuns && dataSize) { 4806 // save styles the same way StyledEdit does 4807 int32 tmpSize = dataSize; 4808 void* data = BTextView::FlattenRunArray(textRuns, &tmpSize); 4809 file.WriteAttr("styles", B_RAW_TYPE, 0, data, (size_t)tmpSize); 4810 free(data); 4811 } 4812 4813 // mark as a clipping file 4814 int32 tmp; 4815 file.WriteAttr(kAttrClippingFile, B_RAW_TYPE, 0, &tmp, 4816 sizeof(int32)); 4817 4818 // set the file type 4819 BNodeInfo info(&file); 4820 info.SetType(kPlainTextMimeType); 4821 4822 return true; 4823 } 4824 4825 if (message->HasData(kBitmapMimeType, B_MESSAGE_TYPE) 4826 || message->HasData(kLargeIconType, B_MESSAGE_TYPE) 4827 || message->HasData(kMiniIconType, B_MESSAGE_TYPE)) { 4828 // bitmap, make into a clipping file 4829 if (!targetModel->IsDirectory()) { 4830 // bail if we are not a directory 4831 return false; 4832 } 4833 4834 BMessage embeddedBitmap; 4835 if (message->FindMessage(kBitmapMimeType, &embeddedBitmap) 4836 != B_OK 4837 && message->FindMessage(kLargeIconType, &embeddedBitmap) 4838 != B_OK 4839 && message->FindMessage(kMiniIconType, &embeddedBitmap) 4840 != B_OK) { 4841 return false; 4842 } 4843 4844 char name[B_FILE_NAME_LENGTH]; 4845 4846 BFile file; 4847 if (CreateClippingFile(poseView, file, name, &targetDirectory, 4848 message, B_TRANSLATE("Untitled bitmap"), targetPose == NULL, 4849 dropPoint) != B_OK) { 4850 return false; 4851 } 4852 4853 int32 size = embeddedBitmap.FlattenedSize(); 4854 if (size > 1024 * 1024) { 4855 // bail if too large 4856 return false; 4857 } 4858 4859 char* buffer = new char [size]; 4860 embeddedBitmap.Flatten(buffer, size); 4861 4862 // write out the file 4863 if (file.Seek(0, SEEK_SET) == B_ERROR 4864 || file.Write(buffer, (size_t)size) < 0 4865 || file.SetSize(size) != B_OK) { 4866 // failed to write file, remove file and bail 4867 file.Unset(); 4868 BEntry entry(&targetDirectory, name); 4869 entry.Remove(); 4870 PRINT(("error writing bitmap into file %s\n", name)); 4871 } 4872 4873 // mark as a clipping file 4874 int32 tmp; 4875 file.WriteAttr(kAttrClippingFile, B_RAW_TYPE, 0, &tmp, 4876 sizeof(int32)); 4877 4878 // set the file type 4879 BNodeInfo info(&file); 4880 info.SetType(kBitmapMimeType); 4881 4882 return true; 4883 } 4884 4885 return false; 4886 } 4887 4888 ASSERT(srcWindow != NULL); 4889 4890 if (srcWindow == containerWindow) { 4891 // drag started in this window 4892 containerWindow->Activate(); 4893 containerWindow->UpdateIfNeeded(); 4894 poseView->ResetPosePlacementHint(); 4895 4896 if (DragSelectionContains(targetPose, message)) { 4897 // drop on self 4898 targetModel = NULL; 4899 } 4900 } 4901 4902 bool wasHandled = false; 4903 bool ignoreTypes = (modifiers() & B_CONTROL_KEY) != 0; 4904 4905 if (targetModel != NULL && containerWindow != NULL) { 4906 // TODO: pick files to drop/launch on a case by case basis 4907 if (targetModel->IsDirectory() || targetModel->IsVirtualDirectory()) { 4908 MoveSelectionInto(targetModel, srcWindow, containerWindow, 4909 buttons, dropPoint, false); 4910 wasHandled = true; 4911 } else if (CanHandleDragSelection(targetModel, message, ignoreTypes)) { 4912 LaunchAppWithSelection(targetModel, message, !ignoreTypes); 4913 wasHandled = true; 4914 } 4915 } 4916 4917 if (poseView != NULL && !wasHandled) { 4918 BPoint clickPoint = message->FindPoint("click_pt"); 4919 // TODO: removed check for root here need to do that, possibly at a 4920 // different level 4921 poseView->MoveSelectionTo(dropPoint, clickPoint, srcWindow); 4922 } 4923 4924 if (poseView != NULL && poseView->fEnsurePosesVisible) 4925 poseView->CheckPoseVisibility(); 4926 4927 return true; 4928 } 4929 4930 4931 struct LaunchParams { 4932 Model* app; 4933 bool checkTypes; 4934 BMessage* refsMessage; 4935 }; 4936 4937 4938 static bool 4939 AddOneToLaunchMessage(BPose* pose, BPoseView*, void* castToParams) 4940 { 4941 LaunchParams* params = (LaunchParams*)castToParams; 4942 ThrowOnAssert(params != NULL); 4943 ThrowOnAssert(pose != NULL); 4944 ThrowOnAssert(pose->TargetModel() != NULL); 4945 4946 if (params->app->IsDropTarget(params->checkTypes 4947 ? pose->TargetModel() : NULL, true)) { 4948 params->refsMessage->AddRef("refs", pose->TargetModel()->EntryRef()); 4949 } 4950 4951 return false; 4952 } 4953 4954 4955 void 4956 BPoseView::LaunchAppWithSelection(Model* appModel, const BMessage* dragMessage, 4957 bool checkTypes) 4958 { 4959 // launch items from the current selection with <appModel>; only pass 4960 // the same files that we previously decided can be handled by <appModel> 4961 BMessage refs(B_REFS_RECEIVED); 4962 LaunchParams params; 4963 params.app = appModel; 4964 params.checkTypes = checkTypes; 4965 params.refsMessage = &refs; 4966 4967 // add Tracker token so that refs received recipients can script us 4968 BContainerWindow* srcWindow; 4969 if (dragMessage->FindPointer("src_window", (void**)&srcWindow) == B_OK 4970 && srcWindow != NULL) { 4971 params.refsMessage->AddMessenger("TrackerViewToken", 4972 BMessenger(srcWindow->PoseView())); 4973 } 4974 4975 EachItemInDraggedSelection(dragMessage, AddOneToLaunchMessage, 0, ¶ms); 4976 if (params.refsMessage->HasRef("refs")) 4977 TrackerLaunch(appModel->EntryRef(), params.refsMessage, true); 4978 } 4979 4980 4981 bool 4982 BPoseView::DragSelectionContains(const BPose* target, 4983 const BMessage* dragMessage) 4984 { 4985 return EachItemInDraggedSelection(dragMessage, OneMatches, 0, 4986 (void*)target); 4987 } 4988 4989 4990 void 4991 BPoseView::MoveSelectionInto(Model* destFolder, BContainerWindow* srcWindow, 4992 bool forceCopy, bool forceMove, bool createLink, bool relativeLink) 4993 { 4994 uint32 buttons; 4995 BPoint loc; 4996 GetMouse(&loc, &buttons); 4997 MoveSelectionInto(destFolder, srcWindow, 4998 dynamic_cast<BContainerWindow*>(Window()), buttons, loc, forceCopy, 4999 forceMove, createLink, relativeLink); 5000 } 5001 5002 5003 void 5004 BPoseView::MoveSelectionInto(Model* destFolder, BContainerWindow* srcWindow, 5005 BContainerWindow* destWindow, uint32 buttons, BPoint loc, bool forceCopy, 5006 bool forceMove, bool createLink, bool relativeLink, BPoint clickPoint, 5007 bool dropOnGrid) 5008 { 5009 AutoLock<BWindow> lock(srcWindow); 5010 if (!lock) 5011 return; 5012 5013 ASSERT(srcWindow->PoseView()->TargetModel() != NULL); 5014 5015 if (srcWindow->PoseView()->CountSelected() == 0) 5016 return; 5017 5018 bool createRelativeLink = relativeLink; 5019 if (SecondaryMouseButtonDown(modifiers(), buttons) 5020 && destWindow != NULL) { 5021 switch (destWindow->ShowDropContextMenu(loc, 5022 srcWindow != NULL ? srcWindow->PoseView() : NULL)) { 5023 case kCreateRelativeLink: 5024 createRelativeLink = true; 5025 break; 5026 5027 case kCreateLink: 5028 createLink = true; 5029 break; 5030 5031 case kMoveSelectionTo: 5032 forceMove = true; 5033 break; 5034 5035 case kCopySelectionTo: 5036 forceCopy = true; 5037 break; 5038 5039 case kCancelButton: 5040 default: 5041 // user canceled context menu 5042 return; 5043 } 5044 } 5045 5046 // make sure source and destination folders are different 5047 if (!createLink && !createRelativeLink 5048 && (*srcWindow->PoseView()->TargetModel()->NodeRef() 5049 == *destFolder->NodeRef())) { 5050 BPoseView* targetView = srcWindow->PoseView(); 5051 if (forceCopy) { 5052 targetView->DuplicateSelection(&clickPoint, &loc); 5053 return; 5054 } 5055 5056 if (targetView->ViewMode() == kListMode) { 5057 // can't move in list view 5058 return; 5059 } 5060 5061 BPoint delta = loc - clickPoint; 5062 int32 selectCount = targetView->CountSelected(); 5063 for (int32 index = 0; index < selectCount; index++) { 5064 BPose* pose = targetView->SelectionList()->ItemAt(index); 5065 5066 // remove pose from VSlist before changing location 5067 // so that we "find" the correct pose to remove 5068 // need to do this because bsearch uses top of pose 5069 // to locate pose to remove 5070 targetView->RemoveFromVSList(pose); 5071 BPoint location(pose->Location(targetView) + delta); 5072 BRect oldBounds(pose->CalcRect(targetView)); 5073 if (dropOnGrid) { 5074 location = targetView->PinToGrid(location, targetView->fGrid, 5075 targetView->fOffset); 5076 } 5077 5078 // TODO: don't drop poses under desktop elements 5079 // ie: replicants, deskbar 5080 pose->MoveTo(location, targetView); 5081 5082 targetView->RemoveFromExtent(oldBounds); 5083 targetView->AddToExtent(pose->CalcRect(targetView)); 5084 5085 // remove and reinsert pose to keep VSlist sorted 5086 targetView->AddToVSList(pose); 5087 } 5088 5089 return; 5090 } 5091 5092 BEntry* destEntry = new BEntry(destFolder->EntryRef()); 5093 bool destIsTrash = destFolder->IsTrash(); 5094 5095 // perform asynchronous copy/move 5096 forceCopy = forceCopy || (modifiers() & B_OPTION_KEY) != 0; 5097 5098 bool okToMove = true; 5099 5100 if (destFolder->IsRoot()) { 5101 BAlert* alert = new BAlert("", 5102 B_TRANSLATE("You must drop items on one of the disk icons " 5103 "in the \"Disks\" window."), B_TRANSLATE("Cancel"), NULL, NULL, 5104 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 5105 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 5106 alert->Go(); 5107 okToMove = false; 5108 } 5109 5110 // can't copy to read-only volume 5111 BVolume destVolume(destFolder->NodeRef()->device); 5112 if (destVolume.InitCheck() == B_OK && destVolume.IsReadOnly()) { 5113 BAlert* alert = new BAlert("", 5114 B_TRANSLATE("You can't move or copy items to read-only volumes."), 5115 B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 5116 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 5117 alert->Go(); 5118 okToMove = false; 5119 } 5120 5121 // can't copy items into the trash 5122 if (forceCopy && destIsTrash) { 5123 BAlert* alert = new BAlert("", 5124 B_TRANSLATE("Sorry, you can't copy items to the Trash."), 5125 B_TRANSLATE("Cancel"), NULL, NULL, B_WIDTH_AS_USUAL, 5126 B_WARNING_ALERT); 5127 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 5128 alert->Go(); 5129 okToMove = false; 5130 } 5131 5132 // can't create symlinks into the trash 5133 if (createLink && destIsTrash) { 5134 BAlert* alert = new BAlert("", 5135 B_TRANSLATE("Sorry, you can't create links in the Trash."), 5136 B_TRANSLATE("Cancel"), NULL, NULL, B_WIDTH_AS_USUAL, 5137 B_WARNING_ALERT); 5138 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 5139 alert->Go(); 5140 okToMove = false; 5141 } 5142 5143 // prompt user if drag was from a query 5144 if (srcWindow->TargetModel()->IsQuery() 5145 && !forceCopy && !destIsTrash && !createLink) { 5146 srcWindow->UpdateIfNeeded(); 5147 BAlert* alert = new BAlert("", 5148 B_TRANSLATE("Are you sure you want to move or copy the selected " 5149 "item(s) to this folder?"), B_TRANSLATE("Cancel"), 5150 B_TRANSLATE("Move"), NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 5151 alert->SetShortcut(0, B_ESCAPE); 5152 okToMove = alert->Go() == 1; 5153 } 5154 5155 if (okToMove) { 5156 PoseList* selectionList = srcWindow->PoseView()->SelectionList(); 5157 BList* pointList = destWindow->PoseView()->GetDropPointList(clickPoint, 5158 loc, selectionList, srcWindow->PoseView()->ViewMode() == kListMode, 5159 dropOnGrid); 5160 int32 selectionSize = selectionList->CountItems(); 5161 BObjectList<entry_ref>* srcList 5162 = new BObjectList<entry_ref>(selectionSize, true); 5163 5164 if (srcWindow->TargetModel()->IsVirtualDirectory()) { 5165 // resolve symlink and add the resulting entry_ref to the list 5166 for (int32 i = 0; i < selectionSize; i++) { 5167 Model* model = selectionList->ItemAt(i)->ResolvedModel(); 5168 if (model != NULL) 5169 srcList->AddItem(new entry_ref(*(model->EntryRef()))); 5170 } 5171 } else 5172 CopySelectionListToEntryRefList(selectionList, srcList); 5173 5174 uint32 moveMode; 5175 if (forceCopy) 5176 moveMode = kCopySelectionTo; 5177 else if (forceMove) 5178 moveMode = kMoveSelectionTo; 5179 else if (createRelativeLink) 5180 moveMode = kCreateRelativeLink; 5181 else if (createLink) 5182 moveMode = kCreateLink; 5183 else if (!CheckDevicesEqual(srcList->ItemAt(0), destFolder)) 5184 moveMode = kCopySelectionTo; 5185 else 5186 moveMode = kMoveSelectionTo; 5187 5188 FSMoveToFolder(srcList, destEntry, moveMode, pointList); 5189 return; 5190 } 5191 5192 delete destEntry; 5193 } 5194 5195 5196 void 5197 BPoseView::MoveSelectionTo(BPoint dropPoint, BPoint clickPoint, 5198 BContainerWindow* srcWindow) 5199 { 5200 // Moves selection from srcWindow into this window, copying if necessary. 5201 5202 BContainerWindow* window = ContainerWindow(); 5203 if (window == NULL) 5204 return; 5205 5206 ASSERT(window->PoseView() != NULL); 5207 ASSERT(TargetModel() != NULL); 5208 5209 // make sure this window is a legal drop target 5210 if (srcWindow != window && !TargetModel()->IsDropTarget()) 5211 return; 5212 5213 uint32 buttons = (uint32)window->CurrentMessage()->FindInt32("buttons"); 5214 bool pinToGrid = (modifiers() & B_COMMAND_KEY) != 0; 5215 MoveSelectionInto(TargetModel(), srcWindow, window, buttons, dropPoint, 5216 false, false, false, false, clickPoint, pinToGrid); 5217 } 5218 5219 5220 inline void 5221 UpdateWasBrokenSymlinkBinder(BPose* pose, Model* model, int32 index, 5222 BPoseView* poseView, BObjectList<Model>* fBrokenLinks) 5223 { 5224 if (!model->IsSymLink()) 5225 return; 5226 5227 BPoint loc(0, index * poseView->ListElemHeight()); 5228 pose->UpdateWasBrokenSymlink(loc, poseView); 5229 if (model->LinkTo() != NULL) 5230 fBrokenLinks->RemoveItem(model); 5231 } 5232 5233 5234 void 5235 BPoseView::TryUpdatingBrokenLinks() 5236 { 5237 AutoLock<BWindow> lock(Window()); 5238 if (!lock) 5239 return; 5240 5241 BObjectList<Model>* brokenLinksCopy = new BObjectList<Model>(*fBrokenLinks); 5242 5243 // try fixing broken symlinks, and detecting broken ones. 5244 EachPoseAndModel(fPoseList, &UpdateWasBrokenSymlinkBinder, this, 5245 fBrokenLinks); 5246 5247 for (int i = brokenLinksCopy->CountItems() - 1; i >= 0; i--) { 5248 if (!fBrokenLinks->HasItem(brokenLinksCopy->ItemAt(i))) 5249 StopWatchingParentsOf(brokenLinksCopy->ItemAt(i)->EntryRef()); 5250 } 5251 5252 delete brokenLinksCopy; 5253 } 5254 5255 5256 void 5257 BPoseView::PoseHandleDeviceUnmounted(BPose* pose, Model* model, int32 index, 5258 BPoseView* poseView, dev_t device) 5259 { 5260 if (model->NodeRef()->device == device) 5261 poseView->DeletePose(model->NodeRef()); 5262 else if (model->IsSymLink() && model->LinkTo() != NULL 5263 && model->LinkTo()->NodeRef()->device == device) { 5264 poseView->DeleteSymLinkPoseTarget(model->LinkTo()->NodeRef(), 5265 pose, index); 5266 } 5267 } 5268 5269 5270 static void 5271 OneMetaMimeChanged(BPose* pose, Model* model, int32 index, 5272 BPoseView* poseView, const char* type) 5273 { 5274 ASSERT(model != NULL); 5275 5276 if (model->IconFrom() != kNode 5277 && model->IconFrom() != kUnknownSource 5278 && model->IconFrom() != kUnknownNotFromNode 5279 // TODO: add supertype compare 5280 && strcasecmp(model->MimeType(), type) == 0) { 5281 // metamime change very likely affected the documents icon 5282 BPoint poseLoc(0, index * poseView->ListElemHeight()); 5283 pose->UpdateIcon(poseLoc, poseView); 5284 } 5285 } 5286 5287 5288 void 5289 BPoseView::MetaMimeChanged(const char* type, const char* preferredApp) 5290 { 5291 IconCache::sIconCache->IconChanged(type, preferredApp); 5292 // wait for other windows to do the same before we start 5293 // updating poses which causes icon recaching 5294 // TODO: this is a design problem that should be solved differently 5295 snooze(10000); 5296 Window()->UpdateIfNeeded(); 5297 5298 EachPoseAndResolvedModel(fPoseList, &OneMetaMimeChanged, this, type); 5299 } 5300 5301 5302 class MetaMimeChangedAccumulator : public AccumulatingFunctionObject { 5303 // pools up matching metamime change notices, executing them as a single 5304 // update 5305 public: 5306 MetaMimeChangedAccumulator(void (BPoseView::*func)(const char* type, 5307 const char* preferredApp), 5308 BContainerWindow* window, const char* type, const char* preferredApp) 5309 : 5310 fCallOnThis(window), 5311 fFunc(func), 5312 fType(type), 5313 fPreferredApp(preferredApp) 5314 { 5315 } 5316 5317 virtual bool CanAccumulate(const AccumulatingFunctionObject* functor) const 5318 { 5319 const MetaMimeChangedAccumulator* accumulator 5320 = dynamic_cast<const MetaMimeChangedAccumulator*>(functor); 5321 if (accumulator == NULL) 5322 return false; 5323 5324 return accumulator && accumulator->fType == fType 5325 && accumulator->fPreferredApp == fPreferredApp; 5326 } 5327 5328 virtual void Accumulate(AccumulatingFunctionObject* DEBUG_ONLY(functor)) 5329 { 5330 ASSERT(CanAccumulate(functor)); 5331 // do nothing, no further accumulating needed 5332 } 5333 5334 protected: 5335 virtual void operator()() 5336 { 5337 AutoLock<BWindow> lock(fCallOnThis); 5338 if (!lock) 5339 return; 5340 5341 (fCallOnThis->PoseView()->*fFunc)(fType.String(), 5342 fPreferredApp.String()); 5343 } 5344 5345 virtual ulong Size() const 5346 { 5347 return sizeof (*this); 5348 } 5349 5350 private: 5351 BContainerWindow* fCallOnThis; 5352 void (BPoseView::*fFunc)(const char* type, const char* preferredApp); 5353 BString fType; 5354 BString fPreferredApp; 5355 }; 5356 5357 5358 bool 5359 BPoseView::NoticeMetaMimeChanged(const BMessage* message) 5360 { 5361 int32 change; 5362 if (message->FindInt32("be:which", &change) != B_OK) 5363 return true; 5364 5365 bool iconChanged = (change & B_ICON_CHANGED) != 0; 5366 bool iconForTypeChanged = (change & B_ICON_FOR_TYPE_CHANGED) != 0; 5367 bool preferredAppChanged = (change & B_APP_HINT_CHANGED) 5368 || (change & B_PREFERRED_APP_CHANGED); 5369 5370 const char* type = NULL; 5371 const char* preferredApp = NULL; 5372 5373 if (iconChanged || preferredAppChanged) 5374 message->FindString("be:type", &type); 5375 5376 if (iconForTypeChanged) { 5377 message->FindString("be:extra_type", &type); 5378 message->FindString("be:type", &preferredApp); 5379 } 5380 5381 if (iconChanged || preferredAppChanged || iconForTypeChanged) { 5382 TaskLoop* taskLoop = ContainerWindow()->DelayedTaskLoop(); 5383 ASSERT(taskLoop != NULL); 5384 taskLoop->AccumulatedRunLater(new MetaMimeChangedAccumulator( 5385 &BPoseView::MetaMimeChanged, ContainerWindow(), type, preferredApp), 5386 200000, 5000000); 5387 } 5388 5389 return true; 5390 } 5391 5392 5393 bool 5394 BPoseView::FSNotification(const BMessage* message) 5395 { 5396 node_ref itemNode; 5397 dev_t device; 5398 Model* targetModel = TargetModel(); 5399 5400 switch (message->FindInt32("opcode")) { 5401 case B_ENTRY_CREATED: 5402 { 5403 ASSERT(targetModel != NULL); 5404 5405 message->FindInt32("device", &itemNode.device); 5406 node_ref dirNode; 5407 dirNode.device = itemNode.device; 5408 message->FindInt64("directory", (int64*)&dirNode.node); 5409 message->FindInt64("node", (int64*)&itemNode.node); 5410 5411 int32 count = fBrokenLinks->CountItems(); 5412 bool createPose = true; 5413 // Query windows can get notices on different dirNodes 5414 // The Disks window can too 5415 // So can the Desktop, as long as the integrate flag is on 5416 TrackerSettings settings; 5417 if (targetModel != NULL && dirNode != *targetModel->NodeRef() 5418 && !targetModel->IsQuery() 5419 && !targetModel->IsVirtualDirectory() 5420 && !targetModel->IsRoot() 5421 && (!settings.ShowDisksIcon() || !IsDesktopView())) { 5422 if (count == 0) 5423 break; 5424 createPose = false; 5425 } 5426 5427 const char* name; 5428 if (message->FindString("name", &name) != B_OK) { 5429 #if DEBUG 5430 SERIAL_PRINT(("no name in entry creation message\n")); 5431 #endif 5432 break; 5433 } 5434 if (count != 0) { 5435 // basically, let's say we have a broken link : 5436 // ./a_link -> ./some_folder/another_folder/a_target 5437 // and that both some_folder and another_folder didn't 5438 // exist yet. We are looking if the just created folder 5439 // is 'some_folder' and watch it, expecting the creation of 5440 // 'another_folder' later and then report the link as fixed. 5441 Model* model = new Model(&dirNode, &itemNode, name); 5442 if (model->IsDirectory()) { 5443 BString createdPath(BPath(model->EntryRef()).Path()); 5444 BDirectory currentDir(targetModel->EntryRef()); 5445 BPath createdDir(model->EntryRef()); 5446 for (int32 i = 0; i < count; i++) { 5447 BSymLink link(fBrokenLinks->ItemAt(i)->EntryRef()); 5448 BPath path; 5449 link.MakeLinkedPath(¤tDir, &path); 5450 BString pathStr(path.Path()); 5451 pathStr.Append("/"); 5452 if (pathStr.Compare(createdPath, 5453 createdPath.Length()) == 0) { 5454 if (pathStr[createdPath.Length()] != '/') 5455 break; 5456 StopWatchingParentsOf(fBrokenLinks->ItemAt(i) 5457 ->EntryRef()); 5458 watch_node(&itemNode, B_WATCH_DIRECTORY, this); 5459 break; 5460 } 5461 } 5462 } 5463 delete model; 5464 } 5465 if (createPose) 5466 EntryCreated(&dirNode, &itemNode, name); 5467 5468 TryUpdatingBrokenLinks(); 5469 break; 5470 } 5471 5472 case B_ENTRY_MOVED: 5473 return EntryMoved(message); 5474 break; 5475 5476 case B_ENTRY_REMOVED: 5477 message->FindInt32("device", &itemNode.device); 5478 message->FindInt64("node", (int64*)&itemNode.node); 5479 5480 // our window itself may be deleted 5481 // we must check to see if this comes as a query 5482 // notification or a node monitor notification because 5483 // if it's a query notification then we're just being told we 5484 // no longer match the query, so we don't want to close the window 5485 // but it's a node monitor notification then that means our query 5486 // file has been deleted so we close the window 5487 5488 if (message->what == B_NODE_MONITOR && targetModel != NULL 5489 && *(targetModel->NodeRef()) == itemNode) { 5490 if (!targetModel->IsRoot()) { 5491 // it is impossible to watch for ENTRY_REMOVED in 5492 // "/" because the notification is ambiguous - the vnode 5493 // is that of the volume but the device is of the parent 5494 // not the same as the device of the volume that way we 5495 // may get aliasing for volumes with vnodes of 1 5496 // (currently the case for iso9660) 5497 DisableSaveLocation(); 5498 Window()->Close(); 5499 } 5500 } else { 5501 int32 index; 5502 BPose* pose = fPoseList->FindPose(&itemNode, &index); 5503 if (pose == NULL) { 5504 // couldn't find pose, first check if the node might be 5505 // target of a symlink pose; 5506 // 5507 // What happens when a node and a symlink to it are in the 5508 // same window? 5509 // They get monitored twice, we get two notifications; the 5510 // first one will get caught by the first FindPose, the 5511 // second one by the DeepFindPose 5512 // 5513 pose = fPoseList->DeepFindPose(&itemNode, &index); 5514 if (pose != NULL) { 5515 DeleteSymLinkPoseTarget(&itemNode, pose, index); 5516 break; 5517 } 5518 } 5519 5520 DeletePose(&itemNode); 5521 TryUpdatingBrokenLinks(); 5522 } 5523 break; 5524 5525 case B_DEVICE_MOUNTED: 5526 { 5527 if (message->FindInt32("new device", &device) != B_OK) 5528 break; 5529 5530 if (targetModel != NULL && targetModel->IsRoot()) { 5531 BVolume volume(device); 5532 if (volume.InitCheck() == B_OK) 5533 CreateVolumePose(&volume, false); 5534 } else if (ContainerWindow()->IsTrash()) { 5535 // add trash items from newly mounted volume 5536 5537 BDirectory trashDir; 5538 BEntry entry; 5539 BVolume volume(device); 5540 if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK 5541 && trashDir.GetEntry(&entry) == B_OK) { 5542 Model model(&entry); 5543 if (model.InitCheck() == B_OK) 5544 AddPoses(&model); 5545 } 5546 } 5547 TaskLoop* taskLoop = ContainerWindow()->DelayedTaskLoop(); 5548 ASSERT(taskLoop); 5549 taskLoop->RunLater(NewMemberFunctionObject( 5550 &BPoseView::TryUpdatingBrokenLinks, this), 500000); 5551 // delay of 500000: wait for volumes to properly finish mounting 5552 // without this in the Model::FinishSettingUpType a symlink 5553 // to a volume would get initialized as a symlink to a directory 5554 // because IsRootDirectory looks like returns false. Either 5555 // there is a race condition or I was doing something wrong. 5556 break; 5557 } 5558 5559 case B_DEVICE_UNMOUNTED: 5560 if (message->FindInt32("device", &device) == B_OK) { 5561 if (targetModel != NULL 5562 && targetModel->NodeRef()->device == device) { 5563 // close the window from a volume that is gone 5564 DisableSaveLocation(); 5565 Window()->Close(); 5566 } else if (targetModel != NULL) { 5567 EachPoseAndModel(fPoseList, &PoseHandleDeviceUnmounted, 5568 this, device); 5569 } 5570 } 5571 break; 5572 5573 case B_STAT_CHANGED: 5574 case B_ATTR_CHANGED: 5575 return AttributeChanged(message); 5576 } 5577 5578 return true; 5579 } 5580 5581 5582 bool 5583 BPoseView::CreateSymlinkPoseTarget(Model* symlink) 5584 { 5585 Model* newResolvedModel = NULL; 5586 Model* result = symlink->LinkTo(); 5587 5588 if (result == NULL) { 5589 BEntry entry(symlink->EntryRef(), true); 5590 if (entry.InitCheck() == B_OK) { 5591 node_ref nref; 5592 entry_ref eref; 5593 entry.GetNodeRef(&nref); 5594 entry.GetRef(&eref); 5595 if (eref.directory != TargetModel()->NodeRef()->node) 5596 WatchNewNode(&nref, B_WATCH_STAT | B_WATCH_ATTR | B_WATCH_NAME 5597 | B_WATCH_INTERIM_STAT, this); 5598 newResolvedModel = new Model(&entry, true); 5599 } else { 5600 fBrokenLinks->AddItem(symlink); 5601 WatchParentOf(symlink->EntryRef()); 5602 return true; 5603 } 5604 result = newResolvedModel; 5605 } 5606 symlink->SetLinkTo(result); 5607 5608 return true; 5609 } 5610 5611 5612 BPose* 5613 BPoseView::EntryCreated(const node_ref* dirNode, const node_ref* itemNode, 5614 const char* name, int32* indexPtr) 5615 { 5616 // reject notification if pose already exists 5617 if (fPoseList->FindPose(itemNode) || FindZombie(itemNode)) 5618 return NULL; 5619 5620 BPoseView::WatchNewNode(itemNode); 5621 // have to node monitor ahead of time because Model will 5622 // cache up the file type and preferred app 5623 Model* model = new Model(dirNode, itemNode, name, true); 5624 5625 if (model->InitCheck() != B_OK) { 5626 // if we have trouble setting up model then we stuff it into 5627 // a zombie list in a half-alive state until we can properly awaken it 5628 PRINT(("2 adding model %s to zombie list, error %s\n", model->Name(), 5629 strerror(model->InitCheck()))); 5630 fZombieList->AddItem(model); 5631 return NULL; 5632 } 5633 5634 PoseInfo poseInfo; 5635 ReadPoseInfo(model, &poseInfo); 5636 5637 if (!PoseVisible(model, &poseInfo)) { 5638 watch_node(model->NodeRef(), B_STOP_WATCHING, this); 5639 delete model; 5640 return NULL; 5641 } 5642 5643 // model is a symlink, cache up the symlink target or scrap 5644 // everything if target is invisible 5645 if (model->IsSymLink() && !CreateSymlinkPoseTarget(model)) { 5646 watch_node(model->NodeRef(), B_STOP_WATCHING, this); 5647 delete model; 5648 return NULL; 5649 } 5650 5651 return CreatePose(model, &poseInfo, true, indexPtr); 5652 } 5653 5654 5655 bool 5656 BPoseView::EntryMoved(const BMessage* message) 5657 { 5658 ino_t oldDir; 5659 node_ref dirNode; 5660 node_ref itemNode; 5661 5662 message->FindInt32("device", &dirNode.device); 5663 itemNode.device = dirNode.device; 5664 message->FindInt64("to directory", (int64*)&dirNode.node); 5665 message->FindInt64("node", (int64*)&itemNode.node); 5666 message->FindInt64("from directory", (int64*)&oldDir); 5667 5668 const char* name; 5669 if (message->FindString("name", &name) != B_OK) 5670 return true; 5671 5672 // handle special case of notifying a name change for a volume 5673 // - the notification is not enough, because the volume's device 5674 // is different than that of the root directory; we have to do a 5675 // lookup using the new volume name and get the volume device from there 5676 StatStruct st; 5677 // get the inode of the root and check if we got a notification on it 5678 if (stat("/", &st) >= 0 5679 && st.st_dev == dirNode.device 5680 && st.st_ino == dirNode.node) { 5681 BString buffer; 5682 buffer << "/" << name; 5683 if (stat(buffer.String(), &st) >= 0) { 5684 // point the dirNode to the actual volume 5685 itemNode.node = st.st_ino; 5686 itemNode.device = st.st_dev; 5687 } 5688 } 5689 5690 Model* targetModel = TargetModel(); 5691 ThrowOnAssert(targetModel != NULL); 5692 5693 node_ref thisDirNode; 5694 if (ContainerWindow()->IsTrash()) { 5695 BDirectory trashDir; 5696 if (FSGetTrashDir(&trashDir, itemNode.device) != B_OK) 5697 return true; 5698 5699 trashDir.GetNodeRef(&thisDirNode); 5700 } else 5701 thisDirNode = *targetModel->NodeRef(); 5702 5703 // see if we need to update window title (and folder itself) 5704 if (thisDirNode == itemNode) { 5705 targetModel->UpdateEntryRef(&dirNode, name); 5706 assert_cast<BContainerWindow*>(Window())->UpdateTitle(); 5707 } 5708 5709 if (oldDir == dirNode.node || targetModel->IsQuery() 5710 || targetModel->IsVirtualDirectory()) { 5711 // rename or move of entry in this directory (or query) 5712 5713 int32 index; 5714 BPose* pose = fPoseList->FindPose(&itemNode, &index); 5715 int32 poseListIndex = index; 5716 bool visible = true; 5717 if (fFiltering) 5718 visible = fFilteredPoseList->FindPose(&itemNode, &index) != NULL; 5719 5720 if (pose != NULL) { 5721 Model* poseModel = pose->TargetModel(); 5722 ASSERT(poseModel != NULL); 5723 poseModel->UpdateEntryRef(&dirNode, name); 5724 // for queries we check for move to trash and remove item if so 5725 if (targetModel->IsQuery()) { 5726 PoseInfo poseInfo; 5727 ReadPoseInfo(poseModel, &poseInfo); 5728 if (!ShouldShowPose(poseModel, &poseInfo)) 5729 return DeletePose(&itemNode, pose, index); 5730 return true; 5731 } 5732 5733 BPoint loc(0, index * fListElemHeight); 5734 // if we get a rename then we need to assume that we might 5735 // have missed some other attr changed notifications so we 5736 // recheck all widgets 5737 if (poseModel->OpenNode() == B_OK) { 5738 pose->UpdateAllWidgets(index, loc, this); 5739 poseModel->CloseNode(); 5740 _CheckPoseSortOrder(fPoseList, pose, poseListIndex); 5741 if (fFiltering) { 5742 if (!visible && FilterPose(pose)) { 5743 BRect bounds = Bounds(); 5744 float scrollBy = 0; 5745 AddPoseToList(fFilteredPoseList, true, true, pose, 5746 bounds, scrollBy, true); 5747 } else if (visible && !FilterPose(pose)) 5748 RemoveFilteredPose(pose, index); 5749 else if (visible) 5750 _CheckPoseSortOrder(fFilteredPoseList, pose, index); 5751 } 5752 } 5753 } else { 5754 // also must watch for renames on zombies 5755 Model* zombie = FindZombie(&itemNode, &index); 5756 if (zombie) { 5757 PRINT(("converting model %s from a zombie\n", zombie->Name())); 5758 zombie->UpdateEntryRef(&dirNode, name); 5759 pose = ConvertZombieToPose(zombie, index); 5760 } else 5761 return false; 5762 } 5763 if (pose != NULL) 5764 pendingNodeMonitorCache.PoseCreatedOrMoved(this, pose); 5765 } else if (oldDir == thisDirNode.node) 5766 DeletePose(&itemNode); 5767 else if (dirNode.node == thisDirNode.node) 5768 EntryCreated(&dirNode, &itemNode, name); 5769 5770 TryUpdatingBrokenLinks(); 5771 5772 return true; 5773 } 5774 5775 5776 void 5777 BPoseView::WatchParentOf(const entry_ref* ref) 5778 { 5779 BPath currentDir(ref); 5780 currentDir.GetParent(¤tDir); 5781 BSymLink symlink(ref); 5782 BPath path; 5783 5784 symlink.MakeLinkedPath(currentDir.Path(), &path); 5785 status_t status = path.GetParent(&path); 5786 5787 while (status == B_BAD_VALUE) 5788 status = path.GetParent(&path); 5789 5790 if (status == B_ENTRY_NOT_FOUND) 5791 return; 5792 5793 node_ref nref; 5794 BNode(path.Path()).GetNodeRef(&nref); 5795 5796 if (nref != *TargetModel()->NodeRef()) 5797 watch_node(&nref, B_WATCH_DIRECTORY, this); 5798 } 5799 5800 5801 void 5802 BPoseView::StopWatchingParentsOf(const entry_ref* ref) 5803 { 5804 BPath path; 5805 BSymLink symlink(ref); 5806 BPath currentDir(ref); 5807 currentDir.GetParent(¤tDir); 5808 symlink.MakeLinkedPath(currentDir.Path(), &path); 5809 5810 if (path.InitCheck() != B_OK) 5811 return; 5812 5813 BObjectList<Model>* brokenLinksCopy = new BObjectList<Model>(*fBrokenLinks); 5814 int32 count = brokenLinksCopy->CountItems(); 5815 5816 while (path.GetParent(&path) == B_OK) { 5817 if (strcmp(path.Path(), "/") == 0) 5818 break; 5819 5820 BNode dir(path.Path()); 5821 node_ref dirNode; 5822 dir.GetNodeRef(&dirNode); 5823 5824 // don't stop watching yourself. 5825 if (dirNode == *TargetModel()->NodeRef()) 5826 continue; 5827 5828 // make sure we don't have another broken links that still requires 5829 // to watch this directory 5830 bool keep = false; 5831 for (int32 i = count - 1; i >= 0; i--) { 5832 BSymLink link(brokenLinksCopy->ItemAt(i)->EntryRef()); 5833 BPath absolutePath; 5834 link.MakeLinkedPath(currentDir.Path(), &absolutePath); 5835 if (BString(absolutePath.Path()).Compare(path.Path(), 5836 strlen(path.Path())) == 0) { 5837 // a broken link still needs to watch this folder, but 5838 // don't let that same link also watch a deeper parent. 5839 brokenLinksCopy->RemoveItemAt(i); 5840 count--; 5841 keep = true; 5842 } 5843 } 5844 if (!keep) 5845 watch_node(&dirNode, B_STOP_WATCHING, this); 5846 } 5847 delete brokenLinksCopy; 5848 } 5849 5850 5851 bool 5852 BPoseView::AttributeChanged(const BMessage* message) 5853 { 5854 node_ref itemNode; 5855 message->FindInt32("device", &itemNode.device); 5856 message->FindInt64("node", (int64*)&itemNode.node); 5857 5858 const char* attrName; 5859 if (message->FindString("attr", &attrName) != B_OK) 5860 attrName = NULL; 5861 5862 Model* targetModel = TargetModel(); 5863 if (targetModel != NULL && *targetModel->NodeRef() == itemNode 5864 && targetModel->IsNodeOpen() 5865 && targetModel->AttrChanged(attrName)) { 5866 // the icon of our target has changed, update drag icon 5867 // TODO: make this simpler (i.e. store the icon with the window) 5868 BView* view = Window()->FindView("MenuBar"); 5869 if (view != NULL) { 5870 view = view->FindView("ThisContainer"); 5871 if (view != NULL) { 5872 IconCache::sIconCache->IconChanged(targetModel); 5873 view->Invalidate(); 5874 } 5875 } 5876 } 5877 5878 int32 index; 5879 attr_info info; 5880 PoseList* posesFound = fPoseList->FindAllPoses(&itemNode); 5881 int32 posesCount = posesFound->CountItems(); 5882 for (int i = 0; i < posesCount; i++) { 5883 BPose* pose = posesFound->ItemAt(i); 5884 Model* poseModel = pose->TargetModel(); 5885 if (poseModel->IsSymLink() && *(poseModel->NodeRef()) != itemNode) { 5886 // change happened on symlink's target 5887 poseModel = poseModel->ResolveIfLink(); 5888 } 5889 ASSERT(poseModel != NULL); 5890 5891 status_t result = B_OK; 5892 for (int32 count = 0; count < 100; count++) { 5893 // if node is busy, wait a little, it may be in the 5894 // middle of mimeset and we wan't to pick up the changes 5895 result = poseModel->OpenNode(); 5896 if (result == B_OK || result != B_BUSY) 5897 break; 5898 5899 PRINT(("poseModel %s busy, retrying in a bit\n", 5900 poseModel->Name())); 5901 snooze(10000); 5902 } 5903 if (result != B_OK) { 5904 PRINT(("Cache Error %s\n", strerror(result))); 5905 continue; 5906 } 5907 5908 bool visible = fPoseList->FindPose(poseModel->NodeRef(), 5909 &index) != NULL; 5910 int32 poseListIndex = index; 5911 5912 if (fFiltering) { 5913 visible = fFilteredPoseList->FindPose( 5914 poseModel->NodeRef(), &index) != NULL; 5915 } 5916 5917 BPoint loc(0, index * fListElemHeight); 5918 if (attrName != NULL && poseModel->Node() != NULL) { 5919 memset(&info, 0, sizeof(attr_info)); 5920 // the call below might fail if the attribute has been removed 5921 poseModel->Node()->GetAttrInfo(attrName, &info); 5922 pose->UpdateWidgetAndModel(poseModel, attrName, info.type, index, 5923 loc, this, visible); 5924 if (strcmp(attrName, kAttrMIMEType) == 0) 5925 RefreshMimeTypeList(); 5926 } else { 5927 pose->UpdateWidgetAndModel(poseModel, 0, 0, index, loc, this, 5928 visible); 5929 } 5930 poseModel->CloseNode(); 5931 if (fFiltering) { 5932 if (!visible && FilterPose(pose)) { 5933 visible = true; 5934 float scrollBy = 0; 5935 BRect bounds = Bounds(); 5936 AddPoseToList(fFilteredPoseList, true, true, pose, bounds, 5937 scrollBy, true); 5938 continue; 5939 } else if (visible && !FilterPose(pose)) { 5940 RemoveFilteredPose(pose, index); 5941 continue; 5942 } 5943 } 5944 5945 if (attrName != NULL) { 5946 // note: the following code is wrong, because this sort of hashing 5947 // may overlap and we get aliasing 5948 uint32 attrHash = AttrHashString(attrName, info.type); 5949 if (attrHash == PrimarySort() || attrHash == SecondarySort()) { 5950 _CheckPoseSortOrder(fPoseList, pose, poseListIndex); 5951 if (fFiltering && visible) 5952 _CheckPoseSortOrder(fFilteredPoseList, pose, index); 5953 } 5954 } else { 5955 int32 fields; 5956 if (message->FindInt32("fields", &fields) != B_OK) 5957 continue; 5958 5959 for (int i = sizeof(sAttrColumnMap) / sizeof(attr_column_relation); 5960 i--;) { 5961 if (sAttrColumnMap[i].attrHash == PrimarySort() 5962 || sAttrColumnMap[i].attrHash == SecondarySort()) { 5963 if ((fields & sAttrColumnMap[i].fieldMask) != 0) { 5964 _CheckPoseSortOrder(fPoseList, pose, poseListIndex); 5965 if (fFiltering && visible) 5966 _CheckPoseSortOrder(fFilteredPoseList, pose, index); 5967 break; 5968 } 5969 } 5970 } 5971 } 5972 } 5973 delete posesFound; 5974 if (posesCount == 0) { 5975 // we received an attr changed notification for a zombie model, it means 5976 // that although we couldn't open the node the first time, it seems 5977 // to be fine now since we're receiving notifications about it, it might 5978 // be a good time to convert it to a non-zombie state. cf. test in #4130 5979 Model* zombie = FindZombie(&itemNode, &index); 5980 if (zombie != NULL) { 5981 PRINT(("converting model %s from a zombie\n", zombie->Name())); 5982 return ConvertZombieToPose(zombie, index) != NULL; 5983 } else { 5984 PRINT(("model has no pose but is not a zombie either!\n")); 5985 return false; 5986 } 5987 } 5988 5989 return true; 5990 } 5991 5992 5993 void 5994 BPoseView::UpdateIcon(BPose* pose) 5995 { 5996 BPoint location; 5997 if (ViewMode() == kListMode) { 5998 // need to find the index of the pose in the pose list 5999 bool found = false; 6000 PoseList* poseList = CurrentPoseList(); 6001 int32 poseCount = poseList->CountItems(); 6002 for (int32 index = 0; index < poseCount; index++) { 6003 if (poseList->ItemAt(index) == pose) { 6004 location.Set(0, index * fListElemHeight); 6005 found = true; 6006 break; 6007 } 6008 } 6009 6010 if (!found) 6011 return; 6012 } 6013 6014 pose->UpdateIcon(location, this); 6015 } 6016 6017 6018 BPose* 6019 BPoseView::ConvertZombieToPose(Model* zombie, int32 index) 6020 { 6021 if (zombie->UpdateStatAndOpenNode() != B_OK) 6022 return NULL; 6023 6024 fZombieList->RemoveItemAt(index); 6025 6026 PoseInfo poseInfo; 6027 ReadPoseInfo(zombie, &poseInfo); 6028 6029 if (ShouldShowPose(zombie, &poseInfo)) { 6030 // TODO: handle symlinks here 6031 return CreatePose(zombie, &poseInfo); 6032 } 6033 6034 delete zombie; 6035 6036 return NULL; 6037 } 6038 6039 6040 BList* 6041 BPoseView::GetDropPointList(BPoint dropStart, BPoint dropEnd, const PoseList* poses, 6042 bool sourceInListMode, bool dropOnGrid) const 6043 { 6044 if (ViewMode() == kListMode) 6045 return NULL; 6046 6047 int32 poseCount = poses->CountItems(); 6048 BList* pointList = new BList(poseCount); 6049 for (int32 index = 0; index < poseCount; index++) { 6050 BPose* pose = poses->ItemAt(index); 6051 BPoint poseLoc; 6052 if (sourceInListMode) 6053 poseLoc = dropEnd + BPoint(0, index * (IconPoseHeight() + 3)); 6054 else 6055 poseLoc = dropEnd + (pose->Location(this) - dropStart); 6056 6057 if (dropOnGrid) 6058 poseLoc = PinToGrid(poseLoc, fGrid, fOffset); 6059 6060 pointList->AddItem(new BPoint(poseLoc)); 6061 } 6062 6063 return pointList; 6064 } 6065 6066 6067 void 6068 BPoseView::DuplicateSelection(BPoint* dropStart, BPoint* dropEnd) 6069 { 6070 // If there is a volume or trash folder, remove them from the list 6071 // because they cannot get copied 6072 int32 selectCount = CountSelected(); 6073 for (int32 index = 0; index < selectCount; index++) { 6074 BPose* pose = (BPose*)fSelectionList->ItemAt(index); 6075 Model* poseModel = pose->TargetModel(); 6076 6077 // can't duplicate a volume or the trash 6078 if (poseModel->IsTrash() || poseModel->IsVolume()) { 6079 fSelectionList->RemoveItemAt(index); 6080 index--; 6081 selectCount--; 6082 if (fSelectionPivotPose == pose) 6083 fSelectionPivotPose = NULL; 6084 6085 if (fRealPivotPose == pose) 6086 fRealPivotPose = NULL; 6087 6088 continue; 6089 } 6090 } 6091 6092 // create entry_ref list from selection 6093 if (!fSelectionList->IsEmpty()) { 6094 BObjectList<entry_ref>* srcList 6095 = new BObjectList<entry_ref>(CountSelected(), true); 6096 CopySelectionListToEntryRefList(fSelectionList, srcList); 6097 6098 BList* dropPoints; 6099 if (dropStart) { 6100 dropPoints = GetDropPointList(*dropStart, *dropEnd, fSelectionList, 6101 ViewMode() == kListMode, (modifiers() & B_COMMAND_KEY) != 0); 6102 } else 6103 dropPoints = NULL; 6104 6105 // perform asynchronous duplicate 6106 FSDuplicate(srcList, dropPoints); 6107 } 6108 } 6109 6110 6111 void 6112 BPoseView::SelectPoseAtLocation(BPoint point) 6113 { 6114 int32 index; 6115 BPose* pose = FindPose(point, &index); 6116 if (pose != NULL) 6117 SelectPose(pose, index); 6118 } 6119 6120 6121 void 6122 BPoseView::MoveListToTrash(BObjectList<entry_ref>* list, bool selectNext, 6123 bool deleteDirectly) 6124 { 6125 if (!list->CountItems()) 6126 return; 6127 6128 BObjectList<FunctionObject>* taskList = 6129 new BObjectList<FunctionObject>(2, true); 6130 // new owning list of tasks 6131 6132 // first move selection to trash, 6133 if (deleteDirectly) { 6134 taskList->AddItem(NewFunctionObject(FSDeleteRefList, list, 6135 false, true)); 6136 } else { 6137 taskList->AddItem(NewFunctionObject(FSMoveToTrash, list, 6138 (BList*)NULL, false)); 6139 } 6140 6141 if (selectNext && ViewMode() == kListMode) { 6142 // next, if in list view mode try selecting the next item after 6143 BPose* pose = fSelectionList->ItemAt(0); 6144 6145 // find a point in the pose 6146 BPoint pointInPose(fListOffset + 5, 5); 6147 int32 index = IndexOfPose(pose); 6148 pointInPose.y += fListElemHeight * index; 6149 6150 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 6151 if (tracker != NULL) { 6152 ThrowOnAssert(TargetModel() != NULL); 6153 6154 // add a function object to the list of tasks to run 6155 // that will select the next item after the one we just 6156 // deleted 6157 taskList->AddItem(NewMemberFunctionObject( 6158 &TTracker::SelectPoseAtLocationSoon, tracker, 6159 *TargetModel()->NodeRef(), pointInPose)); 6160 } 6161 } 6162 // execute the two tasks in order 6163 ThreadSequence::Launch(taskList, true); 6164 } 6165 6166 6167 inline void 6168 CopyOneTrashedRefAsEntry(const entry_ref* ref, BObjectList<entry_ref>* trashList, 6169 BObjectList<entry_ref>* noTrashList, std::map<int32, bool>* deviceHasTrash) 6170 { 6171 std::map<int32, bool> &deviceHasTrashTmp = *deviceHasTrash; 6172 // work around stupid binding problems with EachListItem 6173 6174 BDirectory entryDir(ref); 6175 bool isVolume = entryDir.IsRootDirectory(); 6176 // volumes will get unmounted 6177 6178 // see if pose's device has a trash 6179 int32 device = ref->device; 6180 BDirectory trashDir; 6181 6182 // cache up the result in a map so that we don't have to keep calling 6183 // FSGetTrashDir over and over 6184 if (!isVolume 6185 && deviceHasTrashTmp.find(device) == deviceHasTrashTmp.end()) { 6186 deviceHasTrashTmp[device] = FSGetTrashDir(&trashDir, device) == B_OK; 6187 } 6188 6189 if (isVolume || deviceHasTrashTmp[device]) 6190 trashList->AddItem(new entry_ref(*ref)); 6191 else 6192 noTrashList->AddItem(new entry_ref(*ref)); 6193 } 6194 6195 6196 static void 6197 CopyPoseOneAsEntry(BPose* pose, BObjectList<entry_ref>* trashList, 6198 BObjectList<entry_ref>* noTrashList, std::map<int32, bool>* deviceHasTrash) 6199 { 6200 CopyOneTrashedRefAsEntry(pose->TargetModel()->EntryRef(), trashList, 6201 noTrashList, deviceHasTrash); 6202 } 6203 6204 6205 static bool 6206 CheckVolumeReadOnly(const entry_ref* ref) 6207 { 6208 BVolume volume (ref->device); 6209 if (volume.IsReadOnly()) { 6210 BAlert* alert = new BAlert("", 6211 B_TRANSLATE("Files cannot be moved or deleted from a read-only " 6212 "volume."), B_TRANSLATE("Cancel"), NULL, NULL, B_WIDTH_AS_USUAL, 6213 B_STOP_ALERT); 6214 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 6215 alert->Go(); 6216 return false; 6217 } 6218 6219 return true; 6220 } 6221 6222 6223 void 6224 BPoseView::MoveSelectionOrEntryToTrash(const entry_ref* ref, bool selectNext) 6225 { 6226 BObjectList<entry_ref>* entriesToTrash = new 6227 BObjectList<entry_ref>(CountSelected()); 6228 BObjectList<entry_ref>* entriesToDeleteOnTheSpot = new 6229 BObjectList<entry_ref>(20, true); 6230 std::map<int32, bool> deviceHasTrash; 6231 6232 if (ref != NULL) { 6233 if (!CheckVolumeReadOnly(ref)) { 6234 delete entriesToTrash; 6235 delete entriesToDeleteOnTheSpot; 6236 return; 6237 } 6238 CopyOneTrashedRefAsEntry(ref, entriesToTrash, entriesToDeleteOnTheSpot, 6239 &deviceHasTrash); 6240 } else { 6241 if (!CheckVolumeReadOnly( 6242 fSelectionList->ItemAt(0)->TargetModel()->EntryRef())) { 6243 delete entriesToTrash; 6244 delete entriesToDeleteOnTheSpot; 6245 return; 6246 } 6247 EachListItem(fSelectionList, CopyPoseOneAsEntry, entriesToTrash, 6248 entriesToDeleteOnTheSpot, &deviceHasTrash); 6249 } 6250 6251 if (entriesToDeleteOnTheSpot->CountItems()) { 6252 BString alertText; 6253 if (ref != NULL) { 6254 alertText.SetTo(B_TRANSLATE("The selected item cannot be moved to " 6255 "the Trash. Would you like to delete it instead? " 6256 "(This operation cannot be reverted.)")); 6257 } else { 6258 alertText.SetTo(B_TRANSLATE("Some of the selected items cannot be " 6259 "moved to the Trash. Would you like to delete them instead? " 6260 "(This operation cannot be reverted.)")); 6261 } 6262 6263 BAlert* alert = new BAlert("", alertText.String(), 6264 B_TRANSLATE("Cancel"), B_TRANSLATE("Delete")); 6265 alert->SetShortcut(0, B_ESCAPE); 6266 if (alert->Go() == 0) 6267 return; 6268 } 6269 6270 MoveListToTrash(entriesToTrash, selectNext, false); 6271 MoveListToTrash(entriesToDeleteOnTheSpot, selectNext, true); 6272 } 6273 6274 6275 void 6276 BPoseView::MoveSelectionToTrash(bool selectNext) 6277 { 6278 if (fSelectionList->IsEmpty()) 6279 return; 6280 6281 // create entry_ref list from selection 6282 // separate items that can be trashed from ones that cannot 6283 6284 MoveSelectionOrEntryToTrash(0, selectNext); 6285 } 6286 6287 6288 void 6289 BPoseView::MoveEntryToTrash(const entry_ref* ref, bool selectNext) 6290 { 6291 MoveSelectionOrEntryToTrash(ref, selectNext); 6292 } 6293 6294 6295 void 6296 BPoseView::DeleteSelection(bool selectNext, bool askUser) 6297 { 6298 int32 selectCount = CountSelected(); 6299 if (selectCount <= 0) 6300 return; 6301 6302 if (!CheckVolumeReadOnly( 6303 fSelectionList->ItemAt(0)->TargetModel()->EntryRef())) { 6304 return; 6305 } 6306 6307 BObjectList<entry_ref>* entriesToDelete 6308 = new BObjectList<entry_ref>(selectCount, true); 6309 6310 for (int32 index = 0; index < selectCount; index++) { 6311 entriesToDelete->AddItem(new entry_ref( 6312 *fSelectionList->ItemAt(index)->TargetModel()->EntryRef())); 6313 } 6314 6315 Delete(entriesToDelete, selectNext, askUser); 6316 } 6317 6318 6319 void 6320 BPoseView::RestoreSelectionFromTrash(bool selectNext) 6321 { 6322 int32 selectCount = CountSelected(); 6323 if (selectCount <= 0) 6324 return; 6325 6326 BObjectList<entry_ref>* entriesToRestore 6327 = new BObjectList<entry_ref>(selectCount, true); 6328 6329 for (int32 index = 0; index < selectCount; index++) { 6330 entriesToRestore->AddItem(new entry_ref( 6331 *fSelectionList->ItemAt(index)->TargetModel()->EntryRef())); 6332 } 6333 6334 RestoreItemsFromTrash(entriesToRestore, selectNext); 6335 } 6336 6337 6338 void 6339 BPoseView::Delete(const entry_ref &ref, bool selectNext, bool askUser) 6340 { 6341 BObjectList<entry_ref>* entriesToDelete 6342 = new BObjectList<entry_ref>(1, true); 6343 entriesToDelete->AddItem(new entry_ref(ref)); 6344 6345 Delete(entriesToDelete, selectNext, askUser); 6346 } 6347 6348 6349 void 6350 BPoseView::Delete(BObjectList<entry_ref>* list, bool selectNext, bool askUser) 6351 { 6352 if (list->CountItems() == 0) { 6353 delete list; 6354 return; 6355 } 6356 6357 BObjectList<FunctionObject>* taskList = 6358 new BObjectList<FunctionObject>(2, true); 6359 6360 // first move selection to trash, 6361 taskList->AddItem(NewFunctionObject(FSDeleteRefList, list, false, askUser)); 6362 6363 if (selectNext && ViewMode() == kListMode) { 6364 // next, if in list view mode try selecting the next item after 6365 BPose* pose = fSelectionList->ItemAt(0); 6366 6367 // find a point in the pose 6368 BPoint pointInPose(fListOffset + 5, 5); 6369 int32 index = IndexOfPose(pose); 6370 pointInPose.y += fListElemHeight * index; 6371 6372 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 6373 if (tracker != NULL) { 6374 ThrowOnAssert(TargetModel() != NULL); 6375 6376 // add a function object to the list of tasks to run 6377 // that will select the next item after the one we just 6378 // deleted 6379 Model* targetModel = TargetModel(); 6380 ASSERT(targetModel != NULL); 6381 taskList->AddItem(NewMemberFunctionObject( 6382 &TTracker::SelectPoseAtLocationSoon, tracker, 6383 *targetModel->NodeRef(), pointInPose)); 6384 } 6385 } 6386 6387 // execute the two tasks in order 6388 ThreadSequence::Launch(taskList, true); 6389 } 6390 6391 6392 void 6393 BPoseView::RestoreItemsFromTrash(BObjectList<entry_ref>* list, bool selectNext) 6394 { 6395 if (list->CountItems() == 0) { 6396 delete list; 6397 return; 6398 } 6399 6400 BObjectList<FunctionObject>* taskList = 6401 new BObjectList<FunctionObject>(2, true); 6402 6403 // first restoree selection 6404 taskList->AddItem(NewFunctionObject(FSRestoreRefList, list, false)); 6405 6406 if (selectNext && ViewMode() == kListMode) { 6407 // next, if in list view mode try selecting the next item after 6408 BPose* pose = fSelectionList->ItemAt(0); 6409 6410 // find a point in the pose 6411 BPoint pointInPose(fListOffset + 5, 5); 6412 int32 index = IndexOfPose(pose); 6413 pointInPose.y += fListElemHeight * index; 6414 6415 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 6416 if (tracker != NULL) { 6417 ThrowOnAssert(TargetModel() != NULL); 6418 6419 // add a function object to the list of tasks to run 6420 // that will select the next item after the one we just 6421 // restored 6422 Model* targetModel = TargetModel(); 6423 ASSERT(targetModel != NULL); 6424 taskList->AddItem(NewMemberFunctionObject( 6425 &TTracker::SelectPoseAtLocationSoon, tracker, 6426 *targetModel->NodeRef(), pointInPose)); 6427 } 6428 } 6429 6430 // execute the two tasks in order 6431 ThreadSequence::Launch(taskList, true); 6432 } 6433 6434 6435 void 6436 BPoseView::SelectAll() 6437 { 6438 BRect bounds(Bounds()); 6439 6440 // clear selection list 6441 fSelectionList->MakeEmpty(); 6442 fMimeTypesInSelectionCache.MakeEmpty(); 6443 fSelectionPivotPose = NULL; 6444 fRealPivotPose = NULL; 6445 6446 int32 startIndex = 0; 6447 BPoint loc(0, fListElemHeight * startIndex); 6448 6449 bool iconMode = ViewMode() != kListMode; 6450 6451 PoseList* poseList = CurrentPoseList(); 6452 int32 poseCount = poseList->CountItems(); 6453 for (int32 index = startIndex; index < poseCount; index++) { 6454 BPose* pose = poseList->ItemAt(index); 6455 fSelectionList->AddItem(pose); 6456 if (index == startIndex) 6457 fSelectionPivotPose = pose; 6458 6459 if (!pose->IsSelected()) { 6460 pose->Select(true); 6461 6462 BRect poseRect; 6463 if (iconMode) 6464 poseRect = pose->CalcRect(this); 6465 else 6466 poseRect = pose->CalcRect(loc, this); 6467 6468 if (bounds.Intersects(poseRect)) { 6469 pose->Draw(poseRect, bounds, this, false); 6470 Flush(); 6471 } 6472 } 6473 6474 loc.y += fListElemHeight; 6475 } 6476 6477 if (fSelectionChangedHook) 6478 ContainerWindow()->SelectionChanged(); 6479 } 6480 6481 6482 void 6483 BPoseView::InvertSelection() 6484 { 6485 // Since this function shares most code with 6486 // SelectAll(), we could make SelectAll() empty the selection, 6487 // then call InvertSelection() 6488 6489 BRect bounds(Bounds()); 6490 6491 int32 startIndex = 0; 6492 BPoint loc(0, fListElemHeight * startIndex); 6493 6494 fMimeTypesInSelectionCache.MakeEmpty(); 6495 fSelectionPivotPose = NULL; 6496 fRealPivotPose = NULL; 6497 6498 bool iconMode = ViewMode() != kListMode; 6499 6500 PoseList* poseList = CurrentPoseList(); 6501 int32 poseCount = poseList->CountItems(); 6502 for (int32 index = startIndex; index < poseCount; index++) { 6503 BPose* pose = poseList->ItemAt(index); 6504 6505 if (pose->IsSelected()) { 6506 fSelectionList->RemoveItem(pose); 6507 pose->Select(false); 6508 } else { 6509 if (index == startIndex) 6510 fSelectionPivotPose = pose; 6511 6512 fSelectionList->AddItem(pose); 6513 pose->Select(true); 6514 } 6515 6516 BRect poseRect; 6517 if (iconMode) 6518 poseRect = pose->CalcRect(this); 6519 else 6520 poseRect = pose->CalcRect(loc, this); 6521 6522 if (bounds.Intersects(poseRect)) 6523 Invalidate(); 6524 6525 loc.y += fListElemHeight; 6526 } 6527 6528 if (fSelectionChangedHook) 6529 ContainerWindow()->SelectionChanged(); 6530 } 6531 6532 6533 int32 6534 BPoseView::SelectMatchingEntries(const BMessage* message) 6535 { 6536 int32 matchCount = 0; 6537 SetMultipleSelection(true); 6538 6539 ClearSelection(); 6540 6541 TrackerStringExpressionType expressionType; 6542 BString expression; 6543 const char* expressionPointer; 6544 bool invertSelection; 6545 bool ignoreCase; 6546 6547 message->FindInt32("ExpressionType", (int32*)&expressionType); 6548 message->FindString("Expression", &expressionPointer); 6549 message->FindBool("InvertSelection", &invertSelection); 6550 message->FindBool("IgnoreCase", &ignoreCase); 6551 6552 expression = expressionPointer; 6553 6554 PoseList* poseList = CurrentPoseList(); 6555 int32 poseCount = poseList->CountItems(); 6556 TrackerString name; 6557 6558 RegExp regExpression; 6559 6560 // Make sure we don't have any errors in the expression 6561 // before we match the names: 6562 if (expressionType == kRegexpMatch) { 6563 regExpression.SetTo(expression); 6564 6565 if (regExpression.InitCheck() != B_OK) { 6566 BString message( 6567 B_TRANSLATE("Error in regular expression:\n\n'%errstring'")); 6568 message.ReplaceFirst("%errstring", regExpression.ErrorString()); 6569 BAlert* alert = new BAlert("", message.String(), B_TRANSLATE("OK"), 6570 NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT); 6571 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 6572 alert->Go(); 6573 return 0; 6574 } 6575 } 6576 6577 // There is room for optimizations here: If regexp-type match, the Matches() 6578 // function compiles the expression for every entry. One could use 6579 // TrackerString::CompileRegExp and reuse the expression. However, then we 6580 // have to take care of the case sensitivity ourselves. 6581 for (int32 index = 0; index < poseCount; index++) { 6582 BPose* pose = poseList->ItemAt(index); 6583 name = pose->TargetModel()->Name(); 6584 if (name.Matches(expression.String(), !ignoreCase, expressionType) 6585 ^ invertSelection) { 6586 matchCount++; 6587 AddPoseToSelection(pose, index); 6588 } 6589 } 6590 6591 Window()->Activate(); 6592 // Make sure the window is activated for 6593 // subsequent manipulations. Esp. needed 6594 // for the Desktop window. 6595 6596 return matchCount; 6597 } 6598 6599 6600 void 6601 BPoseView::ShowSelectionWindow() 6602 { 6603 Window()->PostMessage(kShowSelectionWindow); 6604 } 6605 6606 6607 void 6608 BPoseView::KeyDown(const char* bytes, int32 count) 6609 { 6610 char key = bytes[0]; 6611 6612 switch (key) { 6613 case B_LEFT_ARROW: 6614 case B_RIGHT_ARROW: 6615 case B_UP_ARROW: 6616 case B_DOWN_ARROW: 6617 { 6618 int32 index; 6619 BPose* pose = FindNearbyPose(key, &index); 6620 if (pose == NULL) 6621 break; 6622 6623 if (fMultipleSelection && (modifiers() & B_SHIFT_KEY) != 0) { 6624 if (pose->IsSelected()) { 6625 RemovePoseFromSelection(fSelectionList->LastItem()); 6626 fSelectionPivotPose = pose; 6627 ScrollIntoView(pose, index); 6628 } else 6629 AddPoseToSelection(pose, index, true); 6630 } else 6631 SelectPose(pose, index); 6632 6633 break; 6634 } 6635 6636 case B_RETURN: 6637 if (fFiltering && CountSelected() == 0) 6638 SelectPose(fFilteredPoseList->FirstItem(), 0); 6639 6640 OpenSelection(); 6641 6642 if (fFiltering && (modifiers() & B_SHIFT_KEY) != 0) 6643 StopFiltering(); 6644 6645 break; 6646 6647 case B_HOME: 6648 // select the first entry (if in listview mode), and 6649 // scroll to the top of the view 6650 if (ViewMode() == kListMode) 6651 MoveOrChangePoseSelection(0); 6652 else 6653 ScrollView(B_HOME); 6654 break; 6655 6656 case B_END: 6657 // select the last entry (if in listview mode), and 6658 // scroll to the bottom of the view 6659 if (ViewMode() == kListMode) 6660 MoveOrChangePoseSelection(CurrentPoseList()->CountItems() - 1); 6661 else 6662 ScrollView(B_END); 6663 break; 6664 6665 case B_PAGE_UP: 6666 if (ViewMode() == kListMode) { 6667 // Select first visible pose 6668 int32 firstIndex = CurrentPoseList()->IndexOf( 6669 fSelectionList->FirstItem()); 6670 int32 index; 6671 BPose* first = FirstVisiblePose(&index); 6672 if (first != NULL) { 6673 if (index == firstIndex) { 6674 ScrollView(B_PAGE_UP); 6675 first = FirstVisiblePose(&index); 6676 } 6677 MoveOrChangePoseSelection(index); 6678 } 6679 } else 6680 ScrollView(B_PAGE_UP); 6681 break; 6682 6683 case B_PAGE_DOWN: 6684 if (ViewMode() == kListMode) { 6685 // Select last visible pose 6686 int32 lastIndex = CurrentPoseList()->IndexOf( 6687 fSelectionList->LastItem()); 6688 int32 index; 6689 BPose* last = LastVisiblePose(&index); 6690 if (last != NULL) { 6691 if (index == lastIndex) { 6692 ScrollView(B_PAGE_DOWN); 6693 last = LastVisiblePose(&index); 6694 } 6695 MoveOrChangePoseSelection(index); 6696 } 6697 } else 6698 ScrollView(B_PAGE_DOWN); 6699 break; 6700 6701 case B_TAB: 6702 if (IsFilePanel()) 6703 _inherited::KeyDown(bytes, count); 6704 else { 6705 if (ViewMode() == kListMode 6706 && TrackerSettings().TypeAheadFiltering()) { 6707 break; 6708 } 6709 6710 if (fSelectionList->IsEmpty()) 6711 sMatchString.Truncate(0); 6712 else { 6713 BPose* pose = fSelectionList->FirstItem(); 6714 sMatchString.SetTo(pose->TargetModel()->Name()); 6715 } 6716 6717 bool reverse 6718 = (Window()->CurrentMessage()->FindInt32("modifiers") 6719 & B_SHIFT_KEY) != 0; 6720 int32 index; 6721 BPose* pose = FindNextMatch(&index, reverse); 6722 if (pose == NULL) { 6723 // wrap around 6724 if (reverse) 6725 sMatchString.SetTo(0x7f, 1); 6726 else 6727 sMatchString.Truncate(0); 6728 6729 pose = FindNextMatch(&index, reverse); 6730 } 6731 6732 SelectPose(pose, index); 6733 } 6734 break; 6735 6736 case B_DELETE: 6737 { 6738 ExcludeTrashFromSelection(); 6739 if (TargetModel() == NULL) { 6740 // Happens if called from within OpenWith window, for example 6741 break; 6742 } 6743 // Make sure user can't trash something already in the trash. 6744 if (TargetModel()->IsTrash()) { 6745 // Delete without asking from the trash 6746 DeleteSelection(true, false); 6747 } else { 6748 TrackerSettings settings; 6749 6750 if ((modifiers() & B_SHIFT_KEY) != 0 6751 || settings.DontMoveFilesToTrash()) { 6752 DeleteSelection(true, settings.AskBeforeDeleteFile()); 6753 } else 6754 MoveSelectionToTrash(); 6755 } 6756 break; 6757 } 6758 6759 case B_BACKSPACE: 6760 { 6761 if (fFiltering) { 6762 BString* lastString = fFilterStrings.LastItem(); 6763 if (lastString->Length() == 0) { 6764 int32 stringCount = fFilterStrings.CountItems(); 6765 if (stringCount > 1) 6766 delete fFilterStrings.RemoveItemAt(stringCount - 1); 6767 else 6768 break; 6769 } else 6770 lastString->TruncateChars(lastString->CountChars() - 1); 6771 6772 fCountView->RemoveFilterCharacter(); 6773 FilterChanged(); 6774 break; 6775 } 6776 6777 if (sMatchString.Length() == 0) 6778 break; 6779 6780 // remove last char from the typeahead buffer 6781 sMatchString.TruncateChars(sMatchString.CountChars() - 1); 6782 6783 fLastKeyTime = system_time(); 6784 6785 fCountView->SetTypeAhead(sMatchString.String()); 6786 6787 // select our new string 6788 int32 index; 6789 BPose* pose = FindBestMatch(&index); 6790 if (pose == NULL) 6791 break; 6792 6793 SelectPose(pose, index); 6794 break; 6795 } 6796 6797 case B_FUNCTION_KEY: 6798 { 6799 BMessage* message = Window()->CurrentMessage(); 6800 if (message != NULL) { 6801 int32 key; 6802 if (message->FindInt32("key", &key) == B_OK && key == B_F2_KEY) 6803 Window()->PostMessage(kEditItem, this); 6804 } 6805 break; 6806 } 6807 6808 case B_INSERT: 6809 break; 6810 6811 default: 6812 { 6813 // handle typeahead selection / filtering 6814 6815 if (ViewMode() == kListMode 6816 && TrackerSettings().TypeAheadFiltering()) { 6817 if (key == ' ' && modifiers() & B_SHIFT_KEY) { 6818 if (fFilterStrings.LastItem()->Length() == 0) 6819 break; 6820 6821 fFilterStrings.AddItem(new BString()); 6822 fCountView->AddFilterCharacter("|"); 6823 break; 6824 } 6825 6826 fFilterStrings.LastItem()->AppendChars(bytes, 1); 6827 fCountView->AddFilterCharacter(bytes); 6828 FilterChanged(); 6829 break; 6830 } 6831 6832 bigtime_t doubleClickSpeed; 6833 get_click_speed(&doubleClickSpeed); 6834 6835 // start watching 6836 if (fKeyRunner == NULL) { 6837 fKeyRunner = new BMessageRunner(this, 6838 new BMessage(kCheckTypeahead), doubleClickSpeed); 6839 if (fKeyRunner->InitCheck() != B_OK) 6840 return; 6841 } 6842 6843 // figure out the time at which the keypress happened 6844 bigtime_t eventTime; 6845 BMessage* message = Window()->CurrentMessage(); 6846 if (message == NULL 6847 || message->FindInt64("when", &eventTime) < B_OK) { 6848 eventTime = system_time(); 6849 } 6850 6851 // add char to existing matchString or start new match string 6852 if (eventTime - fLastKeyTime < (doubleClickSpeed * 2)) 6853 sMatchString.AppendChars(bytes, 1); 6854 else 6855 sMatchString.SetToChars(bytes, 1); 6856 6857 fLastKeyTime = eventTime; 6858 6859 fCountView->SetTypeAhead(sMatchString.String()); 6860 6861 int32 index; 6862 BPose* pose = FindBestMatch(&index); 6863 if (pose == NULL) 6864 break; 6865 6866 SelectPose(pose, index); 6867 break; 6868 } 6869 } 6870 } 6871 6872 6873 BPose* 6874 BPoseView::FindNextMatch(int32* matchingIndex, bool reverse) 6875 { 6876 char bestSoFar[B_FILE_NAME_LENGTH] = { 0 }; 6877 BPose* poseToSelect = NULL; 6878 6879 // loop through all poses to find match 6880 int32 poseCount = fPoseList->CountItems(); 6881 for (int32 index = 0; index < poseCount; index++) { 6882 BPose* pose = fPoseList->ItemAt(index); 6883 6884 if (reverse) { 6885 if (sMatchString.ICompare(pose->TargetModel()->Name()) > 0) { 6886 if (strcasecmp(pose->TargetModel()->Name(), bestSoFar) >= 0 6887 || !bestSoFar[0]) { 6888 strlcpy(bestSoFar, pose->TargetModel()->Name(), 6889 sizeof(bestSoFar)); 6890 poseToSelect = pose; 6891 *matchingIndex = index; 6892 } 6893 } 6894 } else if (sMatchString.ICompare(pose->TargetModel()->Name()) < 0) { 6895 if (strcasecmp(pose->TargetModel()->Name(), bestSoFar) <= 0 6896 || !bestSoFar[0]) { 6897 strlcpy(bestSoFar, pose->TargetModel()->Name(), 6898 sizeof(bestSoFar)); 6899 poseToSelect = pose; 6900 *matchingIndex = index; 6901 } 6902 } 6903 } 6904 6905 return poseToSelect; 6906 } 6907 6908 6909 BPose* 6910 BPoseView::FindBestMatch(int32* index) 6911 { 6912 BPose* poseToSelect = NULL; 6913 float bestScore = -1; 6914 int32 poseCount = fPoseList->CountItems(); 6915 6916 // loop through all poses to find match 6917 for (int32 j = 0; j < CountColumns(); j++) { 6918 BColumn* column = ColumnAt(j); 6919 6920 for (int32 i = 0; i < poseCount; i++) { 6921 BPose* pose = fPoseList->ItemAt(i); 6922 float score = -1; 6923 6924 if (ViewMode() == kListMode) { 6925 ModelNodeLazyOpener modelOpener(pose->TargetModel()); 6926 BTextWidget* widget = pose->WidgetFor(column, this, 6927 modelOpener); 6928 const char* text = NULL; 6929 if (widget != NULL) 6930 text = widget->Text(this); 6931 6932 if (text != NULL) 6933 score = ComputeTypeAheadScore(text, sMatchString.String()); 6934 } else { 6935 score = ComputeTypeAheadScore(pose->TargetModel()->Name(), 6936 sMatchString.String()); 6937 } 6938 6939 if (score > bestScore) { 6940 poseToSelect = pose; 6941 bestScore = score; 6942 *index = i; 6943 } 6944 if (score == kExactMatchScore) 6945 break; 6946 } 6947 6948 // TODO: we might want to change this to make it always work 6949 // over all columns, but this would require some more changes 6950 // to how Tracker represents data (for example we could filter 6951 // the results out). 6952 if (bestScore > 0 || ViewMode() != kListMode) 6953 break; 6954 } 6955 6956 return poseToSelect; 6957 } 6958 6959 6960 static bool 6961 LinesIntersect(float s1, float e1, float s2, float e2) 6962 { 6963 return std::max(s1, s2) < std::min(e1, e2); 6964 } 6965 6966 6967 BPose* 6968 BPoseView::FindNearbyPose(char arrowKey, int32* poseIndex) 6969 { 6970 int32 resultingIndex = -1; 6971 BPose* poseToSelect = NULL; 6972 BPose* selectedPose = fSelectionList->LastItem(); 6973 6974 if (ViewMode() == kListMode) { 6975 PoseList* poseList = CurrentPoseList(); 6976 6977 switch (arrowKey) { 6978 case B_UP_ARROW: 6979 case B_LEFT_ARROW: 6980 if (selectedPose) { 6981 resultingIndex = poseList->IndexOf(selectedPose) - 1; 6982 poseToSelect = poseList->ItemAt(resultingIndex); 6983 if (poseToSelect == NULL && arrowKey == B_LEFT_ARROW) { 6984 resultingIndex = poseList->CountItems() - 1; 6985 poseToSelect = poseList->LastItem(); 6986 } 6987 } else { 6988 resultingIndex = poseList->CountItems() - 1; 6989 poseToSelect = poseList->LastItem(); 6990 } 6991 break; 6992 6993 case B_DOWN_ARROW: 6994 case B_RIGHT_ARROW: 6995 if (selectedPose) { 6996 resultingIndex = poseList->IndexOf(selectedPose) + 1; 6997 poseToSelect = poseList->ItemAt(resultingIndex); 6998 if (poseToSelect == NULL && arrowKey == B_RIGHT_ARROW) { 6999 resultingIndex = 0; 7000 poseToSelect = poseList->FirstItem(); 7001 } 7002 } else { 7003 resultingIndex = 0; 7004 poseToSelect = poseList->FirstItem(); 7005 } 7006 break; 7007 } 7008 *poseIndex = resultingIndex; 7009 7010 return poseToSelect; 7011 } 7012 7013 // must be in one of the icon modes 7014 7015 // handle case where there is no current selection 7016 if (fSelectionList->IsEmpty()) { 7017 // find the upper-left pose (I know it's ugly!) 7018 poseToSelect = fVSPoseList->FirstItem(); 7019 for (int32 index = 0; ;index++) { 7020 BPose* pose = fVSPoseList->ItemAt(++index); 7021 if (pose == NULL) 7022 break; 7023 7024 if (poseToSelect != NULL) { 7025 BRect selectedBounds; 7026 selectedBounds = poseToSelect->CalcRect(this); 7027 BRect poseRect(pose->CalcRect(this)); 7028 7029 if (poseRect.top > selectedBounds.top) 7030 break; 7031 7032 if (poseRect.left < selectedBounds.left) 7033 poseToSelect = pose; 7034 } 7035 } 7036 7037 return poseToSelect; 7038 } 7039 7040 BRect selectionRect; 7041 if (selectedPose != NULL) 7042 selectionRect = selectedPose->CalcRect(this); 7043 7044 BRect bestRect; 7045 7046 // we're not in list mode so scan visually for pose to select 7047 int32 poseCount = fPoseList->CountItems(); 7048 for (int32 index = 0; index < poseCount; index++) { 7049 BPose* pose = fPoseList->ItemAt(index); 7050 BRect poseRect(pose->CalcRect(this)); 7051 7052 switch (arrowKey) { 7053 case B_LEFT_ARROW: 7054 if (LinesIntersect(poseRect.top, poseRect.bottom, 7055 selectionRect.top, selectionRect.bottom) 7056 && poseRect.left < selectionRect.left 7057 && (poseRect.left > bestRect.left 7058 || !bestRect.IsValid())) { 7059 bestRect = poseRect; 7060 poseToSelect = pose; 7061 } 7062 break; 7063 7064 case B_RIGHT_ARROW: 7065 if (LinesIntersect(poseRect.top, poseRect.bottom, 7066 selectionRect.top, selectionRect.bottom) 7067 && poseRect.right > selectionRect.right 7068 && (poseRect.right < bestRect.right 7069 || !bestRect.IsValid())) { 7070 bestRect = poseRect; 7071 poseToSelect = pose; 7072 } 7073 break; 7074 7075 case B_UP_ARROW: 7076 if (LinesIntersect(poseRect.left, poseRect.right, 7077 selectionRect.left, selectionRect.right) 7078 && poseRect.top < selectionRect.top 7079 && (poseRect.top > bestRect.top 7080 || !bestRect.IsValid())) { 7081 bestRect = poseRect; 7082 poseToSelect = pose; 7083 } 7084 break; 7085 7086 case B_DOWN_ARROW: 7087 if (LinesIntersect(poseRect.left, poseRect.right, 7088 selectionRect.left, selectionRect.right) 7089 && poseRect.bottom > selectionRect.bottom 7090 && (poseRect.bottom < bestRect.bottom 7091 || !bestRect.IsValid())) { 7092 bestRect = poseRect; 7093 poseToSelect = pose; 7094 } 7095 break; 7096 } 7097 } 7098 7099 if (poseToSelect != NULL) 7100 return poseToSelect; 7101 7102 return selectedPose; 7103 } 7104 7105 7106 void 7107 BPoseView::ShowContextMenu(BPoint where) 7108 { 7109 BContainerWindow* window = ContainerWindow(); 7110 if (window == NULL) 7111 return; 7112 7113 // handle pose selection 7114 int32 index; 7115 BPose* pose = FindPose(where, &index); 7116 if (pose != NULL) { 7117 if (!pose->IsSelected()) { 7118 ClearSelection(); 7119 pose->Select(true); 7120 fSelectionList->AddItem(pose); 7121 DrawPose(pose, index, false); 7122 } 7123 } else 7124 ClearSelection(); 7125 7126 window->Activate(); 7127 window->UpdateIfNeeded(); 7128 window->ShowContextMenu(where, pose == NULL ? NULL 7129 : pose->TargetModel()->EntryRef()); 7130 7131 if (fSelectionChangedHook) 7132 window->SelectionChanged(); 7133 } 7134 7135 7136 void 7137 BPoseView::_BeginSelectionRect(const BPoint& point, bool shouldExtend) 7138 { 7139 // set initial empty selection rectangle 7140 fSelectionRectInfo.rect = BRect(point, point - BPoint(1, 1)); 7141 7142 if (!fTransparentSelection) { 7143 SetDrawingMode(B_OP_INVERT); 7144 StrokeRect(fSelectionRectInfo.rect, B_MIXED_COLORS); 7145 SetDrawingMode(B_OP_OVER); 7146 } 7147 7148 fSelectionRectInfo.lastRect = fSelectionRectInfo.rect; 7149 fSelectionRectInfo.selection = new BList; 7150 fSelectionRectInfo.startPoint = point; 7151 fSelectionRectInfo.lastPoint = point; 7152 fSelectionRectInfo.isDragging = true; 7153 7154 if (fAutoScrollState == kAutoScrollOff) { 7155 fAutoScrollState = kAutoScrollOn; 7156 Window()->SetPulseRate(20000); 7157 } 7158 } 7159 7160 7161 static void 7162 AddIfPoseSelected(BPose* pose, PoseList* list) 7163 { 7164 if (pose->IsSelected()) 7165 list->AddItem(pose); 7166 } 7167 7168 7169 void 7170 BPoseView::_UpdateSelectionRect(const BPoint& point) 7171 { 7172 if (point != fSelectionRectInfo.lastPoint) { 7173 fSelectionRectInfo.lastPoint = point; 7174 7175 // erase last rect 7176 if (!fTransparentSelection) { 7177 SetDrawingMode(B_OP_INVERT); 7178 StrokeRect(fSelectionRectInfo.rect, B_MIXED_COLORS); 7179 SetDrawingMode(B_OP_OVER); 7180 } 7181 7182 fSelectionRectInfo.rect.top = std::min(point.y, 7183 fSelectionRectInfo.startPoint.y); 7184 fSelectionRectInfo.rect.left = std::min(point.x, 7185 fSelectionRectInfo.startPoint.x); 7186 fSelectionRectInfo.rect.bottom = std::max(point.y, 7187 fSelectionRectInfo.startPoint.y); 7188 fSelectionRectInfo.rect.right = std::max(point.x, 7189 fSelectionRectInfo.startPoint.x); 7190 7191 fIsDrawingSelectionRect = true; 7192 7193 // use current selection rectangle to scan poses 7194 SelectPoses(fSelectionRectInfo.rect, 7195 &fSelectionRectInfo.selection); 7196 7197 Window()->UpdateIfNeeded(); 7198 7199 // draw new rect 7200 if (!fTransparentSelection) { 7201 SetDrawingMode(B_OP_INVERT); 7202 StrokeRect(fSelectionRectInfo.rect, B_MIXED_COLORS); 7203 SetDrawingMode(B_OP_OVER); 7204 } else { 7205 BRegion updateRegion1; 7206 BRegion updateRegion2; 7207 7208 bool sameWidth = fSelectionRectInfo.rect.Width() 7209 == fSelectionRectInfo.lastRect.Width(); 7210 bool sameHeight = fSelectionRectInfo.rect.Height() 7211 == fSelectionRectInfo.lastRect.Height(); 7212 7213 updateRegion1.Include(fSelectionRectInfo.rect); 7214 updateRegion1.Exclude(fSelectionRectInfo.lastRect.InsetByCopy( 7215 sameWidth ? 0 : 1, sameHeight ? 0 : 1)); 7216 updateRegion2.Include(fSelectionRectInfo.lastRect); 7217 updateRegion2.Exclude(fSelectionRectInfo.rect.InsetByCopy( 7218 sameWidth ? 0 : 1, sameHeight ? 0 : 1)); 7219 updateRegion1.Include(&updateRegion2); 7220 BRect unionRect = fSelectionRectInfo.rect 7221 & fSelectionRectInfo.lastRect; 7222 updateRegion1.Exclude(unionRect 7223 & BRect(-2000, fSelectionRectInfo.startPoint.y, 2000, 7224 fSelectionRectInfo.startPoint.y)); 7225 updateRegion1.Exclude(unionRect 7226 & BRect(fSelectionRectInfo.startPoint.x, -2000, 7227 fSelectionRectInfo.startPoint.x, 2000)); 7228 7229 fSelectionRectInfo.lastRect = fSelectionRectInfo.rect; 7230 7231 Invalidate(&updateRegion1); 7232 Window()->UpdateIfNeeded(); 7233 } 7234 Flush(); 7235 } 7236 } 7237 7238 7239 void 7240 BPoseView::_EndSelectionRect() 7241 { 7242 delete fSelectionRectInfo.selection; 7243 fSelectionRectInfo.selection = NULL; 7244 7245 fSelectionRectInfo.isDragging = false; 7246 fIsDrawingSelectionRect = false; 7247 // TODO: remove BPose dependency? 7248 7249 // do final erase of selection rect 7250 if (!fTransparentSelection) { 7251 SetDrawingMode(B_OP_INVERT); 7252 StrokeRect(fSelectionRectInfo.rect, B_MIXED_COLORS); 7253 SetDrawingMode(B_OP_COPY); 7254 fSelectionRectInfo.rect.Set(0, 0, -1, -1); 7255 } else { 7256 Invalidate(fSelectionRectInfo.rect); 7257 fSelectionRectInfo.rect.Set(0, 0, -1, -1); 7258 Window()->UpdateIfNeeded(); 7259 } 7260 7261 // we now need to update the pose view's selection list by clearing it 7262 // and then polling each pose for selection state and rebuilding list 7263 fSelectionList->MakeEmpty(); 7264 fMimeTypesInSelectionCache.MakeEmpty(); 7265 7266 EachListItem(fPoseList, AddIfPoseSelected, fSelectionList); 7267 7268 // and now make sure that the pivot point is in sync 7269 if (fSelectionPivotPose && !fSelectionList->HasItem(fSelectionPivotPose)) 7270 fSelectionPivotPose = NULL; 7271 if (fRealPivotPose && !fSelectionList->HasItem(fRealPivotPose)) 7272 fRealPivotPose = NULL; 7273 } 7274 7275 7276 void 7277 BPoseView::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage) 7278 { 7279 if (fSelectionRectInfo.isDragging) 7280 _UpdateSelectionRect(where); 7281 7282 if (!fDropEnabled || dragMessage == NULL) 7283 return; 7284 7285 BContainerWindow* window = ContainerWindow(); 7286 if (window == NULL) 7287 return; 7288 7289 if (!window->Dragging()) 7290 window->DragStart(dragMessage); 7291 7292 switch (transit) { 7293 case B_INSIDE_VIEW: 7294 case B_ENTERED_VIEW: 7295 UpdateDropTarget(where, dragMessage, window->ContextMenu()); 7296 if (fAutoScrollState == kAutoScrollOff) { 7297 // turn on auto scrolling if it's not yet on 7298 fAutoScrollState = kWaitForTransition; 7299 window->SetPulseRate(100000); 7300 } 7301 break; 7302 7303 case B_EXITED_VIEW: 7304 DragStop(); 7305 // reset cursor in case we set it to the copy cursor 7306 // in UpdateDropTarget 7307 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); 7308 fCursorCheck = false; 7309 // TODO: autoscroll here 7310 if (!window->ContextMenu()) { 7311 HiliteDropTarget(false); 7312 fDropTarget = NULL; 7313 } 7314 break; 7315 } 7316 } 7317 7318 7319 void 7320 BPoseView::MouseDragged(const BMessage* message) 7321 { 7322 if (fTextWidgetToCheck != NULL) 7323 fTextWidgetToCheck->CancelWait(); 7324 7325 fTrackRightMouseUp = false; 7326 fTrackMouseUp = false; 7327 7328 BPoint where; 7329 uint32 buttons = 0; 7330 if (message->FindPoint("be:view_where", &where) != B_OK 7331 || message->FindInt32("buttons", (int32*)&buttons) != B_OK) { 7332 return; 7333 } 7334 7335 bool extendSelection = (modifiers() & B_COMMAND_KEY) != 0 7336 && fMultipleSelection; 7337 7338 int32 index; 7339 BPose* pose = FindPose(where, &index); 7340 if (pose != NULL) 7341 DragSelectedPoses(pose, where); 7342 else if (buttons == B_PRIMARY_MOUSE_BUTTON) 7343 _BeginSelectionRect(where, extendSelection); 7344 } 7345 7346 7347 void 7348 BPoseView::MouseLongDown(const BMessage* message) 7349 { 7350 fTrackRightMouseUp = false; 7351 fTrackMouseUp = false; 7352 7353 BPoint where; 7354 if (message->FindPoint("where", &where) != B_OK) 7355 return; 7356 7357 ShowContextMenu(where); 7358 } 7359 7360 7361 void 7362 BPoseView::MouseIdle(const BMessage* message) 7363 { 7364 BPoint where; 7365 uint32 buttons = 0; 7366 GetMouse(&where, &buttons); 7367 // We could retrieve 'where' from the incoming 7368 // message but we need the buttons state anyway 7369 // and B_MOUSE_IDLE message doesn't pass it 7370 BContainerWindow* window = ContainerWindow(); 7371 7372 if (buttons == 0 || window == NULL || !window->Dragging()) 7373 return; 7374 7375 if (fDropTarget != NULL) { 7376 FrameForPose(fDropTarget, true, &fStartFrame); 7377 ShowContextMenu(where); 7378 } else 7379 window->Activate(); 7380 } 7381 7382 7383 void 7384 BPoseView::MouseDown(BPoint where) 7385 { 7386 // handle disposing of drag data lazily 7387 DragStop(); 7388 BContainerWindow* window = ContainerWindow(); 7389 if (window == NULL) 7390 return; 7391 7392 if (IsDesktopWindow()) { 7393 BScreen screen(Window()); 7394 rgb_color color = screen.DesktopColor(); 7395 SetLowColor(color); 7396 SetViewColor(color); 7397 } 7398 7399 MakeFocus(); 7400 7401 uint32 buttons = (uint32)window->CurrentMessage()->FindInt32("buttons"); 7402 uint32 modifierKeys = modifiers(); 7403 bool secondaryMouseButtonDown 7404 = SecondaryMouseButtonDown(modifierKeys, buttons); 7405 fTrackRightMouseUp = secondaryMouseButtonDown; 7406 fTrackMouseUp = !secondaryMouseButtonDown; 7407 bool extendSelection = (modifierKeys & B_COMMAND_KEY) != 0 7408 && fMultipleSelection; 7409 7410 CommitActivePose(); 7411 7412 int32 index; 7413 BPose* pose = FindPose(where, &index); 7414 if (pose != NULL) { 7415 if (!pose->IsSelected() || !secondaryMouseButtonDown) 7416 AddRemoveSelectionRange(where, extendSelection, pose); 7417 7418 if (fTextWidgetToCheck != NULL 7419 && (pose != fLastClickedPose || secondaryMouseButtonDown)) { 7420 fTextWidgetToCheck->CancelWait(); 7421 } 7422 7423 if (!extendSelection && WasDoubleClick(pose, where, buttons) 7424 && buttons == B_PRIMARY_MOUSE_BUTTON 7425 && fLastClickButtons == B_PRIMARY_MOUSE_BUTTON 7426 && (modifierKeys & B_CONTROL_KEY) == 0) { 7427 fTrackRightMouseUp = false; 7428 fTrackMouseUp = false; 7429 // special handling for path field double-clicks 7430 if (!WasClickInPath(pose, index, where)) 7431 OpenSelection(pose, &index); 7432 } 7433 } else { 7434 // click was not in any pose 7435 fLastClickedPose = NULL; 7436 if (fTextWidgetToCheck != NULL) 7437 fTextWidgetToCheck->CancelWait(); 7438 7439 window->Activate(); 7440 window->UpdateIfNeeded(); 7441 7442 // only clear selection if we are not extending it 7443 if (!extendSelection || !fSelectionRectEnabled || !fMultipleSelection) 7444 ClearSelection(); 7445 7446 // show desktop context menu 7447 if (SecondaryMouseButtonDown(modifierKeys, buttons)) 7448 ShowContextMenu(where); 7449 } 7450 7451 if (fSelectionChangedHook) 7452 window->SelectionChanged(); 7453 } 7454 7455 7456 void 7457 BPoseView::SetTextWidgetToCheck(BTextWidget* widget, BTextWidget* old) 7458 { 7459 if (old == NULL || fTextWidgetToCheck == old) 7460 fTextWidgetToCheck = widget; 7461 } 7462 7463 7464 void 7465 BPoseView::MouseUp(BPoint where) 7466 { 7467 if (fSelectionRectInfo.isDragging) 7468 _EndSelectionRect(); 7469 7470 int32 index; 7471 BPose* pose = FindPose(where, &index); 7472 uint32 lastButtons = Window()->CurrentMessage()->FindInt32("last_buttons"); 7473 if (pose != NULL && fLastClickedPose != NULL && fAllowPoseEditing 7474 && !fTrackRightMouseUp) { 7475 // This handy field has been added by the tracking filter. 7476 // we need lastButtons for right button mouse-up tracking, 7477 // because there's currently no way to know wich buttons were 7478 // released in BView::MouseUp (unlike BView::KeyUp) 7479 pose->MouseUp(BPoint(0, index * fListElemHeight), this, where, index); 7480 } 7481 7482 // Showing the pose context menu is done on mouse up (or long click) 7483 // to make right button dragging possible 7484 if (pose != NULL && fTrackRightMouseUp 7485 && (SecondaryMouseButtonDown(modifiers(), lastButtons))) { 7486 if (!pose->IsSelected()) { 7487 ClearSelection(); 7488 pose->Select(true); 7489 fSelectionList->AddItem(pose); 7490 DrawPose(pose, index, false); 7491 } 7492 ShowContextMenu(where); 7493 } 7494 7495 if (fTrackMouseUp) 7496 Window()->Activate(); 7497 7498 fTrackRightMouseUp = false; 7499 fTrackMouseUp = false; 7500 } 7501 7502 7503 bool 7504 BPoseView::WasClickInPath(const BPose* pose, int32 index, 7505 BPoint mouseLocation) const 7506 { 7507 if (pose == NULL || (ViewMode() != kListMode)) 7508 return false; 7509 7510 BPoint loc(0, index * fListElemHeight); 7511 BTextWidget* widget; 7512 if (!pose->PointInPose(loc, this, mouseLocation, &widget) || !widget) 7513 return false; 7514 7515 // note: the following code is wrong, because this sort of hashing 7516 // may overlap and we get aliasing 7517 if (widget->AttrHash() != AttrHashString(kAttrPath, B_STRING_TYPE)) 7518 return false; 7519 7520 BEntry entry(widget->Text(this)); 7521 if (entry.InitCheck() != B_OK) 7522 return false; 7523 7524 entry_ref ref; 7525 if (entry.GetRef(&ref) == B_OK) { 7526 BMessage message(B_REFS_RECEIVED); 7527 message.AddRef("refs", &ref); 7528 be_app->PostMessage(&message); 7529 return true; 7530 } 7531 7532 return false; 7533 } 7534 7535 7536 bool 7537 BPoseView::WasDoubleClick(const BPose* pose, BPoint point, int32 buttons) 7538 { 7539 // check proximity 7540 BPoint delta = point - fLastClickPoint; 7541 int32 clicks = Window()->CurrentMessage()->FindInt32("clicks"); 7542 7543 if (clicks == 2 7544 && fabs(delta.x) < kDoubleClickTresh 7545 && fabs(delta.y) < kDoubleClickTresh 7546 && pose == fLastClickedPose) { 7547 fLastClickPoint.Set(INT32_MAX, INT32_MAX); 7548 fLastClickedPose = NULL; 7549 if (fTextWidgetToCheck != NULL) 7550 fTextWidgetToCheck->CancelWait(); 7551 7552 return buttons == fLastClickButtons; 7553 } 7554 7555 fLastClickPoint = point; 7556 fLastClickedPose = pose; 7557 fLastClickButtons = buttons; 7558 7559 return false; 7560 } 7561 7562 7563 static void 7564 AddPoseRefToMessage(Model* model, BMessage* message) 7565 { 7566 // Make sure that every file added to the message has its 7567 // MIME type set. 7568 BNode node(model->EntryRef()); 7569 if (node.InitCheck() == B_OK) { 7570 BNodeInfo info(&node); 7571 char type[B_MIME_TYPE_LENGTH]; 7572 type[0] = '\0'; 7573 if (info.GetType(type) != B_OK) { 7574 BPath path(model->EntryRef()); 7575 if (path.InitCheck() == B_OK) 7576 update_mime_info(path.Path(), false, false, false); 7577 } 7578 } 7579 message->AddRef("refs", model->EntryRef()); 7580 } 7581 7582 7583 void 7584 BPoseView::DragSelectedPoses(const BPose* pose, BPoint clickPoint) 7585 { 7586 if (!fDragEnabled) 7587 return; 7588 7589 ASSERT(pose); 7590 7591 // make sure pose is selected, it could have been deselected as part of 7592 // a click during selection extention 7593 if (!pose->IsSelected()) 7594 return; 7595 7596 // setup tracking rect by unioning all selected pose rects 7597 BMessage message(B_SIMPLE_DATA); 7598 message.AddPointer("src_window", Window()); 7599 message.AddPoint("click_pt", clickPoint); 7600 7601 // add Tracker token so that refs received recipients can script us 7602 message.AddMessenger("TrackerViewToken", BMessenger(this)); 7603 7604 // cannot use EachPoseAndModel here, because that iterates the selected 7605 // poses in reverse order 7606 int32 selectCount = CountSelected(); 7607 for (int32 index = 0; index < selectCount; index++) { 7608 AddPoseRefToMessage(fSelectionList->ItemAt(index)->TargetModel(), 7609 &message); 7610 } 7611 7612 // make sure button is still down 7613 uint32 buttons; 7614 BPoint tempLoc; 7615 GetMouse(&tempLoc, &buttons); 7616 if (buttons != 0) { 7617 int32 index = CurrentPoseList()->IndexOf(pose); 7618 message.AddInt32("buttons", (int32)buttons); 7619 BRect dragRect(GetDragRect(index)); 7620 BBitmap* dragBitmap = NULL; 7621 BPoint offset; 7622 #ifdef DRAG_FRAME 7623 if (dragRect.Width() < kTransparentDragThreshold.x 7624 && dragRect.Height() < kTransparentDragThreshold.y) { 7625 dragBitmap = MakeDragBitmap(dragRect, clickPoint, index, offset); 7626 } 7627 #else 7628 // The bitmap is now always created (if DRAG_FRAME is not defined) 7629 dragBitmap = MakeDragBitmap(dragRect, clickPoint, index, offset); 7630 #endif 7631 if (dragBitmap != NULL) { 7632 DragMessage(&message, dragBitmap, B_OP_ALPHA, offset); 7633 // this DragMessage supports alpha blending 7634 } else 7635 DragMessage(&message, dragRect); 7636 7637 // turn on auto scrolling 7638 fAutoScrollState = kWaitForTransition; 7639 Window()->SetPulseRate(100000); 7640 } 7641 } 7642 7643 7644 BBitmap* 7645 BPoseView::MakeDragBitmap(BRect dragRect, BPoint clickedPoint, 7646 int32 clickedPoseIndex, BPoint &offset) 7647 { 7648 BRect inner(clickedPoint.x - kTransparentDragThreshold.x / 2, 7649 clickedPoint.y - kTransparentDragThreshold.y / 2, 7650 clickedPoint.x + kTransparentDragThreshold.x / 2, 7651 clickedPoint.y + kTransparentDragThreshold.y / 2); 7652 7653 // (BRect & BRect) doesn't work correctly if the rectangles don't intersect 7654 // this catches a bug that is produced somewhere before this function is 7655 // called 7656 if (!inner.Intersects(dragRect)) 7657 return NULL; 7658 7659 inner = inner & dragRect; 7660 7661 // If the selection is bigger than the specified limit, the 7662 // contents will fade out when they come near the borders 7663 bool fadeTop = false; 7664 bool fadeBottom = false; 7665 bool fadeLeft = false; 7666 bool fadeRight = false; 7667 bool fade = false; 7668 if (inner.left > dragRect.left) { 7669 inner.left = std::max(inner.left - 32, dragRect.left); 7670 fade = fadeLeft = true; 7671 } 7672 if (inner.right < dragRect.right) { 7673 inner.right = std::min(inner.right + 32, dragRect.right); 7674 fade = fadeRight = true; 7675 } 7676 if (inner.top > dragRect.top) { 7677 inner.top = std::max(inner.top - 32, dragRect.top); 7678 fade = fadeTop = true; 7679 } 7680 if (inner.bottom < dragRect.bottom) { 7681 inner.bottom = std::min(inner.bottom + 32, dragRect.bottom); 7682 fade = fadeBottom = true; 7683 } 7684 7685 // set the offset for the dragged bitmap (for the BView::DragMessage() call) 7686 offset = clickedPoint - BPoint(2, 1) - inner.LeftTop(); 7687 7688 BRect rect(inner); 7689 rect.OffsetTo(B_ORIGIN); 7690 7691 BBitmap* bitmap = new BBitmap(rect, B_RGBA32, true); 7692 bitmap->Lock(); 7693 BView* view = new BView(bitmap->Bounds(), "", B_FOLLOW_NONE, 0); 7694 bitmap->AddChild(view); 7695 7696 view->SetOrigin(0, 0); 7697 7698 BRect clipRect(view->Bounds()); 7699 BRegion newClip; 7700 newClip.Set(clipRect); 7701 view->ConstrainClippingRegion(&newClip); 7702 7703 memset(bitmap->Bits(), 0, bitmap->BitsLength()); 7704 7705 view->SetDrawingMode(B_OP_ALPHA); 7706 view->SetHighColor(0, 0, 0, uint8(fade ? 164 : 128)); 7707 // set the level of transparency by value 7708 view->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE); 7709 7710 BRect bounds(Bounds()); 7711 7712 PoseList* poseList = CurrentPoseList(); 7713 if (ViewMode() == kListMode) { 7714 int32 poseCount = poseList->CountItems(); 7715 int32 startIndex = (int32)(bounds.top / fListElemHeight); 7716 BPoint loc(0, startIndex * fListElemHeight); 7717 7718 for (int32 index = startIndex; index < poseCount; index++) { 7719 BPose* pose = poseList->ItemAt(index); 7720 if (pose->IsSelected()) { 7721 BRect poseRect(pose->CalcRect(loc, this, true)); 7722 if (poseRect.Intersects(inner)) { 7723 BPoint offsetBy(-inner.LeftTop().x, -inner.LeftTop().y); 7724 pose->Draw(poseRect, poseRect, this, view, true, offsetBy, 7725 false); 7726 } 7727 } 7728 loc.y += fListElemHeight; 7729 if (loc.y > bounds.bottom) 7730 break; 7731 } 7732 } else { 7733 // add rects for visible poses only (uses VSList!!) 7734 int32 startIndex 7735 = FirstIndexAtOrBelow((int32)(bounds.top - IconPoseHeight())); 7736 int32 poseCount = fVSPoseList->CountItems(); 7737 7738 for (int32 index = startIndex; index < poseCount; index++) { 7739 BPose* pose = fVSPoseList->ItemAt(index); 7740 if (pose != NULL && pose->IsSelected()) { 7741 BRect poseRect(pose->CalcRect(this)); 7742 if (!poseRect.Intersects(inner)) 7743 continue; 7744 7745 BPoint offsetBy(-inner.LeftTop().x, -inner.LeftTop().y); 7746 pose->Draw(poseRect, poseRect, this, view, true, offsetBy, 7747 false); 7748 } 7749 } 7750 } 7751 7752 view->Sync(); 7753 7754 // fade out the contents if necessary 7755 if (fade) { 7756 uint32* bits = (uint32*)bitmap->Bits(); 7757 int32 width = bitmap->BytesPerRow() / 4; 7758 7759 if (fadeLeft) 7760 FadeRGBA32Horizontal(bits, width, int32(rect.bottom), 0, 64); 7761 7762 if (fadeRight) { 7763 FadeRGBA32Horizontal(bits, width, int32(rect.bottom), 7764 int32(rect.right), int32(rect.right) - 64); 7765 } 7766 7767 if (fadeTop) 7768 FadeRGBA32Vertical(bits, width, int32(rect.bottom), 0, 64); 7769 7770 if (fadeBottom) { 7771 FadeRGBA32Vertical(bits, width, int32(rect.bottom), 7772 int32(rect.bottom), int32(rect.bottom) - 64); 7773 } 7774 } 7775 7776 bitmap->Unlock(); 7777 return bitmap; 7778 } 7779 7780 7781 BRect 7782 BPoseView::GetDragRect(int32 clickedPoseIndex) 7783 { 7784 BRect result; 7785 BRect bounds(Bounds()); 7786 7787 PoseList* poseList = CurrentPoseList(); 7788 BPose* pose = poseList->ItemAt(clickedPoseIndex); 7789 if (ViewMode() == kListMode) { 7790 // get starting rect of clicked pose 7791 result = CalcPoseRectList(pose, clickedPoseIndex, true); 7792 7793 // add rects for visible poses only 7794 int32 poseCount = poseList->CountItems(); 7795 int32 startIndex = (int32)(bounds.top / fListElemHeight); 7796 BPoint loc(0, startIndex * fListElemHeight); 7797 7798 for (int32 index = startIndex; index < poseCount; index++) { 7799 pose = poseList->ItemAt(index); 7800 if (pose->IsSelected()) 7801 result = result | pose->CalcRect(loc, this, true); 7802 7803 loc.y += fListElemHeight; 7804 if (loc.y > bounds.bottom) 7805 break; 7806 } 7807 } else { 7808 // get starting rect of clicked pose 7809 result = pose->CalcRect(this); 7810 7811 // add rects for visible poses only (uses VSList!!) 7812 int32 poseCount = fVSPoseList->CountItems(); 7813 for (int32 index = FirstIndexAtOrBelow( 7814 (int32)(bounds.top - IconPoseHeight())); 7815 index < poseCount; index++) { 7816 BPose* pose = fVSPoseList->ItemAt(index); 7817 if (pose != NULL) { 7818 if (pose->IsSelected()) 7819 result = result | pose->CalcRect(this); 7820 7821 if (pose->Location(this).y > bounds.bottom) 7822 break; 7823 } 7824 } 7825 } 7826 7827 return result; 7828 } 7829 7830 7831 void 7832 BPoseView::SelectPoses(BRect selectionRect, BList** oldList) 7833 { 7834 // TODO: This is a mess due to pose rect calculation and list management 7835 // being different for list vs. icon modes. Refactoring needed. 7836 7837 const bool inListMode = (ViewMode() == kListMode); 7838 7839 // collect all the poses which are enclosed inside the selection rect 7840 BList* newList = new BList; 7841 BRect bounds(Bounds()); 7842 7843 int32 startIndex; 7844 if (inListMode) { 7845 startIndex = (int32)(selectionRect.top / fListElemHeight); 7846 } else { 7847 startIndex = FirstIndexAtOrBelow( 7848 (int32)(selectionRect.top - IconPoseHeight()), true); 7849 } 7850 if (startIndex < 0) 7851 startIndex = 0; 7852 7853 BPoint listLoc; 7854 if (inListMode) 7855 listLoc.Set(0, startIndex * fListElemHeight); 7856 7857 PoseList* poseList = inListMode ? CurrentPoseList() : fVSPoseList; 7858 const int32 poseCount = inListMode ? poseList->CountItems() : fPoseList->CountItems(); 7859 for (int32 index = startIndex; index < poseCount; index++) { 7860 BPose* pose = poseList->ItemAt(index); 7861 if (pose == NULL) 7862 continue; 7863 7864 BRect poseRect; 7865 if (inListMode) 7866 poseRect = pose->CalcRect(listLoc, this); 7867 else 7868 poseRect = pose->CalcRect(this); 7869 7870 if (selectionRect.Intersects(poseRect)) { 7871 bool selected = pose->IsSelected(); 7872 pose->Select(!fSelectionList->HasItem(pose)); 7873 newList->AddItem((void*)(addr_t)index); 7874 // this sucks, need to clean up using a vector class instead 7875 // of BList 7876 7877 if ((selected != pose->IsSelected()) 7878 && poseRect.Intersects(bounds)) { 7879 Invalidate(poseRect); 7880 } 7881 7882 // first Pose selected gets to be the pivot. 7883 if ((fSelectionPivotPose == NULL) && (selected == false)) 7884 fSelectionPivotPose = pose; 7885 } 7886 7887 if (inListMode) { 7888 listLoc.y += fListElemHeight; 7889 if (listLoc.y > selectionRect.bottom) 7890 break; 7891 } else { 7892 if (pose->Location(this).y > selectionRect.bottom) 7893 break; 7894 } 7895 } 7896 7897 // take the old set of enclosed poses and invert selection state 7898 // on those which are no longer enclosed 7899 int32 count = (*oldList)->CountItems(); 7900 for (int32 index = 0; index < count; index++) { 7901 int32 oldIndex = (addr_t)(*oldList)->ItemAt(index); 7902 7903 if (!newList->HasItem((void*)(addr_t)oldIndex)) { 7904 BPose* pose = poseList->ItemAt(oldIndex); 7905 pose->Select(!pose->IsSelected()); 7906 7907 BRect poseRect; 7908 if (inListMode) { 7909 listLoc.Set(0, oldIndex * fListElemHeight); 7910 poseRect = pose->CalcRect(listLoc, this); 7911 } else { 7912 poseRect = pose->CalcRect(this); 7913 } 7914 7915 if (poseRect.Intersects(bounds)) 7916 Invalidate(poseRect); 7917 } 7918 } 7919 7920 delete *oldList; 7921 *oldList = newList; 7922 } 7923 7924 7925 void 7926 BPoseView::AddRemoveSelectionRange(BPoint where, bool extendSelection, 7927 BPose* pose) 7928 { 7929 ASSERT(pose != NULL); 7930 7931 if (pose == fSelectionPivotPose && !extendSelection) 7932 return; 7933 7934 if (fMultipleSelection && (modifiers() & B_SHIFT_KEY) != 0 && fSelectionPivotPose) { 7935 // multi pose extend/shrink current selection 7936 bool select = !pose->IsSelected() || !extendSelection; 7937 // This weird bit of logic causes the selection to always 7938 // center around the pivot point, unless you choose to hold 7939 // down COMMAND, which will unselect between the pivot and 7940 // the most recently selected Pose. 7941 7942 if (!extendSelection) { 7943 // Remember fSelectionPivotPose because ClearSelection() NULLs it 7944 // and we need it to be preserved. 7945 const BPose* savedPivotPose = fSelectionPivotPose; 7946 ClearSelection(); 7947 fSelectionPivotPose = savedPivotPose; 7948 } 7949 7950 if (ViewMode() == kListMode) { 7951 PoseList* poseList = CurrentPoseList(); 7952 int32 currentSelectedIndex = poseList->IndexOf(pose); 7953 int32 lastSelectedIndex = poseList->IndexOf(fSelectionPivotPose); 7954 7955 int32 startRange; 7956 int32 endRange; 7957 7958 if (lastSelectedIndex < currentSelectedIndex) { 7959 startRange = lastSelectedIndex; 7960 endRange = currentSelectedIndex; 7961 } else { 7962 startRange = currentSelectedIndex; 7963 endRange = lastSelectedIndex; 7964 } 7965 7966 for (int32 i = startRange; i <= endRange; i++) 7967 AddRemovePoseFromSelection(poseList->ItemAt(i), i, select); 7968 } else { 7969 BRect selection(where, fSelectionPivotPose->Location(this)); 7970 7971 // Things will get odd if we don't 'fix' the selection rect. 7972 if (selection.left > selection.right) 7973 std::swap(selection.left, selection.right); 7974 7975 if (selection.top > selection.bottom) 7976 std::swap(selection.top, selection.bottom); 7977 7978 // If the selection rect is not at least 1 pixel high/wide, things 7979 // are also not going to work out. 7980 if (selection.IntegerWidth() < 1) 7981 selection.right = selection.left + 1.0f; 7982 7983 if (selection.IntegerHeight() < 1) 7984 selection.bottom = selection.top + 1.0f; 7985 7986 ASSERT(selection.IsValid()); 7987 7988 int32 poseCount = fPoseList->CountItems(); 7989 for (int32 index = poseCount - 1; index >= 0; index--) { 7990 BPose* currPose = fPoseList->ItemAt(index); 7991 // TODO: works only in non-list mode? 7992 if (selection.Intersects(currPose->CalcRect(this))) 7993 AddRemovePoseFromSelection(currPose, index, select); 7994 } 7995 } 7996 } else { 7997 int32 index = CurrentPoseList()->IndexOf(pose); 7998 if (!extendSelection) { 7999 if (!pose->IsSelected()) { 8000 // create new selection 8001 ClearSelection(); 8002 AddRemovePoseFromSelection(pose, index, true); 8003 fSelectionPivotPose = pose; 8004 } 8005 } else { 8006 fMimeTypesInSelectionCache.MakeEmpty(); 8007 AddRemovePoseFromSelection(pose, index, !pose->IsSelected()); 8008 } 8009 } 8010 8011 // If the list is empty, there cannot be a pivot pose, 8012 // however if the list is not empty there must be a pivot 8013 // pose. 8014 if (fSelectionList->IsEmpty()) { 8015 fSelectionPivotPose = NULL; 8016 fRealPivotPose = NULL; 8017 } else if (fSelectionPivotPose == NULL) { 8018 fSelectionPivotPose = pose; 8019 fRealPivotPose = pose; 8020 } 8021 } 8022 8023 8024 void 8025 BPoseView::DeleteSymLinkPoseTarget(const node_ref* itemNode, BPose* pose, 8026 int32 index) 8027 { 8028 ASSERT(pose->TargetModel()->IsSymLink()); 8029 watch_node(itemNode, B_STOP_WATCHING, this); 8030 8031 // watch the parent of the symlink, so that we know when the symlink 8032 // can be considered fixed. 8033 WatchParentOf(pose->TargetModel()->EntryRef()); 8034 8035 BPoint loc(0, index * fListElemHeight); 8036 pose->TargetModel()->SetLinkTo(NULL); 8037 pose->UpdateBrokenSymLink(loc, this); 8038 } 8039 8040 8041 bool 8042 BPoseView::DeletePose(const node_ref* itemNode, BPose* pose, int32 index) 8043 { 8044 watch_node(itemNode, B_STOP_WATCHING, this); 8045 8046 if (pose == NULL) 8047 pose = fPoseList->FindPose(itemNode, &index); 8048 8049 if (pose != NULL) { 8050 fInsertedNodes.Remove(*itemNode); 8051 if (pose->TargetModel()->IsSymLink()) { 8052 fBrokenLinks->RemoveItem(pose->TargetModel()); 8053 StopWatchingParentsOf(pose->TargetModel()->EntryRef()); 8054 Model* target = pose->TargetModel()->LinkTo(); 8055 if (target) 8056 watch_node(target->NodeRef(), B_STOP_WATCHING, this); 8057 } 8058 8059 ASSERT(TargetModel()); 8060 8061 if (pose == fDropTarget) 8062 fDropTarget = NULL; 8063 8064 if (pose == ActivePose()) 8065 CommitActivePose(); 8066 8067 Window()->UpdateIfNeeded(); 8068 8069 // remove it from list no matter what since it might be in list 8070 // but not "selected" since selection is hidden 8071 fSelectionList->RemoveItem(pose); 8072 if (fSelectionPivotPose == pose) 8073 fSelectionPivotPose = NULL; 8074 if (fRealPivotPose == pose) 8075 fRealPivotPose = NULL; 8076 8077 if (pose->IsSelected() && fSelectionChangedHook) 8078 ContainerWindow()->SelectionChanged(); 8079 8080 fPoseList->RemoveItemAt(index); 8081 8082 bool visible = true; 8083 if (fFiltering) { 8084 if (fFilteredPoseList->FindPose(itemNode, &index) != NULL) 8085 fFilteredPoseList->RemoveItemAt(index); 8086 else 8087 visible = false; 8088 } 8089 8090 fMimeTypeListIsDirty = true; 8091 8092 if (pose->HasLocation()) 8093 RemoveFromVSList(pose); 8094 8095 if (visible) { 8096 BRect invalidRect; 8097 if (ViewMode() == kListMode) 8098 invalidRect = CalcPoseRectList(pose, index); 8099 else 8100 invalidRect = pose->CalcRect(this); 8101 8102 if (ViewMode() == kListMode) 8103 CloseGapInList(&invalidRect); 8104 else 8105 RemoveFromExtent(invalidRect); 8106 8107 Invalidate(invalidRect); 8108 UpdateCount(); 8109 UpdateScrollRange(); 8110 ResetPosePlacementHint(); 8111 8112 if (ViewMode() == kListMode) { 8113 BRect bounds(Bounds()); 8114 int32 index = (int32)(bounds.bottom / fListElemHeight); 8115 BPose* pose = CurrentPoseList()->ItemAt(index); 8116 8117 if (pose == NULL && bounds.top > 0) { 8118 // scroll up a little 8119 BView::ScrollTo(bounds.left, 8120 std::max(bounds.top - fListElemHeight, 0.0f)); 8121 } 8122 } 8123 } 8124 8125 delete pose; 8126 } else { 8127 // we might be getting a delete for an item in the zombie list 8128 Model* zombie = FindZombie(itemNode, &index); 8129 if (zombie) { 8130 PRINT(("deleting zombie model %s\n", zombie->Name())); 8131 fZombieList->RemoveItemAt(index); 8132 delete zombie; 8133 } else 8134 return false; 8135 } 8136 8137 return true; 8138 } 8139 8140 8141 Model* 8142 BPoseView::FindZombie(const node_ref* itemNode, int32* resultingIndex) 8143 { 8144 int32 count = fZombieList->CountItems(); 8145 for (int32 index = 0; index < count; index++) { 8146 Model* zombie = fZombieList->ItemAt(index); 8147 if (*zombie->NodeRef() == *itemNode) { 8148 if (resultingIndex) 8149 *resultingIndex = index; 8150 return zombie; 8151 } 8152 } 8153 8154 return NULL; 8155 } 8156 8157 8158 // return pose at location h,v (search list starting from bottom so 8159 // drawing and hit detection reflect the same pose ordering) 8160 BPose* 8161 BPoseView::FindPose(BPoint point, int32* poseIndex) const 8162 { 8163 if (ViewMode() == kListMode) { 8164 int32 index = (int32)(point.y / fListElemHeight); 8165 if (poseIndex != NULL) 8166 *poseIndex = index; 8167 8168 BPoint loc(0, index * fListElemHeight); 8169 BPose* pose = CurrentPoseList()->ItemAt(index); 8170 if (pose != NULL && pose->PointInPose(loc, this, point)) 8171 return pose; 8172 } else { 8173 int32 poseCount = fPoseList->CountItems(); 8174 for (int32 index = poseCount - 1; index >= 0; index--) { 8175 BPose* pose = fPoseList->ItemAt(index); 8176 if (pose->PointInPose(this, point)) { 8177 if (poseIndex) 8178 *poseIndex = index; 8179 8180 return pose; 8181 } 8182 } 8183 } 8184 8185 return NULL; 8186 } 8187 8188 8189 BPose* 8190 BPoseView::FirstVisiblePose(int32* _index) const 8191 { 8192 ASSERT(ViewMode() == kListMode); 8193 return FindPose(BPoint(fListOffset, 8194 Bounds().top + fListElemHeight - 1), _index); 8195 } 8196 8197 8198 BPose* 8199 BPoseView::LastVisiblePose(int32* _index) const 8200 { 8201 ASSERT(ViewMode() == kListMode); 8202 BPose* pose = FindPose(BPoint(fListOffset, Bounds().top + Frame().Height() 8203 - fListElemHeight + 2), _index); 8204 if (pose == NULL) { 8205 // Just get the last one 8206 pose = CurrentPoseList()->LastItem(); 8207 if (_index != NULL) 8208 *_index = CurrentPoseList()->CountItems() - 1; 8209 } 8210 return pose; 8211 } 8212 8213 8214 void 8215 BPoseView::OpenSelection(BPose* clickedPose, int32* index) 8216 { 8217 BPose* singleWindowBrowsePose = clickedPose; 8218 TrackerSettings settings; 8219 8220 // get first selected pose in selection if none was clicked 8221 if (settings.SingleWindowBrowse() 8222 && !singleWindowBrowsePose 8223 && CountSelected() == 1 8224 && !IsFilePanel()) { 8225 singleWindowBrowsePose = fSelectionList->ItemAt(0); 8226 } 8227 8228 // check if we can use the single window mode 8229 if (settings.SingleWindowBrowse() 8230 && !IsDesktopWindow() 8231 && !IsFilePanel() 8232 && (modifiers() & B_OPTION_KEY) == 0 8233 && TargetModel()->IsDirectory() 8234 && singleWindowBrowsePose 8235 && singleWindowBrowsePose->ResolvedModel() 8236 && singleWindowBrowsePose->ResolvedModel()->IsDirectory()) { 8237 // Switch to new directory 8238 BMessage msg(kSwitchDirectory); 8239 msg.AddRef("refs", singleWindowBrowsePose->ResolvedModel()->EntryRef()); 8240 Window()->PostMessage(&msg); 8241 } else { 8242 // otherwise use standard method 8243 OpenSelectionCommon(clickedPose, index, false); 8244 } 8245 8246 } 8247 8248 8249 void 8250 BPoseView::OpenSelectionUsing(BPose* clickedPose, int32* index) 8251 { 8252 OpenSelectionCommon(clickedPose, index, true); 8253 } 8254 8255 8256 void 8257 BPoseView::OpenSelectionCommon(BPose* clickedPose, int32* poseIndex, 8258 bool openWith) 8259 { 8260 int32 selectCount = CountSelected(); 8261 if (selectCount == 0) 8262 return; 8263 8264 BMessage message(B_REFS_RECEIVED); 8265 8266 for (int32 index = 0; index < selectCount; index++) { 8267 BPose* pose = fSelectionList->ItemAt(index); 8268 message.AddRef("refs", pose->TargetModel()->EntryRef()); 8269 8270 // close parent window if option down and we're not the desktop 8271 // and we're not in single window mode 8272 if (dynamic_cast<TTracker*>(be_app) == NULL 8273 || (modifiers() & B_OPTION_KEY) == 0 8274 || IsFilePanel() 8275 || IsDesktopWindow() 8276 || TrackerSettings().SingleWindowBrowse()) { 8277 continue; 8278 } 8279 8280 ASSERT(TargetModel()); 8281 message.AddData("nodeRefsToClose", B_RAW_TYPE, TargetModel()->NodeRef(), 8282 sizeof (node_ref)); 8283 } 8284 8285 if (openWith) 8286 message.AddInt32("launchUsingSelector", 0); 8287 8288 // add a messenger to the launch message that will be used to 8289 // dispatch scripting calls from apps to the PoseView 8290 message.AddMessenger("TrackerViewToken", BMessenger(this)); 8291 8292 if (fSelectionHandler) 8293 fSelectionHandler->PostMessage(&message); 8294 8295 if (clickedPose) { 8296 ASSERT(poseIndex); 8297 if (ViewMode() == kListMode) 8298 DrawOpenAnimation(CalcPoseRectList(clickedPose, *poseIndex, true)); 8299 else 8300 DrawOpenAnimation(clickedPose->CalcRect(this)); 8301 } 8302 } 8303 8304 8305 void 8306 BPoseView::DrawOpenAnimation(BRect rect) 8307 { 8308 SetDrawingMode(B_OP_INVERT); 8309 8310 BRect box1(rect); 8311 box1.InsetBy(rect.Width() / 2 - 2, rect.Height() / 2 - 2); 8312 BRect box2(box1); 8313 8314 for (int32 index = 0; index < 7; index++) { 8315 box2 = box1; 8316 box2.InsetBy(-2, -2); 8317 StrokeRect(box1, B_MIXED_COLORS); 8318 Sync(); 8319 StrokeRect(box2, B_MIXED_COLORS); 8320 Sync(); 8321 snooze(10000); 8322 StrokeRect(box1, B_MIXED_COLORS); 8323 StrokeRect(box2, B_MIXED_COLORS); 8324 Sync(); 8325 box1 = box2; 8326 } 8327 8328 SetDrawingMode(B_OP_OVER); 8329 } 8330 8331 8332 void 8333 BPoseView::ApplyBackgroundColor() 8334 { 8335 float bgTint = TargetVolumeIsReadOnly() 8336 ? ReadOnlyTint(ui_color(B_DOCUMENT_BACKGROUND_COLOR)) : B_NO_TINT; 8337 SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR, bgTint); 8338 SetLowUIColor(B_DOCUMENT_BACKGROUND_COLOR, bgTint); 8339 } 8340 8341 8342 void 8343 BPoseView::UnmountSelectedVolumes() 8344 { 8345 BVolume boot; 8346 BVolumeRoster().GetBootVolume(&boot); 8347 8348 int32 selectCount = CountSelected(); 8349 for (int32 index = 0; index < selectCount; index++) { 8350 BPose* pose = fSelectionList->ItemAt(index); 8351 if (pose == NULL) 8352 continue; 8353 8354 Model* model = pose->TargetModel(); 8355 if (model->IsVolume()) { 8356 BVolume volume(model->NodeRef()->device); 8357 if (volume != boot) { 8358 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 8359 if (tracker != NULL) 8360 tracker->SaveAllPoseLocations(); 8361 8362 BMessage message(kUnmountVolume); 8363 message.AddInt32("device_id", volume.Device()); 8364 be_app->PostMessage(&message); 8365 } 8366 } 8367 } 8368 } 8369 8370 8371 void 8372 BPoseView::ClearPoses() 8373 { 8374 CommitActivePose(); 8375 SavePoseLocations(); 8376 ClearFilter(); 8377 8378 // clear all pose lists 8379 fPoseList->MakeEmpty(); 8380 fMimeTypeListIsDirty = true; 8381 fVSPoseList->MakeEmpty(); 8382 fZombieList->MakeEmpty(); 8383 fSelectionList->MakeEmpty(); 8384 fSelectionPivotPose = NULL; 8385 fRealPivotPose = NULL; 8386 fMimeTypesInSelectionCache.MakeEmpty(); 8387 fBrokenLinks->MakeEmpty(); 8388 8389 DisableScrollBars(); 8390 ScrollTo(B_ORIGIN); 8391 UpdateScrollRange(); 8392 SetScrollBarsTo(B_ORIGIN); 8393 EnableScrollBars(); 8394 ResetPosePlacementHint(); 8395 ClearExtent(); 8396 8397 if (fSelectionChangedHook) 8398 ContainerWindow()->SelectionChanged(); 8399 } 8400 8401 8402 void 8403 BPoseView::SwitchDir(const entry_ref* newDirRef, AttributeStreamNode* node) 8404 { 8405 ASSERT(TargetModel()); 8406 if (*newDirRef == *TargetModel()->EntryRef()) 8407 // no change 8408 return; 8409 8410 Model* model = new Model(newDirRef, true); 8411 if (model->InitCheck() != B_OK || !model->IsDirectory()) { 8412 delete model; 8413 return; 8414 } 8415 8416 CommitActivePose(); 8417 8418 // before clearing and adding new poses, we reset "blessed" async 8419 // thread id to prevent old add_poses thread from adding any more icons 8420 // the new add_poses thread will then set fAddPosesThread to its ID and it 8421 // will be allowed to add icons 8422 fAddPosesThreads.clear(); 8423 fInsertedNodes.Clear(); 8424 8425 delete fModel; 8426 fModel = model; 8427 8428 // check if model is a trash dir, if so 8429 // update ContainerWindow's fIsTrash, etc. 8430 // variables to indicate new state 8431 if (ContainerWindow() != NULL) 8432 ContainerWindow()->UpdateIfTrash(model); 8433 8434 StopWatching(); 8435 ClearPoses(); 8436 8437 // Restore state, might fail if the state has never been saved for this node 8438 uint32 oldMode = ViewMode(); 8439 bool viewStateRestored = false; 8440 if (node != NULL) { 8441 BViewState* previousState = fViewState; 8442 RestoreState(node); 8443 viewStateRestored = (fViewState != previousState); 8444 } 8445 8446 if (viewStateRestored) { 8447 if (ViewMode() == kListMode && oldMode != kListMode) { 8448 if (ContainerWindow() != NULL) 8449 ContainerWindow()->ShowAttributeMenu(); 8450 8451 fTitleView->Show(); 8452 } else if (ViewMode() != kListMode && oldMode == kListMode) { 8453 fTitleView->Hide(); 8454 8455 if (ContainerWindow() != NULL) 8456 ContainerWindow()->HideAttributeMenu(); 8457 } else if (ViewMode() == kListMode && oldMode == kListMode) 8458 fTitleView->Invalidate(); 8459 8460 BPoint origin; 8461 if (ViewMode() == kListMode) 8462 origin = fViewState->ListOrigin(); 8463 else 8464 origin = fViewState->IconOrigin(); 8465 8466 PinPointToValidRange(origin); 8467 8468 SetIconPoseHeight(); 8469 GetLayoutInfo(ViewMode(), &fGrid, &fOffset); 8470 ResetPosePlacementHint(); 8471 8472 DisableScrollBars(); 8473 ScrollTo(origin); 8474 UpdateScrollRange(); 8475 SetScrollBarsTo(origin); 8476 EnableScrollBars(); 8477 } else { 8478 ResetOrigin(); 8479 ResetPosePlacementHint(); 8480 } 8481 8482 StartWatching(); 8483 8484 // be sure this happens after origin is set and window is sized 8485 // properly for proper icon caching! 8486 8487 if (ContainerWindow() != NULL && ContainerWindow()->IsTrash()) 8488 AddTrashPoses(); 8489 else 8490 AddPoses(TargetModel()); 8491 TargetModel()->CloseNode(); 8492 8493 if (!IsDesktopWindow()) { 8494 ApplyBackgroundColor(); 8495 if (ContainerWindow() != NULL) 8496 ContainerWindow()->UpdateBackgroundImage(); 8497 } 8498 8499 Invalidate(); 8500 8501 fLastKeyTime = 0; 8502 } 8503 8504 8505 void 8506 BPoseView::Refresh() 8507 { 8508 ASSERT(TargetModel()); 8509 if (TargetModel()->OpenNode() != B_OK) 8510 return; 8511 8512 StopWatching(); 8513 fInsertedNodes.Clear(); 8514 ClearPoses(); 8515 StartWatching(); 8516 8517 // be sure this happens after origin is set and window is sized 8518 // properly for proper icon caching! 8519 AddPoses(TargetModel()); 8520 TargetModel()->CloseNode(); 8521 8522 if (fRefFilter != NULL) { 8523 fFiltering = false; 8524 StartFiltering(); 8525 } 8526 8527 Invalidate(); 8528 ResetOrigin(); 8529 ResetPosePlacementHint(); 8530 } 8531 8532 8533 void 8534 BPoseView::ResetOrigin() 8535 { 8536 DisableScrollBars(); 8537 ScrollTo(B_ORIGIN); 8538 UpdateScrollRange(); 8539 SetScrollBarsTo(B_ORIGIN); 8540 EnableScrollBars(); 8541 } 8542 8543 8544 void 8545 BPoseView::EditQueries() 8546 { 8547 // edit selected queries 8548 SendSelectionAsRefs(kEditQuery, true); 8549 } 8550 8551 8552 void 8553 BPoseView::SendSelectionAsRefs(uint32 what, bool onlyQueries) 8554 { 8555 // fix this by having a proper selection iterator 8556 8557 int32 selectCount = CountSelected(); 8558 if (selectCount <= 0) 8559 return; 8560 8561 bool haveRef = false; 8562 BMessage message; 8563 message.what = what; 8564 8565 for (int32 index = 0; index < selectCount; index++) { 8566 BPose* pose = fSelectionList->ItemAt(index); 8567 if (onlyQueries) { 8568 // to check if pose is a query, follow any symlink first 8569 BEntry resolvedEntry(pose->TargetModel()->EntryRef(), true); 8570 if (resolvedEntry.InitCheck() != B_OK) 8571 continue; 8572 8573 Model model(&resolvedEntry); 8574 if (!model.IsQuery() && !model.IsQueryTemplate()) 8575 continue; 8576 } 8577 haveRef = true; 8578 message.AddRef("refs", pose->TargetModel()->EntryRef()); 8579 } 8580 if (!haveRef) 8581 return; 8582 8583 if (onlyQueries) 8584 // this is used to make query templates come up in a special edit window 8585 message.AddBool("editQueryOnPose", onlyQueries); 8586 8587 BMessenger(kTrackerSignature).SendMessage(&message); 8588 } 8589 8590 8591 void 8592 BPoseView::OpenInfoWindows() 8593 { 8594 BMessenger tracker(kTrackerSignature); 8595 if (!tracker.IsValid()) { 8596 BAlert* alert = new BAlert("", 8597 B_TRANSLATE("The Tracker must be running to see Info windows."), 8598 B_TRANSLATE("Cancel"), NULL, NULL, B_WIDTH_AS_USUAL, 8599 B_WARNING_ALERT); 8600 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 8601 alert->Go(); 8602 return; 8603 } 8604 8605 if (fSelectionList != NULL && fSelectionList->CountItems() > 0) 8606 SendSelectionAsRefs(kGetInfo); 8607 else if (TargetModel()->EntryRef() != NULL) { 8608 BMessage message(kGetInfo); 8609 message.AddRef("refs", TargetModel()->EntryRef()); 8610 BMessenger(kTrackerSignature).SendMessage(&message); 8611 } 8612 } 8613 8614 8615 void 8616 BPoseView::SetDefaultPrinter() 8617 { 8618 BMessenger trackerMessenger(kTrackerSignature); 8619 if (!trackerMessenger.IsValid()) { 8620 BAlert* alert = new BAlert("", 8621 B_TRANSLATE("The Tracker must be running to set the default " 8622 "printer."), B_TRANSLATE("Cancel"), NULL, NULL, B_WIDTH_AS_USUAL, 8623 B_WARNING_ALERT); 8624 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 8625 alert->Go(); 8626 return; 8627 } 8628 SendSelectionAsRefs(kMakeActivePrinter); 8629 } 8630 8631 8632 void 8633 BPoseView::OpenParent() 8634 { 8635 if (!TargetModel() || TargetModel()->IsRoot() || IsDesktopWindow()) 8636 return; 8637 8638 BEntry entry(TargetModel()->EntryRef()); 8639 entry_ref ref; 8640 8641 if (FSGetParentVirtualDirectoryAware(entry, entry) != B_OK 8642 || entry.GetRef(&ref) != B_OK) 8643 return; 8644 8645 BEntry root("/"); 8646 if (!TrackerSettings().SingleWindowBrowse() 8647 && !TrackerSettings().ShowNavigator() 8648 && !TrackerSettings().ShowDisksIcon() && entry == root 8649 && (modifiers() & B_CONTROL_KEY) == 0) 8650 return; 8651 8652 Model parentModel(&ref); 8653 8654 BMessage message(B_REFS_RECEIVED); 8655 message.AddRef("refs", &ref); 8656 8657 if (dynamic_cast<TTracker*>(be_app)) { 8658 // add information about the child, so that we can select it 8659 // in the parent view 8660 message.AddData("nodeRefToSelect", B_RAW_TYPE, TargetModel()->NodeRef(), 8661 sizeof (node_ref)); 8662 8663 if ((modifiers() & B_OPTION_KEY) != 0 && !IsFilePanel()) { 8664 // if option down, add instructions to close the parent 8665 message.AddData("nodeRefsToClose", B_RAW_TYPE, 8666 TargetModel()->NodeRef(), sizeof (node_ref)); 8667 } 8668 } 8669 8670 if (TrackerSettings().SingleWindowBrowse()) { 8671 BMessage msg(kSwitchDirectory); 8672 msg.AddRef("refs", &ref); 8673 Window()->PostMessage(&msg); 8674 } else 8675 be_app->PostMessage(&message); 8676 } 8677 8678 8679 void 8680 BPoseView::IdentifySelection(bool force) 8681 { 8682 int32 selectCount = CountSelected(); 8683 for (int32 index = 0; index < selectCount; index++) { 8684 BPose* pose = fSelectionList->ItemAt(index); 8685 BEntry entry(pose->TargetModel()->ResolveIfLink()->EntryRef()); 8686 if (entry.InitCheck() == B_OK) { 8687 BPath path; 8688 if (entry.GetPath(&path) == B_OK) 8689 update_mime_info(path.Path(), true, false, force ? 2 : 1); 8690 } 8691 } 8692 } 8693 8694 8695 void 8696 BPoseView::ClearSelection() 8697 { 8698 CommitActivePose(); 8699 fSelectionPivotPose = NULL; 8700 fRealPivotPose = NULL; 8701 8702 if (CountSelected() > 0) { 8703 // scan all visible poses first 8704 BRect bounds(Bounds()); 8705 8706 if (ViewMode() == kListMode) { 8707 int32 startIndex = (int32)(bounds.top / fListElemHeight); 8708 BPoint loc(0, startIndex * fListElemHeight); 8709 8710 PoseList* poseList = CurrentPoseList(); 8711 int32 poseCount = poseList->CountItems(); 8712 for (int32 index = startIndex; index < poseCount; index++) { 8713 BPose* pose = poseList->ItemAt(index); 8714 if (pose->IsSelected()) { 8715 pose->Select(false); 8716 Invalidate(pose->CalcRect(loc, this, false)); 8717 } 8718 8719 loc.y += fListElemHeight; 8720 if (loc.y > bounds.bottom) 8721 break; 8722 } 8723 } else { 8724 int32 startIndex = FirstIndexAtOrBelow( 8725 (int32)(bounds.top - IconPoseHeight()), true); 8726 int32 poseCount = fVSPoseList->CountItems(); 8727 for (int32 index = startIndex; index < poseCount; index++) { 8728 BPose* pose = fVSPoseList->ItemAt(index); 8729 if (pose != NULL) { 8730 if (pose->IsSelected()) { 8731 pose->Select(false); 8732 Invalidate(pose->CalcRect(this)); 8733 } 8734 8735 if (pose->Location(this).y > bounds.bottom) 8736 break; 8737 } 8738 } 8739 } 8740 8741 // clear selection state in all poses 8742 int32 selectCount = CountSelected(); 8743 for (int32 index = 0; index < selectCount; index++) 8744 fSelectionList->ItemAt(index)->Select(false); 8745 8746 fSelectionList->MakeEmpty(); 8747 } 8748 8749 fMimeTypesInSelectionCache.MakeEmpty(); 8750 } 8751 8752 8753 void 8754 BPoseView::ShowSelection(bool show) 8755 { 8756 if (fSelectionVisible == show) 8757 return; 8758 8759 fSelectionVisible = show; 8760 8761 if (CountSelected() <= 0) 8762 return; 8763 8764 // scan all visible poses first 8765 BRect bounds(Bounds()); 8766 8767 if (ViewMode() == kListMode) { 8768 int32 startIndex = (int32)(bounds.top / fListElemHeight); 8769 BPoint loc(0, startIndex * fListElemHeight); 8770 8771 PoseList* poseList = CurrentPoseList(); 8772 int32 poseCount = poseList->CountItems(); 8773 for (int32 index = startIndex; index < poseCount; index++) { 8774 BPose* pose = poseList->ItemAt(index); 8775 if (fSelectionList->HasItem(pose)) 8776 if (pose->IsSelected() != show 8777 || fShowSelectionWhenInactive) { 8778 if (!fShowSelectionWhenInactive) 8779 pose->Select(show); 8780 8781 pose->Draw(BRect(pose->CalcRect(loc, this, false)), 8782 bounds, this, false); 8783 } 8784 8785 loc.y += fListElemHeight; 8786 if (loc.y > bounds.bottom) 8787 break; 8788 } 8789 } else { 8790 int32 startIndex = FirstIndexAtOrBelow( 8791 (int32)(bounds.top - IconPoseHeight()), true); 8792 int32 poseCount = fVSPoseList->CountItems(); 8793 for (int32 index = startIndex; index < poseCount; index++) { 8794 BPose* pose = fVSPoseList->ItemAt(index); 8795 if (pose != NULL) { 8796 if (fSelectionList->HasItem(pose)) 8797 if (pose->IsSelected() != show 8798 || fShowSelectionWhenInactive) { 8799 if (!fShowSelectionWhenInactive) 8800 pose->Select(show); 8801 8802 Invalidate(pose->CalcRect(this)); 8803 } 8804 8805 if (pose->Location(this).y > bounds.bottom) 8806 break; 8807 } 8808 } 8809 } 8810 8811 // now set all other poses 8812 int32 selectCount = CountSelected(); 8813 for (int32 index = 0; index < selectCount; index++) { 8814 BPose* pose = fSelectionList->ItemAt(index); 8815 if (pose->IsSelected() != show && !fShowSelectionWhenInactive) 8816 pose->Select(show); 8817 } 8818 8819 // finally update fRealPivotPose/fSelectionPivotPose 8820 if (!show) { 8821 fRealPivotPose = fSelectionPivotPose; 8822 fSelectionPivotPose = NULL; 8823 } else { 8824 if (fRealPivotPose) 8825 fSelectionPivotPose = fRealPivotPose; 8826 8827 fRealPivotPose = NULL; 8828 } 8829 } 8830 8831 8832 void 8833 BPoseView::AddRemovePoseFromSelection(BPose* pose, int32 index, bool select) 8834 { 8835 // Do not allow double selection/deselection. 8836 if (select == pose->IsSelected()) 8837 return; 8838 8839 pose->Select(select); 8840 8841 // update display 8842 if (ViewMode() == kListMode) { 8843 Invalidate(pose->CalcRect(BPoint(0, index * fListElemHeight), 8844 this, false)); 8845 } else 8846 Invalidate(pose->CalcRect(this)); 8847 8848 if (select) 8849 fSelectionList->AddItem(pose); 8850 else { 8851 fSelectionList->RemoveItem(pose, false); 8852 if (fSelectionPivotPose == pose) 8853 fSelectionPivotPose = NULL; 8854 8855 if (fRealPivotPose == pose) 8856 fRealPivotPose = NULL; 8857 } 8858 } 8859 8860 8861 bool 8862 BPoseView::SelectedVolumeIsReadOnly() const 8863 { 8864 BVolume volume; 8865 BEntry entry; 8866 BNode parent; 8867 node_ref nref; 8868 int32 selectCount = fSelectionList->CountItems(); 8869 8870 if (selectCount > 1 && TargetModel()->IsQuery()) { 8871 // multiple items selected in query, consider the whole selection 8872 // to be read-only if any item's volume is read-only 8873 for (int32 i = 0; i < selectCount; i++) { 8874 BPose* pose = fSelectionList->ItemAt(i); 8875 if (pose == NULL || pose->TargetModel() == NULL) 8876 continue; 8877 8878 entry.SetTo(pose->TargetModel()->EntryRef()); 8879 if (FSGetParentVirtualDirectoryAware(entry, parent) == B_OK) { 8880 parent.GetNodeRef(&nref); 8881 volume.SetTo(nref.device); 8882 if (volume.InitCheck() == B_OK && volume.IsReadOnly()) 8883 return true; 8884 } 8885 } 8886 } else if (selectCount > 0) { 8887 // only check first item's volume, assume rest are the same 8888 entry.SetTo(fSelectionList->FirstItem()->TargetModel()->EntryRef()); 8889 if (FSGetParentVirtualDirectoryAware(entry, parent) == B_OK) { 8890 parent.GetNodeRef(&nref); 8891 volume.SetTo(nref.device); 8892 } 8893 } else { 8894 // no items selected, check target volume instead 8895 volume.SetTo(TargetModel()->NodeRef()->device); 8896 } 8897 8898 return volume.InitCheck() == B_OK && volume.IsReadOnly(); 8899 } 8900 8901 8902 bool 8903 BPoseView::TargetVolumeIsReadOnly() const 8904 { 8905 Model* target = TargetModel(); 8906 BVolume volume(target->NodeRef()->device); 8907 8908 return target->IsQuery() || target->IsQueryTemplate() 8909 || target->IsVirtualDirectory() 8910 || (volume.InitCheck() == B_OK && volume.IsReadOnly()); 8911 } 8912 8913 8914 bool 8915 BPoseView::CanEditName() const 8916 { 8917 if (CountSelected() != 1) 8918 return false; 8919 8920 Model* selected = fSelectionList->FirstItem()->TargetModel(); 8921 return !ActivePose() && selected != NULL && !selected->IsDesktop() 8922 && !selected->IsRoot() && !selected->IsTrash(); 8923 } 8924 8925 8926 bool 8927 BPoseView::CanMoveToTrashOrDuplicate() const 8928 { 8929 const int32 selectCount = CountSelected(); 8930 if (selectCount < 1) 8931 return false; 8932 8933 if (SelectedVolumeIsReadOnly()) 8934 return false; 8935 8936 BPose* pose; 8937 Model* selected; 8938 for (int32 i = 0; i < selectCount; i++) { 8939 pose = fSelectionList->ItemAt(i); 8940 selected = pose->TargetModel(); 8941 if (pose == NULL || selected == NULL) 8942 continue; 8943 8944 if (selected->IsDesktop() || selected->IsRoot() || selected->IsTrash()) 8945 return false; 8946 } 8947 8948 return true; 8949 } 8950 8951 8952 void 8953 BPoseView::RemoveFromExtent(const BRect &rect) 8954 { 8955 ASSERT(ViewMode() != kListMode); 8956 8957 if (rect.left <= fExtent.left || rect.right >= fExtent.right 8958 || rect.top <= fExtent.top || rect.bottom >= fExtent.bottom) 8959 RecalcExtent(); 8960 } 8961 8962 8963 void 8964 BPoseView::RecalcExtent() 8965 { 8966 ASSERT(ViewMode() != kListMode); 8967 8968 ClearExtent(); 8969 int32 poseCount = fPoseList->CountItems(); 8970 for (int32 index = 0; index < poseCount; index++) 8971 AddToExtent(fPoseList->ItemAt(index)->CalcRect(this)); 8972 } 8973 8974 8975 BRect 8976 BPoseView::Extent() const 8977 { 8978 BRect rect; 8979 8980 if (ViewMode() == kListMode) { 8981 BColumn* column = fColumnList->LastItem(); 8982 if (column != NULL) { 8983 rect.left = rect.top = 0; 8984 rect.right = column->Offset() + column->Width() 8985 + kTitleColumnRightExtraMargin - kRoomForLine / 2.0f; 8986 rect.bottom = fListElemHeight * CurrentPoseList()->CountItems(); 8987 } else 8988 rect.Set(LeftTop().x, LeftTop().y, LeftTop().x, LeftTop().y); 8989 } else { 8990 rect = fExtent; 8991 rect.left -= fOffset.x; 8992 rect.top -= fOffset.y; 8993 rect.right += fOffset.x; 8994 rect.bottom += fOffset.y; 8995 if (!rect.IsValid()) 8996 rect.Set(LeftTop().x, LeftTop().y, LeftTop().x, LeftTop().y); 8997 } 8998 8999 return rect; 9000 } 9001 9002 9003 void 9004 BPoseView::SetScrollBarsTo(BPoint point) 9005 { 9006 if (fHScrollBar && fVScrollBar) { 9007 fHScrollBar->SetValue(point.x); 9008 fVScrollBar->SetValue(point.y); 9009 } else { 9010 // TODO: I don't know what this was supposed to work around 9011 // (ie why it wasn't calling ScrollTo(point) simply). Although 9012 // it cannot have been tested, since it was broken before, I am 9013 // still leaving this, since I know there can be a subtle change in 9014 // behaviour (BView<->BScrollBar feedback effects) when scrolling 9015 // both directions at once versus separately. 9016 BPoint origin = LeftTop(); 9017 ScrollTo(BPoint(origin.x, point.y)); 9018 ScrollTo(point); 9019 } 9020 } 9021 9022 9023 void 9024 BPoseView::PinPointToValidRange(BPoint& origin) 9025 { 9026 // !NaN and valid range 9027 // the following checks are not broken even they look like they are 9028 if (!(origin.x >= 0) && !(origin.x <= 0)) 9029 origin.x = 0; 9030 else if (origin.x < -40000.0 || origin.x > 40000.0) 9031 origin.x = 0; 9032 9033 if (!(origin.y >= 0) && !(origin.y <= 0)) 9034 origin.y = 0; 9035 else if (origin.y < -40000.0 || origin.y > 40000.0) 9036 origin.y = 0; 9037 } 9038 9039 9040 void 9041 BPoseView::UpdateScrollRange() 9042 { 9043 // TODO: some calls to UpdateScrollRange don't do the right thing because 9044 // Extent doesn't return the right value (too early in PoseView lifetime??) 9045 // 9046 // This happened most with file panels, when opening a parent - added 9047 // an extra call to UpdateScrollRange in SelectChildInParent to work 9048 // around this 9049 9050 AutoLock<BWindow> lock(Window()); 9051 if (!lock) 9052 return; 9053 9054 BRect bounds(Bounds()); 9055 9056 BPoint origin(LeftTop()); 9057 BRect extent(Extent()); 9058 9059 lock.Unlock(); 9060 9061 BPoint minVal(std::min(extent.left, origin.x), 9062 std::min(extent.top, origin.y)); 9063 9064 BPoint maxVal((extent.right - bounds.right) + origin.x, 9065 (extent.bottom - bounds.bottom) + origin.y); 9066 9067 maxVal.x = std::max(maxVal.x, origin.x); 9068 maxVal.y = std::max(maxVal.y, origin.y); 9069 9070 if (fHScrollBar) { 9071 float scrollMin; 9072 float scrollMax; 9073 fHScrollBar->GetRange(&scrollMin, &scrollMax); 9074 if (minVal.x != scrollMin || maxVal.x != scrollMax) { 9075 fHScrollBar->SetRange(minVal.x, maxVal.x); 9076 fHScrollBar->SetSteps(fListElemHeight / 2.0f, bounds.Width()); 9077 } 9078 } 9079 9080 if (fVScrollBar) { 9081 float scrollMin; 9082 float scrollMax; 9083 fVScrollBar->GetRange(&scrollMin, &scrollMax); 9084 9085 if (minVal.y != scrollMin || maxVal.y != scrollMax) { 9086 fVScrollBar->SetRange(minVal.y, maxVal.y); 9087 fVScrollBar->SetSteps(fListElemHeight / 2.0f, bounds.Height()); 9088 } 9089 } 9090 9091 // set proportions for bars 9092 BRect totalExtent(extent | bounds); 9093 9094 if (fHScrollBar && totalExtent.Width() != 0.0) { 9095 float proportion = bounds.Width() / totalExtent.Width(); 9096 if (fHScrollBar->Proportion() != proportion) 9097 fHScrollBar->SetProportion(proportion); 9098 } 9099 9100 if (fVScrollBar && totalExtent.Height() != 0.0) { 9101 float proportion = bounds.Height() / totalExtent.Height(); 9102 if (fVScrollBar->Proportion() != proportion) 9103 fVScrollBar->SetProportion(proportion); 9104 } 9105 } 9106 9107 9108 void 9109 BPoseView::DrawPose(BPose* pose, int32 index, bool fullDraw) 9110 { 9111 BRect rect = CalcPoseRect(pose, index, fullDraw); 9112 9113 if (TrackerSettings().ShowVolumeSpaceBar() 9114 && pose->TargetModel()->IsVolume()) { 9115 Invalidate(rect); 9116 } else 9117 pose->Draw(rect, rect, this, fullDraw); 9118 } 9119 9120 9121 rgb_color 9122 BPoseView::TextColor(bool selected) const 9123 { 9124 if (IsDesktopWindow()) 9125 return DeskTextColor(); 9126 9127 if (selected) 9128 return ui_color(B_DOCUMENT_BACKGROUND_COLOR); 9129 else 9130 return ui_color(B_DOCUMENT_TEXT_COLOR); 9131 } 9132 9133 9134 rgb_color 9135 BPoseView::BackColor(bool selected) const 9136 { 9137 if (selected) { 9138 if (IsDesktopWindow()) 9139 return DeskTextBackColor(); 9140 9141 return InvertedBackColor(); 9142 } else { 9143 if (IsDesktopWindow()) 9144 return BView::ViewColor(); 9145 9146 rgb_color background = ui_color(B_DOCUMENT_BACKGROUND_COLOR); 9147 return tint_color(background, 9148 TargetVolumeIsReadOnly() ? ReadOnlyTint(background) : B_NO_TINT); 9149 } 9150 } 9151 9152 9153 rgb_color 9154 BPoseView::InvertedBackColor() const 9155 { 9156 rgb_color background = ui_color(B_DOCUMENT_BACKGROUND_COLOR); 9157 rgb_color inverted = invert_color(background); 9158 int textBrightness = BPrivate::perceptual_brightness(inverted); 9159 int viewBrightness = BPrivate::perceptual_brightness(background); 9160 9161 if (abs(viewBrightness - textBrightness) > 127) { 9162 // The colors are different enough, we can use inverted 9163 return inverted; 9164 } else { 9165 // use black or white 9166 return (viewBrightness > 127 ? kBlack : kWhite); 9167 } 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