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