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