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