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