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