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