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