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