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