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