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