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