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