1 /* 2 Open Tracker License 3 4 Terms and Conditions 5 6 Copyright (c) 1991-2000, Be Incorporated. All rights reserved. 7 8 Permission is hereby granted, free of charge, to any person obtaining a copy of 9 this software and associated documentation files (the "Software"), to deal in 10 the Software without restriction, including without limitation the rights to 11 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 12 of the Software, and to permit persons to whom the Software is furnished to do 13 so, subject to the following conditions: 14 15 The above copyright notice and this permission notice applies to all licensees 16 and shall be included in all copies or substantial portions of the Software. 17 18 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY, 20 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 21 BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN 22 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION 23 WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 25 Except as contained in this notice, the name of Be Incorporated shall not be 26 used in advertising or otherwise to promote the sale, use or other dealings in 27 this Software without prior written authorization from Be Incorporated. 28 29 Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks 30 of Be Incorporated in the United States and other countries. Other brand product 31 names are registered trademarks or trademarks of their respective holders. 32 All rights reserved. 33 */ 34 35 36 #include "PoseView.h" 37 38 #include <algorithm> 39 #include <functional> 40 #include <map> 41 42 #include <ctype.h> 43 #include <errno.h> 44 #include <float.h> 45 #include <stdlib.h> 46 #include <strings.h> 47 48 #include <compat/sys/stat.h> 49 50 #include <Alert.h> 51 #include <Application.h> 52 #include <Catalog.h> 53 #include <Clipboard.h> 54 #include <Debug.h> 55 #include <Dragger.h> 56 #include <fs_attr.h> 57 #include <fs_info.h> 58 #include <Screen.h> 59 #include <Query.h> 60 #include <List.h> 61 #include <Locale.h> 62 #include <LongAndDragTrackingFilter.h> 63 #include <MenuItem.h> 64 #include <NodeMonitor.h> 65 #include <Path.h> 66 #include <StopWatch.h> 67 #include <String.h> 68 #include <SymLink.h> 69 #include <TextView.h> 70 #include <VolumeRoster.h> 71 #include <Volume.h> 72 #include <Window.h> 73 74 #include <ObjectListPrivate.h> 75 #include <PathMonitor.h> 76 77 #include "Attributes.h" 78 #include "AutoLock.h" 79 #include "BackgroundImage.h" 80 #include "Bitmaps.h" 81 #include "Commands.h" 82 #include "CountView.h" 83 #include "Cursors.h" 84 #include "DeskWindow.h" 85 #include "DesktopPoseView.h" 86 #include "FilePanelPriv.h" 87 #include "FSClipboard.h" 88 #include "FSUtils.h" 89 #include "FunctionObject.h" 90 #include "MimeTypes.h" 91 #include "Navigator.h" 92 #include "Pose.h" 93 #include "InfoWindow.h" 94 #include "Tests.h" 95 #include "Thread.h" 96 #include "Tracker.h" 97 #include "TrackerString.h" 98 #include "WidgetAttributeText.h" 99 #include "WidthBuffer.h" 100 101 102 #undef B_TRANSLATION_CONTEXT 103 #define B_TRANSLATION_CONTEXT "PoseView" 104 105 106 const float kDoubleClickTresh = 6; 107 108 const uint32 kAddNewPoses = 'Tanp'; 109 const uint32 kAddPosesCompleted = 'Tapc'; 110 const int32 kMaxAddPosesChunk = 50; 111 const uint32 kMsgMouseDragged = 'Mdrg'; 112 const uint32 kMsgMouseLongDown = 'Mold'; 113 114 const int32 kRoomForLine = 2; 115 116 const int32 kMenuTrackMargin = 20; 117 118 namespace BPrivate { 119 extern bool delete_point(void*); 120 // TODO: exterminate this 121 } 122 123 const float kSlowScrollBucket = 30; 124 const float kBorderHeight = 20; 125 126 enum { 127 kAutoScrollOff, 128 kWaitForTransition, 129 kDelayAutoScroll, 130 kAutoScrollOn 131 }; 132 133 enum { 134 kWasDragged, 135 kContextMenuShown, 136 kNotDragged 137 }; 138 139 enum { 140 kInsertAtFront, 141 kInsertAfter 142 }; 143 144 const BPoint kTransparentDragThreshold(256, 192); 145 // maximum size of the transparent drag bitmap, use a drag rect 146 // if larger in any direction 147 148 struct attr_column_relation { 149 uint32 attrHash; 150 int32 fieldMask; 151 }; 152 153 static struct attr_column_relation sAttrColumnMap[] = { 154 { AttrHashString(kAttrStatModified, B_TIME_TYPE), 155 B_STAT_MODIFICATION_TIME }, 156 { AttrHashString(kAttrStatSize, B_OFF_T_TYPE), 157 B_STAT_SIZE }, 158 { AttrHashString(kAttrStatCreated, B_TIME_TYPE), 159 B_STAT_CREATION_TIME }, 160 { AttrHashString(kAttrStatMode, B_STRING_TYPE), 161 B_STAT_MODE } 162 }; 163 164 struct AddPosesResult { 165 ~AddPosesResult(); 166 void ReleaseModels(); 167 168 Model* fModels[kMaxAddPosesChunk]; 169 PoseInfo fPoseInfos[kMaxAddPosesChunk]; 170 int32 fCount; 171 }; 172 173 174 AddPosesResult::~AddPosesResult(void) 175 { 176 for (int32 i = 0; i < fCount; i++) 177 delete fModels[i]; 178 } 179 180 181 void 182 AddPosesResult::ReleaseModels(void) 183 { 184 for (int32 i = 0; i < kMaxAddPosesChunk; i++) 185 fModels[i] = NULL; 186 } 187 188 189 static BPose* 190 BSearch(PoseList* table, const BPose* key, BPoseView* view, 191 int (*cmp)(const BPose*, const BPose*, BPoseView*), 192 bool returnClosest = true); 193 194 static int 195 PoseCompareAddWidget(const BPose* p1, const BPose* p2, BPoseView* view); 196 197 198 static bool 199 OneMatches(BPose* pose, BPoseView*, void* castToPose) 200 { 201 return pose == (const BPose*)castToPose; 202 } 203 204 205 static void 206 CopySelectionListToEntryRefList(const PoseList* original, 207 BObjectList<entry_ref>* copy) 208 { 209 int32 count = original->CountItems(); 210 for (int32 index = 0; index < count; index++) { 211 copy->AddItem(new entry_ref(*(original->ItemAt( 212 index)->TargetModel()->EntryRef()))); 213 } 214 } 215 216 217 // #pragma mark - BPoseView 218 219 220 BPoseView::BPoseView(Model* model, uint32 viewMode) 221 : 222 BView("BPoseView", B_WILL_DRAW | B_PULSE_NEEDED), 223 fIsDrawingSelectionRect(false), 224 fHScrollBar(NULL), 225 fVScrollBar(NULL), 226 fModel(model), 227 fActivePose(NULL), 228 fExtent(INT32_MAX, INT32_MAX, INT32_MIN, INT32_MIN), 229 fPoseList(new PoseList(40, true)), 230 fFilteredPoseList(new PoseList()), 231 fVSPoseList(new PoseList()), 232 fSelectionList(new PoseList()), 233 fMimeTypesInSelectionCache(20, true), 234 fZombieList(new BObjectList<Model>(10, true)), 235 fColumnList(new BObjectList<BColumn>(4, true)), 236 fMimeTypeList(new BObjectList<BString>(10, true)), 237 fBrokenLinks(new BObjectList<Model>(10, false)), 238 fMimeTypeListIsDirty(false), 239 fViewState(new BViewState), 240 fStateNeedsSaving(false), 241 fCountView(NULL), 242 fListElemHeight(0.0f), 243 fIconPoseHeight(0.0f), 244 fDropTarget(NULL), 245 fAlreadySelectedDropTarget(NULL), 246 fSelectionHandler(be_app), 247 fLastClickPoint(INT32_MAX, INT32_MAX), 248 fLastClickButtons(0), 249 fLastClickedPose(NULL), 250 fLastExtent(INT32_MAX, INT32_MAX, INT32_MIN, INT32_MIN), 251 fTitleView(NULL), 252 fRefFilter(NULL), 253 fAutoScrollInc(20), 254 fAutoScrollState(kAutoScrollOff), 255 fWidgetTextOutline(false), 256 fSelectionPivotPose(NULL), 257 fRealPivotPose(NULL), 258 fKeyRunner(NULL), 259 fTrackRightMouseUp(false), 260 fSelectionVisible(true), 261 fMultipleSelection(true), 262 fDragEnabled(true), 263 fDropEnabled(true), 264 fSelectionRectEnabled(true), 265 fAlwaysAutoPlace(false), 266 fAllowPoseEditing(true), 267 fSelectionChangedHook(false), 268 fSavePoseLocations(true), 269 fShowHideSelection(true), 270 fOkToMapIcons(false), 271 fEnsurePosesVisible(false), 272 fShouldAutoScroll(true), 273 fIsDesktopWindow(false), 274 fIsWatchingDateFormatChange(false), 275 fHasPosesInClipboard(false), 276 fCursorCheck(false), 277 fFiltering(false), 278 fFilterStrings(4, true), 279 fLastFilterStringCount(1), 280 fLastFilterStringLength(0), 281 fLastKeyTime(0), 282 fLastDeskbarFrameCheckTime(LONGLONG_MIN), 283 fDeskbarFrame(0, 0, -1, -1), 284 fTextWidgetToCheck(NULL) 285 { 286 fViewState->SetViewMode(viewMode); 287 fShowSelectionWhenInactive 288 = TrackerSettings().ShowSelectionWhenInactive(); 289 fTransparentSelection = TrackerSettings().TransparentSelection(); 290 fFilterStrings.AddItem(new BString("")); 291 } 292 293 294 BPoseView::~BPoseView() 295 { 296 delete fPoseList; 297 delete fFilteredPoseList; 298 delete fVSPoseList; 299 delete fColumnList; 300 delete fSelectionList; 301 delete fMimeTypeList; 302 delete fZombieList; 303 delete fViewState; 304 delete fModel; 305 delete fKeyRunner; 306 delete fBrokenLinks; 307 308 IconCache::sIconCache->Deleting(this); 309 } 310 311 312 void 313 BPoseView::Init(AttributeStreamNode* node) 314 { 315 RestoreState(node); 316 InitCommon(); 317 } 318 319 320 void 321 BPoseView::Init(const BMessage &message) 322 { 323 RestoreState(message); 324 InitCommon(); 325 } 326 327 328 void 329 BPoseView::InitCommon() 330 { 331 BContainerWindow* window = ContainerWindow(); 332 333 // Create the TitleView and CountView 334 fTitleView = new BTitleView(this); 335 if (fHScrollBar != NULL) 336 fHScrollBar->SetTitleView(fTitleView); 337 338 fCountView = new BCountView(this); 339 340 BPoint origin; 341 if (ViewMode() == kListMode) 342 origin = fViewState->ListOrigin(); 343 else 344 origin = fViewState->IconOrigin(); 345 346 PinPointToValidRange(origin); 347 348 // init things related to laying out items 349 fListElemHeight = ceilf(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); 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 (fFiltering) { 3431 if (fFilteredPoseList->FindPose(&destNodeRef, &index) == NULL) { 3432 float scrollBy = 0; 3433 BRect bounds = Bounds(); 3434 AddPoseToList(fFilteredPoseList, true, true, pose, bounds, scrollBy, 3435 true, &index); 3436 } 3437 } 3438 3439 if (pose != NULL) { 3440 WatchNewNode(pose->TargetModel()->NodeRef()); 3441 UpdateScrollRange(); 3442 CommitActivePose(); 3443 SelectPose(pose, index); 3444 pose->EditFirstWidget(BPoint(0, index * fListElemHeight), this); 3445 } 3446 } 3447 3448 3449 void 3450 BPoseView::NewFolder(const BMessage* message) 3451 { 3452 Model* targetModel = TargetModel(); 3453 ThrowOnAssert(targetModel != NULL); 3454 3455 entry_ref ref; 3456 node_ref nodeRef; 3457 3458 if (FSCreateNewFolderIn(targetModel->NodeRef(), &ref, &nodeRef) == B_OK) { 3459 // try to place new folder at click point or under mouse if possible 3460 3461 PlaceFolder(&ref, message); 3462 3463 int32 index; 3464 BPose* pose = EntryCreated(targetModel->NodeRef(), &nodeRef, ref.name, 3465 &index); 3466 3467 if (fFiltering) { 3468 if (fFilteredPoseList->FindPose(&nodeRef, &index) == NULL) { 3469 float scrollBy = 0; 3470 BRect bounds = Bounds(); 3471 AddPoseToList(fFilteredPoseList, true, true, pose, bounds, 3472 scrollBy, true, &index); 3473 } 3474 } 3475 3476 if (pose != NULL) { 3477 UpdateScrollRange(); 3478 CommitActivePose(); 3479 SelectPose(pose, index); 3480 pose->EditFirstWidget(BPoint(0, index * fListElemHeight), this); 3481 } 3482 } 3483 } 3484 3485 3486 void 3487 BPoseView::Cleanup(bool doAll) 3488 { 3489 if (ViewMode() == kListMode) 3490 return; 3491 3492 BContainerWindow* window = ContainerWindow(); 3493 if (window == NULL) 3494 return; 3495 3496 // replace all icons from the top 3497 if (doAll) { 3498 // sort by sort field 3499 SortPoses(); 3500 3501 DisableScrollBars(); 3502 ClearExtent(); 3503 ClearSelection(); 3504 ScrollTo(B_ORIGIN); 3505 UpdateScrollRange(); 3506 SetScrollBarsTo(B_ORIGIN); 3507 ResetPosePlacementHint(); 3508 3509 BRect viewBounds(Bounds()); 3510 3511 // relocate all poses in list (reset vs list) 3512 fVSPoseList->MakeEmpty(); 3513 int32 count = fPoseList->CountItems(); 3514 for (int32 index = 0; index < count; index++) { 3515 BPose* pose = fPoseList->ItemAt(index); 3516 PlacePose(pose, viewBounds); 3517 AddToVSList(pose); 3518 } 3519 3520 RecalcExtent(); 3521 3522 // scroll icons into view so that leftmost icon is "fOffset" from left 3523 UpdateScrollRange(); 3524 EnableScrollBars(); 3525 3526 if (HScrollBar()) { 3527 float min; 3528 float max; 3529 HScrollBar()->GetRange(&min, &max); 3530 HScrollBar()->SetValue(min); 3531 } 3532 3533 UpdateScrollRange(); 3534 Invalidate(viewBounds); 3535 } else { 3536 // clean up items to nearest locations 3537 BRect viewBounds(Bounds()); 3538 int32 count = fPoseList->CountItems(); 3539 for (int32 index = 0; index < count; index++) { 3540 BPose* pose = fPoseList->ItemAt(index); 3541 BPoint location(pose->Location(this)); 3542 BPoint newLocation(PinToGrid(location, fGrid, fOffset)); 3543 3544 bool intersectsDesktopElements = !IsValidLocation(pose); 3545 3546 // do we need to move pose to a grid location? 3547 if (newLocation != location || intersectsDesktopElements) { 3548 // remove pose from VSlist so it doesn't "bump" into itself 3549 RemoveFromVSList(pose); 3550 3551 // try new grid location 3552 BRect oldBounds(pose->CalcRect(this)); 3553 BRect poseBounds(oldBounds); 3554 pose->MoveTo(newLocation, this); 3555 if (SlotOccupied(oldBounds, viewBounds) 3556 || intersectsDesktopElements) { 3557 ResetPosePlacementHint(); 3558 PlacePose(pose, viewBounds); 3559 poseBounds = pose->CalcRect(this); 3560 } 3561 3562 AddToVSList(pose); 3563 AddToExtent(poseBounds); 3564 3565 if (viewBounds.Intersects(poseBounds)) 3566 Invalidate(poseBounds); 3567 if (viewBounds.Intersects(oldBounds)) 3568 Invalidate(oldBounds); 3569 } 3570 } 3571 } 3572 } 3573 3574 3575 void 3576 BPoseView::PlacePose(BPose* pose, BRect &viewBounds) 3577 { 3578 // move pose to probable location 3579 pose->SetLocation(fHintLocation, this); 3580 BRect rect(pose->CalcRect(this)); 3581 BPoint deltaFromBounds(fHintLocation - rect.LeftTop()); 3582 3583 // make pose rect a little bigger to ensure space between poses 3584 rect.InsetBy(-3, 0); 3585 3586 bool checkValidLocation = IsDesktopWindow(); 3587 3588 // find an empty slot to put pose into 3589 while (SlotOccupied(rect, viewBounds) 3590 // check good location on the desktop 3591 || (checkValidLocation && !IsValidLocation(rect))) { 3592 NextSlot(pose, rect, viewBounds); 3593 // we've scanned the entire desktop without finding an available 3594 // position, give up and simply place it towards the top left. 3595 if (checkValidLocation && !rect.Intersects(viewBounds)) { 3596 fHintLocation = PinToGrid(BPoint(0.0, 0.0), fGrid, fOffset); 3597 pose->SetLocation(fHintLocation, this); 3598 rect = pose->CalcRect(this); 3599 break; 3600 } 3601 } 3602 3603 rect.InsetBy(3, 0); 3604 3605 fHintLocation = pose->Location(this) + BPoint(fGrid.x, 0); 3606 3607 pose->SetLocation(rect.LeftTop() + deltaFromBounds, this); 3608 pose->SetSaveLocation(); 3609 } 3610 3611 3612 bool 3613 BPoseView::IsValidLocation(const BPose* pose) 3614 { 3615 if (!IsDesktopWindow()) 3616 return true; 3617 3618 BRect rect(pose->CalcRect(this)); 3619 rect.InsetBy(-3, 0); 3620 return IsValidLocation(rect); 3621 } 3622 3623 3624 bool 3625 BPoseView::IsValidLocation(const BRect& rect) 3626 { 3627 if (!IsDesktopWindow()) 3628 return true; 3629 3630 // on the desktop, don't allow icons outside of the view bounds 3631 if (!Bounds().Contains(rect)) 3632 return false; 3633 3634 // also check the deskbar frame 3635 BRect deskbarFrame; 3636 if (GetDeskbarFrame(&deskbarFrame) == B_OK) { 3637 deskbarFrame.InsetBy(-10, -10); 3638 if (deskbarFrame.Intersects(rect)) 3639 return false; 3640 } 3641 3642 // check replicants 3643 for (int32 i = 0; BView* child = ChildAt(i); i++) { 3644 BRect childFrame = child->Frame(); 3645 childFrame.InsetBy(-5, -5); 3646 if (childFrame.Intersects(rect)) 3647 return false; 3648 } 3649 3650 // location is ok 3651 return true; 3652 } 3653 3654 3655 status_t 3656 BPoseView::GetDeskbarFrame(BRect* frame) 3657 { 3658 // only really check the Deskbar frame every half a second, 3659 // use a cached value otherwise 3660 status_t result = B_OK; 3661 bigtime_t now = system_time(); 3662 if (fLastDeskbarFrameCheckTime + 500000 < now) { 3663 // it's time to check the Deskbar frame again 3664 result = get_deskbar_frame(&fDeskbarFrame); 3665 fLastDeskbarFrameCheckTime = now; 3666 } 3667 *frame = fDeskbarFrame; 3668 3669 return result; 3670 } 3671 3672 3673 void 3674 BPoseView::CheckAutoPlacedPoses() 3675 { 3676 if (ViewMode() == kListMode) 3677 return; 3678 3679 BRect viewBounds(Bounds()); 3680 3681 int32 count = fPoseList->CountItems(); 3682 for (int32 index = 0; index < count; index++) { 3683 BPose* pose = fPoseList->ItemAt(index); 3684 if (pose->WasAutoPlaced()) { 3685 RemoveFromVSList(pose); 3686 fHintLocation = pose->Location(this); 3687 BRect oldBounds(pose->CalcRect(this)); 3688 PlacePose(pose, viewBounds); 3689 3690 BRect newBounds(pose->CalcRect(this)); 3691 AddToVSList(pose); 3692 pose->SetAutoPlaced(false); 3693 AddToExtent(newBounds); 3694 3695 Invalidate(oldBounds); 3696 Invalidate(newBounds); 3697 } 3698 } 3699 } 3700 3701 3702 void 3703 BPoseView::CheckPoseVisibility(BRect* newFrame) 3704 { 3705 bool desktop = IsDesktopWindow() && newFrame != 0; 3706 3707 BRect deskFrame; 3708 if (desktop) { 3709 ASSERT(newFrame); 3710 deskFrame = *newFrame; 3711 } 3712 3713 ASSERT(ViewMode() != kListMode); 3714 3715 BRect bounds(Bounds()); 3716 bounds.InsetBy(20, 20); 3717 3718 int32 count = fPoseList->CountItems(); 3719 for (int32 index = 0; index < count; index++) { 3720 BPose* pose = fPoseList->ItemAt(index); 3721 BPoint newLocation(pose->Location(this)); 3722 bool locationNeedsUpdating = false; 3723 3724 if (desktop) { 3725 // we just switched screen resolution, pick up the right 3726 // icon locations for the new resolution 3727 Model* model = pose->TargetModel(); 3728 ExtendedPoseInfo* info = ReadExtendedPoseInfo(model); 3729 if (info && info->HasLocationForFrame(deskFrame)) { 3730 BPoint locationForFrame = info->LocationForFrame(deskFrame); 3731 if (locationForFrame != newLocation) { 3732 // found one and it is different from the current 3733 newLocation = locationForFrame; 3734 locationNeedsUpdating = true; 3735 Invalidate(pose->CalcRect(this)); 3736 // make sure the old icon gets erased 3737 RemoveFromVSList(pose); 3738 pose->SetLocation(newLocation, this); 3739 // set the new location 3740 } 3741 } 3742 delete[] (char*)info; 3743 // TODO: fix up this mess 3744 } 3745 3746 BRect rect(pose->CalcRect(this)); 3747 if (!rect.Intersects(bounds)) { 3748 // pose doesn't fit on screen 3749 if (!locationNeedsUpdating) { 3750 // didn't already invalidate and remove in the desktop case 3751 Invalidate(rect); 3752 RemoveFromVSList(pose); 3753 } 3754 BPoint loc(pose->Location(this)); 3755 loc.ConstrainTo(bounds); 3756 // place it onscreen 3757 3758 pose->SetLocation(loc, this); 3759 // set the new location 3760 locationNeedsUpdating = true; 3761 } 3762 3763 if (locationNeedsUpdating) { 3764 // pose got reposition by one or both of the above 3765 pose->SetSaveLocation(); 3766 AddToVSList(pose); 3767 // add it at the new location 3768 Invalidate(pose->CalcRect(this)); 3769 // make sure the new pose location updates properly 3770 } 3771 } 3772 } 3773 3774 3775 bool 3776 BPoseView::SlotOccupied(BRect poseRect, BRect viewBounds) const 3777 { 3778 if (fVSPoseList->IsEmpty()) 3779 return false; 3780 3781 // ## be sure to keep this code in sync with calls to NextSlot 3782 // ## in terms of the comparison of fHintLocation and PinToGrid 3783 if (poseRect.right >= viewBounds.right) { 3784 BPoint point(viewBounds.left + fOffset.x, 0); 3785 point = PinToGrid(point, fGrid, fOffset); 3786 if (fHintLocation.x != point.x) 3787 return true; 3788 } 3789 3790 // search only nearby poses (vertically) 3791 int32 index = FirstIndexAtOrBelow((int32)(poseRect.top - IconPoseHeight())); 3792 int32 numPoses = fVSPoseList->CountItems(); 3793 3794 while (index < numPoses && fVSPoseList->ItemAt(index)->Location(this).y 3795 < poseRect.bottom) { 3796 BRect rect(fVSPoseList->ItemAt(index)->CalcRect(this)); 3797 if (poseRect.Intersects(rect)) 3798 return true; 3799 3800 index++; 3801 } 3802 3803 return false; 3804 } 3805 3806 3807 void 3808 BPoseView::NextSlot(BPose* pose, BRect &poseRect, BRect viewBounds) 3809 { 3810 // move to next slot 3811 poseRect.OffsetBy(fGrid.x, 0); 3812 3813 // if we reached the end of row go down to next row 3814 if (poseRect.right > viewBounds.right) { 3815 fHintLocation.y += fGrid.y; 3816 fHintLocation.x = viewBounds.left + fOffset.x; 3817 fHintLocation = PinToGrid(fHintLocation, fGrid, fOffset); 3818 pose->SetLocation(fHintLocation, this); 3819 poseRect = pose->CalcRect(this); 3820 poseRect.InsetBy(-3, 0); 3821 } 3822 } 3823 3824 3825 int32 3826 BPoseView::FirstIndexAtOrBelow(int32 y, bool constrainIndex) const 3827 { 3828 // This method performs a binary search on the vertically sorted pose list 3829 // and returns either the index of the first pose at a given y location or 3830 // the proper index to insert a new pose into the list. 3831 3832 int32 index = 0; 3833 int32 l = 0; 3834 int32 r = fVSPoseList->CountItems() - 1; 3835 3836 while (l <= r) { 3837 index = (l + r) >> 1; 3838 int32 result 3839 = (int32)(y - fVSPoseList->ItemAt(index)->Location(this).y); 3840 3841 if (result < 0) 3842 r = index - 1; 3843 else if (result > 0) 3844 l = index + 1; 3845 else { 3846 // compare turned out equal, find first pose 3847 while (index > 0 3848 && y == fVSPoseList->ItemAt(index - 1)->Location(this).y) 3849 index--; 3850 return index; 3851 } 3852 } 3853 3854 // didn't find pose AT location y - bump index to proper insert point 3855 while (index < fVSPoseList->CountItems() 3856 && fVSPoseList->ItemAt(index)->Location(this).y <= y) { 3857 index++; 3858 } 3859 3860 // if flag is true then constrain index to legal value since this 3861 // method returns the proper insertion point which could be outside 3862 // the current bounds of the list 3863 if (constrainIndex && index >= fVSPoseList->CountItems()) 3864 index = fVSPoseList->CountItems() - 1; 3865 3866 return index; 3867 } 3868 3869 3870 void 3871 BPoseView::AddToVSList(BPose* pose) 3872 { 3873 int32 index = FirstIndexAtOrBelow((int32)pose->Location(this).y, false); 3874 fVSPoseList->AddItem(pose, index); 3875 } 3876 3877 3878 int32 3879 BPoseView::RemoveFromVSList(const BPose* pose) 3880 { 3881 //int32 index = FirstIndexAtOrBelow((int32)pose->Location(this).y); 3882 // This optimisation is buggy and the index returned can be greater 3883 // than the actual index of the pose we search, thus missing it 3884 // and failing to remove it. This having severe implications 3885 // everywhere in the code as it is asserted that it must be always 3886 // in sync with fPoseList. See ticket #4322. 3887 int32 index = 0; 3888 3889 int32 count = fVSPoseList->CountItems(); 3890 for (; index < count; index++) { 3891 BPose* matchingPose = fVSPoseList->ItemAt(index); 3892 ASSERT(matchingPose); 3893 if (!matchingPose) 3894 return -1; 3895 3896 if (pose == matchingPose) { 3897 fVSPoseList->RemoveItemAt(index); 3898 return index; 3899 } 3900 } 3901 3902 return -1; 3903 } 3904 3905 3906 BPoint 3907 BPoseView::PinToGrid(BPoint point, BPoint grid, BPoint offset) const 3908 { 3909 if (grid.x == 0 || grid.y == 0) 3910 return point; 3911 3912 point -= offset; 3913 BPoint gridLoc(point); 3914 3915 if (point.x >= 0) 3916 gridLoc.x = floorf((point.x / grid.x) + 0.5f) * grid.x; 3917 else 3918 gridLoc.x = ceilf((point.x / grid.x) - 0.5f) * grid.x; 3919 3920 if (point.y >= 0) 3921 gridLoc.y = floorf((point.y / grid.y) + 0.5f) * grid.y; 3922 else 3923 gridLoc.y = ceilf((point.y / grid.y) - 0.5f) * grid.y; 3924 3925 gridLoc += offset; 3926 3927 return gridLoc; 3928 } 3929 3930 3931 void 3932 BPoseView::ResetPosePlacementHint() 3933 { 3934 fHintLocation = PinToGrid(BPoint(LeftTop().x + fOffset.x, 3935 LeftTop().y + fOffset.y), fGrid, fOffset); 3936 } 3937 3938 3939 void 3940 BPoseView::SelectPoses(int32 start, int32 end) 3941 { 3942 // clear selection list 3943 fSelectionList->MakeEmpty(); 3944 fMimeTypesInSelectionCache.MakeEmpty(); 3945 fSelectionPivotPose = NULL; 3946 fRealPivotPose = NULL; 3947 3948 bool iconMode = ViewMode() != kListMode; 3949 BPoint loc(0, start * fListElemHeight); 3950 BRect bounds(Bounds()); 3951 3952 PoseList* poseList = CurrentPoseList(); 3953 int32 count = poseList->CountItems(); 3954 for (int32 index = start; index < end && index < count; index++) { 3955 BPose* pose = poseList->ItemAt(index); 3956 fSelectionList->AddItem(pose); 3957 if (index == start) 3958 fSelectionPivotPose = pose; 3959 if (!pose->IsSelected()) { 3960 pose->Select(true); 3961 BRect poseRect; 3962 if (iconMode) 3963 poseRect = pose->CalcRect(this); 3964 else 3965 poseRect = pose->CalcRect(loc, this, false); 3966 3967 if (bounds.Intersects(poseRect)) { 3968 Invalidate(poseRect); 3969 } 3970 } 3971 3972 loc.y += fListElemHeight; 3973 } 3974 } 3975 3976 3977 void 3978 BPoseView::MoveOrChangePoseSelection(int32 to) 3979 { 3980 PoseList* poseList = CurrentPoseList(); 3981 BPose* first = fSelectionList->FirstItem(); 3982 3983 if (first != NULL && fMultipleSelection 3984 && (modifiers() & B_SHIFT_KEY) != 0) { 3985 // Extend selection 3986 BPose* target = poseList->ItemAt(to); 3987 BPose* last = fSelectionList->LastItem(); 3988 int32 firstIndex = poseList->IndexOf(first); 3989 int32 lastIndex = poseList->IndexOf(last); 3990 3991 int32 from = to < firstIndex ? firstIndex : lastIndex; 3992 int32 step = from < to ? 1 : -1; 3993 3994 // TODO: shrink selection depending on anchor 3995 bool select = true; 3996 3997 for (int32 index = from; step > 0 ? index <= to : index >= to; 3998 index += step) { 3999 BPose* pose = poseList->ItemAt(index); 4000 if (pose != NULL && pose->IsSelected() != select) 4001 AddRemovePoseFromSelection(pose, index, select); 4002 } 4003 if (target != NULL) 4004 ScrollIntoView(target, to); 4005 } else { 4006 SelectPose(poseList->ItemAt(to), to); 4007 } 4008 } 4009 4010 4011 void 4012 BPoseView::ScrollIntoView(BPose* pose, int32 index) 4013 { 4014 ScrollIntoView(CalcPoseRect(pose, index, true)); 4015 } 4016 4017 4018 void 4019 BPoseView::ScrollIntoView(BRect poseRect) 4020 { 4021 if (IsDesktopWindow()) 4022 return; 4023 4024 if (ViewMode() == kListMode) { 4025 // if we're in list view then we only care that the entire 4026 // pose is visible vertically, not horizontally 4027 poseRect.left = 0; 4028 poseRect.right = poseRect.left + 1; 4029 } 4030 4031 if (!Bounds().Contains(poseRect)) 4032 SetScrollBarsTo(poseRect.LeftTop()); 4033 } 4034 4035 4036 void 4037 BPoseView::SelectPose(BPose* pose, int32 index, bool scrollIntoView) 4038 { 4039 if (pose == NULL || fSelectionList->CountItems() > 1 || !pose->IsSelected()) 4040 ClearSelection(); 4041 4042 AddPoseToSelection(pose, index, scrollIntoView); 4043 } 4044 4045 4046 void 4047 BPoseView::AddPoseToSelection(BPose* pose, int32 index, bool scrollIntoView) 4048 { 4049 // TODO: need to check if pose is member of selection list 4050 if (pose != NULL && !pose->IsSelected()) { 4051 pose->Select(true); 4052 fSelectionList->AddItem(pose); 4053 4054 BRect poseRect = CalcPoseRect(pose, index); 4055 Invalidate(poseRect); 4056 4057 if (scrollIntoView) 4058 ScrollIntoView(poseRect); 4059 4060 if (fSelectionChangedHook) 4061 ContainerWindow()->SelectionChanged(); 4062 } 4063 } 4064 4065 4066 void 4067 BPoseView::RemovePoseFromSelection(BPose* pose) 4068 { 4069 if (fSelectionPivotPose == pose) 4070 fSelectionPivotPose = NULL; 4071 4072 if (fRealPivotPose == pose) 4073 fRealPivotPose = NULL; 4074 4075 if (!fSelectionList->RemoveItem(pose)) { 4076 // wasn't selected to begin with 4077 return; 4078 } 4079 4080 pose->Select(false); 4081 if (ViewMode() == kListMode) { 4082 // TODO: need a simple call to CalcRect that works both in listView and 4083 // icon view modes without the need for an index/pos 4084 PoseList* poseList = CurrentPoseList(); 4085 int32 count = poseList->CountItems(); 4086 BPoint loc(0, 0); 4087 for (int32 index = 0; index < count; index++) { 4088 if (pose == poseList->ItemAt(index)) { 4089 Invalidate(pose->CalcRect(loc, this)); 4090 break; 4091 } 4092 loc.y += fListElemHeight; 4093 } 4094 } else 4095 Invalidate(pose->CalcRect(this)); 4096 4097 if (fSelectionChangedHook) 4098 ContainerWindow()->SelectionChanged(); 4099 } 4100 4101 4102 bool 4103 BPoseView::EachItemInDraggedSelection(const BMessage* message, 4104 bool (*func)(BPose*, BPoseView*, void*), BPoseView* poseView, 4105 void* passThru) 4106 { 4107 BContainerWindow* srcWindow; 4108 if (message->FindPointer("src_window", (void**)&srcWindow) != B_OK) 4109 return false; 4110 4111 AutoLock<BWindow> lock(srcWindow); 4112 if (!lock) 4113 return false; 4114 4115 PoseList* selectionList = srcWindow->PoseView()->SelectionList(); 4116 int32 count = selectionList->CountItems(); 4117 4118 for (int32 index = 0; index < count; index++) { 4119 BPose* pose = selectionList->ItemAt(index); 4120 if (func(pose, poseView, passThru)) 4121 // early iteration termination 4122 return true; 4123 } 4124 4125 return false; 4126 } 4127 4128 4129 static bool 4130 ContainsOne(BString* string, const char* matchString) 4131 { 4132 return strcmp(string->String(), matchString) == 0; 4133 } 4134 4135 4136 bool 4137 BPoseView::FindDragNDropAction(const BMessage* dragMessage, bool &canCopy, 4138 bool &canMove, bool &canLink, bool &canErase) 4139 { 4140 canCopy = false; 4141 canMove = false; 4142 canErase = false; 4143 canLink = false; 4144 if (!dragMessage->HasInt32("be:actions")) 4145 return false; 4146 4147 int32 action; 4148 for (int32 index = 0; 4149 dragMessage->FindInt32("be:actions", index, &action) == B_OK; 4150 index++) { 4151 switch (action) { 4152 case B_MOVE_TARGET: 4153 canMove = true; 4154 break; 4155 4156 case B_COPY_TARGET: 4157 canCopy = true; 4158 break; 4159 4160 case B_TRASH_TARGET: 4161 canErase = true; 4162 break; 4163 4164 case B_LINK_TARGET: 4165 canLink = true; 4166 break; 4167 } 4168 } 4169 4170 return canCopy || canMove || canErase || canLink; 4171 } 4172 4173 4174 bool 4175 BPoseView::CanTrashForeignDrag(const Model* targetModel) 4176 { 4177 return targetModel->IsTrash(); 4178 } 4179 4180 4181 bool 4182 BPoseView::CanCopyOrMoveForeignDrag(const Model* targetModel, 4183 const BMessage* dragMessage) 4184 { 4185 if (!targetModel->IsDirectory()) 4186 return false; 4187 4188 // in order to handle a clipping file, the drag initiator must be able 4189 // do deal with B_FILE_MIME_TYPE 4190 for (int32 index = 0; ; index++) { 4191 const char* type; 4192 if (dragMessage->FindString("be:types", index, &type) != B_OK) 4193 break; 4194 4195 if (strcasecmp(type, B_FILE_MIME_TYPE) == 0) 4196 return true; 4197 } 4198 4199 return false; 4200 } 4201 4202 4203 bool 4204 BPoseView::CanHandleDragSelection(const Model* target, 4205 const BMessage* dragMessage, bool ignoreTypes) 4206 { 4207 if (ignoreTypes) 4208 return target->IsDropTarget(); 4209 4210 ASSERT(dragMessage != NULL); 4211 4212 BContainerWindow* srcWindow; 4213 status_t result = dragMessage->FindPointer("src_window", (void**)&srcWindow); 4214 if (result != B_OK || srcWindow == NULL) { 4215 // handle a foreign drag 4216 bool canCopy; 4217 bool canMove; 4218 bool canErase; 4219 bool canLink; 4220 FindDragNDropAction(dragMessage, canCopy, canMove, canLink, canErase); 4221 if (canErase && CanTrashForeignDrag(target)) 4222 return true; 4223 4224 if (canCopy || canMove) { 4225 if (CanCopyOrMoveForeignDrag(target, dragMessage)) 4226 return true; 4227 4228 // TODO: collect all mime types here and pass into 4229 // target->IsDropTargetForList(mimeTypeList); 4230 } 4231 4232 // handle an old style entry_refs only darg message 4233 if (dragMessage->HasRef("refs") && target->IsDirectory()) 4234 return true; 4235 4236 // handle simple text clipping drag&drop message 4237 if (dragMessage->HasData(kPlainTextMimeType, B_MIME_TYPE) 4238 && target->IsDirectory()) { 4239 return true; 4240 } 4241 4242 // handle simple bitmap clipping drag&drop message 4243 if (target->IsDirectory() 4244 && (dragMessage->HasData(kBitmapMimeType, B_MESSAGE_TYPE) 4245 || dragMessage->HasData(kLargeIconType, B_MESSAGE_TYPE) 4246 || dragMessage->HasData(kMiniIconType, B_MESSAGE_TYPE))) { 4247 return true; 4248 } 4249 4250 // TODO: check for a drag message full of refs, feed a list of their 4251 // types to target->IsDropTargetForList(mimeTypeList); 4252 return false; 4253 } 4254 4255 ASSERT(srcWindow != NULL); 4256 4257 AutoLock<BWindow> lock(srcWindow); 4258 if (!lock) 4259 return false; 4260 4261 BObjectList<BString>* mimeTypeList 4262 = srcWindow->PoseView()->MimeTypesInSelection(); 4263 if (mimeTypeList->IsEmpty()) { 4264 PoseList* selectionList = srcWindow->PoseView()->SelectionList(); 4265 if (!selectionList->IsEmpty()) { 4266 // no cached data yet, build the cache 4267 int32 count = selectionList->CountItems(); 4268 4269 for (int32 index = 0; index < count; index++) { 4270 // get the mime type of the model, following a possible symlink 4271 BEntry entry(selectionList->ItemAt( 4272 index)->TargetModel()->EntryRef(), true); 4273 if (entry.InitCheck() != B_OK) 4274 continue; 4275 4276 BFile file(&entry, O_RDONLY); 4277 BNodeInfo mime(&file); 4278 4279 if (mime.InitCheck() != B_OK) 4280 continue; 4281 4282 char mimeType[B_MIME_TYPE_LENGTH]; 4283 mime.GetType(mimeType); 4284 4285 // add unique type string 4286 if (!WhileEachListItem(mimeTypeList, ContainsOne, 4287 (const char*)mimeType)) { 4288 BString* newMimeString = new BString(mimeType); 4289 mimeTypeList->AddItem(newMimeString); 4290 } 4291 } 4292 } 4293 } 4294 4295 return target->IsDropTargetForList(mimeTypeList); 4296 } 4297 4298 4299 void 4300 BPoseView::TrySettingPoseLocation(BNode* node, BPoint point) 4301 { 4302 if (ViewMode() == kListMode) 4303 return; 4304 4305 if ((modifiers() & B_COMMAND_KEY) != 0) { 4306 // align to grid if needed 4307 point = PinToGrid(point, fGrid, fOffset); 4308 } 4309 4310 Model* targetModel = TargetModel(); 4311 ASSERT(targetModel != NULL); 4312 4313 if (targetModel != NULL && targetModel->NodeRef() != NULL 4314 && FSSetPoseLocation(targetModel->NodeRef()->node, node, point) 4315 == B_OK) { 4316 // get rid of opposite endianness attribute 4317 node->RemoveAttr(kAttrPoseInfoForeign); 4318 } 4319 } 4320 4321 4322 status_t 4323 BPoseView::CreateClippingFile(BPoseView* poseView, BFile &result, 4324 char* resultingName, BDirectory* directory, BMessage* message, 4325 const char* fallbackName, bool setLocation, BPoint dropPoint) 4326 { 4327 // build a file name 4328 // try picking it up from the message 4329 const char* suggestedName; 4330 if (message && message->FindString("be:clip_name", &suggestedName) == B_OK) 4331 strncpy(resultingName, suggestedName, B_FILE_NAME_LENGTH - 1); 4332 else 4333 strcpy(resultingName, fallbackName); 4334 4335 FSMakeOriginalName(resultingName, directory, ""); 4336 4337 // create a clipping file 4338 status_t error = directory->CreateFile(resultingName, &result, true); 4339 if (error != B_OK) 4340 return error; 4341 4342 if (setLocation && poseView != NULL) 4343 poseView->TrySettingPoseLocation(&result, dropPoint); 4344 4345 return B_OK; 4346 } 4347 4348 4349 static int32 4350 RunMimeTypeDestinationMenu(const char* actionText, 4351 const BObjectList<BString>* types, 4352 const BObjectList<BString>* specificItems, BPoint where) 4353 { 4354 int32 count; 4355 if (types != NULL) 4356 count = types->CountItems(); 4357 else 4358 count = specificItems->CountItems(); 4359 4360 if (count == 0) 4361 return 0; 4362 4363 BPopUpMenu* menu = new BPopUpMenu("create clipping"); 4364 menu->SetFont(be_plain_font); 4365 4366 for (int32 index = 0; index < count; index++) { 4367 const char* embedTypeAs = NULL; 4368 char buffer[256]; 4369 if (types) { 4370 types->ItemAt(index)->String(); 4371 BMimeType mimeType(embedTypeAs); 4372 4373 if (mimeType.GetShortDescription(buffer) == B_OK) 4374 embedTypeAs = buffer; 4375 } 4376 4377 BString description; 4378 if (specificItems->ItemAt(index)->Length()) { 4379 description << (const BString &)(*specificItems->ItemAt(index)); 4380 4381 if (embedTypeAs) 4382 description << " (" << embedTypeAs << ")"; 4383 4384 } else if (types) 4385 description = embedTypeAs; 4386 4387 BString labelText; 4388 if (actionText) { 4389 int32 length = 1024 - 1 - (int32)strlen(actionText); 4390 if (length > 0) { 4391 description.Truncate(length); 4392 labelText.SetTo(actionText); 4393 labelText.ReplaceFirst("%s", description.String()); 4394 } else 4395 labelText.SetTo(B_TRANSLATE("label too long")); 4396 } else 4397 labelText = description; 4398 4399 menu->AddItem(new BMenuItem(labelText.String(), 0)); 4400 } 4401 4402 menu->AddSeparatorItem(); 4403 menu->AddItem(new BMenuItem(B_TRANSLATE("Cancel"), 0)); 4404 4405 int32 result = -1; 4406 BMenuItem* resultingItem = menu->Go(where, false, true); 4407 if (resultingItem) { 4408 int32 index = menu->IndexOf(resultingItem); 4409 if (index < count) 4410 result = index; 4411 } 4412 4413 delete menu; 4414 4415 return result; 4416 } 4417 4418 4419 bool 4420 BPoseView::HandleMessageDropped(BMessage* message) 4421 { 4422 ASSERT(message->WasDropped()); 4423 4424 // reset system cursor in case it was altered by drag and drop 4425 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); 4426 fCursorCheck = false; 4427 4428 if (!fDropEnabled) 4429 return false; 4430 4431 BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window()); 4432 if (window != NULL && message->HasData("RGBColor", 'RGBC')) { 4433 // do not handle roColor-style drops here, pass them on to the desktop 4434 BMessenger((BHandler*)window).SendMessage(message); 4435 4436 return true; 4437 } 4438 4439 if (fDropTarget && !DragSelectionContains(fDropTarget, message)) 4440 HiliteDropTarget(false); 4441 4442 fDropTarget = NULL; 4443 4444 ASSERT(TargetModel() != NULL); 4445 BPoint offset; 4446 BPoint dropPoint(message->DropPoint(&offset)); 4447 ConvertFromScreen(&dropPoint); 4448 4449 // tenatively figure out the pose we dropped the file onto 4450 int32 index; 4451 BPose* targetPose = FindPose(dropPoint, &index); 4452 Model tmpTarget; 4453 Model* targetModel = NULL; 4454 if (targetPose != NULL) { 4455 targetModel = targetPose->TargetModel(); 4456 if (targetModel->IsSymLink() 4457 && tmpTarget.SetTo(targetPose->TargetModel()->EntryRef(), 4458 true, true) == B_OK) { 4459 targetModel = &tmpTarget; 4460 } 4461 } 4462 4463 return HandleDropCommon(message, targetModel, targetPose, this, dropPoint); 4464 } 4465 4466 4467 bool 4468 BPoseView::HandleDropCommon(BMessage* message, Model* targetModel, 4469 BPose* targetPose, BView* view, BPoint dropPoint) 4470 { 4471 uint32 buttons = (uint32)message->FindInt32("buttons"); 4472 4473 BContainerWindow* containerWindow = NULL; 4474 BPoseView* poseView = dynamic_cast<BPoseView*>(view); 4475 if (poseView != NULL) 4476 containerWindow = poseView->ContainerWindow(); 4477 4478 // look for srcWindow to determine whether drag was initiated in tracker 4479 BContainerWindow* srcWindow = NULL; 4480 status_t result = message->FindPointer("src_window", (void**)&srcWindow); 4481 if (result != B_OK || srcWindow == NULL) { 4482 // drag was from another app 4483 4484 if (targetModel == NULL && poseView != NULL) 4485 targetModel = poseView->TargetModel(); 4486 4487 // figure out if we dropped a file onto a directory and set 4488 // the targetDirectory to it, else set it to this pose view 4489 BDirectory targetDirectory; 4490 if (targetModel != NULL && targetModel->IsDirectory()) 4491 targetDirectory.SetTo(targetModel->EntryRef()); 4492 4493 if (targetModel != NULL && targetModel->IsRoot()) { 4494 // don't drop anything into the root disk 4495 return false; 4496 } 4497 4498 bool canCopy; 4499 bool canMove; 4500 bool canErase; 4501 bool canLink; 4502 if (FindDragNDropAction(message, canCopy, canMove, canLink, canErase)) { 4503 // new D&D protocol 4504 // what action can the drag initiator do? 4505 if (canErase && CanTrashForeignDrag(targetModel)) { 4506 BMessage reply(B_TRASH_TARGET); 4507 message->SendReply(&reply); 4508 return true; 4509 } 4510 4511 if ((canCopy || canMove) 4512 && CanCopyOrMoveForeignDrag(targetModel, message)) { 4513 // handle the promise style drag&drop 4514 4515 // fish for specification of specialized menu items 4516 BObjectList<BString> actionSpecifiers(10, true); 4517 for (int32 index = 0; ; index++) { 4518 const char* string; 4519 if (message->FindString("be:actionspecifier", index, 4520 &string) != B_OK) { 4521 break; 4522 } 4523 4524 ASSERT(string != NULL); 4525 actionSpecifiers.AddItem(new BString(string)); 4526 } 4527 4528 // build the list of types the drag originator offers 4529 BObjectList<BString> types(10, true); 4530 BObjectList<BString> typeNames(10, true); 4531 for (int32 index = 0; ; index++) { 4532 const char* string; 4533 if (message->FindString("be:filetypes", index, &string) 4534 != B_OK) { 4535 break; 4536 } 4537 4538 ASSERT(string != NULL); 4539 types.AddItem(new BString(string)); 4540 4541 const char* typeName = ""; 4542 message->FindString("be:type_descriptions", index, 4543 &typeName); 4544 typeNames.AddItem(new BString(typeName)); 4545 } 4546 4547 int32 specificTypeIndex = -1; 4548 int32 specificActionIndex = -1; 4549 4550 // if control down, run a popup menu 4551 if (canCopy 4552 && SecondaryMouseButtonDown(modifiers(), buttons)) { 4553 if (actionSpecifiers.CountItems() > 0) { 4554 specificActionIndex = RunMimeTypeDestinationMenu(NULL, 4555 NULL, &actionSpecifiers, 4556 view->ConvertToScreen(dropPoint)); 4557 4558 if (specificActionIndex == -1) 4559 return false; 4560 } else if (types.CountItems() > 0) { 4561 specificTypeIndex = RunMimeTypeDestinationMenu( 4562 B_TRANSLATE("Create %s clipping"), 4563 &types, &typeNames, 4564 view->ConvertToScreen(dropPoint)); 4565 4566 if (specificTypeIndex == -1) 4567 return false; 4568 } 4569 } 4570 4571 char name[B_FILE_NAME_LENGTH]; 4572 BFile file; 4573 if (CreateClippingFile(poseView, file, name, &targetDirectory, 4574 message, B_TRANSLATE("Untitled clipping"), 4575 targetPose == NULL, dropPoint) != B_OK) { 4576 return false; 4577 } 4578 4579 // here is a file for the drag initiator, it is up to it now 4580 // to stuff it with the goods 4581 4582 // build the reply message 4583 BMessage reply(canCopy ? B_COPY_TARGET : B_MOVE_TARGET); 4584 reply.AddString("be:types", B_FILE_MIME_TYPE); 4585 if (specificTypeIndex != -1) { 4586 // we had the user pick a specific type from a menu, use it 4587 reply.AddString("be:filetypes", 4588 types.ItemAt(specificTypeIndex)->String()); 4589 4590 if (typeNames.ItemAt(specificTypeIndex)->Length()) { 4591 reply.AddString("be:type_descriptions", 4592 typeNames.ItemAt(specificTypeIndex)->String()); 4593 } 4594 } 4595 4596 if (specificActionIndex != -1) { 4597 // we had the user pick a specific type from a menu, use it 4598 reply.AddString("be:actionspecifier", 4599 actionSpecifiers.ItemAt(specificActionIndex)->String()); 4600 } 4601 4602 reply.AddRef("directory", targetModel->EntryRef()); 4603 reply.AddString("name", name); 4604 4605 // Attach any data the originator may have tagged on 4606 BMessage data; 4607 if (message->FindMessage("be:originator-data", &data) == B_OK) 4608 reply.AddMessage("be:originator-data", &data); 4609 4610 // copy over all the file types the drag initiator claimed to 4611 // support 4612 for (int32 index = 0; ; index++) { 4613 const char* type; 4614 if (message->FindString("be:filetypes", index, &type) 4615 != B_OK) { 4616 break; 4617 } 4618 reply.AddString("be:filetypes", type); 4619 } 4620 4621 message->SendReply(&reply); 4622 return true; 4623 } 4624 } 4625 4626 if (message->HasRef("refs")) { 4627 // TODO: decide here on copy, move or create symlink 4628 // look for specific command or bring up popup 4629 // Unify this with local drag&drop 4630 4631 if (!targetModel->IsDirectory()) { 4632 // bail if we are not a directory 4633 return false; 4634 } 4635 4636 bool canRelativeLink = false; 4637 if (!canCopy && !canMove && !canLink && containerWindow) { 4638 if (SecondaryMouseButtonDown(modifiers(), buttons)) { 4639 switch (containerWindow->ShowDropContextMenu(dropPoint)) { 4640 case kCreateRelativeLink: 4641 canRelativeLink = true; 4642 break; 4643 4644 case kCreateLink: 4645 canLink = true; 4646 break; 4647 4648 case kMoveSelectionTo: 4649 canMove = true; 4650 break; 4651 4652 case kCopySelectionTo: 4653 canCopy = true; 4654 break; 4655 4656 case kCancelButton: 4657 default: 4658 // user canceled context menu 4659 return true; 4660 } 4661 } else 4662 canCopy = true; 4663 } 4664 4665 uint32 moveMode; 4666 if (canCopy) 4667 moveMode = kCopySelectionTo; 4668 else if (canMove) 4669 moveMode = kMoveSelectionTo; 4670 else if (canLink) 4671 moveMode = kCreateLink; 4672 else if (canRelativeLink) 4673 moveMode = kCreateRelativeLink; 4674 else { 4675 TRESPASS(); 4676 return true; 4677 } 4678 4679 // handle refs by performing a copy 4680 BObjectList<entry_ref>* entryList 4681 = new BObjectList<entry_ref>(10, true); 4682 4683 for (int32 index = 0; ; index++) { 4684 // copy all enclosed refs into a list 4685 entry_ref ref; 4686 if (message->FindRef("refs", index, &ref) != B_OK) 4687 break; 4688 entryList->AddItem(new entry_ref(ref)); 4689 } 4690 4691 int32 count = entryList->CountItems(); 4692 if (count != 0) { 4693 BList* pointList = 0; 4694 if (poseView != NULL && targetPose != NULL) { 4695 // calculate a pointList to make the icons land 4696 // were we dropped them 4697 pointList = new BList(count); 4698 // force the the icons to lay out in 5 columns 4699 for (int32 index = 0; count; index++) { 4700 for (int32 j = 0; count && j < 4; j++, count--) { 4701 BPoint point( 4702 dropPoint + BPoint(j * poseView->fGrid.x, 4703 index * poseView->fGrid.y)); 4704 pointList->AddItem( 4705 new BPoint(poseView->PinToGrid(point, 4706 poseView->fGrid, poseView->fOffset))); 4707 } 4708 } 4709 } 4710 4711 // perform asynchronous copy 4712 FSMoveToFolder(entryList, new BEntry(targetModel->EntryRef()), 4713 moveMode, pointList); 4714 4715 return true; 4716 } 4717 4718 // nothing to copy, list doesn't get consumed 4719 delete entryList; 4720 return true; 4721 } 4722 if (message->HasData(kPlainTextMimeType, B_MIME_TYPE)) { 4723 // text dropped, make into a clipping file 4724 if (!targetModel->IsDirectory()) { 4725 // bail if we are not a directory 4726 return false; 4727 } 4728 4729 // find the text 4730 ssize_t textLength; 4731 const char* text; 4732 if (message->FindData(kPlainTextMimeType, B_MIME_TYPE, 4733 (const void**)&text, &textLength) != B_OK) { 4734 return false; 4735 } 4736 4737 char name[B_FILE_NAME_LENGTH]; 4738 BFile file; 4739 if (CreateClippingFile(poseView, file, name, &targetDirectory, 4740 message, B_TRANSLATE("Untitled clipping"), !targetPose, 4741 dropPoint) != B_OK) { 4742 return false; 4743 } 4744 4745 // write out the file 4746 if (file.Seek(0, SEEK_SET) == B_ERROR 4747 || file.Write(text, (size_t)textLength) < 0 4748 || file.SetSize(textLength) != B_OK) { 4749 // failed to write file, remove file and bail 4750 file.Unset(); 4751 BEntry entry(&targetDirectory, name); 4752 entry.Remove(); 4753 PRINT(("error writing text into file %s\n", name)); 4754 } 4755 4756 // pick up TextView styles if available and save them with the file 4757 const text_run_array* textRuns = NULL; 4758 ssize_t dataSize = 0; 4759 if (message->FindData("application/x-vnd.Be-text_run_array", 4760 B_MIME_TYPE, (const void**)&textRuns, &dataSize) == B_OK 4761 && textRuns && dataSize) { 4762 // save styles the same way StyledEdit does 4763 int32 tmpSize = dataSize; 4764 void* data = BTextView::FlattenRunArray(textRuns, &tmpSize); 4765 file.WriteAttr("styles", B_RAW_TYPE, 0, data, (size_t)tmpSize); 4766 free(data); 4767 } 4768 4769 // mark as a clipping file 4770 int32 tmp; 4771 file.WriteAttr(kAttrClippingFile, B_RAW_TYPE, 0, &tmp, 4772 sizeof(int32)); 4773 4774 // set the file type 4775 BNodeInfo info(&file); 4776 info.SetType(kPlainTextMimeType); 4777 4778 return true; 4779 } 4780 4781 if (message->HasData(kBitmapMimeType, B_MESSAGE_TYPE) 4782 || message->HasData(kLargeIconType, B_MESSAGE_TYPE) 4783 || message->HasData(kMiniIconType, B_MESSAGE_TYPE)) { 4784 // bitmap, make into a clipping file 4785 if (!targetModel->IsDirectory()) { 4786 // bail if we are not a directory 4787 return false; 4788 } 4789 4790 BMessage embeddedBitmap; 4791 if (message->FindMessage(kBitmapMimeType, &embeddedBitmap) 4792 != B_OK 4793 && message->FindMessage(kLargeIconType, &embeddedBitmap) 4794 != B_OK 4795 && message->FindMessage(kMiniIconType, &embeddedBitmap) 4796 != B_OK) { 4797 return false; 4798 } 4799 4800 char name[B_FILE_NAME_LENGTH]; 4801 4802 BFile file; 4803 if (CreateClippingFile(poseView, file, name, &targetDirectory, 4804 message, B_TRANSLATE("Untitled bitmap"), targetPose == NULL, 4805 dropPoint) != B_OK) { 4806 return false; 4807 } 4808 4809 int32 size = embeddedBitmap.FlattenedSize(); 4810 if (size > 1024 * 1024) { 4811 // bail if too large 4812 return false; 4813 } 4814 4815 char* buffer = new char [size]; 4816 embeddedBitmap.Flatten(buffer, size); 4817 4818 // write out the file 4819 if (file.Seek(0, SEEK_SET) == B_ERROR 4820 || file.Write(buffer, (size_t)size) < 0 4821 || file.SetSize(size) != B_OK) { 4822 // failed to write file, remove file and bail 4823 file.Unset(); 4824 BEntry entry(&targetDirectory, name); 4825 entry.Remove(); 4826 PRINT(("error writing bitmap into file %s\n", name)); 4827 } 4828 4829 // mark as a clipping file 4830 int32 tmp; 4831 file.WriteAttr(kAttrClippingFile, B_RAW_TYPE, 0, &tmp, 4832 sizeof(int32)); 4833 4834 // set the file type 4835 BNodeInfo info(&file); 4836 info.SetType(kBitmapMimeType); 4837 4838 return true; 4839 } 4840 4841 return false; 4842 } 4843 4844 ASSERT(srcWindow != NULL); 4845 4846 if (srcWindow == containerWindow) { 4847 // drag started in this window 4848 containerWindow->Activate(); 4849 containerWindow->UpdateIfNeeded(); 4850 poseView->ResetPosePlacementHint(); 4851 4852 if (DragSelectionContains(targetPose, message)) { 4853 // drop on self 4854 targetModel = NULL; 4855 } 4856 } 4857 4858 bool wasHandled = false; 4859 bool ignoreTypes = (modifiers() & B_CONTROL_KEY) != 0; 4860 4861 if (targetModel != NULL && containerWindow != NULL) { 4862 // TODO: pick files to drop/launch on a case by case basis 4863 if (targetModel->IsDirectory()) { 4864 MoveSelectionInto(targetModel, srcWindow, containerWindow, 4865 buttons, dropPoint, false); 4866 wasHandled = true; 4867 } else if (CanHandleDragSelection(targetModel, message, ignoreTypes)) { 4868 LaunchAppWithSelection(targetModel, message, !ignoreTypes); 4869 wasHandled = true; 4870 } 4871 } 4872 4873 if (poseView != NULL && !wasHandled) { 4874 BPoint clickPoint = message->FindPoint("click_pt"); 4875 // TODO: removed check for root here need to do that, possibly at a 4876 // different level 4877 poseView->MoveSelectionTo(dropPoint, clickPoint, srcWindow); 4878 } 4879 4880 if (poseView != NULL && poseView->fEnsurePosesVisible) 4881 poseView->CheckPoseVisibility(); 4882 4883 return true; 4884 } 4885 4886 4887 struct LaunchParams { 4888 Model* app; 4889 bool checkTypes; 4890 BMessage* refsMessage; 4891 }; 4892 4893 4894 static bool 4895 AddOneToLaunchMessage(BPose* pose, BPoseView*, void* castToParams) 4896 { 4897 LaunchParams* params = (LaunchParams*)castToParams; 4898 ThrowOnAssert(params != NULL); 4899 ThrowOnAssert(pose != NULL); 4900 ThrowOnAssert(pose->TargetModel() != NULL); 4901 4902 if (params->app->IsDropTarget(params->checkTypes 4903 ? pose->TargetModel() : NULL, true)) { 4904 params->refsMessage->AddRef("refs", pose->TargetModel()->EntryRef()); 4905 } 4906 4907 return false; 4908 } 4909 4910 4911 void 4912 BPoseView::LaunchAppWithSelection(Model* appModel, const BMessage* dragMessage, 4913 bool checkTypes) 4914 { 4915 // launch items from the current selection with <appModel>; only pass 4916 // the same files that we previously decided can be handled by <appModel> 4917 BMessage refs(B_REFS_RECEIVED); 4918 LaunchParams params; 4919 params.app = appModel; 4920 params.checkTypes = checkTypes; 4921 params.refsMessage = &refs; 4922 4923 // add Tracker token so that refs received recipients can script us 4924 BContainerWindow* srcWindow; 4925 if (dragMessage->FindPointer("src_window", (void**)&srcWindow) == B_OK 4926 && srcWindow != NULL) { 4927 params.refsMessage->AddMessenger("TrackerViewToken", 4928 BMessenger(srcWindow->PoseView())); 4929 } 4930 4931 EachItemInDraggedSelection(dragMessage, AddOneToLaunchMessage, 0, ¶ms); 4932 if (params.refsMessage->HasRef("refs")) 4933 TrackerLaunch(appModel->EntryRef(), params.refsMessage, true); 4934 } 4935 4936 4937 bool 4938 BPoseView::DragSelectionContains(const BPose* target, 4939 const BMessage* dragMessage) 4940 { 4941 return EachItemInDraggedSelection(dragMessage, OneMatches, 0, 4942 (void*)target); 4943 } 4944 4945 4946 void 4947 BPoseView::MoveSelectionInto(Model* destFolder, BContainerWindow* srcWindow, 4948 bool forceCopy, bool forceMove, bool createLink, bool relativeLink) 4949 { 4950 uint32 buttons; 4951 BPoint loc; 4952 GetMouse(&loc, &buttons); 4953 MoveSelectionInto(destFolder, srcWindow, 4954 dynamic_cast<BContainerWindow*>(Window()), buttons, loc, forceCopy, 4955 forceMove, createLink, relativeLink); 4956 } 4957 4958 4959 void 4960 BPoseView::MoveSelectionInto(Model* destFolder, BContainerWindow* srcWindow, 4961 BContainerWindow* destWindow, uint32 buttons, BPoint loc, bool forceCopy, 4962 bool forceMove, bool createLink, bool relativeLink, BPoint clickPoint, 4963 bool dropOnGrid) 4964 { 4965 AutoLock<BWindow> lock(srcWindow); 4966 if (!lock) 4967 return; 4968 4969 ASSERT(srcWindow->PoseView()->TargetModel() != NULL); 4970 4971 if (srcWindow->PoseView()->SelectionList()->CountItems() == 0) 4972 return; 4973 4974 bool createRelativeLink = relativeLink; 4975 if (SecondaryMouseButtonDown(modifiers(), buttons) 4976 && destWindow != NULL) { 4977 switch (destWindow->ShowDropContextMenu(loc)) { 4978 case kCreateRelativeLink: 4979 createRelativeLink = true; 4980 break; 4981 4982 case kCreateLink: 4983 createLink = true; 4984 break; 4985 4986 case kMoveSelectionTo: 4987 forceMove = true; 4988 break; 4989 4990 case kCopySelectionTo: 4991 forceCopy = true; 4992 break; 4993 4994 case kCancelButton: 4995 default: 4996 // user canceled context menu 4997 return; 4998 } 4999 } 5000 5001 // make sure source and destination folders are different 5002 if (!createLink && !createRelativeLink 5003 && (*srcWindow->PoseView()->TargetModel()->NodeRef() 5004 == *destFolder->NodeRef())) { 5005 BPoseView* targetView = srcWindow->PoseView(); 5006 if (forceCopy) { 5007 targetView->DuplicateSelection(&clickPoint, &loc); 5008 return; 5009 } 5010 5011 if (targetView->ViewMode() == kListMode) { 5012 // can't move in list view 5013 return; 5014 } 5015 5016 BPoint delta = loc - clickPoint; 5017 int32 count = targetView->fSelectionList->CountItems(); 5018 for (int32 index = 0; index < count; index++) { 5019 BPose* pose = targetView->fSelectionList->ItemAt(index); 5020 5021 // remove pose from VSlist before changing location 5022 // so that we "find" the correct pose to remove 5023 // need to do this because bsearch uses top of pose 5024 // to locate pose to remove 5025 targetView->RemoveFromVSList(pose); 5026 BPoint location (pose->Location(targetView) + delta); 5027 BRect oldBounds(pose->CalcRect(targetView)); 5028 if (dropOnGrid) { 5029 location = targetView->PinToGrid(location, targetView->fGrid, 5030 targetView->fOffset); 5031 } 5032 5033 // TODO: don't drop poses under desktop elements 5034 // ie: replicants, deskbar 5035 pose->MoveTo(location, targetView); 5036 5037 targetView->RemoveFromExtent(oldBounds); 5038 targetView->AddToExtent(pose->CalcRect(targetView)); 5039 5040 // remove and reinsert pose to keep VSlist sorted 5041 targetView->AddToVSList(pose); 5042 } 5043 5044 return; 5045 } 5046 5047 BEntry* destEntry = new BEntry(destFolder->EntryRef()); 5048 bool destIsTrash = destFolder->IsTrash(); 5049 5050 // perform asynchronous copy/move 5051 forceCopy = forceCopy || (modifiers() & B_OPTION_KEY) != 0; 5052 5053 bool okToMove = true; 5054 5055 if (destFolder->IsRoot()) { 5056 BAlert* alert = new BAlert("", 5057 B_TRANSLATE("You must drop items on one of the disk icons " 5058 "in the \"Disks\" window."), B_TRANSLATE("Cancel"), NULL, NULL, 5059 B_WIDTH_AS_USUAL, B_WARNING_ALERT); 5060 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 5061 alert->Go(); 5062 okToMove = false; 5063 } 5064 5065 // can't copy items into the trash 5066 if (forceCopy && destIsTrash) { 5067 BAlert* alert = new BAlert("", 5068 B_TRANSLATE("Sorry, you can't copy items to the Trash."), 5069 B_TRANSLATE("Cancel"), NULL, NULL, B_WIDTH_AS_USUAL, 5070 B_WARNING_ALERT); 5071 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 5072 alert->Go(); 5073 okToMove = false; 5074 } 5075 5076 // can't create symlinks into the trash 5077 if (createLink && destIsTrash) { 5078 BAlert* alert = new BAlert("", 5079 B_TRANSLATE("Sorry, you can't create links in the Trash."), 5080 B_TRANSLATE("Cancel"), NULL, NULL, B_WIDTH_AS_USUAL, 5081 B_WARNING_ALERT); 5082 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 5083 alert->Go(); 5084 okToMove = false; 5085 } 5086 5087 // prompt user if drag was from a query 5088 if (srcWindow->TargetModel()->IsQuery() 5089 && !forceCopy && !destIsTrash && !createLink) { 5090 srcWindow->UpdateIfNeeded(); 5091 BAlert* alert = new BAlert("", 5092 B_TRANSLATE("Are you sure you want to move or copy the selected " 5093 "item(s) to this folder?"), B_TRANSLATE("Cancel"), 5094 B_TRANSLATE("Move"), NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT); 5095 alert->SetShortcut(0, B_ESCAPE); 5096 okToMove = alert->Go() == 1; 5097 } 5098 5099 if (okToMove) { 5100 PoseList* selectionList = srcWindow->PoseView()->SelectionList(); 5101 BList* pointList = destWindow->PoseView()->GetDropPointList(clickPoint, 5102 loc, selectionList, srcWindow->PoseView()->ViewMode() == kListMode, 5103 dropOnGrid); 5104 int32 selectionSize = selectionList->CountItems(); 5105 BObjectList<entry_ref>* srcList 5106 = new BObjectList<entry_ref>(selectionSize, true); 5107 5108 if (srcWindow->TargetModel()->IsVirtualDirectory()) { 5109 // resolve symlink and add the resulting entry_ref to the list 5110 for (int32 i = 0; i < selectionSize; i++) { 5111 Model* model = selectionList->ItemAt(i)->ResolvedModel(); 5112 if (model != NULL) 5113 srcList->AddItem(new entry_ref(*(model->EntryRef()))); 5114 } 5115 } else 5116 CopySelectionListToEntryRefList(selectionList, srcList); 5117 5118 uint32 moveMode; 5119 if (forceCopy) 5120 moveMode = kCopySelectionTo; 5121 else if (forceMove) 5122 moveMode = kMoveSelectionTo; 5123 else if (createRelativeLink) 5124 moveMode = kCreateRelativeLink; 5125 else if (createLink) 5126 moveMode = kCreateLink; 5127 else if (!CheckDevicesEqual(srcList->ItemAt(0), destFolder)) 5128 moveMode = kCopySelectionTo; 5129 else 5130 moveMode = kMoveSelectionTo; 5131 5132 FSMoveToFolder(srcList, destEntry, moveMode, pointList); 5133 return; 5134 } 5135 5136 delete destEntry; 5137 } 5138 5139 5140 void 5141 BPoseView::MoveSelectionTo(BPoint dropPoint, BPoint clickPoint, 5142 BContainerWindow* srcWindow) 5143 { 5144 // Moves selection from srcWindow into this window, copying if necessary. 5145 5146 BContainerWindow* window = ContainerWindow(); 5147 if (window == NULL) 5148 return; 5149 5150 ASSERT(window->PoseView() != NULL); 5151 ASSERT(TargetModel() != NULL); 5152 5153 // make sure this window is a legal drop target 5154 if (srcWindow != window && !TargetModel()->IsDropTarget()) 5155 return; 5156 5157 uint32 buttons = (uint32)window->CurrentMessage()->FindInt32("buttons"); 5158 bool pinToGrid = (modifiers() & B_COMMAND_KEY) != 0; 5159 MoveSelectionInto(TargetModel(), srcWindow, window, buttons, dropPoint, 5160 false, false, false, false, clickPoint, pinToGrid); 5161 } 5162 5163 5164 inline void 5165 UpdateWasBrokenSymlinkBinder(BPose* pose, Model* model, int32 index, 5166 BPoseView* poseView, BObjectList<Model>* fBrokenLinks) 5167 { 5168 if (!model->IsSymLink()) 5169 return; 5170 5171 BPoint loc(0, index * poseView->ListElemHeight()); 5172 pose->UpdateWasBrokenSymlink(loc, poseView); 5173 if (model->LinkTo() != NULL) 5174 fBrokenLinks->RemoveItem(model); 5175 } 5176 5177 5178 void 5179 BPoseView::TryUpdatingBrokenLinks() 5180 { 5181 AutoLock<BWindow> lock(Window()); 5182 if (!lock) 5183 return; 5184 5185 BObjectList<Model>* brokenLinksCopy = new BObjectList<Model>(*fBrokenLinks); 5186 5187 // try fixing broken symlinks, and detecting broken ones. 5188 EachPoseAndModel(fPoseList, &UpdateWasBrokenSymlinkBinder, this, 5189 fBrokenLinks); 5190 5191 for (int i = brokenLinksCopy->CountItems() - 1; i >= 0; i--) { 5192 if (!fBrokenLinks->HasItem(brokenLinksCopy->ItemAt(i))) 5193 StopWatchingParentsOf(brokenLinksCopy->ItemAt(i)->EntryRef()); 5194 } 5195 5196 delete brokenLinksCopy; 5197 } 5198 5199 5200 void 5201 BPoseView::PoseHandleDeviceUnmounted(BPose* pose, Model* model, int32 index, 5202 BPoseView* poseView, dev_t device) 5203 { 5204 if (model->NodeRef()->device == device) 5205 poseView->DeletePose(model->NodeRef()); 5206 else if (model->IsSymLink() && model->LinkTo() != NULL 5207 && model->LinkTo()->NodeRef()->device == device) { 5208 poseView->DeleteSymLinkPoseTarget(model->LinkTo()->NodeRef(), 5209 pose, index); 5210 } 5211 } 5212 5213 5214 static void 5215 OneMetaMimeChanged(BPose* pose, Model* model, int32 index, 5216 BPoseView* poseView, const char* type) 5217 { 5218 ASSERT(model != NULL); 5219 5220 if (model->IconFrom() != kNode 5221 && model->IconFrom() != kUnknownSource 5222 && model->IconFrom() != kUnknownNotFromNode 5223 // TODO: add supertype compare 5224 && strcasecmp(model->MimeType(), type) == 0) { 5225 // metamime change very likely affected the documents icon 5226 BPoint poseLoc(0, index * poseView->ListElemHeight()); 5227 pose->UpdateIcon(poseLoc, poseView); 5228 } 5229 } 5230 5231 5232 void 5233 BPoseView::MetaMimeChanged(const char* type, const char* preferredApp) 5234 { 5235 IconCache::sIconCache->IconChanged(type, preferredApp); 5236 // wait for other windows to do the same before we start 5237 // updating poses which causes icon recaching 5238 // TODO: this is a design problem that should be solved differently 5239 snooze(10000); 5240 Window()->UpdateIfNeeded(); 5241 5242 EachPoseAndResolvedModel(fPoseList, &OneMetaMimeChanged, this, type); 5243 } 5244 5245 5246 class MetaMimeChangedAccumulator : public AccumulatingFunctionObject { 5247 // pools up matching metamime change notices, executing them as a single 5248 // update 5249 public: 5250 MetaMimeChangedAccumulator(void (BPoseView::*func)(const char* type, 5251 const char* preferredApp), 5252 BContainerWindow* window, const char* type, const char* preferredApp) 5253 : 5254 fCallOnThis(window), 5255 fFunc(func), 5256 fType(type), 5257 fPreferredApp(preferredApp) 5258 { 5259 } 5260 5261 virtual bool CanAccumulate(const AccumulatingFunctionObject* functor) const 5262 { 5263 const MetaMimeChangedAccumulator* accumulator 5264 = dynamic_cast<const MetaMimeChangedAccumulator*>(functor); 5265 if (accumulator == NULL) 5266 return false; 5267 5268 return accumulator && accumulator->fType == fType 5269 && accumulator->fPreferredApp == fPreferredApp; 5270 } 5271 5272 virtual void Accumulate(AccumulatingFunctionObject* DEBUG_ONLY(functor)) 5273 { 5274 ASSERT(CanAccumulate(functor)); 5275 // do nothing, no further accumulating needed 5276 } 5277 5278 protected: 5279 virtual void operator()() 5280 { 5281 AutoLock<BWindow> lock(fCallOnThis); 5282 if (!lock) 5283 return; 5284 5285 (fCallOnThis->PoseView()->*fFunc)(fType.String(), 5286 fPreferredApp.String()); 5287 } 5288 5289 virtual ulong Size() const 5290 { 5291 return sizeof (*this); 5292 } 5293 5294 private: 5295 BContainerWindow* fCallOnThis; 5296 void (BPoseView::*fFunc)(const char* type, const char* preferredApp); 5297 BString fType; 5298 BString fPreferredApp; 5299 }; 5300 5301 5302 bool 5303 BPoseView::NoticeMetaMimeChanged(const BMessage* message) 5304 { 5305 int32 change; 5306 if (message->FindInt32("be:which", &change) != B_OK) 5307 return true; 5308 5309 bool iconChanged = (change & B_ICON_CHANGED) != 0; 5310 bool iconForTypeChanged = (change & B_ICON_FOR_TYPE_CHANGED) != 0; 5311 bool preferredAppChanged = (change & B_APP_HINT_CHANGED) 5312 || (change & B_PREFERRED_APP_CHANGED); 5313 5314 const char* type = NULL; 5315 const char* preferredApp = NULL; 5316 5317 if (iconChanged || preferredAppChanged) 5318 message->FindString("be:type", &type); 5319 5320 if (iconForTypeChanged) { 5321 message->FindString("be:extra_type", &type); 5322 message->FindString("be:type", &preferredApp); 5323 } 5324 5325 if (iconChanged || preferredAppChanged || iconForTypeChanged) { 5326 TaskLoop* taskLoop = ContainerWindow()->DelayedTaskLoop(); 5327 ASSERT(taskLoop != NULL); 5328 taskLoop->AccumulatedRunLater(new MetaMimeChangedAccumulator( 5329 &BPoseView::MetaMimeChanged, ContainerWindow(), type, preferredApp), 5330 200000, 5000000); 5331 } 5332 5333 return true; 5334 } 5335 5336 5337 bool 5338 BPoseView::FSNotification(const BMessage* message) 5339 { 5340 node_ref itemNode; 5341 dev_t device; 5342 Model* targetModel = TargetModel(); 5343 5344 switch (message->FindInt32("opcode")) { 5345 case B_ENTRY_CREATED: 5346 { 5347 ASSERT(targetModel != NULL); 5348 5349 message->FindInt32("device", &itemNode.device); 5350 node_ref dirNode; 5351 dirNode.device = itemNode.device; 5352 message->FindInt64("directory", (int64*)&dirNode.node); 5353 message->FindInt64("node", (int64*)&itemNode.node); 5354 5355 int32 count = fBrokenLinks->CountItems(); 5356 bool createPose = true; 5357 // Query windows can get notices on different dirNodes 5358 // The Disks window can too 5359 // So can the Desktop, as long as the integrate flag is on 5360 TrackerSettings settings; 5361 if (targetModel != NULL && dirNode != *targetModel->NodeRef() 5362 && !targetModel->IsQuery() 5363 && !targetModel->IsVirtualDirectory() 5364 && !targetModel->IsRoot() 5365 && (!settings.ShowDisksIcon() || !IsDesktopView())) { 5366 if (count == 0) 5367 break; 5368 createPose = false; 5369 } 5370 5371 const char* name; 5372 if (message->FindString("name", &name) != B_OK) { 5373 #if DEBUG 5374 SERIAL_PRINT(("no name in entry creation message\n")); 5375 #endif 5376 break; 5377 } 5378 if (count != 0) { 5379 // basically, let's say we have a broken link : 5380 // ./a_link -> ./some_folder/another_folder/a_target 5381 // and that both some_folder and another_folder didn't 5382 // exist yet. We are looking if the just created folder 5383 // is 'some_folder' and watch it, expecting the creation of 5384 // 'another_folder' later and then report the link as fixed. 5385 Model* model = new Model(&dirNode, &itemNode, name); 5386 if (model->IsDirectory()) { 5387 BString createdPath(BPath(model->EntryRef()).Path()); 5388 BDirectory currentDir(targetModel->EntryRef()); 5389 BPath createdDir(model->EntryRef()); 5390 for (int32 i = 0; i < count; i++) { 5391 BSymLink link(fBrokenLinks->ItemAt(i)->EntryRef()); 5392 BPath path; 5393 link.MakeLinkedPath(¤tDir, &path); 5394 BString pathStr(path.Path()); 5395 pathStr.Append("/"); 5396 if (pathStr.Compare(createdPath, 5397 createdPath.Length()) == 0) { 5398 if (pathStr[createdPath.Length()] != '/') 5399 break; 5400 StopWatchingParentsOf(fBrokenLinks->ItemAt(i) 5401 ->EntryRef()); 5402 watch_node(&itemNode, B_WATCH_DIRECTORY, this); 5403 break; 5404 } 5405 } 5406 } 5407 delete model; 5408 } 5409 if (createPose) 5410 EntryCreated(&dirNode, &itemNode, name); 5411 5412 TryUpdatingBrokenLinks(); 5413 break; 5414 } 5415 5416 case B_ENTRY_MOVED: 5417 return EntryMoved(message); 5418 break; 5419 5420 case B_ENTRY_REMOVED: 5421 message->FindInt32("device", &itemNode.device); 5422 message->FindInt64("node", (int64*)&itemNode.node); 5423 5424 // our window itself may be deleted 5425 // we must check to see if this comes as a query 5426 // notification or a node monitor notification because 5427 // if it's a query notification then we're just being told we 5428 // no longer match the query, so we don't want to close the window 5429 // but it's a node monitor notification then that means our query 5430 // file has been deleted so we close the window 5431 5432 if (message->what == B_NODE_MONITOR && targetModel != NULL 5433 && *(targetModel->NodeRef()) == itemNode) { 5434 if (!targetModel->IsRoot()) { 5435 // it is impossible to watch for ENTRY_REMOVED in 5436 // "/" because the notification is ambiguous - the vnode 5437 // is that of the volume but the device is of the parent 5438 // not the same as the device of the volume that way we 5439 // may get aliasing for volumes with vnodes of 1 5440 // (currently the case for iso9660) 5441 DisableSaveLocation(); 5442 Window()->Close(); 5443 } 5444 } else { 5445 int32 index; 5446 BPose* pose = fPoseList->FindPose(&itemNode, &index); 5447 if (pose == NULL) { 5448 // couldn't find pose, first check if the node might be 5449 // target of a symlink pose; 5450 // 5451 // What happens when a node and a symlink to it are in the 5452 // same window? 5453 // They get monitored twice, we get two notifications; the 5454 // first one will get caught by the first FindPose, the 5455 // second one by the DeepFindPose 5456 // 5457 pose = fPoseList->DeepFindPose(&itemNode, &index); 5458 if (pose != NULL) { 5459 DeleteSymLinkPoseTarget(&itemNode, pose, index); 5460 break; 5461 } 5462 } 5463 5464 DeletePose(&itemNode); 5465 TryUpdatingBrokenLinks(); 5466 } 5467 break; 5468 5469 case B_DEVICE_MOUNTED: 5470 { 5471 if (message->FindInt32("new device", &device) != B_OK) 5472 break; 5473 5474 if (targetModel != NULL && targetModel->IsRoot()) { 5475 BVolume volume(device); 5476 if (volume.InitCheck() == B_OK) 5477 CreateVolumePose(&volume, false); 5478 } else if (ContainerWindow()->IsTrash()) { 5479 // add trash items from newly mounted volume 5480 5481 BDirectory trashDir; 5482 BEntry entry; 5483 BVolume volume(device); 5484 if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK 5485 && trashDir.GetEntry(&entry) == B_OK) { 5486 Model model(&entry); 5487 if (model.InitCheck() == B_OK) 5488 AddPoses(&model); 5489 } 5490 } 5491 TaskLoop* taskLoop = ContainerWindow()->DelayedTaskLoop(); 5492 ASSERT(taskLoop); 5493 taskLoop->RunLater(NewMemberFunctionObject( 5494 &BPoseView::TryUpdatingBrokenLinks, this), 500000); 5495 // delay of 500000: wait for volumes to properly finish mounting 5496 // without this in the Model::FinishSettingUpType a symlink 5497 // to a volume would get initialized as a symlink to a directory 5498 // because IsRootDirectory looks like returns false. Either 5499 // there is a race condition or I was doing something wrong. 5500 break; 5501 } 5502 5503 case B_DEVICE_UNMOUNTED: 5504 if (message->FindInt32("device", &device) == B_OK) { 5505 if (targetModel != NULL 5506 && targetModel->NodeRef()->device == device) { 5507 // close the window from a volume that is gone 5508 DisableSaveLocation(); 5509 Window()->Close(); 5510 } else if (targetModel != NULL) { 5511 EachPoseAndModel(fPoseList, &PoseHandleDeviceUnmounted, 5512 this, device); 5513 } 5514 } 5515 break; 5516 5517 case B_STAT_CHANGED: 5518 case B_ATTR_CHANGED: 5519 return AttributeChanged(message); 5520 } 5521 5522 return true; 5523 } 5524 5525 5526 bool 5527 BPoseView::CreateSymlinkPoseTarget(Model* symlink) 5528 { 5529 Model* newResolvedModel = NULL; 5530 Model* result = symlink->LinkTo(); 5531 5532 if (result == NULL) { 5533 BEntry entry(symlink->EntryRef(), true); 5534 if (entry.InitCheck() == B_OK) { 5535 node_ref nref; 5536 entry_ref eref; 5537 entry.GetNodeRef(&nref); 5538 entry.GetRef(&eref); 5539 if (eref.directory != TargetModel()->NodeRef()->node) 5540 WatchNewNode(&nref, B_WATCH_STAT | B_WATCH_ATTR | B_WATCH_NAME 5541 | B_WATCH_INTERIM_STAT, this); 5542 newResolvedModel = new Model(&entry, true); 5543 } else { 5544 fBrokenLinks->AddItem(symlink); 5545 WatchParentOf(symlink->EntryRef()); 5546 return true; 5547 } 5548 result = newResolvedModel; 5549 } 5550 symlink->SetLinkTo(result); 5551 5552 return true; 5553 } 5554 5555 5556 BPose* 5557 BPoseView::EntryCreated(const node_ref* dirNode, const node_ref* itemNode, 5558 const char* name, int32* indexPtr) 5559 { 5560 // reject notification if pose already exists 5561 if (fPoseList->FindPose(itemNode) || FindZombie(itemNode)) 5562 return NULL; 5563 5564 BPoseView::WatchNewNode(itemNode); 5565 // have to node monitor ahead of time because Model will 5566 // cache up the file type and preferred app 5567 Model* model = new Model(dirNode, itemNode, name, true); 5568 5569 if (model->InitCheck() != B_OK) { 5570 // if we have trouble setting up model then we stuff it into 5571 // a zombie list in a half-alive state until we can properly awaken it 5572 PRINT(("2 adding model %s to zombie list, error %s\n", model->Name(), 5573 strerror(model->InitCheck()))); 5574 fZombieList->AddItem(model); 5575 return NULL; 5576 } 5577 5578 PoseInfo poseInfo; 5579 ReadPoseInfo(model, &poseInfo); 5580 5581 if (!PoseVisible(model, &poseInfo)) { 5582 watch_node(model->NodeRef(), B_STOP_WATCHING, this); 5583 delete model; 5584 return NULL; 5585 } 5586 5587 // model is a symlink, cache up the symlink target or scrap 5588 // everything if target is invisible 5589 if (model->IsSymLink() && !CreateSymlinkPoseTarget(model)) { 5590 watch_node(model->NodeRef(), B_STOP_WATCHING, this); 5591 delete model; 5592 return NULL; 5593 } 5594 5595 return CreatePose(model, &poseInfo, true, indexPtr); 5596 } 5597 5598 5599 bool 5600 BPoseView::EntryMoved(const BMessage* message) 5601 { 5602 ino_t oldDir; 5603 node_ref dirNode; 5604 node_ref itemNode; 5605 5606 message->FindInt32("device", &dirNode.device); 5607 itemNode.device = dirNode.device; 5608 message->FindInt64("to directory", (int64*)&dirNode.node); 5609 message->FindInt64("node", (int64*)&itemNode.node); 5610 message->FindInt64("from directory", (int64*)&oldDir); 5611 5612 const char* name; 5613 if (message->FindString("name", &name) != B_OK) 5614 return true; 5615 5616 // handle special case of notifying a name change for a volume 5617 // - the notification is not enough, because the volume's device 5618 // is different than that of the root directory; we have to do a 5619 // lookup using the new volume name and get the volume device from there 5620 StatStruct st; 5621 // get the inode of the root and check if we got a notification on it 5622 if (stat("/", &st) >= 0 5623 && st.st_dev == dirNode.device 5624 && st.st_ino == dirNode.node) { 5625 BString buffer; 5626 buffer << "/" << name; 5627 if (stat(buffer.String(), &st) >= 0) { 5628 // point the dirNode to the actual volume 5629 itemNode.node = st.st_ino; 5630 itemNode.device = st.st_dev; 5631 } 5632 } 5633 5634 Model* targetModel = TargetModel(); 5635 ThrowOnAssert(targetModel != NULL); 5636 5637 node_ref thisDirNode; 5638 if (ContainerWindow()->IsTrash()) { 5639 BDirectory trashDir; 5640 if (FSGetTrashDir(&trashDir, itemNode.device) != B_OK) 5641 return true; 5642 5643 trashDir.GetNodeRef(&thisDirNode); 5644 } else 5645 thisDirNode = *targetModel->NodeRef(); 5646 5647 // see if we need to update window title (and folder itself) 5648 if (thisDirNode == itemNode) { 5649 targetModel->UpdateEntryRef(&dirNode, name); 5650 assert_cast<BContainerWindow*>(Window())->UpdateTitle(); 5651 } 5652 5653 if (oldDir == dirNode.node || targetModel->IsQuery() 5654 || targetModel->IsVirtualDirectory()) { 5655 // rename or move of entry in this directory (or query) 5656 5657 int32 index; 5658 BPose* pose = fPoseList->FindPose(&itemNode, &index); 5659 int32 poseListIndex = index; 5660 bool visible = true; 5661 if (fFiltering) 5662 visible = fFilteredPoseList->FindPose(&itemNode, &index) != NULL; 5663 5664 if (pose != NULL) { 5665 Model* poseModel = pose->TargetModel(); 5666 ASSERT(poseModel != NULL); 5667 poseModel->UpdateEntryRef(&dirNode, name); 5668 // for queries we check for move to trash and remove item if so 5669 if (targetModel->IsQuery()) { 5670 PoseInfo poseInfo; 5671 ReadPoseInfo(poseModel, &poseInfo); 5672 if (!ShouldShowPose(poseModel, &poseInfo)) 5673 return DeletePose(&itemNode, pose, index); 5674 return true; 5675 } 5676 5677 BPoint loc(0, index * fListElemHeight); 5678 // if we get a rename then we need to assume that we might 5679 // have missed some other attr changed notifications so we 5680 // recheck all widgets 5681 if (poseModel->OpenNode() == B_OK) { 5682 pose->UpdateAllWidgets(index, loc, this); 5683 poseModel->CloseNode(); 5684 _CheckPoseSortOrder(fPoseList, pose, poseListIndex); 5685 if (fFiltering) { 5686 if (!visible && FilterPose(pose)) { 5687 BRect bounds = Bounds(); 5688 float scrollBy = 0; 5689 AddPoseToList(fFilteredPoseList, true, true, pose, 5690 bounds, scrollBy, true); 5691 } else if (visible && !FilterPose(pose)) 5692 RemoveFilteredPose(pose, index); 5693 else if (visible) 5694 _CheckPoseSortOrder(fFilteredPoseList, pose, index); 5695 } 5696 } 5697 } else { 5698 // also must watch for renames on zombies 5699 Model* zombie = FindZombie(&itemNode, &index); 5700 if (zombie) { 5701 PRINT(("converting model %s from a zombie\n", zombie->Name())); 5702 zombie->UpdateEntryRef(&dirNode, name); 5703 pose = ConvertZombieToPose(zombie, index); 5704 } else 5705 return false; 5706 } 5707 if (pose != NULL) 5708 pendingNodeMonitorCache.PoseCreatedOrMoved(this, pose); 5709 } else if (oldDir == thisDirNode.node) 5710 DeletePose(&itemNode); 5711 else if (dirNode.node == thisDirNode.node) 5712 EntryCreated(&dirNode, &itemNode, name); 5713 5714 TryUpdatingBrokenLinks(); 5715 5716 return true; 5717 } 5718 5719 5720 void 5721 BPoseView::WatchParentOf(const entry_ref* ref) 5722 { 5723 BPath currentDir(ref); 5724 currentDir.GetParent(¤tDir); 5725 BSymLink symlink(ref); 5726 BPath path; 5727 5728 symlink.MakeLinkedPath(currentDir.Path(), &path); 5729 status_t status = path.GetParent(&path); 5730 5731 while (status == B_BAD_VALUE) 5732 status = path.GetParent(&path); 5733 5734 if (status == B_ENTRY_NOT_FOUND) 5735 return; 5736 5737 node_ref nref; 5738 BNode(path.Path()).GetNodeRef(&nref); 5739 5740 if (nref != *TargetModel()->NodeRef()) 5741 watch_node(&nref, B_WATCH_DIRECTORY, this); 5742 } 5743 5744 5745 void 5746 BPoseView::StopWatchingParentsOf(const entry_ref* ref) 5747 { 5748 BPath path; 5749 BSymLink symlink(ref); 5750 BPath currentDir(ref); 5751 currentDir.GetParent(¤tDir); 5752 symlink.MakeLinkedPath(currentDir.Path(), &path); 5753 5754 if (path.InitCheck() != B_OK) 5755 return; 5756 5757 BObjectList<Model>* brokenLinksCopy = new BObjectList<Model>(*fBrokenLinks); 5758 int32 count = brokenLinksCopy->CountItems(); 5759 5760 while (path.GetParent(&path) == B_OK) { 5761 if (strcmp(path.Path(), "/") == 0) 5762 break; 5763 5764 BNode dir(path.Path()); 5765 node_ref dirNode; 5766 dir.GetNodeRef(&dirNode); 5767 5768 // don't stop watching yourself. 5769 if (dirNode == *TargetModel()->NodeRef()) 5770 continue; 5771 5772 // make sure we don't have another broken links that still requires 5773 // to watch this directory 5774 bool keep = false; 5775 for (int32 i = count - 1; i >= 0; i--) { 5776 BSymLink link(brokenLinksCopy->ItemAt(i)->EntryRef()); 5777 BPath absolutePath; 5778 link.MakeLinkedPath(currentDir.Path(), &absolutePath); 5779 if (BString(absolutePath.Path()).Compare(path.Path(), 5780 strlen(path.Path())) == 0) { 5781 // a broken link still needs to watch this folder, but 5782 // don't let that same link also watch a deeper parent. 5783 brokenLinksCopy->RemoveItemAt(i); 5784 count--; 5785 keep = true; 5786 } 5787 } 5788 if (!keep) 5789 watch_node(&dirNode, B_STOP_WATCHING, this); 5790 } 5791 delete brokenLinksCopy; 5792 } 5793 5794 5795 bool 5796 BPoseView::AttributeChanged(const BMessage* message) 5797 { 5798 node_ref itemNode; 5799 message->FindInt32("device", &itemNode.device); 5800 message->FindInt64("node", (int64*)&itemNode.node); 5801 5802 const char* attrName; 5803 if (message->FindString("attr", &attrName) != B_OK) 5804 attrName = NULL; 5805 5806 Model* targetModel = TargetModel(); 5807 if (targetModel != NULL && *targetModel->NodeRef() == itemNode 5808 && targetModel->IsNodeOpen() 5809 && targetModel->AttrChanged(attrName)) { 5810 // the icon of our target has changed, update drag icon 5811 // TODO: make this simpler (i.e. store the icon with the window) 5812 BView* view = Window()->FindView("MenuBar"); 5813 if (view != NULL) { 5814 view = view->FindView("ThisContainer"); 5815 if (view != NULL) { 5816 IconCache::sIconCache->IconChanged(targetModel); 5817 view->Invalidate(); 5818 } 5819 } 5820 } 5821 5822 int32 index; 5823 attr_info info; 5824 PoseList* posesFound = fPoseList->FindAllPoses(&itemNode); 5825 int32 posesCount = posesFound->CountItems(); 5826 for (int i = 0; i < posesCount; i++) { 5827 BPose* pose = posesFound->ItemAt(i); 5828 Model* poseModel = pose->TargetModel(); 5829 if (poseModel->IsSymLink() && *(poseModel->NodeRef()) != itemNode) { 5830 // change happened on symlink's target 5831 poseModel = poseModel->ResolveIfLink(); 5832 } 5833 ASSERT(poseModel != NULL); 5834 5835 status_t result = B_OK; 5836 for (int32 count = 0; count < 100; count++) { 5837 // if node is busy, wait a little, it may be in the 5838 // middle of mimeset and we wan't to pick up the changes 5839 result = poseModel->OpenNode(); 5840 if (result == B_OK || result != B_BUSY) 5841 break; 5842 5843 PRINT(("poseModel %s busy, retrying in a bit\n", 5844 poseModel->Name())); 5845 snooze(10000); 5846 } 5847 if (result != B_OK) { 5848 PRINT(("Cache Error %s\n", strerror(result))); 5849 continue; 5850 } 5851 5852 bool visible = fPoseList->FindPose(poseModel->NodeRef(), 5853 &index) != NULL; 5854 int32 poseListIndex = index; 5855 5856 if (fFiltering) { 5857 visible = fFilteredPoseList->FindPose( 5858 poseModel->NodeRef(), &index) != NULL; 5859 } 5860 5861 BPoint loc(0, index * fListElemHeight); 5862 if (attrName != NULL && poseModel->Node() != NULL) { 5863 memset(&info, 0, sizeof(attr_info)); 5864 // the call below might fail if the attribute has been removed 5865 poseModel->Node()->GetAttrInfo(attrName, &info); 5866 pose->UpdateWidgetAndModel(poseModel, attrName, info.type, index, 5867 loc, this, visible); 5868 if (strcmp(attrName, kAttrMIMEType) == 0) 5869 RefreshMimeTypeList(); 5870 } else { 5871 pose->UpdateWidgetAndModel(poseModel, 0, 0, index, loc, this, 5872 visible); 5873 } 5874 poseModel->CloseNode(); 5875 if (fFiltering) { 5876 if (!visible && FilterPose(pose)) { 5877 visible = true; 5878 float scrollBy = 0; 5879 BRect bounds = Bounds(); 5880 AddPoseToList(fFilteredPoseList, true, true, pose, bounds, 5881 scrollBy, true); 5882 continue; 5883 } else if (visible && !FilterPose(pose)) { 5884 RemoveFilteredPose(pose, index); 5885 continue; 5886 } 5887 } 5888 5889 if (attrName != NULL) { 5890 // note: the following code is wrong, because this sort of hashing 5891 // may overlap and we get aliasing 5892 uint32 attrHash = AttrHashString(attrName, info.type); 5893 if (attrHash == PrimarySort() || attrHash == SecondarySort()) { 5894 _CheckPoseSortOrder(fPoseList, pose, poseListIndex); 5895 if (fFiltering && visible) 5896 _CheckPoseSortOrder(fFilteredPoseList, pose, index); 5897 } 5898 } else { 5899 int32 fields; 5900 if (message->FindInt32("fields", &fields) != B_OK) 5901 continue; 5902 5903 for (int i = sizeof(sAttrColumnMap) / sizeof(attr_column_relation); 5904 i--;) { 5905 if (sAttrColumnMap[i].attrHash == PrimarySort() 5906 || sAttrColumnMap[i].attrHash == SecondarySort()) { 5907 if ((fields & sAttrColumnMap[i].fieldMask) != 0) { 5908 _CheckPoseSortOrder(fPoseList, pose, poseListIndex); 5909 if (fFiltering && visible) 5910 _CheckPoseSortOrder(fFilteredPoseList, pose, index); 5911 break; 5912 } 5913 } 5914 } 5915 } 5916 } 5917 delete posesFound; 5918 if (posesCount == 0) { 5919 // we received an attr changed notification for a zombie model, it means 5920 // that although we couldn't open the node the first time, it seems 5921 // to be fine now since we're receiving notifications about it, it might 5922 // be a good time to convert it to a non-zombie state. cf. test in #4130 5923 Model* zombie = FindZombie(&itemNode, &index); 5924 if (zombie != NULL) { 5925 PRINT(("converting model %s from a zombie\n", zombie->Name())); 5926 return ConvertZombieToPose(zombie, index) != NULL; 5927 } else { 5928 PRINT(("model has no pose but is not a zombie either!\n")); 5929 return false; 5930 } 5931 } 5932 5933 return true; 5934 } 5935 5936 5937 void 5938 BPoseView::UpdateIcon(BPose* pose) 5939 { 5940 BPoint location; 5941 if (ViewMode() == kListMode) { 5942 // need to find the index of the pose in the pose list 5943 bool found = false; 5944 PoseList* poseList = CurrentPoseList(); 5945 int32 count = poseList->CountItems(); 5946 for (int32 index = 0; index < count; index++) { 5947 if (poseList->ItemAt(index) == pose) { 5948 location.Set(0, index * fListElemHeight); 5949 found = true; 5950 break; 5951 } 5952 } 5953 5954 if (!found) 5955 return; 5956 } 5957 5958 pose->UpdateIcon(location, this); 5959 } 5960 5961 5962 BPose* 5963 BPoseView::ConvertZombieToPose(Model* zombie, int32 index) 5964 { 5965 if (zombie->UpdateStatAndOpenNode() != B_OK) 5966 return NULL; 5967 5968 fZombieList->RemoveItemAt(index); 5969 5970 PoseInfo poseInfo; 5971 ReadPoseInfo(zombie, &poseInfo); 5972 5973 if (ShouldShowPose(zombie, &poseInfo)) { 5974 // TODO: handle symlinks here 5975 return CreatePose(zombie, &poseInfo); 5976 } 5977 5978 delete zombie; 5979 5980 return NULL; 5981 } 5982 5983 5984 BList* 5985 BPoseView::GetDropPointList(BPoint dropStart, BPoint dropEnd, const PoseList* poses, 5986 bool sourceInListMode, bool dropOnGrid) const 5987 { 5988 if (ViewMode() == kListMode) 5989 return NULL; 5990 5991 int32 count = poses->CountItems(); 5992 BList* pointList = new BList(count); 5993 for (int32 index = 0; index < count; index++) { 5994 BPose* pose = poses->ItemAt(index); 5995 BPoint poseLoc; 5996 if (sourceInListMode) 5997 poseLoc = dropEnd + BPoint(0, index * (IconPoseHeight() + 3)); 5998 else 5999 poseLoc = dropEnd + (pose->Location(this) - dropStart); 6000 6001 if (dropOnGrid) 6002 poseLoc = PinToGrid(poseLoc, fGrid, fOffset); 6003 6004 pointList->AddItem(new BPoint(poseLoc)); 6005 } 6006 6007 return pointList; 6008 } 6009 6010 6011 void 6012 BPoseView::DuplicateSelection(BPoint* dropStart, BPoint* dropEnd) 6013 { 6014 // If there is a volume or trash folder, remove them from the list 6015 // because they cannot get copied 6016 int32 selectionSize = fSelectionList->CountItems(); 6017 for (int32 index = 0; index < selectionSize; index++) { 6018 BPose* pose = (BPose*)fSelectionList->ItemAt(index); 6019 Model* poseModel = pose->TargetModel(); 6020 6021 // can't duplicate a volume or the trash 6022 if (poseModel->IsTrash() || poseModel->IsVolume()) { 6023 fSelectionList->RemoveItemAt(index); 6024 index--; 6025 selectionSize--; 6026 if (fSelectionPivotPose == pose) 6027 fSelectionPivotPose = NULL; 6028 6029 if (fRealPivotPose == pose) 6030 fRealPivotPose = NULL; 6031 6032 continue; 6033 } 6034 } 6035 6036 // create entry_ref list from selection 6037 if (!fSelectionList->IsEmpty()) { 6038 BObjectList<entry_ref>* srcList = new BObjectList<entry_ref>( 6039 fSelectionList->CountItems(), true); 6040 CopySelectionListToEntryRefList(fSelectionList, srcList); 6041 6042 BList* dropPoints; 6043 if (dropStart) { 6044 dropPoints = GetDropPointList(*dropStart, *dropEnd, fSelectionList, 6045 ViewMode() == kListMode, (modifiers() & B_COMMAND_KEY) != 0); 6046 } else 6047 dropPoints = NULL; 6048 6049 // perform asynchronous duplicate 6050 FSDuplicate(srcList, dropPoints); 6051 } 6052 } 6053 6054 6055 void 6056 BPoseView::SelectPoseAtLocation(BPoint point) 6057 { 6058 int32 index; 6059 BPose* pose = FindPose(point, &index); 6060 if (pose != NULL) 6061 SelectPose(pose, index); 6062 } 6063 6064 6065 void 6066 BPoseView::MoveListToTrash(BObjectList<entry_ref>* list, bool selectNext, 6067 bool deleteDirectly) 6068 { 6069 if (!list->CountItems()) 6070 return; 6071 6072 BObjectList<FunctionObject>* taskList = 6073 new BObjectList<FunctionObject>(2, true); 6074 // new owning list of tasks 6075 6076 // first move selection to trash, 6077 if (deleteDirectly) { 6078 taskList->AddItem(NewFunctionObject(FSDeleteRefList, list, 6079 false, true)); 6080 } else { 6081 taskList->AddItem(NewFunctionObject(FSMoveToTrash, list, 6082 (BList*)NULL, false)); 6083 } 6084 6085 if (selectNext && ViewMode() == kListMode) { 6086 // next, if in list view mode try selecting the next item after 6087 BPose* pose = fSelectionList->ItemAt(0); 6088 6089 // find a point in the pose 6090 BPoint pointInPose(kListOffset + 5, 5); 6091 int32 index = IndexOfPose(pose); 6092 pointInPose.y += fListElemHeight * index; 6093 6094 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 6095 if (tracker != NULL) { 6096 ThrowOnAssert(TargetModel() != NULL); 6097 6098 // add a function object to the list of tasks to run 6099 // that will select the next item after the one we just 6100 // deleted 6101 taskList->AddItem(NewMemberFunctionObject( 6102 &TTracker::SelectPoseAtLocationSoon, tracker, 6103 *TargetModel()->NodeRef(), pointInPose)); 6104 } 6105 } 6106 // execute the two tasks in order 6107 ThreadSequence::Launch(taskList, true); 6108 } 6109 6110 6111 inline void 6112 CopyOneTrashedRefAsEntry(const entry_ref* ref, BObjectList<entry_ref>* trashList, 6113 BObjectList<entry_ref>* noTrashList, std::map<int32, bool>* deviceHasTrash) 6114 { 6115 std::map<int32, bool> &deviceHasTrashTmp = *deviceHasTrash; 6116 // work around stupid binding problems with EachListItem 6117 6118 BDirectory entryDir(ref); 6119 bool isVolume = entryDir.IsRootDirectory(); 6120 // volumes will get unmounted 6121 6122 // see if pose's device has a trash 6123 int32 device = ref->device; 6124 BDirectory trashDir; 6125 6126 // cache up the result in a map so that we don't have to keep calling 6127 // FSGetTrashDir over and over 6128 if (!isVolume 6129 && deviceHasTrashTmp.find(device) == deviceHasTrashTmp.end()) { 6130 deviceHasTrashTmp[device] = FSGetTrashDir(&trashDir, device) == B_OK; 6131 } 6132 6133 if (isVolume || deviceHasTrashTmp[device]) 6134 trashList->AddItem(new entry_ref(*ref)); 6135 else 6136 noTrashList->AddItem(new entry_ref(*ref)); 6137 } 6138 6139 6140 static void 6141 CopyPoseOneAsEntry(BPose* pose, BObjectList<entry_ref>* trashList, 6142 BObjectList<entry_ref>* noTrashList, std::map<int32, bool>* deviceHasTrash) 6143 { 6144 CopyOneTrashedRefAsEntry(pose->TargetModel()->EntryRef(), trashList, 6145 noTrashList, deviceHasTrash); 6146 } 6147 6148 6149 static bool 6150 CheckVolumeReadOnly(const entry_ref* ref) 6151 { 6152 BVolume volume (ref->device); 6153 if (volume.IsReadOnly()) { 6154 BAlert* alert = new BAlert ("", 6155 B_TRANSLATE("Files cannot be moved or deleted from a read-only " 6156 "volume."), B_TRANSLATE("Cancel"), NULL, NULL, B_WIDTH_AS_USUAL, 6157 B_STOP_ALERT); 6158 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 6159 alert->Go(); 6160 return false; 6161 } 6162 6163 return true; 6164 } 6165 6166 6167 void 6168 BPoseView::MoveSelectionOrEntryToTrash(const entry_ref* ref, bool selectNext) 6169 { 6170 BObjectList<entry_ref>* entriesToTrash = new 6171 BObjectList<entry_ref>(fSelectionList->CountItems()); 6172 BObjectList<entry_ref>* entriesToDeleteOnTheSpot = new 6173 BObjectList<entry_ref>(20, true); 6174 std::map<int32, bool> deviceHasTrash; 6175 6176 if (ref != NULL) { 6177 if (!CheckVolumeReadOnly(ref)) { 6178 delete entriesToTrash; 6179 delete entriesToDeleteOnTheSpot; 6180 return; 6181 } 6182 CopyOneTrashedRefAsEntry(ref, entriesToTrash, entriesToDeleteOnTheSpot, 6183 &deviceHasTrash); 6184 } else { 6185 if (!CheckVolumeReadOnly( 6186 fSelectionList->ItemAt(0)->TargetModel()->EntryRef())) { 6187 delete entriesToTrash; 6188 delete entriesToDeleteOnTheSpot; 6189 return; 6190 } 6191 EachListItem(fSelectionList, CopyPoseOneAsEntry, entriesToTrash, 6192 entriesToDeleteOnTheSpot, &deviceHasTrash); 6193 } 6194 6195 if (entriesToDeleteOnTheSpot->CountItems()) { 6196 BString alertText; 6197 if (ref != NULL) { 6198 alertText.SetTo(B_TRANSLATE("The selected item cannot be moved to " 6199 "the Trash. Would you like to delete it instead? " 6200 "(This operation cannot be reverted.)")); 6201 } else { 6202 alertText.SetTo(B_TRANSLATE("Some of the selected items cannot be " 6203 "moved to the Trash. Would you like to delete them instead? " 6204 "(This operation cannot be reverted.)")); 6205 } 6206 6207 BAlert* alert = new BAlert("", alertText.String(), 6208 B_TRANSLATE("Cancel"), B_TRANSLATE("Delete")); 6209 alert->SetShortcut(0, B_ESCAPE); 6210 if (alert->Go() == 0) 6211 return; 6212 } 6213 6214 MoveListToTrash(entriesToTrash, selectNext, false); 6215 MoveListToTrash(entriesToDeleteOnTheSpot, selectNext, true); 6216 } 6217 6218 6219 void 6220 BPoseView::MoveSelectionToTrash(bool selectNext) 6221 { 6222 if (fSelectionList->IsEmpty()) 6223 return; 6224 6225 // create entry_ref list from selection 6226 // separate items that can be trashed from ones that cannot 6227 6228 MoveSelectionOrEntryToTrash(0, selectNext); 6229 } 6230 6231 6232 void 6233 BPoseView::MoveEntryToTrash(const entry_ref* ref, bool selectNext) 6234 { 6235 MoveSelectionOrEntryToTrash(ref, selectNext); 6236 } 6237 6238 6239 void 6240 BPoseView::DeleteSelection(bool selectNext, bool askUser) 6241 { 6242 int32 count = fSelectionList->CountItems(); 6243 if (count <= 0) 6244 return; 6245 6246 if (!CheckVolumeReadOnly( 6247 fSelectionList->ItemAt(0)->TargetModel()->EntryRef())) { 6248 return; 6249 } 6250 6251 BObjectList<entry_ref>* entriesToDelete 6252 = new BObjectList<entry_ref>(count, true); 6253 6254 for (int32 index = 0; index < count; index++) { 6255 entriesToDelete->AddItem(new entry_ref( 6256 *fSelectionList->ItemAt(index)->TargetModel()->EntryRef())); 6257 } 6258 6259 Delete(entriesToDelete, selectNext, askUser); 6260 } 6261 6262 6263 void 6264 BPoseView::RestoreSelectionFromTrash(bool selectNext) 6265 { 6266 int32 count = fSelectionList -> CountItems(); 6267 if (count <= 0) 6268 return; 6269 6270 BObjectList<entry_ref>* entriesToRestore 6271 = new BObjectList<entry_ref>(count, true); 6272 6273 for (int32 index = 0; index < count; index++) { 6274 entriesToRestore->AddItem(new entry_ref( 6275 *fSelectionList->ItemAt(index)->TargetModel()->EntryRef())); 6276 } 6277 6278 RestoreItemsFromTrash(entriesToRestore, selectNext); 6279 } 6280 6281 6282 void 6283 BPoseView::Delete(const entry_ref &ref, bool selectNext, bool askUser) 6284 { 6285 BObjectList<entry_ref>* entriesToDelete 6286 = new BObjectList<entry_ref>(1, true); 6287 entriesToDelete->AddItem(new entry_ref(ref)); 6288 6289 Delete(entriesToDelete, selectNext, askUser); 6290 } 6291 6292 6293 void 6294 BPoseView::Delete(BObjectList<entry_ref>* list, bool selectNext, bool askUser) 6295 { 6296 if (list->CountItems() == 0) { 6297 delete list; 6298 return; 6299 } 6300 6301 BObjectList<FunctionObject>* taskList = 6302 new BObjectList<FunctionObject>(2, true); 6303 6304 // first move selection to trash, 6305 taskList->AddItem(NewFunctionObject(FSDeleteRefList, list, false, askUser)); 6306 6307 if (selectNext && ViewMode() == kListMode) { 6308 // next, if in list view mode try selecting the next item after 6309 BPose* pose = fSelectionList->ItemAt(0); 6310 6311 // find a point in the pose 6312 BPoint pointInPose(kListOffset + 5, 5); 6313 int32 index = IndexOfPose(pose); 6314 pointInPose.y += fListElemHeight * index; 6315 6316 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 6317 if (tracker != NULL) { 6318 ThrowOnAssert(TargetModel() != NULL); 6319 6320 // add a function object to the list of tasks to run 6321 // that will select the next item after the one we just 6322 // deleted 6323 Model* targetModel = TargetModel(); 6324 ASSERT(targetModel != NULL); 6325 taskList->AddItem(NewMemberFunctionObject( 6326 &TTracker::SelectPoseAtLocationSoon, tracker, 6327 *targetModel->NodeRef(), pointInPose)); 6328 } 6329 } 6330 6331 // execute the two tasks in order 6332 ThreadSequence::Launch(taskList, true); 6333 } 6334 6335 6336 void 6337 BPoseView::RestoreItemsFromTrash(BObjectList<entry_ref>* list, bool selectNext) 6338 { 6339 if (list->CountItems() == 0) { 6340 delete list; 6341 return; 6342 } 6343 6344 BObjectList<FunctionObject>* taskList = 6345 new BObjectList<FunctionObject>(2, true); 6346 6347 // first restoree selection 6348 taskList->AddItem(NewFunctionObject(FSRestoreRefList, list, false)); 6349 6350 if (selectNext && ViewMode() == kListMode) { 6351 // next, if in list view mode try selecting the next item after 6352 BPose* pose = fSelectionList->ItemAt(0); 6353 6354 // find a point in the pose 6355 BPoint pointInPose(kListOffset + 5, 5); 6356 int32 index = IndexOfPose(pose); 6357 pointInPose.y += fListElemHeight * index; 6358 6359 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 6360 if (tracker != NULL) { 6361 ThrowOnAssert(TargetModel() != NULL); 6362 6363 // add a function object to the list of tasks to run 6364 // that will select the next item after the one we just 6365 // restored 6366 Model* targetModel = TargetModel(); 6367 ASSERT(targetModel != NULL); 6368 taskList->AddItem(NewMemberFunctionObject( 6369 &TTracker::SelectPoseAtLocationSoon, tracker, 6370 *targetModel->NodeRef(), pointInPose)); 6371 } 6372 } 6373 6374 // execute the two tasks in order 6375 ThreadSequence::Launch(taskList, true); 6376 } 6377 6378 6379 void 6380 BPoseView::SelectAll() 6381 { 6382 BRect bounds(Bounds()); 6383 6384 // clear selection list 6385 fSelectionList->MakeEmpty(); 6386 fMimeTypesInSelectionCache.MakeEmpty(); 6387 fSelectionPivotPose = NULL; 6388 fRealPivotPose = NULL; 6389 6390 int32 startIndex = 0; 6391 BPoint loc(0, fListElemHeight * startIndex); 6392 6393 bool iconMode = ViewMode() != kListMode; 6394 6395 PoseList* poseList = CurrentPoseList(); 6396 int32 count = poseList->CountItems(); 6397 for (int32 index = startIndex; index < count; index++) { 6398 BPose* pose = poseList->ItemAt(index); 6399 fSelectionList->AddItem(pose); 6400 if (index == startIndex) 6401 fSelectionPivotPose = pose; 6402 6403 if (!pose->IsSelected()) { 6404 pose->Select(true); 6405 6406 BRect poseRect; 6407 if (iconMode) 6408 poseRect = pose->CalcRect(this); 6409 else 6410 poseRect = pose->CalcRect(loc, this); 6411 6412 if (bounds.Intersects(poseRect)) { 6413 pose->Draw(poseRect, bounds, this, false); 6414 Flush(); 6415 } 6416 } 6417 6418 loc.y += fListElemHeight; 6419 } 6420 6421 if (fSelectionChangedHook) 6422 ContainerWindow()->SelectionChanged(); 6423 } 6424 6425 6426 void 6427 BPoseView::InvertSelection() 6428 { 6429 // Since this function shares most code with 6430 // SelectAll(), we could make SelectAll() empty the selection, 6431 // then call InvertSelection() 6432 6433 BRect bounds(Bounds()); 6434 6435 int32 startIndex = 0; 6436 BPoint loc(0, fListElemHeight * startIndex); 6437 6438 fMimeTypesInSelectionCache.MakeEmpty(); 6439 fSelectionPivotPose = NULL; 6440 fRealPivotPose = NULL; 6441 6442 bool iconMode = ViewMode() != kListMode; 6443 6444 PoseList* poseList = CurrentPoseList(); 6445 int32 count = poseList->CountItems(); 6446 for (int32 index = startIndex; index < count; index++) { 6447 BPose* pose = poseList->ItemAt(index); 6448 6449 if (pose->IsSelected()) { 6450 fSelectionList->RemoveItem(pose); 6451 pose->Select(false); 6452 } else { 6453 if (index == startIndex) 6454 fSelectionPivotPose = pose; 6455 6456 fSelectionList->AddItem(pose); 6457 pose->Select(true); 6458 } 6459 6460 BRect poseRect; 6461 if (iconMode) 6462 poseRect = pose->CalcRect(this); 6463 else 6464 poseRect = pose->CalcRect(loc, this); 6465 6466 if (bounds.Intersects(poseRect)) 6467 Invalidate(); 6468 6469 loc.y += fListElemHeight; 6470 } 6471 6472 if (fSelectionChangedHook) 6473 ContainerWindow()->SelectionChanged(); 6474 } 6475 6476 6477 int32 6478 BPoseView::SelectMatchingEntries(const BMessage* message) 6479 { 6480 int32 matchCount = 0; 6481 SetMultipleSelection(true); 6482 6483 ClearSelection(); 6484 6485 TrackerStringExpressionType expressionType; 6486 BString expression; 6487 const char* expressionPointer; 6488 bool invertSelection; 6489 bool ignoreCase; 6490 6491 message->FindInt32("ExpressionType", (int32*)&expressionType); 6492 message->FindString("Expression", &expressionPointer); 6493 message->FindBool("InvertSelection", &invertSelection); 6494 message->FindBool("IgnoreCase", &ignoreCase); 6495 6496 expression = expressionPointer; 6497 6498 PoseList* poseList = CurrentPoseList(); 6499 int32 count = poseList->CountItems(); 6500 TrackerString name; 6501 6502 RegExp regExpression; 6503 6504 // Make sure we don't have any errors in the expression 6505 // before we match the names: 6506 if (expressionType == kRegexpMatch) { 6507 regExpression.SetTo(expression); 6508 6509 if (regExpression.InitCheck() != B_OK) { 6510 BString message( 6511 B_TRANSLATE("Error in regular expression:\n\n'%errstring'")); 6512 message.ReplaceFirst("%errstring", regExpression.ErrorString()); 6513 BAlert* alert = new BAlert("", message.String(), B_TRANSLATE("OK"), 6514 NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT); 6515 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 6516 alert->Go(); 6517 return 0; 6518 } 6519 } 6520 6521 // There is room for optimizations here: If regexp-type match, the Matches() 6522 // function compiles the expression for every entry. One could use 6523 // TrackerString::CompileRegExp and reuse the expression. However, then we 6524 // have to take care of the case sensitivity ourselves. 6525 for (int32 index = 0; index < count; index++) { 6526 BPose* pose = poseList->ItemAt(index); 6527 name = pose->TargetModel()->Name(); 6528 if (name.Matches(expression.String(), !ignoreCase, expressionType) 6529 ^ invertSelection) { 6530 matchCount++; 6531 AddPoseToSelection(pose, index); 6532 } 6533 } 6534 6535 Window()->Activate(); 6536 // Make sure the window is activated for 6537 // subsequent manipulations. Esp. needed 6538 // for the Desktop window. 6539 6540 return matchCount; 6541 } 6542 6543 6544 void 6545 BPoseView::ShowSelectionWindow() 6546 { 6547 Window()->PostMessage(kShowSelectionWindow); 6548 } 6549 6550 6551 void 6552 BPoseView::KeyDown(const char* bytes, int32 count) 6553 { 6554 char key = bytes[0]; 6555 6556 switch (key) { 6557 case B_LEFT_ARROW: 6558 case B_RIGHT_ARROW: 6559 case B_UP_ARROW: 6560 case B_DOWN_ARROW: 6561 { 6562 int32 index; 6563 BPose* pose = FindNearbyPose(key, &index); 6564 if (pose == NULL) 6565 break; 6566 6567 if (fMultipleSelection && (modifiers() & B_SHIFT_KEY) != 0) { 6568 if (pose->IsSelected()) { 6569 RemovePoseFromSelection(fSelectionList->LastItem()); 6570 fSelectionPivotPose = pose; 6571 ScrollIntoView(pose, index); 6572 } else 6573 AddPoseToSelection(pose, index, true); 6574 } else 6575 SelectPose(pose, index); 6576 6577 break; 6578 } 6579 6580 case B_RETURN: 6581 if (fFiltering && fSelectionList->CountItems() == 0) 6582 SelectPose(fFilteredPoseList->FirstItem(), 0); 6583 6584 OpenSelection(); 6585 6586 if (fFiltering && (modifiers() & B_SHIFT_KEY) != 0) 6587 StopFiltering(); 6588 6589 break; 6590 6591 case B_HOME: 6592 // select the first entry (if in listview mode), and 6593 // scroll to the top of the view 6594 if (ViewMode() == kListMode) 6595 MoveOrChangePoseSelection(0); 6596 else 6597 ScrollView(B_HOME); 6598 break; 6599 6600 case B_END: 6601 // select the last entry (if in listview mode), and 6602 // scroll to the bottom of the view 6603 if (ViewMode() == kListMode) 6604 MoveOrChangePoseSelection(CurrentPoseList()->CountItems() - 1); 6605 else 6606 ScrollView(B_END); 6607 break; 6608 6609 case B_PAGE_UP: 6610 if (ViewMode() == kListMode) { 6611 // Select first visible pose 6612 int32 firstIndex = CurrentPoseList()->IndexOf( 6613 fSelectionList->FirstItem()); 6614 int32 index; 6615 BPose* first = FirstVisiblePose(&index); 6616 if (first != NULL) { 6617 if (index == firstIndex) { 6618 ScrollView(B_PAGE_UP); 6619 first = FirstVisiblePose(&index); 6620 } 6621 MoveOrChangePoseSelection(index); 6622 } 6623 } else 6624 ScrollView(B_PAGE_UP); 6625 break; 6626 6627 case B_PAGE_DOWN: 6628 if (ViewMode() == kListMode) { 6629 // Select last visible pose 6630 int32 lastIndex = CurrentPoseList()->IndexOf( 6631 fSelectionList->LastItem()); 6632 int32 index; 6633 BPose* last = LastVisiblePose(&index); 6634 if (last != NULL) { 6635 if (index == lastIndex) { 6636 ScrollView(B_PAGE_DOWN); 6637 last = LastVisiblePose(&index); 6638 } 6639 MoveOrChangePoseSelection(index); 6640 } 6641 } else 6642 ScrollView(B_PAGE_DOWN); 6643 break; 6644 6645 case B_TAB: 6646 if (IsFilePanel()) 6647 _inherited::KeyDown(bytes, count); 6648 else { 6649 if (ViewMode() == kListMode 6650 && TrackerSettings().TypeAheadFiltering()) { 6651 break; 6652 } 6653 6654 if (fSelectionList->IsEmpty()) 6655 sMatchString.Truncate(0); 6656 else { 6657 BPose* pose = fSelectionList->FirstItem(); 6658 sMatchString.SetTo(pose->TargetModel()->Name()); 6659 } 6660 6661 bool reverse 6662 = (Window()->CurrentMessage()->FindInt32("modifiers") 6663 & B_SHIFT_KEY) != 0; 6664 int32 index; 6665 BPose* pose = FindNextMatch(&index, reverse); 6666 if (pose == NULL) { 6667 // wrap around 6668 if (reverse) 6669 sMatchString.SetTo(0x7f, 1); 6670 else 6671 sMatchString.Truncate(0); 6672 6673 pose = FindNextMatch(&index, reverse); 6674 } 6675 6676 SelectPose(pose, index); 6677 } 6678 break; 6679 6680 case B_DELETE: 6681 { 6682 ExcludeTrashFromSelection(); 6683 if (TargetModel() == NULL) { 6684 // Happens if called from within OpenWith window, for example 6685 break; 6686 } 6687 // Make sure user can't trash something already in the trash. 6688 if (TargetModel()->IsTrash()) { 6689 // Delete without asking from the trash 6690 DeleteSelection(true, false); 6691 } else { 6692 TrackerSettings settings; 6693 6694 if ((modifiers() & B_SHIFT_KEY) != 0 6695 || settings.DontMoveFilesToTrash()) { 6696 DeleteSelection(true, settings.AskBeforeDeleteFile()); 6697 } else 6698 MoveSelectionToTrash(); 6699 } 6700 break; 6701 } 6702 6703 case B_BACKSPACE: 6704 { 6705 if (fFiltering) { 6706 BString* lastString = fFilterStrings.LastItem(); 6707 if (lastString->Length() == 0) { 6708 int32 stringCount = fFilterStrings.CountItems(); 6709 if (stringCount > 1) 6710 delete fFilterStrings.RemoveItemAt(stringCount - 1); 6711 else 6712 break; 6713 } else 6714 lastString->TruncateChars(lastString->CountChars() - 1); 6715 6716 fCountView->RemoveFilterCharacter(); 6717 FilterChanged(); 6718 break; 6719 } 6720 6721 if (sMatchString.Length() == 0) 6722 break; 6723 6724 // remove last char from the typeahead buffer 6725 sMatchString.TruncateChars(sMatchString.CountChars() - 1); 6726 6727 fLastKeyTime = system_time(); 6728 6729 fCountView->SetTypeAhead(sMatchString.String()); 6730 6731 // select our new string 6732 int32 index; 6733 BPose* pose = FindBestMatch(&index); 6734 if (pose == NULL) 6735 break; 6736 6737 SelectPose(pose, index); 6738 break; 6739 } 6740 6741 case B_FUNCTION_KEY: 6742 { 6743 BMessage* message = Window()->CurrentMessage(); 6744 if (message != NULL) { 6745 int32 key; 6746 if (message->FindInt32("key", &key) == B_OK && key == B_F2_KEY) 6747 Window()->PostMessage(kEditItem, this); 6748 } 6749 break; 6750 } 6751 6752 case B_INSERT: 6753 break; 6754 6755 default: 6756 { 6757 // handle typeahead selection / filtering 6758 6759 if (ViewMode() == kListMode 6760 && TrackerSettings().TypeAheadFiltering()) { 6761 if (key == ' ' && modifiers() & B_SHIFT_KEY) { 6762 if (fFilterStrings.LastItem()->Length() == 0) 6763 break; 6764 6765 fFilterStrings.AddItem(new BString()); 6766 fCountView->AddFilterCharacter("|"); 6767 break; 6768 } 6769 6770 fFilterStrings.LastItem()->AppendChars(bytes, 1); 6771 fCountView->AddFilterCharacter(bytes); 6772 FilterChanged(); 6773 break; 6774 } 6775 6776 bigtime_t doubleClickSpeed; 6777 get_click_speed(&doubleClickSpeed); 6778 6779 // start watching 6780 if (fKeyRunner == NULL) { 6781 fKeyRunner = new BMessageRunner(this, 6782 new BMessage(kCheckTypeahead), doubleClickSpeed); 6783 if (fKeyRunner->InitCheck() != B_OK) 6784 return; 6785 } 6786 6787 // figure out the time at which the keypress happened 6788 bigtime_t eventTime; 6789 BMessage* message = Window()->CurrentMessage(); 6790 if (message == NULL 6791 || message->FindInt64("when", &eventTime) < B_OK) { 6792 eventTime = system_time(); 6793 } 6794 6795 // add char to existing matchString or start new match string 6796 if (eventTime - fLastKeyTime < (doubleClickSpeed * 2)) 6797 sMatchString.AppendChars(bytes, 1); 6798 else 6799 sMatchString.SetToChars(bytes, 1); 6800 6801 fLastKeyTime = eventTime; 6802 6803 fCountView->SetTypeAhead(sMatchString.String()); 6804 6805 int32 index; 6806 BPose* pose = FindBestMatch(&index); 6807 if (pose == NULL) 6808 break; 6809 6810 SelectPose(pose, index); 6811 break; 6812 } 6813 } 6814 } 6815 6816 6817 BPose* 6818 BPoseView::FindNextMatch(int32* matchingIndex, bool reverse) 6819 { 6820 char bestSoFar[B_FILE_NAME_LENGTH] = { 0 }; 6821 BPose* poseToSelect = NULL; 6822 6823 // loop through all poses to find match 6824 int32 count = fPoseList->CountItems(); 6825 for (int32 index = 0; index < count; index++) { 6826 BPose* pose = fPoseList->ItemAt(index); 6827 6828 if (reverse) { 6829 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 } else if (sMatchString.ICompare(pose->TargetModel()->Name()) < 0) { 6839 if (strcasecmp(pose->TargetModel()->Name(), bestSoFar) <= 0 6840 || !bestSoFar[0]) { 6841 strlcpy(bestSoFar, pose->TargetModel()->Name(), 6842 sizeof(bestSoFar)); 6843 poseToSelect = pose; 6844 *matchingIndex = index; 6845 } 6846 } 6847 } 6848 6849 return poseToSelect; 6850 } 6851 6852 6853 BPose* 6854 BPoseView::FindBestMatch(int32* index) 6855 { 6856 BPose* poseToSelect = NULL; 6857 float bestScore = -1; 6858 int32 count = fPoseList->CountItems(); 6859 6860 // loop through all poses to find match 6861 for (int32 j = 0; j < CountColumns(); j++) { 6862 BColumn* column = ColumnAt(j); 6863 6864 for (int32 i = 0; i < count; i++) { 6865 BPose* pose = fPoseList->ItemAt(i); 6866 float score = -1; 6867 6868 if (ViewMode() == kListMode) { 6869 ModelNodeLazyOpener modelOpener(pose->TargetModel()); 6870 BTextWidget* widget = pose->WidgetFor(column, this, 6871 modelOpener); 6872 const char* text = NULL; 6873 if (widget != NULL) 6874 text = widget->Text(this); 6875 6876 if (text != NULL) 6877 score = ComputeTypeAheadScore(text, sMatchString.String()); 6878 } else { 6879 score = ComputeTypeAheadScore(pose->TargetModel()->Name(), 6880 sMatchString.String()); 6881 } 6882 6883 if (score > bestScore) { 6884 poseToSelect = pose; 6885 bestScore = score; 6886 *index = i; 6887 } 6888 if (score == kExactMatchScore) 6889 break; 6890 } 6891 6892 // TODO: we might want to change this to make it always work 6893 // over all columns, but this would require some more changes 6894 // to how Tracker represents data (for example we could filter 6895 // the results out). 6896 if (bestScore > 0 || ViewMode() != kListMode) 6897 break; 6898 } 6899 6900 return poseToSelect; 6901 } 6902 6903 6904 static bool 6905 LinesIntersect(float s1, float e1, float s2, float e2) 6906 { 6907 return std::max(s1, s2) < std::min(e1, e2); 6908 } 6909 6910 6911 BPose* 6912 BPoseView::FindNearbyPose(char arrowKey, int32* poseIndex) 6913 { 6914 int32 resultingIndex = -1; 6915 BPose* poseToSelect = NULL; 6916 BPose* selectedPose = fSelectionList->LastItem(); 6917 6918 if (ViewMode() == kListMode) { 6919 PoseList* poseList = CurrentPoseList(); 6920 6921 switch (arrowKey) { 6922 case B_UP_ARROW: 6923 case B_LEFT_ARROW: 6924 if (selectedPose) { 6925 resultingIndex = poseList->IndexOf(selectedPose) - 1; 6926 poseToSelect = poseList->ItemAt(resultingIndex); 6927 if (poseToSelect == NULL && arrowKey == B_LEFT_ARROW) { 6928 resultingIndex = poseList->CountItems() - 1; 6929 poseToSelect = poseList->LastItem(); 6930 } 6931 } else { 6932 resultingIndex = poseList->CountItems() - 1; 6933 poseToSelect = poseList->LastItem(); 6934 } 6935 break; 6936 6937 case B_DOWN_ARROW: 6938 case B_RIGHT_ARROW: 6939 if (selectedPose) { 6940 resultingIndex = poseList->IndexOf(selectedPose) + 1; 6941 poseToSelect = poseList->ItemAt(resultingIndex); 6942 if (poseToSelect == NULL && arrowKey == B_RIGHT_ARROW) { 6943 resultingIndex = 0; 6944 poseToSelect = poseList->FirstItem(); 6945 } 6946 } else { 6947 resultingIndex = 0; 6948 poseToSelect = poseList->FirstItem(); 6949 } 6950 break; 6951 } 6952 *poseIndex = resultingIndex; 6953 6954 return poseToSelect; 6955 } 6956 6957 // must be in one of the icon modes 6958 6959 // handle case where there is no current selection 6960 if (fSelectionList->IsEmpty()) { 6961 // find the upper-left pose (I know it's ugly!) 6962 poseToSelect = fVSPoseList->FirstItem(); 6963 for (int32 index = 0; ;index++) { 6964 BPose* pose = fVSPoseList->ItemAt(++index); 6965 if (pose == NULL) 6966 break; 6967 6968 if (poseToSelect != NULL) { 6969 BRect selectedBounds; 6970 selectedBounds = poseToSelect->CalcRect(this); 6971 BRect poseRect(pose->CalcRect(this)); 6972 6973 if (poseRect.top > selectedBounds.top) 6974 break; 6975 6976 if (poseRect.left < selectedBounds.left) 6977 poseToSelect = pose; 6978 } 6979 } 6980 6981 return poseToSelect; 6982 } 6983 6984 BRect selectionRect; 6985 if (selectedPose != NULL) 6986 selectionRect = selectedPose->CalcRect(this); 6987 6988 BRect bestRect; 6989 6990 // we're not in list mode so scan visually for pose to select 6991 int32 count = fPoseList->CountItems(); 6992 for (int32 index = 0; index < count; index++) { 6993 BPose* pose = fPoseList->ItemAt(index); 6994 BRect poseRect(pose->CalcRect(this)); 6995 6996 switch (arrowKey) { 6997 case B_LEFT_ARROW: 6998 if (LinesIntersect(poseRect.top, poseRect.bottom, 6999 selectionRect.top, selectionRect.bottom) 7000 && poseRect.left < selectionRect.left 7001 && (poseRect.left > bestRect.left 7002 || !bestRect.IsValid())) { 7003 bestRect = poseRect; 7004 poseToSelect = pose; 7005 } 7006 break; 7007 7008 case B_RIGHT_ARROW: 7009 if (LinesIntersect(poseRect.top, poseRect.bottom, 7010 selectionRect.top, selectionRect.bottom) 7011 && poseRect.right > selectionRect.right 7012 && (poseRect.right < bestRect.right 7013 || !bestRect.IsValid())) { 7014 bestRect = poseRect; 7015 poseToSelect = pose; 7016 } 7017 break; 7018 7019 case B_UP_ARROW: 7020 if (LinesIntersect(poseRect.left, poseRect.right, 7021 selectionRect.left, selectionRect.right) 7022 && poseRect.top < selectionRect.top 7023 && (poseRect.top > bestRect.top 7024 || !bestRect.IsValid())) { 7025 bestRect = poseRect; 7026 poseToSelect = pose; 7027 } 7028 break; 7029 7030 case B_DOWN_ARROW: 7031 if (LinesIntersect(poseRect.left, poseRect.right, 7032 selectionRect.left, selectionRect.right) 7033 && poseRect.bottom > selectionRect.bottom 7034 && (poseRect.bottom < bestRect.bottom 7035 || !bestRect.IsValid())) { 7036 bestRect = poseRect; 7037 poseToSelect = pose; 7038 } 7039 break; 7040 } 7041 } 7042 7043 if (poseToSelect != NULL) 7044 return poseToSelect; 7045 7046 return selectedPose; 7047 } 7048 7049 7050 void 7051 BPoseView::ShowContextMenu(BPoint where) 7052 { 7053 BContainerWindow* window = ContainerWindow(); 7054 if (window == NULL) 7055 return; 7056 7057 // handle pose selection 7058 int32 index; 7059 BPose* pose = FindPose(where, &index); 7060 if (pose != NULL) { 7061 if (!pose->IsSelected()) { 7062 ClearSelection(); 7063 pose->Select(true); 7064 fSelectionList->AddItem(pose); 7065 DrawPose(pose, index, false); 7066 } 7067 } else 7068 ClearSelection(); 7069 7070 window->Activate(); 7071 window->UpdateIfNeeded(); 7072 window->ShowContextMenu(where, 7073 pose ? pose->TargetModel()->EntryRef() : 0, this); 7074 7075 if (fSelectionChangedHook) 7076 window->SelectionChanged(); 7077 } 7078 7079 7080 void 7081 BPoseView::_BeginSelectionRect(const BPoint& point, bool shouldExtend) 7082 { 7083 // set initial empty selection rectangle 7084 fSelectionRectInfo.rect = BRect(point, point - BPoint(1, 1)); 7085 7086 if (!fTransparentSelection) { 7087 SetDrawingMode(B_OP_INVERT); 7088 StrokeRect(fSelectionRectInfo.rect, B_MIXED_COLORS); 7089 SetDrawingMode(B_OP_OVER); 7090 } 7091 7092 fSelectionRectInfo.lastRect = fSelectionRectInfo.rect; 7093 fSelectionRectInfo.selection = new BList; 7094 fSelectionRectInfo.startPoint = point; 7095 fSelectionRectInfo.lastPoint = point; 7096 fSelectionRectInfo.isDragging = true; 7097 7098 if (fAutoScrollState == kAutoScrollOff) { 7099 fAutoScrollState = kAutoScrollOn; 7100 Window()->SetPulseRate(20000); 7101 } 7102 } 7103 7104 7105 static void 7106 AddIfPoseSelected(BPose* pose, PoseList* list) 7107 { 7108 if (pose->IsSelected()) 7109 list->AddItem(pose); 7110 } 7111 7112 7113 void 7114 BPoseView::_UpdateSelectionRect(const BPoint& point) 7115 { 7116 if (point != fSelectionRectInfo.lastPoint) { 7117 fSelectionRectInfo.lastPoint = point; 7118 7119 // erase last rect 7120 if (!fTransparentSelection) { 7121 SetDrawingMode(B_OP_INVERT); 7122 StrokeRect(fSelectionRectInfo.rect, B_MIXED_COLORS); 7123 SetDrawingMode(B_OP_OVER); 7124 } 7125 7126 fSelectionRectInfo.rect.top = std::min(point.y, 7127 fSelectionRectInfo.startPoint.y); 7128 fSelectionRectInfo.rect.left = std::min(point.x, 7129 fSelectionRectInfo.startPoint.x); 7130 fSelectionRectInfo.rect.bottom = std::max(point.y, 7131 fSelectionRectInfo.startPoint.y); 7132 fSelectionRectInfo.rect.right = std::max(point.x, 7133 fSelectionRectInfo.startPoint.x); 7134 7135 fIsDrawingSelectionRect = true; 7136 7137 // use current selection rectangle to scan poses 7138 if (ViewMode() == kListMode) { 7139 SelectPosesListMode(fSelectionRectInfo.rect, 7140 &fSelectionRectInfo.selection); 7141 } else { 7142 SelectPosesIconMode(fSelectionRectInfo.rect, 7143 &fSelectionRectInfo.selection); 7144 } 7145 7146 Window()->UpdateIfNeeded(); 7147 7148 // draw new rect 7149 if (!fTransparentSelection) { 7150 SetDrawingMode(B_OP_INVERT); 7151 StrokeRect(fSelectionRectInfo.rect, B_MIXED_COLORS); 7152 SetDrawingMode(B_OP_OVER); 7153 } else { 7154 BRegion updateRegion1; 7155 BRegion updateRegion2; 7156 7157 bool sameWidth = fSelectionRectInfo.rect.Width() 7158 == fSelectionRectInfo.lastRect.Width(); 7159 bool sameHeight = fSelectionRectInfo.rect.Height() 7160 == fSelectionRectInfo.lastRect.Height(); 7161 7162 updateRegion1.Include(fSelectionRectInfo.rect); 7163 updateRegion1.Exclude(fSelectionRectInfo.lastRect.InsetByCopy( 7164 sameWidth ? 0 : 1, sameHeight ? 0 : 1)); 7165 updateRegion2.Include(fSelectionRectInfo.lastRect); 7166 updateRegion2.Exclude(fSelectionRectInfo.rect.InsetByCopy( 7167 sameWidth ? 0 : 1, sameHeight ? 0 : 1)); 7168 updateRegion1.Include(&updateRegion2); 7169 BRect unionRect = fSelectionRectInfo.rect 7170 & fSelectionRectInfo.lastRect; 7171 updateRegion1.Exclude(unionRect 7172 & BRect(-2000, fSelectionRectInfo.startPoint.y, 2000, 7173 fSelectionRectInfo.startPoint.y)); 7174 updateRegion1.Exclude(unionRect 7175 & BRect(fSelectionRectInfo.startPoint.x, -2000, 7176 fSelectionRectInfo.startPoint.x, 2000)); 7177 7178 fSelectionRectInfo.lastRect = fSelectionRectInfo.rect; 7179 7180 Invalidate(&updateRegion1); 7181 Window()->UpdateIfNeeded(); 7182 } 7183 Flush(); 7184 } 7185 } 7186 7187 7188 void 7189 BPoseView::_EndSelectionRect() 7190 { 7191 delete fSelectionRectInfo.selection; 7192 fSelectionRectInfo.selection = NULL; 7193 7194 fSelectionRectInfo.isDragging = false; 7195 fIsDrawingSelectionRect = false; 7196 // TODO: remove BPose dependency? 7197 7198 // do final erase of selection rect 7199 if (!fTransparentSelection) { 7200 SetDrawingMode(B_OP_INVERT); 7201 StrokeRect(fSelectionRectInfo.rect, B_MIXED_COLORS); 7202 SetDrawingMode(B_OP_COPY); 7203 fSelectionRectInfo.rect.Set(0, 0, -1, -1); 7204 } else { 7205 Invalidate(fSelectionRectInfo.rect); 7206 fSelectionRectInfo.rect.Set(0, 0, -1, -1); 7207 Window()->UpdateIfNeeded(); 7208 } 7209 7210 // we now need to update the pose view's selection list by clearing it 7211 // and then polling each pose for selection state and rebuilding list 7212 fSelectionList->MakeEmpty(); 7213 fMimeTypesInSelectionCache.MakeEmpty(); 7214 7215 EachListItem(fPoseList, AddIfPoseSelected, fSelectionList); 7216 7217 // and now make sure that the pivot point is in sync 7218 if (fSelectionPivotPose && !fSelectionList->HasItem(fSelectionPivotPose)) 7219 fSelectionPivotPose = NULL; 7220 if (fRealPivotPose && !fSelectionList->HasItem(fRealPivotPose)) 7221 fRealPivotPose = NULL; 7222 } 7223 7224 7225 void 7226 BPoseView::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage) 7227 { 7228 if (fSelectionRectInfo.isDragging) 7229 _UpdateSelectionRect(where); 7230 7231 if (!fDropEnabled || dragMessage == NULL) 7232 return; 7233 7234 BContainerWindow* window = ContainerWindow(); 7235 if (window == NULL) 7236 return; 7237 7238 if (!window->Dragging()) 7239 window->DragStart(dragMessage); 7240 7241 switch (transit) { 7242 case B_INSIDE_VIEW: 7243 case B_ENTERED_VIEW: 7244 UpdateDropTarget(where, dragMessage, window->ContextMenu()); 7245 if (fAutoScrollState == kAutoScrollOff) { 7246 // turn on auto scrolling if it's not yet on 7247 fAutoScrollState = kWaitForTransition; 7248 window->SetPulseRate(100000); 7249 } 7250 break; 7251 7252 case B_EXITED_VIEW: 7253 DragStop(); 7254 // reset cursor in case we set it to the copy cursor 7255 // in UpdateDropTarget 7256 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); 7257 fCursorCheck = false; 7258 // TODO: autoscroll here 7259 if (!window->ContextMenu()) { 7260 HiliteDropTarget(false); 7261 fDropTarget = NULL; 7262 } 7263 break; 7264 } 7265 } 7266 7267 7268 void 7269 BPoseView::MouseDragged(const BMessage* message) 7270 { 7271 if (fTextWidgetToCheck != NULL) 7272 fTextWidgetToCheck->CancelWait(); 7273 7274 fTrackRightMouseUp = false; 7275 7276 BPoint where; 7277 uint32 buttons = 0; 7278 if (message->FindPoint("be:view_where", &where) != B_OK 7279 || message->FindInt32("buttons", (int32*)&buttons) != B_OK) { 7280 return; 7281 } 7282 7283 bool extendSelection = (modifiers() & B_COMMAND_KEY) != 0 7284 && fMultipleSelection; 7285 7286 int32 index; 7287 BPose* pose = FindPose(where, &index); 7288 if (pose != NULL) 7289 DragSelectedPoses(pose, where); 7290 else if (buttons == B_PRIMARY_MOUSE_BUTTON) 7291 _BeginSelectionRect(where, extendSelection); 7292 } 7293 7294 7295 void 7296 BPoseView::MouseLongDown(const BMessage* message) 7297 { 7298 fTrackRightMouseUp = false; 7299 7300 BPoint where; 7301 if (message->FindPoint("where", &where) != B_OK) 7302 return; 7303 7304 ShowContextMenu(where); 7305 } 7306 7307 7308 void 7309 BPoseView::MouseIdle(const BMessage* message) 7310 { 7311 BPoint where; 7312 uint32 buttons = 0; 7313 GetMouse(&where, &buttons); 7314 // We could retrieve 'where' from the incoming 7315 // message but we need the buttons state anyway 7316 // and B_MOUSE_IDLE message doesn't pass it 7317 BContainerWindow* window = ContainerWindow(); 7318 7319 if (buttons == 0 || window == NULL || !window->Dragging()) 7320 return; 7321 7322 if (fDropTarget != NULL) { 7323 FrameForPose(fDropTarget, true, &fStartFrame); 7324 ShowContextMenu(where); 7325 } else 7326 window->Activate(); 7327 } 7328 7329 7330 void 7331 BPoseView::MouseDown(BPoint where) 7332 { 7333 // handle disposing of drag data lazily 7334 DragStop(); 7335 BContainerWindow* window = ContainerWindow(); 7336 if (window == NULL) 7337 return; 7338 7339 if (IsDesktopWindow()) { 7340 BScreen screen(Window()); 7341 rgb_color color = screen.DesktopColor(); 7342 SetLowColor(color); 7343 SetViewColor(color); 7344 } 7345 7346 MakeFocus(); 7347 7348 uint32 buttons = (uint32)window->CurrentMessage()->FindInt32("buttons"); 7349 uint32 modifierKeys = modifiers(); 7350 bool secondaryMouseButtonDown 7351 = SecondaryMouseButtonDown(modifierKeys, buttons); 7352 fTrackRightMouseUp = secondaryMouseButtonDown; 7353 bool extendSelection = (modifierKeys & B_COMMAND_KEY) != 0 7354 && fMultipleSelection; 7355 7356 CommitActivePose(); 7357 7358 int32 index; 7359 BPose* pose = FindPose(where, &index); 7360 if (pose != NULL) { 7361 if (!pose->IsSelected() || !secondaryMouseButtonDown) 7362 AddRemoveSelectionRange(where, extendSelection, pose); 7363 7364 if (fTextWidgetToCheck != NULL 7365 && (pose != fLastClickedPose || secondaryMouseButtonDown)) { 7366 fTextWidgetToCheck->CancelWait(); 7367 } 7368 7369 if (!extendSelection && WasDoubleClick(pose, where, buttons) 7370 && buttons == B_PRIMARY_MOUSE_BUTTON 7371 && fLastClickButtons == B_PRIMARY_MOUSE_BUTTON 7372 && (modifierKeys & B_CONTROL_KEY) == 0) { 7373 // special handling for path field double-clicks 7374 if (!WasClickInPath(pose, index, where)) 7375 OpenSelection(pose, &index); 7376 } 7377 } else { 7378 // click was not in any pose 7379 fLastClickedPose = NULL; 7380 if (fTextWidgetToCheck != NULL) 7381 fTextWidgetToCheck->CancelWait(); 7382 7383 window->Activate(); 7384 window->UpdateIfNeeded(); 7385 7386 // only clear selection if we are not extending it 7387 if (!extendSelection || !fSelectionRectEnabled || !fMultipleSelection) 7388 ClearSelection(); 7389 7390 // show desktop context menu 7391 if (SecondaryMouseButtonDown(modifierKeys, buttons)) 7392 ShowContextMenu(where); 7393 } 7394 7395 if (fSelectionChangedHook) 7396 window->SelectionChanged(); 7397 } 7398 7399 7400 void 7401 BPoseView::SetTextWidgetToCheck(BTextWidget* widget, BTextWidget* old) 7402 { 7403 if (old == NULL || fTextWidgetToCheck == old) 7404 fTextWidgetToCheck = widget; 7405 } 7406 7407 7408 void 7409 BPoseView::MouseUp(BPoint where) 7410 { 7411 if (fSelectionRectInfo.isDragging) 7412 _EndSelectionRect(); 7413 7414 int32 index; 7415 BPose* pose = FindPose(where, &index); 7416 uint32 lastButtons = Window()->CurrentMessage()->FindInt32("last_buttons"); 7417 if (pose != NULL && fLastClickedPose != NULL && fAllowPoseEditing 7418 && !fTrackRightMouseUp) { 7419 // This handy field has been added by the tracking filter. 7420 // we need lastButtons for right button mouse-up tracking, 7421 // because there's currently no way to know wich buttons were 7422 // released in BView::MouseUp (unlike BView::KeyUp) 7423 pose->MouseUp(BPoint(0, index * fListElemHeight), this, where, index); 7424 } 7425 7426 // Showing the pose context menu is done on mouse up (or long click) 7427 // to make right button dragging possible 7428 if (pose != NULL && fTrackRightMouseUp 7429 && (SecondaryMouseButtonDown(modifiers(), lastButtons))) { 7430 if (!pose->IsSelected()) { 7431 ClearSelection(); 7432 pose->Select(true); 7433 fSelectionList->AddItem(pose); 7434 DrawPose(pose, index, false); 7435 } 7436 ShowContextMenu(where); 7437 } 7438 fTrackRightMouseUp = false; 7439 } 7440 7441 7442 bool 7443 BPoseView::WasClickInPath(const BPose* pose, int32 index, 7444 BPoint mouseLocation) const 7445 { 7446 if (pose == NULL || (ViewMode() != kListMode)) 7447 return false; 7448 7449 BPoint loc(0, index * fListElemHeight); 7450 BTextWidget* widget; 7451 if (!pose->PointInPose(loc, this, mouseLocation, &widget) || !widget) 7452 return false; 7453 7454 // note: the following code is wrong, because this sort of hashing 7455 // may overlap and we get aliasing 7456 if (widget->AttrHash() != AttrHashString(kAttrPath, B_STRING_TYPE)) 7457 return false; 7458 7459 BEntry entry(widget->Text(this)); 7460 if (entry.InitCheck() != B_OK) 7461 return false; 7462 7463 entry_ref ref; 7464 if (entry.GetRef(&ref) == B_OK) { 7465 BMessage message(B_REFS_RECEIVED); 7466 message.AddRef("refs", &ref); 7467 be_app->PostMessage(&message); 7468 return true; 7469 } 7470 7471 return false; 7472 } 7473 7474 7475 bool 7476 BPoseView::WasDoubleClick(const BPose* pose, BPoint point, int32 buttons) 7477 { 7478 // check proximity 7479 BPoint delta = point - fLastClickPoint; 7480 int32 clicks = Window()->CurrentMessage()->FindInt32("clicks"); 7481 7482 if (clicks == 2 7483 && fabs(delta.x) < kDoubleClickTresh 7484 && fabs(delta.y) < kDoubleClickTresh 7485 && pose == fLastClickedPose) { 7486 fLastClickPoint.Set(INT32_MAX, INT32_MAX); 7487 fLastClickedPose = NULL; 7488 if (fTextWidgetToCheck != NULL) 7489 fTextWidgetToCheck->CancelWait(); 7490 7491 return buttons == fLastClickButtons; 7492 } 7493 7494 fLastClickPoint = point; 7495 fLastClickedPose = pose; 7496 fLastClickButtons = buttons; 7497 7498 return false; 7499 } 7500 7501 7502 static void 7503 AddPoseRefToMessage(Model* model, BMessage* message) 7504 { 7505 // Make sure that every file added to the message has its 7506 // MIME type set. 7507 BNode node(model->EntryRef()); 7508 if (node.InitCheck() == B_OK) { 7509 BNodeInfo info(&node); 7510 char type[B_MIME_TYPE_LENGTH]; 7511 type[0] = '\0'; 7512 if (info.GetType(type) != B_OK) { 7513 BPath path(model->EntryRef()); 7514 if (path.InitCheck() == B_OK) 7515 update_mime_info(path.Path(), false, false, false); 7516 } 7517 } 7518 message->AddRef("refs", model->EntryRef()); 7519 } 7520 7521 7522 void 7523 BPoseView::DragSelectedPoses(const BPose* pose, BPoint clickPoint) 7524 { 7525 if (!fDragEnabled) 7526 return; 7527 7528 ASSERT(pose); 7529 7530 // make sure pose is selected, it could have been deselected as part of 7531 // a click during selection extention 7532 if (!pose->IsSelected()) 7533 return; 7534 7535 // setup tracking rect by unioning all selected pose rects 7536 BMessage message(B_SIMPLE_DATA); 7537 message.AddPointer("src_window", Window()); 7538 message.AddPoint("click_pt", clickPoint); 7539 7540 // add Tracker token so that refs received recipients can script us 7541 message.AddMessenger("TrackerViewToken", BMessenger(this)); 7542 7543 // cannot use EachPoseAndModel here, because that iterates the selected 7544 // poses in reverse order 7545 for (int32 index = 0; index < fSelectionList->CountItems(); index++) { 7546 AddPoseRefToMessage(fSelectionList->ItemAt(index)->TargetModel(), 7547 &message); 7548 } 7549 7550 // make sure button is still down 7551 uint32 buttons; 7552 BPoint tempLoc; 7553 GetMouse(&tempLoc, &buttons); 7554 if (buttons != 0) { 7555 int32 index = CurrentPoseList()->IndexOf(pose); 7556 message.AddInt32("buttons", (int32)buttons); 7557 BRect dragRect(GetDragRect(index)); 7558 BBitmap* dragBitmap = NULL; 7559 BPoint offset; 7560 #ifdef DRAG_FRAME 7561 if (dragRect.Width() < kTransparentDragThreshold.x 7562 && dragRect.Height() < kTransparentDragThreshold.y) { 7563 dragBitmap = MakeDragBitmap(dragRect, clickPoint, index, offset); 7564 } 7565 #else 7566 // The bitmap is now always created (if DRAG_FRAME is not defined) 7567 dragBitmap = MakeDragBitmap(dragRect, clickPoint, index, offset); 7568 #endif 7569 if (dragBitmap != NULL) { 7570 DragMessage(&message, dragBitmap, B_OP_ALPHA, offset); 7571 // this DragMessage supports alpha blending 7572 } else 7573 DragMessage(&message, dragRect); 7574 7575 // turn on auto scrolling 7576 fAutoScrollState = kWaitForTransition; 7577 Window()->SetPulseRate(100000); 7578 } 7579 } 7580 7581 7582 BBitmap* 7583 BPoseView::MakeDragBitmap(BRect dragRect, BPoint clickedPoint, 7584 int32 clickedPoseIndex, BPoint &offset) 7585 { 7586 BRect inner(clickedPoint.x - kTransparentDragThreshold.x / 2, 7587 clickedPoint.y - kTransparentDragThreshold.y / 2, 7588 clickedPoint.x + kTransparentDragThreshold.x / 2, 7589 clickedPoint.y + kTransparentDragThreshold.y / 2); 7590 7591 // (BRect & BRect) doesn't work correctly if the rectangles don't intersect 7592 // this catches a bug that is produced somewhere before this function is 7593 // called 7594 if (!inner.Intersects(dragRect)) 7595 return NULL; 7596 7597 inner = inner & dragRect; 7598 7599 // If the selection is bigger than the specified limit, the 7600 // contents will fade out when they come near the borders 7601 bool fadeTop = false; 7602 bool fadeBottom = false; 7603 bool fadeLeft = false; 7604 bool fadeRight = false; 7605 bool fade = false; 7606 if (inner.left > dragRect.left) { 7607 inner.left = std::max(inner.left - 32, dragRect.left); 7608 fade = fadeLeft = true; 7609 } 7610 if (inner.right < dragRect.right) { 7611 inner.right = std::min(inner.right + 32, dragRect.right); 7612 fade = fadeRight = true; 7613 } 7614 if (inner.top > dragRect.top) { 7615 inner.top = std::max(inner.top - 32, dragRect.top); 7616 fade = fadeTop = true; 7617 } 7618 if (inner.bottom < dragRect.bottom) { 7619 inner.bottom = std::min(inner.bottom + 32, dragRect.bottom); 7620 fade = fadeBottom = true; 7621 } 7622 7623 // set the offset for the dragged bitmap (for the BView::DragMessage() call) 7624 offset = clickedPoint - BPoint(2, 1) - inner.LeftTop(); 7625 7626 BRect rect(inner); 7627 rect.OffsetTo(B_ORIGIN); 7628 7629 BBitmap* bitmap = new BBitmap(rect, B_RGBA32, true); 7630 bitmap->Lock(); 7631 BView* view = new BView(bitmap->Bounds(), "", B_FOLLOW_NONE, 0); 7632 bitmap->AddChild(view); 7633 7634 view->SetOrigin(0, 0); 7635 7636 BRect clipRect(view->Bounds()); 7637 BRegion newClip; 7638 newClip.Set(clipRect); 7639 view->ConstrainClippingRegion(&newClip); 7640 7641 memset(bitmap->Bits(), 0, bitmap->BitsLength()); 7642 7643 view->SetDrawingMode(B_OP_ALPHA); 7644 view->SetHighColor(0, 0, 0, uint8(fade ? 164 : 128)); 7645 // set the level of transparency by value 7646 view->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE); 7647 7648 BRect bounds(Bounds()); 7649 7650 PoseList* poseList = CurrentPoseList(); 7651 if (ViewMode() == kListMode) { 7652 int32 count = poseList->CountItems(); 7653 int32 startIndex = (int32)(bounds.top / fListElemHeight); 7654 BPoint loc(0, startIndex * fListElemHeight); 7655 7656 for (int32 index = startIndex; index < count; index++) { 7657 BPose* pose = poseList->ItemAt(index); 7658 if (pose->IsSelected()) { 7659 BRect poseRect(pose->CalcRect(loc, this, true)); 7660 if (poseRect.Intersects(inner)) { 7661 BPoint offsetBy(-inner.LeftTop().x, -inner.LeftTop().y); 7662 pose->Draw(poseRect, poseRect, this, view, true, offsetBy, 7663 false); 7664 } 7665 } 7666 loc.y += fListElemHeight; 7667 if (loc.y > bounds.bottom) 7668 break; 7669 } 7670 } else { 7671 // add rects for visible poses only (uses VSList!!) 7672 int32 startIndex 7673 = FirstIndexAtOrBelow((int32)(bounds.top - IconPoseHeight())); 7674 int32 count = fVSPoseList->CountItems(); 7675 7676 for (int32 index = startIndex; index < count; index++) { 7677 BPose* pose = fVSPoseList->ItemAt(index); 7678 if (pose != NULL && pose->IsSelected()) { 7679 BRect poseRect(pose->CalcRect(this)); 7680 if (!poseRect.Intersects(inner)) 7681 continue; 7682 7683 BPoint offsetBy(-inner.LeftTop().x, -inner.LeftTop().y); 7684 pose->Draw(poseRect, poseRect, this, view, true, offsetBy, 7685 false); 7686 } 7687 } 7688 } 7689 7690 view->Sync(); 7691 7692 // fade out the contents if necessary 7693 if (fade) { 7694 uint32* bits = (uint32*)bitmap->Bits(); 7695 int32 width = bitmap->BytesPerRow() / 4; 7696 7697 if (fadeLeft) 7698 FadeRGBA32Horizontal(bits, width, int32(rect.bottom), 0, 64); 7699 7700 if (fadeRight) { 7701 FadeRGBA32Horizontal(bits, width, int32(rect.bottom), 7702 int32(rect.right), int32(rect.right) - 64); 7703 } 7704 7705 if (fadeTop) 7706 FadeRGBA32Vertical(bits, width, int32(rect.bottom), 0, 64); 7707 7708 if (fadeBottom) { 7709 FadeRGBA32Vertical(bits, width, int32(rect.bottom), 7710 int32(rect.bottom), int32(rect.bottom) - 64); 7711 } 7712 } 7713 7714 bitmap->Unlock(); 7715 return bitmap; 7716 } 7717 7718 7719 BRect 7720 BPoseView::GetDragRect(int32 clickedPoseIndex) 7721 { 7722 BRect result; 7723 BRect bounds(Bounds()); 7724 7725 PoseList* poseList = CurrentPoseList(); 7726 BPose* pose = poseList->ItemAt(clickedPoseIndex); 7727 if (ViewMode() == kListMode) { 7728 // get starting rect of clicked pose 7729 result = CalcPoseRectList(pose, clickedPoseIndex, true); 7730 7731 // add rects for visible poses only 7732 int32 count = poseList->CountItems(); 7733 int32 startIndex = (int32)(bounds.top / fListElemHeight); 7734 BPoint loc(0, startIndex * fListElemHeight); 7735 7736 for (int32 index = startIndex; index < count; index++) { 7737 pose = poseList->ItemAt(index); 7738 if (pose->IsSelected()) 7739 result = result | pose->CalcRect(loc, this, true); 7740 7741 loc.y += fListElemHeight; 7742 if (loc.y > bounds.bottom) 7743 break; 7744 } 7745 } else { 7746 // get starting rect of clicked pose 7747 result = pose->CalcRect(this); 7748 7749 // add rects for visible poses only (uses VSList!!) 7750 int32 count = fVSPoseList->CountItems(); 7751 for (int32 index = FirstIndexAtOrBelow( 7752 (int32)(bounds.top - IconPoseHeight())); 7753 index < count; index++) { 7754 BPose* pose = fVSPoseList->ItemAt(index); 7755 if (pose != NULL) { 7756 if (pose->IsSelected()) 7757 result = result | pose->CalcRect(this); 7758 7759 if (pose->Location(this).y > bounds.bottom) 7760 break; 7761 } 7762 } 7763 } 7764 7765 return result; 7766 } 7767 7768 7769 // TODO: SelectPosesListMode and SelectPosesIconMode are terrible and share 7770 // most code 7771 void 7772 BPoseView::SelectPosesListMode(BRect selectionRect, BList** oldList) 7773 { 7774 ASSERT(ViewMode() == kListMode); 7775 7776 // collect all the poses which are enclosed inside the selection rect 7777 BList* newList = new BList; 7778 BRect bounds(Bounds()); 7779 SetDrawingMode(B_OP_COPY); 7780 // TODO: I _think_ there is no more synchronous drawing here, 7781 // so this should be save to remove 7782 7783 int32 startIndex = (int32)(selectionRect.top / fListElemHeight); 7784 if (startIndex < 0) 7785 startIndex = 0; 7786 7787 BPoint loc(0, startIndex * fListElemHeight); 7788 7789 PoseList* poseList = CurrentPoseList(); 7790 int32 count = poseList->CountItems(); 7791 for (int32 index = startIndex; index < count; index++) { 7792 BPose* pose = poseList->ItemAt(index); 7793 BRect poseRect(pose->CalcRect(loc, this)); 7794 7795 if (selectionRect.Intersects(poseRect)) { 7796 bool selected = pose->IsSelected(); 7797 pose->Select(!fSelectionList->HasItem(pose)); 7798 newList->AddItem((void*)(addr_t)index); 7799 // this sucks, need to clean up using a vector class instead 7800 // of BList 7801 7802 if ((selected != pose->IsSelected()) 7803 && poseRect.Intersects(bounds)) { 7804 Invalidate(poseRect); 7805 } 7806 7807 // First Pose selected gets to be the pivot. 7808 if ((fSelectionPivotPose == NULL) && (selected == false)) 7809 fSelectionPivotPose = pose; 7810 } 7811 7812 loc.y += fListElemHeight; 7813 if (loc.y > selectionRect.bottom) 7814 break; 7815 } 7816 7817 // take the old set of enclosed poses and invert selection state 7818 // on those which are no longer enclosed 7819 count = (*oldList)->CountItems(); 7820 for (int32 index = 0; index < count; index++) { 7821 int32 oldIndex = (addr_t)(*oldList)->ItemAt(index); 7822 7823 if (!newList->HasItem((void*)(addr_t)oldIndex)) { 7824 BPose* pose = poseList->ItemAt(oldIndex); 7825 pose->Select(!pose->IsSelected()); 7826 loc.Set(0, oldIndex * fListElemHeight); 7827 BRect poseRect(pose->CalcRect(loc, this)); 7828 7829 if (poseRect.Intersects(bounds)) 7830 Invalidate(poseRect); 7831 } 7832 } 7833 7834 delete* oldList; 7835 *oldList = newList; 7836 } 7837 7838 7839 void 7840 BPoseView::SelectPosesIconMode(BRect selectionRect, BList** oldList) 7841 { 7842 ASSERT(ViewMode() != kListMode); 7843 7844 // collect all the poses which are enclosed inside the selection rect 7845 BList* newList = new BList; 7846 BRect bounds(Bounds()); 7847 SetDrawingMode(B_OP_COPY); 7848 7849 int32 startIndex = FirstIndexAtOrBelow( 7850 (int32)(selectionRect.top - IconPoseHeight()), true); 7851 if (startIndex < 0) 7852 startIndex = 0; 7853 7854 int32 count = fPoseList->CountItems(); 7855 for (int32 index = startIndex; index < count; index++) { 7856 BPose* pose = fVSPoseList->ItemAt(index); 7857 if (pose != NULL) { 7858 BRect poseRect(pose->CalcRect(this)); 7859 7860 if (selectionRect.Intersects(poseRect)) { 7861 bool selected = pose->IsSelected(); 7862 pose->Select(!fSelectionList->HasItem(pose)); 7863 newList->AddItem((void*)(addr_t)index); 7864 7865 if ((selected != pose->IsSelected()) 7866 && poseRect.Intersects(bounds)) { 7867 Invalidate(poseRect); 7868 } 7869 7870 // first Pose selected gets to be the pivot 7871 if ((fSelectionPivotPose == NULL) && (selected == false)) 7872 fSelectionPivotPose = pose; 7873 } 7874 7875 if (pose->Location(this).y > selectionRect.bottom) 7876 break; 7877 } 7878 } 7879 7880 // take the old set of enclosed poses and invert selection state 7881 // on those which are no longer enclosed 7882 count = (*oldList)->CountItems(); 7883 for (int32 index = 0; index < count; index++) { 7884 int32 oldIndex = (addr_t)(*oldList)->ItemAt(index); 7885 7886 if (!newList->HasItem((void*)(addr_t)oldIndex)) { 7887 BPose* pose = fVSPoseList->ItemAt(oldIndex); 7888 pose->Select(!pose->IsSelected()); 7889 BRect poseRect(pose->CalcRect(this)); 7890 7891 if (poseRect.Intersects(bounds)) 7892 Invalidate(poseRect); 7893 } 7894 } 7895 7896 delete* oldList; 7897 *oldList = newList; 7898 } 7899 7900 7901 void 7902 BPoseView::AddRemoveSelectionRange(BPoint where, bool extendSelection, 7903 BPose* pose) 7904 { 7905 ASSERT(pose != NULL); 7906 7907 if (pose == fSelectionPivotPose && !extendSelection) 7908 return; 7909 7910 if ((modifiers() & B_SHIFT_KEY) != 0 && fSelectionPivotPose) { 7911 // multi pose extend/shrink current selection 7912 bool select = !pose->IsSelected() || !extendSelection; 7913 // This weird bit of logic causes the selection to always 7914 // center around the pivot point, unless you choose to hold 7915 // down COMMAND, which will unselect between the pivot and 7916 // the most recently selected Pose. 7917 7918 if (!extendSelection) { 7919 // Remember fSelectionPivotPose because ClearSelection() NULLs it 7920 // and we need it to be preserved. 7921 const BPose* savedPivotPose = fSelectionPivotPose; 7922 ClearSelection(); 7923 fSelectionPivotPose = savedPivotPose; 7924 } 7925 7926 if (ViewMode() == kListMode) { 7927 PoseList* poseList = CurrentPoseList(); 7928 int32 currentSelectedIndex = poseList->IndexOf(pose); 7929 int32 lastSelectedIndex = poseList->IndexOf(fSelectionPivotPose); 7930 7931 int32 startRange; 7932 int32 endRange; 7933 7934 if (lastSelectedIndex < currentSelectedIndex) { 7935 startRange = lastSelectedIndex; 7936 endRange = currentSelectedIndex; 7937 } else { 7938 startRange = currentSelectedIndex; 7939 endRange = lastSelectedIndex; 7940 } 7941 7942 for (int32 i = startRange; i <= endRange; i++) 7943 AddRemovePoseFromSelection(poseList->ItemAt(i), i, select); 7944 } else { 7945 BRect selection(where, fSelectionPivotPose->Location(this)); 7946 7947 // Things will get odd if we don't 'fix' the selection rect. 7948 if (selection.left > selection.right) 7949 std::swap(selection.left, selection.right); 7950 7951 if (selection.top > selection.bottom) 7952 std::swap(selection.top, selection.bottom); 7953 7954 // If the selection rect is not at least 1 pixel high/wide, things 7955 // are also not going to work out. 7956 if (selection.IntegerWidth() < 1) 7957 selection.right = selection.left + 1.0f; 7958 7959 if (selection.IntegerHeight() < 1) 7960 selection.bottom = selection.top + 1.0f; 7961 7962 ASSERT(selection.IsValid()); 7963 7964 int32 count = fPoseList->CountItems(); 7965 for (int32 index = count - 1; index >= 0; index--) { 7966 BPose* currPose = fPoseList->ItemAt(index); 7967 // TODO: works only in non-list mode? 7968 if (selection.Intersects(currPose->CalcRect(this))) 7969 AddRemovePoseFromSelection(currPose, index, select); 7970 } 7971 } 7972 } else { 7973 int32 index = CurrentPoseList()->IndexOf(pose); 7974 if (!extendSelection) { 7975 if (!pose->IsSelected()) { 7976 // create new selection 7977 ClearSelection(); 7978 AddRemovePoseFromSelection(pose, index, true); 7979 fSelectionPivotPose = pose; 7980 } 7981 } else { 7982 fMimeTypesInSelectionCache.MakeEmpty(); 7983 AddRemovePoseFromSelection(pose, index, !pose->IsSelected()); 7984 } 7985 } 7986 7987 // If the list is empty, there cannot be a pivot pose, 7988 // however if the list is not empty there must be a pivot 7989 // pose. 7990 if (fSelectionList->IsEmpty()) { 7991 fSelectionPivotPose = NULL; 7992 fRealPivotPose = NULL; 7993 } else if (fSelectionPivotPose == NULL) { 7994 fSelectionPivotPose = pose; 7995 fRealPivotPose = pose; 7996 } 7997 } 7998 7999 8000 void 8001 BPoseView::DeleteSymLinkPoseTarget(const node_ref* itemNode, BPose* pose, 8002 int32 index) 8003 { 8004 ASSERT(pose->TargetModel()->IsSymLink()); 8005 watch_node(itemNode, B_STOP_WATCHING, this); 8006 8007 // watch the parent of the symlink, so that we know when the symlink 8008 // can be considered fixed. 8009 WatchParentOf(pose->TargetModel()->EntryRef()); 8010 8011 BPoint loc(0, index * fListElemHeight); 8012 pose->TargetModel()->SetLinkTo(NULL); 8013 pose->UpdateBrokenSymLink(loc, this); 8014 } 8015 8016 8017 bool 8018 BPoseView::DeletePose(const node_ref* itemNode, BPose* pose, int32 index) 8019 { 8020 watch_node(itemNode, B_STOP_WATCHING, this); 8021 8022 if (pose == NULL) 8023 pose = fPoseList->FindPose(itemNode, &index); 8024 8025 if (pose != NULL) { 8026 fInsertedNodes.erase(fInsertedNodes.find(*itemNode)); 8027 if (pose->TargetModel()->IsSymLink()) { 8028 fBrokenLinks->RemoveItem(pose->TargetModel()); 8029 StopWatchingParentsOf(pose->TargetModel()->EntryRef()); 8030 Model* target = pose->TargetModel()->LinkTo(); 8031 if (target) 8032 watch_node(target->NodeRef(), B_STOP_WATCHING, this); 8033 } 8034 8035 ASSERT(TargetModel()); 8036 8037 if (pose == fDropTarget) 8038 fDropTarget = NULL; 8039 8040 if (pose == ActivePose()) 8041 CommitActivePose(); 8042 8043 Window()->UpdateIfNeeded(); 8044 8045 // remove it from list no matter what since it might be in list 8046 // but not "selected" since selection is hidden 8047 fSelectionList->RemoveItem(pose); 8048 if (fSelectionPivotPose == pose) 8049 fSelectionPivotPose = NULL; 8050 if (fRealPivotPose == pose) 8051 fRealPivotPose = NULL; 8052 8053 if (pose->IsSelected() && fSelectionChangedHook) 8054 ContainerWindow()->SelectionChanged(); 8055 8056 fPoseList->RemoveItemAt(index); 8057 8058 bool visible = true; 8059 if (fFiltering) { 8060 if (fFilteredPoseList->FindPose(itemNode, &index) != NULL) 8061 fFilteredPoseList->RemoveItemAt(index); 8062 else 8063 visible = false; 8064 } 8065 8066 fMimeTypeListIsDirty = true; 8067 8068 if (pose->HasLocation()) 8069 RemoveFromVSList(pose); 8070 8071 if (visible) { 8072 BRect invalidRect; 8073 if (ViewMode() == kListMode) 8074 invalidRect = CalcPoseRectList(pose, index); 8075 else 8076 invalidRect = pose->CalcRect(this); 8077 8078 if (ViewMode() == kListMode) 8079 CloseGapInList(&invalidRect); 8080 else 8081 RemoveFromExtent(invalidRect); 8082 8083 Invalidate(invalidRect); 8084 UpdateCount(); 8085 UpdateScrollRange(); 8086 ResetPosePlacementHint(); 8087 8088 if (ViewMode() == kListMode) { 8089 BRect bounds(Bounds()); 8090 int32 index = (int32)(bounds.bottom / fListElemHeight); 8091 BPose* pose = CurrentPoseList()->ItemAt(index); 8092 8093 if (pose == NULL && bounds.top > 0) { 8094 // scroll up a little 8095 BView::ScrollTo(bounds.left, 8096 std::max(bounds.top - fListElemHeight, 0.0f)); 8097 } 8098 } 8099 } 8100 8101 delete pose; 8102 } else { 8103 // we might be getting a delete for an item in the zombie list 8104 Model* zombie = FindZombie(itemNode, &index); 8105 if (zombie) { 8106 PRINT(("deleting zombie model %s\n", zombie->Name())); 8107 fZombieList->RemoveItemAt(index); 8108 delete zombie; 8109 } else 8110 return false; 8111 } 8112 8113 return true; 8114 } 8115 8116 8117 Model* 8118 BPoseView::FindZombie(const node_ref* itemNode, int32* resultingIndex) 8119 { 8120 int32 count = fZombieList->CountItems(); 8121 for (int32 index = 0; index < count; index++) { 8122 Model* zombie = fZombieList->ItemAt(index); 8123 if (*zombie->NodeRef() == *itemNode) { 8124 if (resultingIndex) 8125 *resultingIndex = index; 8126 return zombie; 8127 } 8128 } 8129 8130 return NULL; 8131 } 8132 8133 8134 // return pose at location h,v (search list starting from bottom so 8135 // drawing and hit detection reflect the same pose ordering) 8136 BPose* 8137 BPoseView::FindPose(BPoint point, int32* poseIndex) const 8138 { 8139 if (ViewMode() == kListMode) { 8140 int32 index = (int32)(point.y / fListElemHeight); 8141 if (poseIndex) 8142 *poseIndex = index; 8143 8144 BPoint loc(0, index * fListElemHeight); 8145 BPose* pose = CurrentPoseList()->ItemAt(index); 8146 if (pose != NULL && pose->PointInPose(loc, this, point)) 8147 return pose; 8148 } else { 8149 int32 count = fPoseList->CountItems(); 8150 for (int32 index = count - 1; index >= 0; index--) { 8151 BPose* pose = fPoseList->ItemAt(index); 8152 if (pose->PointInPose(this, point)) { 8153 if (poseIndex) 8154 *poseIndex = index; 8155 8156 return pose; 8157 } 8158 } 8159 } 8160 8161 return NULL; 8162 } 8163 8164 8165 BPose* 8166 BPoseView::FirstVisiblePose(int32* _index) const 8167 { 8168 ASSERT(ViewMode() == kListMode); 8169 return FindPose(BPoint(kListOffset, 8170 Bounds().top + fListElemHeight - 1), _index); 8171 } 8172 8173 8174 BPose* 8175 BPoseView::LastVisiblePose(int32* _index) const 8176 { 8177 ASSERT(ViewMode() == kListMode); 8178 BPose* pose = FindPose(BPoint(kListOffset, Bounds().top + Frame().Height() 8179 - fListElemHeight + 2), _index); 8180 if (pose == NULL) { 8181 // Just get the last one 8182 pose = CurrentPoseList()->LastItem(); 8183 if (_index != NULL) 8184 *_index = CurrentPoseList()->CountItems() - 1; 8185 } 8186 return pose; 8187 } 8188 8189 8190 void 8191 BPoseView::OpenSelection(BPose* clickedPose, int32* index) 8192 { 8193 BPose* singleWindowBrowsePose = clickedPose; 8194 TrackerSettings settings; 8195 8196 // get first selected pose in selection if none was clicked 8197 if (settings.SingleWindowBrowse() 8198 && !singleWindowBrowsePose 8199 && fSelectionList->CountItems() == 1 8200 && !IsFilePanel()) { 8201 singleWindowBrowsePose = fSelectionList->ItemAt(0); 8202 } 8203 8204 // check if we can use the single window mode 8205 if (settings.SingleWindowBrowse() 8206 && !IsDesktopWindow() 8207 && !IsFilePanel() 8208 && (modifiers() & B_OPTION_KEY) == 0 8209 && TargetModel()->IsDirectory() 8210 && singleWindowBrowsePose 8211 && singleWindowBrowsePose->ResolvedModel() 8212 && singleWindowBrowsePose->ResolvedModel()->IsDirectory()) { 8213 // Switch to new directory 8214 BMessage msg(kSwitchDirectory); 8215 msg.AddRef("refs", singleWindowBrowsePose->ResolvedModel()->EntryRef()); 8216 Window()->PostMessage(&msg); 8217 } else { 8218 // otherwise use standard method 8219 OpenSelectionCommon(clickedPose, index, false); 8220 } 8221 8222 } 8223 8224 8225 void 8226 BPoseView::OpenSelectionUsing(BPose* clickedPose, int32* index) 8227 { 8228 OpenSelectionCommon(clickedPose, index, true); 8229 } 8230 8231 8232 void 8233 BPoseView::OpenSelectionCommon(BPose* clickedPose, int32* poseIndex, 8234 bool openWith) 8235 { 8236 int32 count = fSelectionList->CountItems(); 8237 if (count == 0) 8238 return; 8239 8240 BMessage message(B_REFS_RECEIVED); 8241 8242 for (int32 index = 0; index < count; index++) { 8243 BPose* pose = fSelectionList->ItemAt(index); 8244 message.AddRef("refs", pose->TargetModel()->EntryRef()); 8245 8246 // close parent window if option down and we're not the desktop 8247 // and we're not in single window mode 8248 if (dynamic_cast<TTracker*>(be_app) == NULL 8249 || (modifiers() & B_OPTION_KEY) == 0 8250 || IsFilePanel() 8251 || IsDesktopWindow() 8252 || TrackerSettings().SingleWindowBrowse()) { 8253 continue; 8254 } 8255 8256 ASSERT(TargetModel()); 8257 message.AddData("nodeRefsToClose", B_RAW_TYPE, TargetModel()->NodeRef(), 8258 sizeof (node_ref)); 8259 } 8260 8261 if (openWith) 8262 message.AddInt32("launchUsingSelector", 0); 8263 8264 // add a messenger to the launch message that will be used to 8265 // dispatch scripting calls from apps to the PoseView 8266 message.AddMessenger("TrackerViewToken", BMessenger(this)); 8267 8268 if (fSelectionHandler) 8269 fSelectionHandler->PostMessage(&message); 8270 8271 if (clickedPose) { 8272 ASSERT(poseIndex); 8273 if (ViewMode() == kListMode) 8274 DrawOpenAnimation(CalcPoseRectList(clickedPose, *poseIndex, true)); 8275 else 8276 DrawOpenAnimation(clickedPose->CalcRect(this)); 8277 } 8278 } 8279 8280 8281 void 8282 BPoseView::DrawOpenAnimation(BRect rect) 8283 { 8284 SetDrawingMode(B_OP_INVERT); 8285 8286 BRect box1(rect); 8287 box1.InsetBy(rect.Width() / 2 - 2, rect.Height() / 2 - 2); 8288 BRect box2(box1); 8289 8290 for (int32 index = 0; index < 7; index++) { 8291 box2 = box1; 8292 box2.InsetBy(-2, -2); 8293 StrokeRect(box1, B_MIXED_COLORS); 8294 Sync(); 8295 StrokeRect(box2, B_MIXED_COLORS); 8296 Sync(); 8297 snooze(10000); 8298 StrokeRect(box1, B_MIXED_COLORS); 8299 StrokeRect(box2, B_MIXED_COLORS); 8300 Sync(); 8301 box1 = box2; 8302 } 8303 8304 SetDrawingMode(B_OP_OVER); 8305 } 8306 8307 8308 void 8309 BPoseView::UnmountSelectedVolumes() 8310 { 8311 BVolume boot; 8312 BVolumeRoster().GetBootVolume(&boot); 8313 8314 int32 select_count = fSelectionList->CountItems(); 8315 for (int32 index = 0; index < select_count; index++) { 8316 BPose* pose = fSelectionList->ItemAt(index); 8317 if (pose == NULL) 8318 continue; 8319 8320 Model* model = pose->TargetModel(); 8321 if (model->IsVolume()) { 8322 BVolume volume(model->NodeRef()->device); 8323 if (volume != boot) { 8324 TTracker* tracker = dynamic_cast<TTracker*>(be_app); 8325 if (tracker != NULL) 8326 tracker->SaveAllPoseLocations(); 8327 8328 BMessage message(kUnmountVolume); 8329 message.AddInt32("device_id", volume.Device()); 8330 be_app->PostMessage(&message); 8331 } 8332 } 8333 } 8334 } 8335 8336 8337 void 8338 BPoseView::ClearPoses() 8339 { 8340 CommitActivePose(); 8341 SavePoseLocations(); 8342 ClearFilter(); 8343 8344 // clear all pose lists 8345 fPoseList->MakeEmpty(); 8346 fMimeTypeListIsDirty = true; 8347 fVSPoseList->MakeEmpty(); 8348 fZombieList->MakeEmpty(); 8349 fSelectionList->MakeEmpty(); 8350 fSelectionPivotPose = NULL; 8351 fRealPivotPose = NULL; 8352 fMimeTypesInSelectionCache.MakeEmpty(); 8353 fBrokenLinks->MakeEmpty(); 8354 8355 DisableScrollBars(); 8356 ScrollTo(BPoint(0, 0)); 8357 UpdateScrollRange(); 8358 SetScrollBarsTo(BPoint(0, 0)); 8359 EnableScrollBars(); 8360 ResetPosePlacementHint(); 8361 ClearExtent(); 8362 8363 if (fSelectionChangedHook) 8364 ContainerWindow()->SelectionChanged(); 8365 } 8366 8367 8368 void 8369 BPoseView::SwitchDir(const entry_ref* newDirRef, AttributeStreamNode* node) 8370 { 8371 ASSERT(TargetModel()); 8372 if (*newDirRef == *TargetModel()->EntryRef()) 8373 // no change 8374 return; 8375 8376 Model* model = new Model(newDirRef, true); 8377 if (model->InitCheck() != B_OK || !model->IsDirectory()) { 8378 delete model; 8379 return; 8380 } 8381 8382 CommitActivePose(); 8383 8384 // before clearing and adding new poses, we reset "blessed" async 8385 // thread id to prevent old add_poses thread from adding any more icons 8386 // the new add_poses thread will then set fAddPosesThread to its ID and it 8387 // will be allowed to add icons 8388 fAddPosesThreads.clear(); 8389 fInsertedNodes.clear(); 8390 8391 delete fModel; 8392 fModel = model; 8393 8394 // check if model is a trash dir, if so 8395 // update ContainerWindow's fIsTrash, etc. 8396 // variables to indicate new state 8397 ContainerWindow()->UpdateIfTrash(model); 8398 8399 StopWatching(); 8400 ClearPoses(); 8401 8402 // Restore state, might fail if the state has never been saved for this node 8403 uint32 oldMode = ViewMode(); 8404 bool viewStateRestored = false; 8405 if (node != NULL) { 8406 BViewState* previousState = fViewState; 8407 RestoreState(node); 8408 viewStateRestored = (fViewState != previousState); 8409 } 8410 8411 // Make sure fTitleView is rebuilt, as fColumnList might have changed 8412 fTitleView->Reset(); 8413 8414 if (viewStateRestored) { 8415 if (ViewMode() == kListMode && oldMode != kListMode) { 8416 if (ContainerWindow()) 8417 ContainerWindow()->ShowAttributeMenu(); 8418 8419 fTitleView->Show(); 8420 } else if (ViewMode() != kListMode && oldMode == kListMode) { 8421 fTitleView->Hide(); 8422 8423 if (ContainerWindow()) 8424 ContainerWindow()->HideAttributeMenu(); 8425 } else if (ViewMode() == kListMode && oldMode == kListMode) 8426 fTitleView->Invalidate(); 8427 8428 BPoint origin; 8429 if (ViewMode() == kListMode) 8430 origin = fViewState->ListOrigin(); 8431 else 8432 origin = fViewState->IconOrigin(); 8433 8434 PinPointToValidRange(origin); 8435 8436 SetIconPoseHeight(); 8437 GetLayoutInfo(ViewMode(), &fGrid, &fOffset); 8438 ResetPosePlacementHint(); 8439 8440 DisableScrollBars(); 8441 ScrollTo(origin); 8442 UpdateScrollRange(); 8443 SetScrollBarsTo(origin); 8444 EnableScrollBars(); 8445 } else { 8446 ResetOrigin(); 8447 ResetPosePlacementHint(); 8448 } 8449 8450 StartWatching(); 8451 8452 // be sure this happens after origin is set and window is sized 8453 // properly for proper icon caching! 8454 8455 if (ContainerWindow()->IsTrash()) 8456 AddTrashPoses(); 8457 else 8458 AddPoses(TargetModel()); 8459 TargetModel()->CloseNode(); 8460 8461 Invalidate(); 8462 8463 fLastKeyTime = 0; 8464 } 8465 8466 8467 void 8468 BPoseView::Refresh() 8469 { 8470 BEntry entry; 8471 8472 ASSERT(TargetModel()); 8473 if (TargetModel()->OpenNode() != B_OK) 8474 return; 8475 8476 StopWatching(); 8477 fInsertedNodes.clear(); 8478 ClearPoses(); 8479 StartWatching(); 8480 8481 // be sure this happens after origin is set and window is sized 8482 // properly for proper icon caching! 8483 AddPoses(TargetModel()); 8484 TargetModel()->CloseNode(); 8485 8486 if (fRefFilter != NULL) { 8487 fFiltering = false; 8488 StartFiltering(); 8489 } 8490 8491 Invalidate(); 8492 ResetOrigin(); 8493 ResetPosePlacementHint(); 8494 } 8495 8496 8497 void 8498 BPoseView::ResetOrigin() 8499 { 8500 DisableScrollBars(); 8501 ScrollTo(B_ORIGIN); 8502 UpdateScrollRange(); 8503 SetScrollBarsTo(B_ORIGIN); 8504 EnableScrollBars(); 8505 } 8506 8507 8508 void 8509 BPoseView::EditQueries() 8510 { 8511 // edit selected queries 8512 SendSelectionAsRefs(kEditQuery, true); 8513 } 8514 8515 8516 void 8517 BPoseView::SendSelectionAsRefs(uint32 what, bool onlyQueries) 8518 { 8519 // fix this by having a proper selection iterator 8520 8521 int32 numItems = fSelectionList->CountItems(); 8522 if (!numItems) 8523 return; 8524 8525 bool haveRef = false; 8526 BMessage message; 8527 message.what = what; 8528 8529 for (int32 index = 0; index < numItems; index++) { 8530 BPose* pose = fSelectionList->ItemAt(index); 8531 if (onlyQueries) { 8532 // to check if pose is a query, follow any symlink first 8533 BEntry resolvedEntry(pose->TargetModel()->EntryRef(), true); 8534 if (resolvedEntry.InitCheck() != B_OK) 8535 continue; 8536 8537 Model model(&resolvedEntry); 8538 if (!model.IsQuery() && !model.IsQueryTemplate()) 8539 continue; 8540 } 8541 haveRef = true; 8542 message.AddRef("refs", pose->TargetModel()->EntryRef()); 8543 } 8544 if (!haveRef) 8545 return; 8546 8547 if (onlyQueries) 8548 // this is used to make query templates come up in a special edit window 8549 message.AddBool("editQueryOnPose", onlyQueries); 8550 8551 BMessenger(kTrackerSignature).SendMessage(&message); 8552 } 8553 8554 8555 void 8556 BPoseView::OpenInfoWindows() 8557 { 8558 BMessenger tracker(kTrackerSignature); 8559 if (!tracker.IsValid()) { 8560 BAlert* alert = new BAlert("", 8561 B_TRANSLATE("The Tracker must be running to see Info windows."), 8562 B_TRANSLATE("Cancel"), NULL, NULL, B_WIDTH_AS_USUAL, 8563 B_WARNING_ALERT); 8564 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 8565 alert->Go(); 8566 return; 8567 } 8568 SendSelectionAsRefs(kGetInfo); 8569 } 8570 8571 8572 void 8573 BPoseView::SetDefaultPrinter() 8574 { 8575 BMessenger trackerMessenger(kTrackerSignature); 8576 if (!trackerMessenger.IsValid()) { 8577 BAlert* alert = new BAlert("", 8578 B_TRANSLATE("The Tracker must be running to see set the default " 8579 "printer."), B_TRANSLATE("Cancel"), NULL, NULL, B_WIDTH_AS_USUAL, 8580 B_WARNING_ALERT); 8581 alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE); 8582 alert->Go(); 8583 return; 8584 } 8585 SendSelectionAsRefs(kMakeActivePrinter); 8586 } 8587 8588 8589 void 8590 BPoseView::OpenParent() 8591 { 8592 if (!TargetModel() || TargetModel()->IsRoot() || IsDesktopWindow()) 8593 return; 8594 8595 BEntry entry(TargetModel()->EntryRef()); 8596 entry_ref ref; 8597 8598 if (FSGetParentVirtualDirectoryAware(entry, entry) != B_OK 8599 || entry.GetRef(&ref) != B_OK) 8600 return; 8601 8602 BEntry root("/"); 8603 if (!TrackerSettings().SingleWindowBrowse() 8604 && !TrackerSettings().ShowNavigator() 8605 && !TrackerSettings().ShowDisksIcon() && entry == root 8606 && (modifiers() & B_CONTROL_KEY) == 0) 8607 return; 8608 8609 Model parentModel(&ref); 8610 8611 BMessage message(B_REFS_RECEIVED); 8612 message.AddRef("refs", &ref); 8613 8614 if (dynamic_cast<TTracker*>(be_app)) { 8615 // add information about the child, so that we can select it 8616 // in the parent view 8617 message.AddData("nodeRefToSelect", B_RAW_TYPE, TargetModel()->NodeRef(), 8618 sizeof (node_ref)); 8619 8620 if ((modifiers() & B_OPTION_KEY) != 0 && !IsFilePanel()) { 8621 // if option down, add instructions to close the parent 8622 message.AddData("nodeRefsToClose", B_RAW_TYPE, 8623 TargetModel()->NodeRef(), sizeof (node_ref)); 8624 } 8625 } 8626 8627 if (TrackerSettings().SingleWindowBrowse()) { 8628 BMessage msg(kSwitchDirectory); 8629 msg.AddRef("refs", &ref); 8630 Window()->PostMessage(&msg); 8631 } else 8632 be_app->PostMessage(&message); 8633 } 8634 8635 8636 void 8637 BPoseView::IdentifySelection(bool force) 8638 { 8639 int32 count = fSelectionList->CountItems(); 8640 for (int32 index = 0; index < count; index++) { 8641 BPose* pose = fSelectionList->ItemAt(index); 8642 BEntry entry(pose->TargetModel()->ResolveIfLink()->EntryRef()); 8643 if (entry.InitCheck() == B_OK) { 8644 BPath path; 8645 if (entry.GetPath(&path) == B_OK) 8646 update_mime_info(path.Path(), true, false, force ? 2 : 1); 8647 } 8648 } 8649 } 8650 8651 8652 void 8653 BPoseView::ClearSelection() 8654 { 8655 CommitActivePose(); 8656 fSelectionPivotPose = NULL; 8657 fRealPivotPose = NULL; 8658 8659 if (fSelectionList->CountItems() > 0) { 8660 // scan all visible poses first 8661 BRect bounds(Bounds()); 8662 8663 if (ViewMode() == kListMode) { 8664 int32 startIndex = (int32)(bounds.top / fListElemHeight); 8665 BPoint loc(0, startIndex * fListElemHeight); 8666 8667 PoseList* poseList = CurrentPoseList(); 8668 int32 count = poseList->CountItems(); 8669 for (int32 index = startIndex; index < count; index++) { 8670 BPose* pose = poseList->ItemAt(index); 8671 if (pose->IsSelected()) { 8672 pose->Select(false); 8673 Invalidate(pose->CalcRect(loc, this, false)); 8674 } 8675 8676 loc.y += fListElemHeight; 8677 if (loc.y > bounds.bottom) 8678 break; 8679 } 8680 } else { 8681 int32 startIndex = FirstIndexAtOrBelow( 8682 (int32)(bounds.top - IconPoseHeight()), true); 8683 int32 count = fVSPoseList->CountItems(); 8684 for (int32 index = startIndex; index < count; index++) { 8685 BPose* pose = fVSPoseList->ItemAt(index); 8686 if (pose != NULL) { 8687 if (pose->IsSelected()) { 8688 pose->Select(false); 8689 Invalidate(pose->CalcRect(this)); 8690 } 8691 8692 if (pose->Location(this).y > bounds.bottom) 8693 break; 8694 } 8695 } 8696 } 8697 8698 // clear selection state in all poses 8699 int32 count = fSelectionList->CountItems(); 8700 for (int32 index = 0; index < count; index++) 8701 fSelectionList->ItemAt(index)->Select(false); 8702 8703 fSelectionList->MakeEmpty(); 8704 } 8705 8706 fMimeTypesInSelectionCache.MakeEmpty(); 8707 } 8708 8709 8710 void 8711 BPoseView::ShowSelection(bool show) 8712 { 8713 if (fSelectionVisible == show) 8714 return; 8715 8716 fSelectionVisible = show; 8717 8718 if (fSelectionList->CountItems() <= 0) 8719 return; 8720 8721 // scan all visible poses first 8722 BRect bounds(Bounds()); 8723 8724 if (ViewMode() == kListMode) { 8725 int32 startIndex = (int32)(bounds.top / fListElemHeight); 8726 BPoint loc(0, startIndex * fListElemHeight); 8727 8728 PoseList* poseList = CurrentPoseList(); 8729 int32 count = poseList->CountItems(); 8730 for (int32 index = startIndex; index < count; index++) { 8731 BPose* pose = poseList->ItemAt(index); 8732 if (fSelectionList->HasItem(pose)) 8733 if (pose->IsSelected() != show 8734 || fShowSelectionWhenInactive) { 8735 if (!fShowSelectionWhenInactive) 8736 pose->Select(show); 8737 8738 pose->Draw(BRect(pose->CalcRect(loc, this, false)), 8739 bounds, this, false); 8740 } 8741 8742 loc.y += fListElemHeight; 8743 if (loc.y > bounds.bottom) 8744 break; 8745 } 8746 } else { 8747 int32 startIndex = FirstIndexAtOrBelow( 8748 (int32)(bounds.top - IconPoseHeight()), true); 8749 int32 count = fVSPoseList->CountItems(); 8750 for (int32 index = startIndex; index < count; index++) { 8751 BPose* pose = fVSPoseList->ItemAt(index); 8752 if (pose != NULL) { 8753 if (fSelectionList->HasItem(pose)) 8754 if (pose->IsSelected() != show 8755 || fShowSelectionWhenInactive) { 8756 if (!fShowSelectionWhenInactive) 8757 pose->Select(show); 8758 8759 Invalidate(pose->CalcRect(this)); 8760 } 8761 8762 if (pose->Location(this).y > bounds.bottom) 8763 break; 8764 } 8765 } 8766 } 8767 8768 // now set all other poses 8769 int32 count = fSelectionList->CountItems(); 8770 for (int32 index = 0; index < count; index++) { 8771 BPose* pose = fSelectionList->ItemAt(index); 8772 if (pose->IsSelected() != show && !fShowSelectionWhenInactive) 8773 pose->Select(show); 8774 } 8775 8776 // finally update fRealPivotPose/fSelectionPivotPose 8777 if (!show) { 8778 fRealPivotPose = fSelectionPivotPose; 8779 fSelectionPivotPose = NULL; 8780 } else { 8781 if (fRealPivotPose) 8782 fSelectionPivotPose = fRealPivotPose; 8783 8784 fRealPivotPose = NULL; 8785 } 8786 } 8787 8788 8789 void 8790 BPoseView::AddRemovePoseFromSelection(BPose* pose, int32 index, bool select) 8791 { 8792 // Do not allow double selection/deselection. 8793 if (select == pose->IsSelected()) 8794 return; 8795 8796 pose->Select(select); 8797 8798 // update display 8799 if (ViewMode() == kListMode) { 8800 Invalidate(pose->CalcRect(BPoint(0, index * fListElemHeight), 8801 this, false)); 8802 } else 8803 Invalidate(pose->CalcRect(this)); 8804 8805 if (select) 8806 fSelectionList->AddItem(pose); 8807 else { 8808 fSelectionList->RemoveItem(pose, false); 8809 if (fSelectionPivotPose == pose) 8810 fSelectionPivotPose = NULL; 8811 8812 if (fRealPivotPose == pose) 8813 fRealPivotPose = NULL; 8814 } 8815 } 8816 8817 8818 void 8819 BPoseView::RemoveFromExtent(const BRect &rect) 8820 { 8821 ASSERT(ViewMode() != kListMode); 8822 8823 if (rect.left <= fExtent.left || rect.right >= fExtent.right 8824 || rect.top <= fExtent.top || rect.bottom >= fExtent.bottom) 8825 RecalcExtent(); 8826 } 8827 8828 8829 void 8830 BPoseView::RecalcExtent() 8831 { 8832 ASSERT(ViewMode() != kListMode); 8833 8834 ClearExtent(); 8835 int32 count = fPoseList->CountItems(); 8836 for (int32 index = 0; index < count; index++) 8837 AddToExtent(fPoseList->ItemAt(index)->CalcRect(this)); 8838 } 8839 8840 8841 BRect 8842 BPoseView::Extent() const 8843 { 8844 BRect rect; 8845 8846 if (ViewMode() == kListMode) { 8847 BColumn* column = fColumnList->LastItem(); 8848 if (column != NULL) { 8849 rect.left = rect.top = 0; 8850 rect.right = column->Offset() + column->Width() 8851 + kTitleColumnRightExtraMargin - kRoomForLine / 2.0f; 8852 rect.bottom = fListElemHeight * CurrentPoseList()->CountItems(); 8853 } else 8854 rect.Set(LeftTop().x, LeftTop().y, LeftTop().x, LeftTop().y); 8855 } else { 8856 rect = fExtent; 8857 rect.left -= fOffset.x; 8858 rect.top -= fOffset.y; 8859 rect.right += fOffset.x; 8860 rect.bottom += fOffset.y; 8861 if (!rect.IsValid()) 8862 rect.Set(LeftTop().x, LeftTop().y, LeftTop().x, LeftTop().y); 8863 } 8864 8865 return rect; 8866 } 8867 8868 8869 void 8870 BPoseView::SetScrollBarsTo(BPoint point) 8871 { 8872 if (fHScrollBar && fVScrollBar) { 8873 fHScrollBar->SetValue(point.x); 8874 fVScrollBar->SetValue(point.y); 8875 } else { 8876 // TODO: I don't know what this was supposed to work around 8877 // (ie why it wasn't calling ScrollTo(point) simply). Although 8878 // it cannot have been tested, since it was broken before, I am 8879 // still leaving this, since I know there can be a subtle change in 8880 // behaviour (BView<->BScrollBar feedback effects) when scrolling 8881 // both directions at once versus separately. 8882 BPoint origin = LeftTop(); 8883 ScrollTo(BPoint(origin.x, point.y)); 8884 ScrollTo(point); 8885 } 8886 } 8887 8888 8889 void 8890 BPoseView::PinPointToValidRange(BPoint& origin) 8891 { 8892 // !NaN and valid range 8893 // the following checks are not broken even they look like they are 8894 if (!(origin.x >= 0) && !(origin.x <= 0)) 8895 origin.x = 0; 8896 else if (origin.x < -40000.0 || origin.x > 40000.0) 8897 origin.x = 0; 8898 8899 if (!(origin.y >= 0) && !(origin.y <= 0)) 8900 origin.y = 0; 8901 else if (origin.y < -40000.0 || origin.y > 40000.0) 8902 origin.y = 0; 8903 } 8904 8905 8906 void 8907 BPoseView::UpdateScrollRange() 8908 { 8909 // TODO: some calls to UpdateScrollRange don't do the right thing because 8910 // Extent doesn't return the right value (too early in PoseView lifetime??) 8911 // 8912 // This happened most with file panels, when opening a parent - added 8913 // an extra call to UpdateScrollRange in SelectChildInParent to work 8914 // around this 8915 8916 AutoLock<BWindow> lock(Window()); 8917 if (!lock) 8918 return; 8919 8920 BRect bounds(Bounds()); 8921 8922 BPoint origin(LeftTop()); 8923 BRect extent(Extent()); 8924 8925 lock.Unlock(); 8926 8927 BPoint minVal(std::min(extent.left, origin.x), 8928 std::min(extent.top, origin.y)); 8929 8930 BPoint maxVal((extent.right - bounds.right) + origin.x, 8931 (extent.bottom - bounds.bottom) + origin.y); 8932 8933 maxVal.x = std::max(maxVal.x, origin.x); 8934 maxVal.y = std::max(maxVal.y, origin.y); 8935 8936 if (fHScrollBar) { 8937 float scrollMin; 8938 float scrollMax; 8939 fHScrollBar->GetRange(&scrollMin, &scrollMax); 8940 if (minVal.x != scrollMin || maxVal.x != scrollMax) { 8941 fHScrollBar->SetRange(minVal.x, maxVal.x); 8942 fHScrollBar->SetSteps(kSmallStep, bounds.Width()); 8943 } 8944 } 8945 8946 if (fVScrollBar) { 8947 float scrollMin; 8948 float scrollMax; 8949 fVScrollBar->GetRange(&scrollMin, &scrollMax); 8950 8951 if (minVal.y != scrollMin || maxVal.y != scrollMax) { 8952 fVScrollBar->SetRange(minVal.y, maxVal.y); 8953 fVScrollBar->SetSteps(kSmallStep, bounds.Height()); 8954 } 8955 } 8956 8957 // set proportions for bars 8958 BRect totalExtent(extent | bounds); 8959 8960 if (fHScrollBar && totalExtent.Width() != 0.0) { 8961 float proportion = bounds.Width() / totalExtent.Width(); 8962 if (fHScrollBar->Proportion() != proportion) 8963 fHScrollBar->SetProportion(proportion); 8964 } 8965 8966 if (fVScrollBar && totalExtent.Height() != 0.0) { 8967 float proportion = bounds.Height() / totalExtent.Height(); 8968 if (fVScrollBar->Proportion() != proportion) 8969 fVScrollBar->SetProportion(proportion); 8970 } 8971 } 8972 8973 8974 void 8975 BPoseView::DrawPose(BPose* pose, int32 index, bool fullDraw) 8976 { 8977 BRect rect = CalcPoseRect(pose, index, fullDraw); 8978 8979 if (TrackerSettings().ShowVolumeSpaceBar() 8980 && pose->TargetModel()->IsVolume()) { 8981 Invalidate(rect); 8982 } else 8983 pose->Draw(rect, rect, this, fullDraw); 8984 } 8985 8986 8987 rgb_color 8988 BPoseView::DeskTextColor() const 8989 { 8990 rgb_color color = ViewColor(); 8991 float thresh = color.red + (color.green * 1.5f) + (color.blue * 0.50f); 8992 8993 if (thresh >= 300) { 8994 color.red = 0; 8995 color.green = 0; 8996 color.blue = 0; 8997 } else { 8998 color.red = 255; 8999 color.green = 255; 9000 color.blue = 255; 9001 } 9002 9003 return color; 9004 } 9005 9006 9007 rgb_color 9008 BPoseView::DeskTextBackColor() const 9009 { 9010 // returns black or white color depending on the desktop background 9011 int32 thresh = 0; 9012 rgb_color color = LowColor(); 9013 9014 if (color.red > 150) 9015 thresh++; 9016 9017 if (color.green > 150) 9018 thresh++; 9019 9020 if (color.blue > 150) 9021 thresh++; 9022 9023 if (thresh > 1) { 9024 color.red = 255; 9025 color.green = 255; 9026 color.blue = 255; 9027 } else { 9028 color.red = 0; 9029 color.green = 0; 9030 color.blue = 0; 9031 } 9032 9033 return color; 9034 } 9035 9036 9037 void 9038 BPoseView::Draw(BRect updateRect) 9039 { 9040 if (IsDesktopWindow()) { 9041 BScreen screen(Window()); 9042 rgb_color color = screen.DesktopColor(); 9043 SetLowColor(color); 9044 SetViewColor(color); 9045 } 9046 DrawViewCommon(updateRect); 9047 9048 if ((Flags() & B_DRAW_ON_CHILDREN) == 0) 9049 DrawAfterChildren(updateRect); 9050 } 9051 9052 9053 void 9054 BPoseView::DrawAfterChildren(BRect updateRect) 9055 { 9056 if (fTransparentSelection && fSelectionRectInfo.rect.IsValid()) { 9057 SetDrawingMode(B_OP_ALPHA); 9058 rgb_color color = ui_color(B_NAVIGATION_BASE_COLOR); 9059 color.alpha = 128; 9060 SetHighColor(color); 9061 if (fSelectionRectInfo.rect.Width() == 0 9062 || fSelectionRectInfo.rect.Height() == 0) { 9063 StrokeLine(fSelectionRectInfo.rect.LeftTop(), 9064 fSelectionRectInfo.rect.RightBottom()); 9065 } else { 9066 StrokeRect(fSelectionRectInfo.rect); 9067 BRect interior = fSelectionRectInfo.rect; 9068 interior.InsetBy(1, 1); 9069 if (interior.IsValid()) { 9070 color = ui_color(B_CONTROL_HIGHLIGHT_COLOR); 9071 color.alpha = 90; 9072 SetHighColor(color); 9073 FillRect(interior); 9074 } 9075 } 9076 SetDrawingMode(B_OP_OVER); 9077 } 9078 } 9079 9080 9081 void 9082 BPoseView::SynchronousUpdate(BRect updateRect, bool clip) 9083 { 9084 if (clip) { 9085 BRegion updateRegion; 9086 updateRegion.Set(updateRect); 9087 ConstrainClippingRegion(&updateRegion); 9088 } 9089 9090 Invalidate(updateRect); 9091 Window()->UpdateIfNeeded(); 9092 9093 if (clip) 9094 ConstrainClippingRegion(NULL); 9095 } 9096 9097 9098 void 9099 BPoseView::DrawViewCommon(const BRect& updateRect) 9100 { 9101 if (ViewMode() == kListMode) { 9102 PoseList* poseList = CurrentPoseList(); 9103 int32 count = poseList->CountItems(); 9104 int32 startIndex 9105 = (int32)((updateRect.top - fListElemHeight) / fListElemHeight); 9106 9107 if (startIndex < 0) 9108 startIndex = 0; 9109 9110 BPoint loc(0, startIndex * fListElemHeight); 9111 9112 for (int32 index = startIndex; index < count; index++) { 9113 BPose* pose = poseList->ItemAt(index); 9114 BRect poseRect(pose->CalcRect(loc, this, true)); 9115 pose->Draw(poseRect, updateRect, this, true); 9116 loc.y += fListElemHeight; 9117 if (loc.y >= updateRect.bottom) 9118 break; 9119 } 9120 } else { 9121 int32 count = fPoseList->CountItems(); 9122 for (int32 index = 0; index < count; index++) { 9123 BPose* pose = fPoseList->ItemAt(index); 9124 BRect poseRect(pose->CalcRect(this)); 9125 if (updateRect.Intersects(poseRect)) 9126 pose->Draw(poseRect, updateRect, this, true); 9127 } 9128 } 9129 } 9130 9131 9132 void 9133 BPoseView::ColumnRedraw(BRect updateRect) 9134 { 9135 // used for dynamic column resizing using an offscreen draw buffer 9136 ASSERT(ViewMode() == kListMode); 9137 9138 if (IsDesktopWindow()) { 9139 BScreen screen(Window()); 9140 rgb_color d = screen.DesktopColor(); 9141 SetLowColor(d); 9142 SetViewColor(d); 9143 } 9144 9145 int32 startIndex 9146 = (int32)((updateRect.top - fListElemHeight) / fListElemHeight); 9147 if (startIndex < 0) 9148 startIndex = 0; 9149 9150 PoseList* poseList = CurrentPoseList(); 9151 int32 count = poseList->CountItems(); 9152 if (!count) 9153 return; 9154 9155 BPoint loc(0, startIndex * fListElemHeight); 9156 BRect srcRect = poseList->ItemAt(0)->CalcRect(BPoint(0, 0), this, false); 9157 srcRect.right += 1024; // need this to erase correctly 9158 sOffscreen->BeginUsing(srcRect); 9159 BView* offscreenView = sOffscreen->View(); 9160 9161 BRegion updateRegion; 9162 updateRegion.Set(updateRect); 9163 ConstrainClippingRegion(&updateRegion); 9164 9165 for (int32 index = startIndex; index < count; index++) { 9166 BPose* pose = poseList->ItemAt(index); 9167 9168 offscreenView->SetDrawingMode(B_OP_COPY); 9169 offscreenView->SetLowColor(LowColor()); 9170 offscreenView->FillRect(offscreenView->Bounds(), B_SOLID_LOW); 9171 9172 BRect dstRect = srcRect; 9173 dstRect.OffsetTo(loc); 9174 9175 BPoint offsetBy(0, -(index * ListElemHeight())); 9176 pose->Draw(dstRect, updateRect, this, offscreenView, true, 9177 offsetBy, pose->IsSelected()); 9178 9179 offscreenView->Sync(); 9180 SetDrawingMode(B_OP_COPY); 9181 DrawBitmap(sOffscreen->Bitmap(), srcRect, dstRect); 9182 loc.y += fListElemHeight; 9183 if (loc.y > updateRect.bottom) 9184 break; 9185 } 9186 sOffscreen->DoneUsing(); 9187 ConstrainClippingRegion(0); 9188 } 9189 9190 9191 void 9192 BPoseView::CloseGapInList(BRect* invalidRect) 9193 { 9194 (*invalidRect).bottom = Extent().bottom + fListElemHeight; 9195 BRect bounds(Bounds()); 9196 9197 if (bounds.Intersects(*invalidRect)) { 9198 BRect destRect(*invalidRect); 9199 destRect = destRect & bounds; 9200 destRect.bottom -= fListElemHeight; 9201 9202 BRect srcRect(destRect); 9203 srcRect.OffsetBy(0, fListElemHeight); 9204 9205 if (srcRect.Intersects(bounds) || destRect.Intersects(bounds)) 9206 CopyBits(srcRect, destRect); 9207 9208 *invalidRect = srcRect; 9209 (*invalidRect).top = destRect.bottom; 9210 } 9211 } 9212 9213 9214 void 9215 BPoseView::CheckPoseSortOrder(BPose* pose, int32 oldIndex) 9216 { 9217 _CheckPoseSortOrder(CurrentPoseList(), pose, oldIndex); 9218 } 9219 9220 9221 void 9222 BPoseView::_CheckPoseSortOrder(PoseList* poseList, BPose* pose, int32 oldIndex) 9223 { 9224 if (ViewMode() != kListMode) 9225 return; 9226 9227 Window()->UpdateIfNeeded(); 9228 9229 // take pose out of list for BSearch 9230 poseList->RemoveItemAt(oldIndex); 9231 int32 afterIndex; 9232 int32 orientation = BSearchList(poseList, pose, &afterIndex, oldIndex); 9233 9234 int32 newIndex; 9235 if (orientation == kInsertAtFront) 9236 newIndex = 0; 9237 else 9238 newIndex = afterIndex + 1; 9239 9240 if (newIndex == oldIndex) { 9241 poseList->AddItem(pose, oldIndex); 9242 return; 9243 } 9244 9245 if (fFiltering && poseList != fFilteredPoseList) { 9246 poseList->AddItem(pose, newIndex); 9247 return; 9248 } 9249 9250 BRect invalidRect(CalcPoseRectList(pose, oldIndex)); 9251 CloseGapInList(&invalidRect); 9252 Invalidate(invalidRect); 9253 // need to invalidate for the last item in the list 9254 InsertPoseAfter(pose, &afterIndex, orientation, &invalidRect); 9255 poseList->AddItem(pose, newIndex); 9256 Invalidate(invalidRect); 9257 } 9258 9259 9260 static int 9261 PoseCompareAddWidget(const BPose* p1, const BPose* p2, BPoseView* view) 9262 { 9263 // pose comparison and lazy text widget adding 9264 9265 uint32 sort = view->PrimarySort(); 9266 BColumn* column = view->ColumnFor(sort); 9267 if (column == NULL) 9268 return 0; 9269 9270 BPose* primary; 9271 BPose* secondary; 9272 if (!view->ReverseSort()) { 9273 primary = const_cast<BPose*>(p1); 9274 secondary = const_cast<BPose*>(p2); 9275 } else { 9276 primary = const_cast<BPose*>(p2); 9277 secondary = const_cast<BPose*>(p1); 9278 } 9279 9280 int32 result = 0; 9281 for (int32 count = 0; ; count++) { 9282 BTextWidget* widget1 = primary->WidgetFor(sort); 9283 if (widget1 == NULL) 9284 widget1 = primary->AddWidget(view, column); 9285 9286 BTextWidget* widget2 = secondary->WidgetFor(sort); 9287 if (widget2 == NULL) 9288 widget2 = secondary->AddWidget(view, column); 9289 9290 if (widget1 == NULL || widget2 == NULL) 9291 return result; 9292 9293 result = widget1->Compare(*widget2, view); 9294 9295 if (count != 0) 9296 return result; 9297 9298 // do we need to sort by secondary attribute? 9299 if (result == 0) { 9300 sort = view->SecondarySort(); 9301 if (!sort) 9302 return result; 9303 9304 column = view->ColumnFor(sort); 9305 if (column == NULL) 9306 return result; 9307 } 9308 } 9309 9310 return result; 9311 } 9312 9313 9314 static BPose* 9315 BSearch(PoseList* table, const BPose* key, BPoseView* view, 9316 int (*cmp)(const BPose*, const BPose*, BPoseView*), bool returnClosest) 9317 { 9318 int32 r = table->CountItems(); 9319 BPose* result = 0; 9320 9321 for (int32 l = 1; l <= r;) { 9322 int32 m = (l + r) / 2; 9323 9324 result = table->ItemAt(m - 1); 9325 int32 compareResult = (cmp)(result, key, view); 9326 if (compareResult == 0) 9327 return result; 9328 else if (compareResult < 0) 9329 l = m + 1; 9330 else 9331 r = m - 1; 9332 } 9333 if (returnClosest) 9334 return result; 9335 9336 return NULL; 9337 } 9338 9339 9340 int32 9341 BPoseView::BSearchList(PoseList* poseList, const BPose* pose, 9342 int32* resultingIndex, int32 oldIndex) 9343 { 9344 // check to see if insertion should be at beginning of list 9345 const BPose* firstPose = poseList->FirstItem(); 9346 if (!firstPose) 9347 return kInsertAtFront; 9348 9349 if (PoseCompareAddWidget(pose, firstPose, this) < 0) { 9350 *resultingIndex = 0; 9351 return kInsertAtFront; 9352 } 9353 9354 int32 count = poseList->CountItems(); 9355 9356 // look if old position is still ok, by comparing to siblings 9357 bool valid = oldIndex > 0 && oldIndex < count - 1; 9358 valid = valid && PoseCompareAddWidget(pose, 9359 poseList->ItemAt(oldIndex - 1), this) >= 0; 9360 // the current item is gone, so not oldIndex+1 9361 valid = valid && PoseCompareAddWidget(pose, 9362 poseList->ItemAt(oldIndex), this) <= 0; 9363 9364 if (valid) { 9365 *resultingIndex = oldIndex - 1; 9366 return kInsertAfter; 9367 } 9368 9369 *resultingIndex = count - 1; 9370 9371 const BPose* searchResult = BSearch(poseList, pose, this, 9372 PoseCompareAddWidget); 9373 9374 if (searchResult != NULL) { 9375 // what are we doing here?? 9376 // looks like we are skipping poses with identical search results or 9377 // something 9378 int32 index = poseList->IndexOf(searchResult); 9379 for (; index < count; index++) { 9380 int32 result = PoseCompareAddWidget(pose, poseList->ItemAt(index), 9381 this); 9382 if (result <= 0) { 9383 --index; 9384 break; 9385 } 9386 } 9387 9388 if (index != count) 9389 *resultingIndex = index; 9390 } 9391 9392 return kInsertAfter; 9393 } 9394 9395 9396 void 9397 BPoseView::SetPrimarySort(uint32 attrHash) 9398 { 9399 BColumn* column = ColumnFor(attrHash); 9400 if (column != NULL) { 9401 fViewState->SetPrimarySort(attrHash); 9402 fViewState->SetPrimarySortType(column->AttrType()); 9403 } 9404 } 9405 9406 9407 void 9408 BPoseView::SetSecondarySort(uint32 attrHash) 9409 { 9410 BColumn* column = ColumnFor(attrHash); 9411 if (column != NULL) { 9412 fViewState->SetSecondarySort(attrHash); 9413 fViewState->SetSecondarySortType(column->AttrType()); 9414 } else { 9415 fViewState->SetSecondarySort(0); 9416 fViewState->SetSecondarySortType(0); 9417 } 9418 } 9419 9420 9421 void 9422 BPoseView::SetReverseSort(bool reverse) 9423 { 9424 fViewState->SetReverseSort(reverse); 9425 } 9426 9427 9428 inline int 9429 PoseCompareAddWidgetBinder(const BPose* p1, const BPose* p2, 9430 void* castToPoseView) 9431 { 9432 return PoseCompareAddWidget(p1, p2, (BPoseView*)castToPoseView); 9433 } 9434 9435 9436 struct PoseComparator : public std::binary_function<const BPose*, 9437 const BPose*, bool> 9438 { 9439 PoseComparator(BPoseView* poseView): fPoseView(poseView) { } 9440 9441 bool operator() (const BPose* p1, const BPose* p2) 9442 { 9443 return PoseCompareAddWidget(p1, p2, fPoseView) < 0; 9444 } 9445 9446 BPoseView* fPoseView; 9447 }; 9448 9449 9450 #if xDEBUG 9451 static BPose* 9452 DumpOne(BPose* pose, void*) 9453 { 9454 pose->TargetModel()->PrintToStream(0); 9455 return 0; 9456 } 9457 #endif 9458 9459 9460 void 9461 BPoseView::SortPoses() 9462 { 9463 if (fTextWidgetToCheck != NULL) 9464 fTextWidgetToCheck->CancelWait(); 9465 9466 CommitActivePose(); 9467 // PRINT(("pose list count %d\n", fPoseList->CountItems())); 9468 #if xDEBUG 9469 fPoseList->EachElement(DumpOne, 0); 9470 PRINT(("===================\n")); 9471 #endif 9472 9473 BPose** poses = reinterpret_cast<BPose**>( 9474 PoseList::Private(fPoseList).AsBList()->Items()); 9475 std::stable_sort(poses, &poses[fPoseList->CountItems()], 9476 PoseComparator(this)); 9477 if (fFiltering) { 9478 poses = reinterpret_cast<BPose**>( 9479 PoseList::Private(fFilteredPoseList).AsBList()->Items()); 9480 std::stable_sort(poses, &poses[fFilteredPoseList->CountItems()], 9481 PoseComparator(this)); 9482 } 9483 } 9484 9485 9486 BColumn* 9487 BPoseView::ColumnFor(uint32 attr) const 9488 { 9489 int32 count = fColumnList->CountItems(); 9490 for (int32 index = 0; index < count; index++) { 9491 BColumn* column = ColumnAt(index); 9492 if (column->AttrHash() == attr) 9493 return column; 9494 } 9495 9496 return NULL; 9497 } 9498 9499 9500 bool // returns true if actually resized 9501 BPoseView::ResizeColumnToWidest(BColumn* column) 9502 { 9503 ASSERT(ViewMode() == kListMode); 9504 9505 float maxWidth = kMinColumnWidth; 9506 9507 PoseList* poseList = CurrentPoseList(); 9508 int32 count = poseList->CountItems(); 9509 for (int32 i = 0; i < count; ++i) { 9510 BTextWidget* widget 9511 = poseList->ItemAt(i)->WidgetFor(column->AttrHash()); 9512 if (widget != NULL) { 9513 float width = widget->PreferredWidth(this); 9514 if (width > maxWidth) 9515 maxWidth = width; 9516 } 9517 } 9518 9519 if (maxWidth > kMinColumnWidth || maxWidth < column->Width()) { 9520 ResizeColumn(column, maxWidth); 9521 return true; 9522 } 9523 9524 return false; 9525 } 9526 9527 9528 BPoint 9529 BPoseView::ResizeColumn(BColumn* column, float newSize, 9530 float* lastLineDrawPos, 9531 void (*drawLineFunc)(BPoseView*, BPoint, BPoint), 9532 void (*undrawLineFunc)(BPoseView*, BPoint, BPoint)) 9533 { 9534 BRect sourceRect(Bounds()); 9535 BPoint result(sourceRect.RightBottom()); 9536 9537 BRect destRect(sourceRect); 9538 // we will use sourceRect and destRect for copyBits 9539 BRect invalidateRect(sourceRect); 9540 // this will serve to clean up after the invalidate 9541 BRect columnDrawRect(sourceRect); 9542 // we will use columnDrawRect to draw the actual resized column 9543 9544 bool shrinking = newSize < column->Width(); 9545 columnDrawRect.left = column->Offset(); 9546 columnDrawRect.right = column->Offset() + kTitleColumnRightExtraMargin 9547 - kRoomForLine + newSize; 9548 sourceRect.left = column->Offset() + kTitleColumnRightExtraMargin 9549 - kRoomForLine + column->Width(); 9550 destRect.left = columnDrawRect.right; 9551 destRect.right = destRect.left + sourceRect.Width(); 9552 invalidateRect.left = destRect.right; 9553 invalidateRect.right = sourceRect.right; 9554 9555 column->SetWidth(newSize); 9556 9557 float offset = kColumnStart; 9558 9559 int32 count = fColumnList->CountItems(); 9560 for (int32 index = 0; index < count; index++) { 9561 column = fColumnList->ItemAt(index); 9562 column->SetOffset(offset); 9563 BColumn* last = column; 9564 offset = last->Offset() + last->Width() + kTitleColumnExtraMargin; 9565 } 9566 9567 if (shrinking) { 9568 ColumnRedraw(columnDrawRect); 9569 // dont have to undraw when shrinking 9570 CopyBits(sourceRect, destRect); 9571 if (drawLineFunc != NULL) { 9572 ASSERT(lastLineDrawPos != NULL); 9573 (drawLineFunc)(this, BPoint(destRect.left + kRoomForLine, 9574 destRect.top), 9575 BPoint(destRect.left + kRoomForLine, destRect.bottom)); 9576 *lastLineDrawPos = destRect.left + kRoomForLine; 9577 } 9578 } else { 9579 CopyBits(sourceRect, destRect); 9580 if (undrawLineFunc != NULL) { 9581 ASSERT(lastLineDrawPos != NULL); 9582 (undrawLineFunc)(this, BPoint(*lastLineDrawPos, sourceRect.top), 9583 BPoint(*lastLineDrawPos, sourceRect.bottom)); 9584 } 9585 if (drawLineFunc != NULL) { 9586 ASSERT(lastLineDrawPos); 9587 (drawLineFunc)(this, BPoint(destRect.left + kRoomForLine, 9588 destRect.top), 9589 BPoint(destRect.left + kRoomForLine, destRect.bottom)); 9590 *lastLineDrawPos = destRect.left + kRoomForLine; 9591 } 9592 ColumnRedraw(columnDrawRect); 9593 } 9594 if (invalidateRect.left < invalidateRect.right) 9595 SynchronousUpdate(invalidateRect, true); 9596 9597 fStateNeedsSaving = true; 9598 9599 return result; 9600 } 9601 9602 9603 void 9604 BPoseView::MoveColumnTo(BColumn* src, BColumn* dest) 9605 { 9606 // find the leftmost boundary of columns we are about to reshuffle 9607 float miny = src->Offset(); 9608 if (miny > dest->Offset()) 9609 miny = dest->Offset(); 9610 9611 // ensure columns are in proper order in list 9612 int32 index = fColumnList->IndexOf(dest); 9613 fColumnList->RemoveItem(src, false); 9614 fColumnList->AddItem(src, index); 9615 9616 float offset = kColumnStart; 9617 int32 count = fColumnList->CountItems(); 9618 9619 for (int32 index = 0; index < count; index++) { 9620 BColumn* column = fColumnList->ItemAt(index); 9621 column->SetOffset(offset); 9622 BColumn* last = column; 9623 offset = last->Offset() + last->Width() + kTitleColumnExtraMargin 9624 - kRoomForLine / 2; 9625 } 9626 9627 // invalidate everything to the right of miny 9628 BRect bounds(Bounds()); 9629 bounds.left = miny; 9630 Invalidate(bounds); 9631 9632 fStateNeedsSaving = true; 9633 } 9634 9635 9636 bool 9637 BPoseView::UpdateDropTarget(BPoint mouseLoc, const BMessage* dragMessage, 9638 bool trackingContextMenu) 9639 { 9640 ASSERT(dragMessage != NULL); 9641 9642 int32 index; 9643 BPose* targetPose = FindPose(mouseLoc, &index); 9644 if (targetPose != NULL && DragSelectionContains(targetPose, dragMessage)) 9645 targetPose = NULL; 9646 9647 if ((fCursorCheck && targetPose == fDropTarget) 9648 || (trackingContextMenu && !targetPose)) { 9649 // no change 9650 return false; 9651 } 9652 9653 fCursorCheck = true; 9654 if (fDropTarget && !DragSelectionContains(fDropTarget, dragMessage)) 9655 HiliteDropTarget(false); 9656 9657 fDropTarget = targetPose; 9658 9659 // dereference if symlink 9660 Model* targetModel = NULL; 9661 if (targetPose != NULL) 9662 targetModel = targetPose->TargetModel(); 9663 9664 Model tmpTarget; 9665 if (targetModel != NULL && targetModel->IsSymLink() 9666 && tmpTarget.SetTo(targetPose->TargetModel()->EntryRef(), true, true) 9667 == B_OK) { 9668 targetModel = &tmpTarget; 9669 } 9670 9671 bool ignoreTypes = (modifiers() & B_CONTROL_KEY) != 0; 9672 if (targetPose != NULL) { 9673 if (targetModel != NULL 9674 && CanHandleDragSelection(targetModel, dragMessage, ignoreTypes)) { 9675 // new target is valid, select it 9676 HiliteDropTarget(true); 9677 } else { 9678 fDropTarget = NULL; 9679 fCursorCheck = false; 9680 } 9681 } 9682 if (targetModel == NULL) 9683 targetModel = TargetModel(); 9684 9685 // if this is an OpenWith window, we'll have no target model 9686 if (targetModel == NULL) 9687 return false; 9688 9689 entry_ref srcRef; 9690 if (targetModel->IsDirectory() && dragMessage->HasRef("refs") 9691 && dragMessage->FindRef("refs", &srcRef) == B_OK) { 9692 Model srcModel(&srcRef); 9693 if (!CheckDevicesEqual(&srcRef, targetModel) 9694 && !srcModel.IsVolume() 9695 && !srcModel.IsRoot()) { 9696 BCursor copyCursor(B_CURSOR_ID_COPY); 9697 SetViewCursor(©Cursor); 9698 return true; 9699 } 9700 } 9701 9702 SetViewCursor(B_CURSOR_SYSTEM_DEFAULT); 9703 9704 return true; 9705 } 9706 9707 9708 bool 9709 BPoseView::FrameForPose(BPose* targetPose, bool convert, BRect* poseRect) 9710 { 9711 bool frameIsValid = false; 9712 BRect bounds(Bounds()); 9713 9714 if (ViewMode() == kListMode) { 9715 PoseList* poseList = CurrentPoseList(); 9716 int32 count = poseList->CountItems(); 9717 int32 startIndex = (int32)(bounds.top / fListElemHeight); 9718 9719 BPoint loc(0, startIndex * fListElemHeight); 9720 for (int32 index = startIndex; index < count; index++) { 9721 if (targetPose == poseList->ItemAt(index)) { 9722 *poseRect = fDropTarget->CalcRect(loc, this, false); 9723 frameIsValid = true; 9724 } 9725 9726 loc.y += fListElemHeight; 9727 if (loc.y > bounds.bottom) 9728 frameIsValid = false; 9729 } 9730 } else { 9731 int32 startIndex = FirstIndexAtOrBelow((int32)(bounds.top 9732 - IconPoseHeight()), true); 9733 int32 count = fVSPoseList->CountItems(); 9734 9735 for (int32 index = startIndex; index < count; index++) { 9736 BPose* pose = fVSPoseList->ItemAt(index); 9737 if (pose != NULL) { 9738 if (pose == fDropTarget) { 9739 *poseRect = pose->CalcRect(this); 9740 frameIsValid = true; 9741 break; 9742 } 9743 9744 if (pose->Location(this).y > bounds.bottom) { 9745 frameIsValid = false; 9746 break; 9747 } 9748 } 9749 } 9750 } 9751 9752 if (convert) 9753 ConvertToScreen(poseRect); 9754 9755 return frameIsValid; 9756 } 9757 9758 9759 bool 9760 BPoseView::MenuTrackingHook(BMenu* menu, void*) 9761 { 9762 // return true if the menu should go away 9763 if (!menu->LockLooper()) 9764 return false; 9765 9766 uint32 buttons; 9767 BPoint location; 9768 menu->GetMouse(&location, &buttons); 9769 9770 bool mouseInMenu = true; 9771 // don't test for buttons up here and try to circumvent messaging 9772 // lest you miss an invoke that will happen after the window goes away 9773 9774 BRect bounds(menu->Bounds()); 9775 bounds.InsetBy(-kMenuTrackMargin, -kMenuTrackMargin); 9776 if (bounds.Contains(location)) { 9777 // still in menu 9778 mouseInMenu = false; 9779 } 9780 9781 if (mouseInMenu) { 9782 menu->ConvertToScreen(&location); 9783 int32 count = menu->CountItems(); 9784 for (int32 index = 0 ; index < count; index++) { 9785 // iterate through all of the items in the menu 9786 // if the submenu is showing, see if the mouse is in the submenu 9787 BMenuItem* item = menu->ItemAt(index); 9788 if (item && item->Submenu()) { 9789 BWindow* window = item->Submenu()->Window(); 9790 bool inSubmenu = false; 9791 if (window && window->Lock()) { 9792 if (!window->IsHidden()) { 9793 BRect frame(window->Frame()); 9794 9795 frame.InsetBy(-kMenuTrackMargin, -kMenuTrackMargin); 9796 inSubmenu = frame.Contains(location); 9797 } 9798 window->Unlock(); 9799 if (inSubmenu) { 9800 // only one menu can have its window open bail now 9801 mouseInMenu = false; 9802 break; 9803 } 9804 } 9805 } 9806 } 9807 } 9808 9809 menu->UnlockLooper(); 9810 9811 return mouseInMenu; 9812 } 9813 9814 9815 void 9816 BPoseView::DragStop() 9817 { 9818 fStartFrame.Set(0, 0, 0, 0); 9819 BContainerWindow* window = ContainerWindow(); 9820 if (window != NULL) 9821 window->DragStop(); 9822 } 9823 9824 9825 void 9826 BPoseView::HiliteDropTarget(bool hiliteState) 9827 { 9828 // hilites current drop target while dragging, does not modify 9829 // selection list 9830 if (fDropTarget == NULL) 9831 return; 9832 9833 // note: fAlreadySelectedDropTarget is a trick to avoid to really search 9834 // fSelectionList. Another solution would be to add Hilite/IsHilited just 9835 // like Select/IsSelected in BPose and let it handle this case internally 9836 9837 // can happen when starting a new drag 9838 if (fAlreadySelectedDropTarget != fDropTarget) 9839 fAlreadySelectedDropTarget = NULL; 9840 9841 // don't select, this droptarget was already part of a user selection 9842 if (fDropTarget->IsSelected() && hiliteState) { 9843 fAlreadySelectedDropTarget = fDropTarget; 9844 return; 9845 } 9846 9847 // don't unselect the fAlreadySelectedDropTarget 9848 if ((fAlreadySelectedDropTarget == fDropTarget) && !hiliteState) { 9849 fAlreadySelectedDropTarget = NULL; 9850 return; 9851 } 9852 9853 fDropTarget->Select(hiliteState); 9854 9855 // scan all visible poses 9856 BRect bounds(Bounds()); 9857 9858 if (ViewMode() == kListMode) { 9859 PoseList* poseList = CurrentPoseList(); 9860 int32 count = poseList->CountItems(); 9861 int32 startIndex = (int32)(bounds.top / fListElemHeight); 9862 9863 BPoint loc(0, startIndex * fListElemHeight); 9864 9865 for (int32 index = startIndex; index < count; index++) { 9866 if (fDropTarget == poseList->ItemAt(index)) { 9867 BRect poseRect = fDropTarget->CalcRect(loc, this, false); 9868 fDropTarget->Draw(poseRect, poseRect, this, false); 9869 break; 9870 } 9871 9872 loc.y += fListElemHeight; 9873 if (loc.y > bounds.bottom) 9874 break; 9875 } 9876 } else { 9877 int32 startIndex = FirstIndexAtOrBelow( 9878 (int32)(bounds.top - IconPoseHeight()), true); 9879 int32 count = fVSPoseList->CountItems(); 9880 9881 for (int32 index = startIndex; index < count; index++) { 9882 BPose* pose = fVSPoseList->ItemAt(index); 9883 if (pose != NULL) { 9884 if (pose == fDropTarget) { 9885 BRect poseRect = pose->CalcRect(this); 9886 // TODO: maybe leave just the else part 9887 if (!hiliteState) 9888 // deselecting an icon with widget drawn over background 9889 // have to be a little tricky here - draw just the icon, 9890 // invalidate the widget 9891 pose->DeselectWithoutErasingBackground(poseRect, this); 9892 else 9893 pose->Draw(poseRect, poseRect, this, false); 9894 break; 9895 } 9896 9897 if (pose->Location(this).y > bounds.bottom) 9898 break; 9899 } 9900 } 9901 } 9902 } 9903 9904 9905 bool 9906 BPoseView::CheckAutoScroll(BPoint mouseLoc, bool shouldScroll) 9907 { 9908 if (!fShouldAutoScroll) 9909 return false; 9910 9911 // make sure window is in front before attempting scrolling 9912 BContainerWindow* window = ContainerWindow(); 9913 if (window == NULL) 9914 return false; 9915 9916 BRect bounds(Bounds()); 9917 BRect extent(Extent()); 9918 9919 bool wouldScroll = false; 9920 bool keepGoing; 9921 float scrollIncrement; 9922 9923 BRect border(bounds); 9924 border.bottom = border.top; 9925 border.top -= kBorderHeight; 9926 if (ViewMode() == kListMode) 9927 border.top -= TitleView()->Bounds().Height(); 9928 9929 bool selectionScrolling = fSelectionRectInfo.isDragging; 9930 9931 if (bounds.top > extent.top) { 9932 if (selectionScrolling) { 9933 keepGoing = mouseLoc.y < bounds.top; 9934 if (fabs(bounds.top - mouseLoc.y) > kSlowScrollBucket) 9935 scrollIncrement = fAutoScrollInc / 1.5f; 9936 else 9937 scrollIncrement = fAutoScrollInc / 4; 9938 } else { 9939 keepGoing = border.Contains(mouseLoc); 9940 scrollIncrement = fAutoScrollInc; 9941 } 9942 9943 if (keepGoing) { 9944 wouldScroll = true; 9945 if (shouldScroll) { 9946 if (fVScrollBar != NULL) { 9947 fVScrollBar->SetValue( 9948 fVScrollBar->Value() - scrollIncrement); 9949 } else 9950 ScrollBy(0, -scrollIncrement); 9951 } 9952 } 9953 } 9954 9955 border = bounds; 9956 border.top = border.bottom; 9957 border.bottom += (float)B_H_SCROLL_BAR_HEIGHT; 9958 if (bounds.bottom < extent.bottom) { 9959 if (selectionScrolling) { 9960 keepGoing = mouseLoc.y > bounds.bottom; 9961 if (fabs(bounds.bottom - mouseLoc.y) > kSlowScrollBucket) 9962 scrollIncrement = fAutoScrollInc / 1.5f; 9963 else 9964 scrollIncrement = fAutoScrollInc / 4; 9965 } else { 9966 keepGoing = border.Contains(mouseLoc); 9967 scrollIncrement = fAutoScrollInc; 9968 } 9969 9970 if (keepGoing) { 9971 wouldScroll = true; 9972 if (shouldScroll) { 9973 if (fVScrollBar != NULL) { 9974 fVScrollBar->SetValue( 9975 fVScrollBar->Value() + scrollIncrement); 9976 } else 9977 ScrollBy(0, scrollIncrement); 9978 } 9979 } 9980 } 9981 9982 border = bounds; 9983 border.right = border.left; 9984 border.left -= 6; 9985 if (bounds.left > extent.left) { 9986 if (selectionScrolling) { 9987 keepGoing = mouseLoc.x < bounds.left; 9988 if (fabs(bounds.left - mouseLoc.x) > kSlowScrollBucket) 9989 scrollIncrement = fAutoScrollInc / 1.5f; 9990 else 9991 scrollIncrement = fAutoScrollInc / 4; 9992 } else { 9993 keepGoing = border.Contains(mouseLoc); 9994 scrollIncrement = fAutoScrollInc; 9995 } 9996 9997 if (keepGoing) { 9998 wouldScroll = true; 9999 if (shouldScroll) { 10000 if (fHScrollBar != NULL) { 10001 fHScrollBar->SetValue( 10002 fHScrollBar->Value() - scrollIncrement); 10003 } else 10004 ScrollBy(-scrollIncrement, 0); 10005 } 10006 } 10007 } 10008 10009 border = bounds; 10010 border.left = border.right; 10011 border.right += (float)B_V_SCROLL_BAR_WIDTH; 10012 if (bounds.right < extent.right) { 10013 if (selectionScrolling) { 10014 keepGoing = mouseLoc.x > bounds.right; 10015 if (fabs(bounds.right - mouseLoc.x) > kSlowScrollBucket) 10016 scrollIncrement = fAutoScrollInc / 1.5f; 10017 else 10018 scrollIncrement = fAutoScrollInc / 4; 10019 } else { 10020 keepGoing = border.Contains(mouseLoc); 10021 scrollIncrement = fAutoScrollInc; 10022 } 10023 10024 if (keepGoing) { 10025 wouldScroll = true; 10026 if (shouldScroll) { 10027 if (fHScrollBar != NULL) { 10028 fHScrollBar->SetValue( 10029 fHScrollBar->Value() + scrollIncrement); 10030 } else 10031 ScrollBy(scrollIncrement, 0); 10032 } 10033 } 10034 } 10035 10036 // Force selection rect update to account for the new scrolled coords 10037 // without a mouse move 10038 if (selectionScrolling) 10039 _UpdateSelectionRect(mouseLoc); 10040 10041 return wouldScroll; 10042 } 10043 10044 10045 void 10046 BPoseView::HandleAutoScroll() 10047 { 10048 if (!fShouldAutoScroll) 10049 return; 10050 10051 uint32 buttons; 10052 BPoint mouseLoc; 10053 GetMouse(&mouseLoc, &buttons); 10054 10055 if (buttons == 0) { 10056 fAutoScrollState = kAutoScrollOff; 10057 Window()->SetPulseRate(500000); 10058 return; 10059 } 10060 10061 switch (fAutoScrollState) { 10062 case kWaitForTransition: 10063 if (CheckAutoScroll(mouseLoc, false) == false) 10064 fAutoScrollState = kDelayAutoScroll; 10065 break; 10066 10067 case kDelayAutoScroll: 10068 if (CheckAutoScroll(mouseLoc, false) == true) { 10069 snooze(600000); 10070 GetMouse(&mouseLoc, &buttons); 10071 if (CheckAutoScroll(mouseLoc, false) == true) 10072 fAutoScrollState = kAutoScrollOn; 10073 } 10074 break; 10075 10076 case kAutoScrollOn: 10077 CheckAutoScroll(mouseLoc, true); 10078 break; 10079 } 10080 } 10081 10082 10083 BRect 10084 BPoseView::CalcPoseRect(const BPose* pose, int32 index, 10085 bool firstColumnOnly) const 10086 { 10087 if (ViewMode() == kListMode) 10088 return CalcPoseRectList(pose, index, firstColumnOnly); 10089 else 10090 return CalcPoseRectIcon(pose); 10091 } 10092 10093 10094 BRect 10095 BPoseView::CalcPoseRectIcon(const BPose* pose) const 10096 { 10097 return pose->CalcRect(this); 10098 } 10099 10100 10101 BRect 10102 BPoseView::CalcPoseRectList(const BPose* pose, int32 index, 10103 bool firstColumnOnly) const 10104 { 10105 return pose->CalcRect(BPoint(0, index * fListElemHeight), this, 10106 firstColumnOnly); 10107 } 10108 10109 10110 bool 10111 BPoseView::Represents(const node_ref* node) const 10112 { 10113 return *(fModel->NodeRef()) == *node; 10114 } 10115 10116 10117 bool 10118 BPoseView::Represents(const entry_ref* ref) const 10119 { 10120 return *fModel->EntryRef() == *ref; 10121 } 10122 10123 10124 void 10125 BPoseView::ShowBarberPole() 10126 { 10127 if (fCountView) { 10128 AutoLock<BWindow> lock(Window()); 10129 if (!lock) 10130 return; 10131 fCountView->StartBarberPole(); 10132 } 10133 } 10134 10135 10136 void 10137 BPoseView::HideBarberPole() 10138 { 10139 if (fCountView) { 10140 AutoLock<BWindow> lock(Window()); 10141 if (!lock) 10142 return; 10143 fCountView->EndBarberPole(); 10144 } 10145 } 10146 10147 10148 bool 10149 BPoseView::IsWatchingDateFormatChange() 10150 { 10151 return fIsWatchingDateFormatChange; 10152 } 10153 10154 10155 void 10156 BPoseView::StartWatchDateFormatChange() 10157 { 10158 BMessenger trackerMessenger(kTrackerSignature); 10159 BHandler::StartWatching(trackerMessenger, kDateFormatChanged); 10160 fIsWatchingDateFormatChange = true; 10161 } 10162 10163 10164 void 10165 BPoseView::StopWatchDateFormatChange() 10166 { 10167 if (IsFilePanel()) { 10168 BMessenger trackerMessenger(kTrackerSignature); 10169 BHandler::StopWatching(trackerMessenger, kDateFormatChanged); 10170 } else if (be_app->LockLooper()) { 10171 be_app->StopWatching(this, kDateFormatChanged); 10172 be_app->UnlockLooper(); 10173 } 10174 10175 fIsWatchingDateFormatChange = false; 10176 } 10177 10178 10179 void 10180 BPoseView::UpdateDateColumns(BMessage* message) 10181 { 10182 int32 columnCount = CountColumns(); 10183 BRect columnRect(Bounds()); 10184 10185 for (int32 i = 0; i < columnCount; i++) { 10186 BColumn* col = ColumnAt(i); 10187 if (col && col->AttrType() == B_TIME_TYPE) { 10188 columnRect.left = col->Offset(); 10189 columnRect.right = columnRect.left + col->Width(); 10190 Invalidate(columnRect); 10191 } 10192 } 10193 } 10194 10195 10196 void 10197 BPoseView::AdaptToVolumeChange(BMessage*) 10198 { 10199 } 10200 10201 10202 void 10203 BPoseView::AdaptToDesktopIntegrationChange(BMessage*) 10204 { 10205 } 10206 10207 10208 bool 10209 BPoseView::WidgetTextOutline() const 10210 { 10211 return fWidgetTextOutline; 10212 } 10213 10214 10215 void 10216 BPoseView::SetWidgetTextOutline(bool on) 10217 { 10218 fWidgetTextOutline = on; 10219 } 10220 10221 10222 void 10223 BPoseView::EnsurePoseUnselected(BPose* pose) 10224 { 10225 if (pose == fDropTarget) 10226 fDropTarget = NULL; 10227 10228 if (pose == ActivePose()) 10229 CommitActivePose(); 10230 10231 fSelectionList->RemoveItem(pose); 10232 if (fSelectionPivotPose == pose) 10233 fSelectionPivotPose = NULL; 10234 10235 if (fRealPivotPose == pose) 10236 fRealPivotPose = NULL; 10237 10238 if (pose->IsSelected()) { 10239 pose->Select(false); 10240 if (fSelectionChangedHook) 10241 ContainerWindow()->SelectionChanged(); 10242 } 10243 } 10244 10245 10246 void 10247 BPoseView::RemoveFilteredPose(BPose* pose, int32 index) 10248 { 10249 EnsurePoseUnselected(pose); 10250 fFilteredPoseList->RemoveItemAt(index); 10251 10252 BRect invalidRect = CalcPoseRectList(pose, index); 10253 CloseGapInList(&invalidRect); 10254 10255 Invalidate(invalidRect); 10256 } 10257 10258 10259 void 10260 BPoseView::FilterChanged() 10261 { 10262 if (ViewMode() != kListMode) 10263 return; 10264 10265 int32 stringCount = fFilterStrings.CountItems(); 10266 int32 length = fFilterStrings.LastItem()->CountChars(); 10267 10268 if (!fFiltering && (length > 0 || fRefFilter != NULL)) 10269 StartFiltering(); 10270 else if (fFiltering && stringCount == 1 && length == 0 10271 && fRefFilter == NULL) { 10272 ClearFilter(); 10273 } else { 10274 if (fLastFilterStringCount > stringCount 10275 || (fLastFilterStringCount == stringCount 10276 && fLastFilterStringLength > length) 10277 || fRefFilter != NULL) { 10278 // something was removed, need to start over 10279 fFilteredPoseList->MakeEmpty(); 10280 fFiltering = false; 10281 StartFiltering(); 10282 } else { 10283 int32 count = fFilteredPoseList->CountItems(); 10284 for (int32 i = count - 1; i >= 0; i--) { 10285 BPose* pose = fFilteredPoseList->ItemAt(i); 10286 if (!FilterPose(pose)) 10287 RemoveFilteredPose(pose, i); 10288 } 10289 } 10290 } 10291 10292 fLastFilterStringCount = stringCount; 10293 fLastFilterStringLength = length; 10294 UpdateAfterFilterChange(); 10295 } 10296 10297 10298 void 10299 BPoseView::UpdateAfterFilterChange() 10300 { 10301 UpdateCount(); 10302 10303 BPose* pose = fFilteredPoseList->LastItem(); 10304 if (pose == NULL) 10305 BView::ScrollTo(0, 0); 10306 else { 10307 BRect bounds = Bounds(); 10308 float height = fFilteredPoseList->CountItems() * fListElemHeight; 10309 if (bounds.top > 0 && bounds.bottom > height) 10310 BView::ScrollTo(0, std::max(height - bounds.Height(), 0.0f)); 10311 } 10312 10313 UpdateScrollRange(); 10314 } 10315 10316 10317 bool 10318 BPoseView::FilterPose(BPose* pose) 10319 { 10320 if (!fFiltering || pose == NULL) 10321 return false; 10322 10323 if (fRefFilter != NULL) { 10324 PoseInfo poseInfo; 10325 ReadPoseInfo(pose->TargetModel(), &poseInfo); 10326 pose->TargetModel()->OpenNode(); 10327 if (!ShouldShowPose(pose->TargetModel(), &poseInfo)) 10328 return false; 10329 } 10330 10331 int32 stringCount = fFilterStrings.CountItems(); 10332 int32 matchesLeft = stringCount; 10333 10334 bool found[stringCount]; 10335 memset(found, 0, sizeof(found)); 10336 10337 ModelNodeLazyOpener modelOpener(pose->TargetModel()); 10338 for (int32 i = 0; i < CountColumns(); i++) { 10339 BTextWidget* widget = pose->WidgetFor(ColumnAt(i), this, modelOpener); 10340 const char* text = NULL; 10341 if (widget == NULL) 10342 continue; 10343 10344 text = widget->Text(this); 10345 if (text == NULL) 10346 continue; 10347 10348 for (int32 j = 0; j < stringCount; j++) { 10349 if (found[j]) 10350 continue; 10351 10352 if (strcasestr(text, fFilterStrings.ItemAt(j)->String()) != NULL) { 10353 if (--matchesLeft == 0) 10354 return true; 10355 10356 found[j] = true; 10357 } 10358 } 10359 } 10360 10361 return false; 10362 } 10363 10364 10365 void 10366 BPoseView::StartFiltering() 10367 { 10368 if (fFiltering) 10369 return; 10370 10371 fFiltering = true; 10372 int32 count = fPoseList->CountItems(); 10373 for (int32 i = 0; i < count; i++) { 10374 BPose* pose = fPoseList->ItemAt(i); 10375 if (FilterPose(pose)) 10376 fFilteredPoseList->AddItem(pose); 10377 else 10378 EnsurePoseUnselected(pose); 10379 } 10380 10381 Invalidate(); 10382 } 10383 10384 10385 bool 10386 BPoseView::IsFiltering() const 10387 { 10388 return fFiltering; 10389 } 10390 10391 10392 void 10393 BPoseView::StopFiltering() 10394 { 10395 ClearFilter(); 10396 UpdateAfterFilterChange(); 10397 } 10398 10399 10400 void 10401 BPoseView::ClearFilter() 10402 { 10403 if (!fFiltering) 10404 return; 10405 10406 fCountView->CancelFilter(); 10407 10408 int32 stringCount = fFilterStrings.CountItems(); 10409 for (int32 i = stringCount - 1; i > 0; i--) 10410 delete fFilterStrings.RemoveItemAt(i); 10411 10412 fFilterStrings.LastItem()->Truncate(0); 10413 fLastFilterStringCount = 1; 10414 fLastFilterStringLength = 0; 10415 10416 if (fRefFilter == NULL) 10417 fFiltering = false; 10418 10419 fFilteredPoseList->MakeEmpty(); 10420 10421 Invalidate(); 10422 } 10423 10424 10425 void 10426 BPoseView::ExcludeTrashFromSelection() 10427 { 10428 int32 count = fSelectionList->CountItems(); 10429 for (int index = 0; index < count; index++) { 10430 BPose* pose = fSelectionList->ItemAt(index); 10431 if (CanTrashForeignDrag(pose->TargetModel())) { 10432 RemovePoseFromSelection(pose); 10433 break; 10434 } 10435 } 10436 } 10437 10438 10439 // #pragma mark - TScrollBar 10440 10441 10442 TScrollBar::TScrollBar(const char* name, BView* target, float min, float max) 10443 : 10444 BScrollBar(name, target, min, max, B_HORIZONTAL), 10445 fTitleView(NULL) 10446 { 10447 } 10448 10449 10450 void 10451 TScrollBar::ValueChanged(float value) 10452 { 10453 if (fTitleView) { 10454 BPoint origin = fTitleView->LeftTop(); 10455 fTitleView->ScrollTo(BPoint(value, origin.y)); 10456 } 10457 10458 _inherited::ValueChanged(value); 10459 } 10460 10461 10462 TPoseViewFilter::TPoseViewFilter(BPoseView* pose) 10463 : 10464 BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE), 10465 fPoseView(pose) 10466 { 10467 } 10468 10469 10470 TPoseViewFilter::~TPoseViewFilter() 10471 { 10472 } 10473 10474 10475 filter_result 10476 TPoseViewFilter::Filter(BMessage* message, BHandler**) 10477 { 10478 filter_result result = B_DISPATCH_MESSAGE; 10479 10480 switch (message->what) { 10481 case B_ARCHIVED_OBJECT: 10482 bool handled = fPoseView->HandleMessageDropped(message); 10483 if (handled) 10484 result = B_SKIP_MESSAGE; 10485 break; 10486 } 10487 10488 return result; 10489 } 10490 10491 10492 // #pragma mark - static member initializations 10493 10494 float BPoseView::sFontHeight = -1; 10495 font_height BPoseView::sFontInfo = { 0, 0, 0 }; 10496 BFont BPoseView::sCurrentFont; 10497 OffscreenBitmap* BPoseView::sOffscreen = new OffscreenBitmap; 10498 BString BPoseView::sMatchString = ""; 10499