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