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