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