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