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