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