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