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