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