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