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