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