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