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