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