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