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