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