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