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