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