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