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 inline bool 2765 BPoseView::HasPosesInClipboard() 2766 { 2767 return fHasPosesInClipboard; 2768 } 2769 2770 2771 inline void 2772 BPoseView::SetHasPosesInClipboard(bool hasPoses) 2773 { 2774 fHasPosesInClipboard = hasPoses; 2775 } 2776 2777 2778 void 2779 BPoseView::SetPosesClipboardMode(uint32 clipboardMode) 2780 { 2781 int32 count = fPoseList->CountItems(); 2782 if (ViewMode() == kListMode) { 2783 BPoint loc(0,0); 2784 for (int32 index = 0; index < count; index++) { 2785 BPose *pose = fPoseList->ItemAt(index); 2786 if (pose->ClipboardMode() != clipboardMode) { 2787 pose->SetClipboardMode(clipboardMode); 2788 Invalidate(pose->CalcRect(loc, this, false)); 2789 } 2790 loc.y += fListElemHeight; 2791 } 2792 } else { 2793 for (int32 index = 0; index < count; index++) { 2794 BPose *pose = fPoseList->ItemAt(index); 2795 if (pose->ClipboardMode() != clipboardMode) { 2796 pose->SetClipboardMode(clipboardMode); 2797 BRect poseRect(pose->CalcRect(this)); 2798 Invalidate(poseRect); 2799 } 2800 } 2801 } 2802 } 2803 2804 2805 void 2806 BPoseView::UpdatePosesClipboardModeFromClipboard(BMessage *clipboardReport) 2807 { 2808 CommitActivePose(); 2809 fSelectionPivotPose = NULL; 2810 fRealPivotPose = NULL; 2811 bool fullInvalidateNeeded = false; 2812 2813 node_ref node; 2814 clipboardReport->FindInt32("device", &node.device); 2815 clipboardReport->FindInt64("directory", &node.node); 2816 2817 bool clearClipboard = false; 2818 clipboardReport->FindBool("clearClipboard", &clearClipboard); 2819 2820 if (clearClipboard && fHasPosesInClipboard) { 2821 // clear all poses 2822 int32 count = fPoseList->CountItems(); 2823 for (int32 index = 0; index < count; index++) { 2824 BPose *pose = fPoseList->ItemAt(index); 2825 pose->Select(false); 2826 pose->SetClipboardMode(0); 2827 } 2828 SetHasPosesInClipboard(false); 2829 fullInvalidateNeeded = true; 2830 fHasPosesInClipboard = false; 2831 } 2832 2833 BRect bounds(Bounds()); 2834 BPoint loc(0, 0); 2835 bool hasPosesInClipboard = false; 2836 int32 foundNodeIndex = 0; 2837 2838 TClipboardNodeRef *clipNode = NULL; 2839 ssize_t size; 2840 for (int32 index = 0; clipboardReport->FindData("tcnode", T_CLIPBOARD_NODE, index, 2841 (const void **)&clipNode, &size) == B_OK; index++) { 2842 BPose *pose = fPoseList->FindPose(&clipNode->node, &foundNodeIndex); 2843 if (pose == NULL) 2844 continue; 2845 2846 if (clipNode->moveMode != pose->ClipboardMode() || pose->IsSelected()) { 2847 pose->SetClipboardMode(clipNode->moveMode); 2848 pose->Select(false); 2849 2850 if (!fullInvalidateNeeded) { 2851 if (ViewMode() == kListMode) { 2852 loc.y = foundNodeIndex * fListElemHeight; 2853 if (loc.y <= bounds.bottom && loc.y >= bounds.top) 2854 Invalidate(pose->CalcRect(loc, this, false)); 2855 } else { 2856 BRect poseRect(pose->CalcRect(this)); 2857 if (bounds.Contains(poseRect.LeftTop()) 2858 || bounds.Contains(poseRect.LeftBottom()) 2859 || bounds.Contains(poseRect.RightBottom()) 2860 || bounds.Contains(poseRect.RightTop())) { 2861 if (!EraseWidgetTextBackground() 2862 || clipNode->moveMode == kMoveSelectionTo) 2863 Invalidate(poseRect); 2864 else 2865 pose->Draw(poseRect, this, false); 2866 } 2867 } 2868 } 2869 if (clipNode->moveMode) 2870 hasPosesInClipboard = true; 2871 } 2872 } 2873 2874 fSelectionList->MakeEmpty(); 2875 fMimeTypesInSelectionCache.MakeEmpty(); 2876 2877 SetHasPosesInClipboard(hasPosesInClipboard | fHasPosesInClipboard); 2878 2879 if (fullInvalidateNeeded) 2880 Invalidate(); 2881 } 2882 2883 2884 void 2885 BPoseView::PlaceFolder(const entry_ref *ref, const BMessage *message) 2886 { 2887 BNode node(ref); 2888 BPoint location; 2889 bool setPosition = false; 2890 2891 if (message->FindPoint("be:invoke_origin", &location) == B_OK) { 2892 // new folder created from popup, place on click point 2893 setPosition = true; 2894 location = ConvertFromScreen(location); 2895 } else if (ViewMode() != kListMode) { 2896 // new folder created by keyboard shortcut 2897 uint32 buttons; 2898 GetMouse(&location, &buttons); 2899 BPoint globalLocation(location); 2900 ConvertToScreen(&globalLocation); 2901 // check if mouse over window 2902 if (Window()->Frame().Contains(globalLocation)) 2903 // create folder under mouse 2904 setPosition = true; 2905 } 2906 2907 if (setPosition) 2908 FSSetPoseLocation(TargetModel()->NodeRef()->node, &node, 2909 location); 2910 } 2911 2912 2913 void 2914 BPoseView::NewFileFromTemplate(const BMessage *message) 2915 { 2916 ASSERT(TargetModel()); 2917 2918 entry_ref destEntryRef; 2919 node_ref destNodeRef; 2920 2921 BDirectory destDir(TargetModel()->NodeRef()); 2922 if (destDir.InitCheck() != B_OK) 2923 return; 2924 2925 char fileName[B_FILE_NAME_LENGTH] = "New "; 2926 strcat(fileName, message->FindString("name")); 2927 FSMakeOriginalName(fileName, &destDir, " copy"); 2928 2929 entry_ref srcRef; 2930 message->FindRef("refs_template", &srcRef); 2931 2932 BDirectory dir(&srcRef); 2933 2934 if (dir.InitCheck() == B_OK) { 2935 // special handling of directories 2936 if (FSCreateNewFolderIn(TargetModel()->NodeRef(), &destEntryRef, &destNodeRef) == B_OK) { 2937 BEntry destEntry(&destEntryRef); 2938 destEntry.Rename(fileName); 2939 } 2940 } else { 2941 BFile srcFile(&srcRef, B_READ_ONLY); 2942 BFile destFile(&destDir, fileName, B_READ_WRITE | B_CREATE_FILE); 2943 2944 // copy the data from the template file 2945 char *buffer = new char[1024]; 2946 ssize_t result; 2947 do { 2948 result = srcFile.Read(buffer, 1024); 2949 2950 if (result > 0) { 2951 ssize_t written = destFile.Write(buffer, (size_t)result); 2952 if (written != result) 2953 result = written < B_OK ? written : B_ERROR; 2954 } 2955 } while (result > 0); 2956 delete[] buffer; 2957 } 2958 2959 // todo: create an UndoItem 2960 2961 // copy the attributes from the template file 2962 BNode srcNode(&srcRef); 2963 BNode destNode(&destDir, fileName); 2964 FSCopyAttributesAndStats(&srcNode, &destNode); 2965 2966 BEntry entry(&destDir, fileName); 2967 entry.GetRef(&destEntryRef); 2968 2969 // try to place new item at click point or under mouse if possible 2970 PlaceFolder(&destEntryRef, message); 2971 2972 if (dir.InitCheck() == B_OK) { 2973 // special-case directories - start renaming them 2974 int32 index; 2975 BPose *pose = EntryCreated(TargetModel()->NodeRef(), &destNodeRef, 2976 destEntryRef.name, &index); 2977 2978 if (pose) { 2979 UpdateScrollRange(); 2980 CommitActivePose(); 2981 SelectPose(pose, index); 2982 pose->EditFirstWidget(BPoint(0, index * fListElemHeight), this); 2983 } 2984 } else { 2985 // open the corresponding application 2986 BMessage openMessage(B_REFS_RECEIVED); 2987 openMessage.AddRef("refs", &destEntryRef); 2988 2989 // add a messenger to the launch message that will be used to 2990 // dispatch scripting calls from apps to the PoseView 2991 openMessage.AddMessenger("TrackerViewToken", BMessenger(this)); 2992 2993 if (fSelectionHandler) 2994 fSelectionHandler->PostMessage(&openMessage); 2995 } 2996 } 2997 2998 2999 void 3000 BPoseView::NewFolder(const BMessage *message) 3001 { 3002 ASSERT(TargetModel()); 3003 3004 entry_ref ref; 3005 node_ref nodeRef; 3006 3007 if (FSCreateNewFolderIn(TargetModel()->NodeRef(), &ref, &nodeRef) == B_OK) { 3008 // try to place new folder at click point or under mouse if possible 3009 3010 PlaceFolder(&ref, message); 3011 3012 int32 index; 3013 BPose *pose = EntryCreated(TargetModel()->NodeRef(), &nodeRef, ref.name, &index); 3014 if (pose) { 3015 UpdateScrollRange(); 3016 CommitActivePose(); 3017 SelectPose(pose, index); 3018 pose->EditFirstWidget(BPoint(0, index * fListElemHeight), this); 3019 } 3020 } 3021 } 3022 3023 3024 void 3025 BPoseView::Cleanup(bool doAll) 3026 { 3027 if (ViewMode() == kListMode) 3028 return; 3029 3030 BContainerWindow *window = ContainerWindow(); 3031 if (!window) 3032 return; 3033 3034 // replace all icons from the top 3035 if (doAll) { 3036 // sort by sort field 3037 SortPoses(); 3038 3039 DisableScrollBars(); 3040 ClearExtent(); 3041 ClearSelection(); 3042 ScrollTo(B_ORIGIN); 3043 UpdateScrollRange(); 3044 SetScrollBarsTo(B_ORIGIN); 3045 ResetPosePlacementHint(); 3046 3047 BRect viewBounds(Bounds()); 3048 3049 // relocate all poses in list (reset vs list) 3050 fVSPoseList->MakeEmpty(); 3051 int32 count = fPoseList->CountItems(); 3052 for (int32 index = 0; index < count; index++) { 3053 BPose *pose = fPoseList->ItemAt(index); 3054 PlacePose(pose, viewBounds); 3055 AddToVSList(pose); 3056 } 3057 3058 RecalcExtent(); 3059 3060 // scroll icons into view so that leftmost icon is "fOffset" from left 3061 UpdateScrollRange(); 3062 EnableScrollBars(); 3063 3064 if (HScrollBar()) { 3065 float min; 3066 float max; 3067 HScrollBar()->GetRange(&min, &max); 3068 HScrollBar()->SetValue(min); 3069 } 3070 3071 UpdateScrollRange(); 3072 Invalidate(viewBounds); 3073 3074 } else { 3075 // clean up items to nearest locations 3076 BRect viewBounds(Bounds()); 3077 int32 count = fPoseList->CountItems(); 3078 for (int32 index = 0; index < count; index++) { 3079 BPose *pose = fPoseList->ItemAt(index); 3080 BPoint location(pose->Location()); 3081 BPoint newLocation(PinToGrid(location, fGrid, fOffset)); 3082 3083 // do we need to move pose to a grid location? 3084 if (newLocation != location) { 3085 // remove pose from VSlist so it doesn't "bump" into itself 3086 RemoveFromVSList(pose); 3087 3088 // try new grid location 3089 BRect oldBounds(pose->CalcRect(this)); 3090 BRect poseBounds(oldBounds); 3091 pose->MoveTo(newLocation, this); 3092 if (SlotOccupied(oldBounds, viewBounds)) { 3093 ResetPosePlacementHint(); 3094 PlacePose(pose, viewBounds); 3095 poseBounds = pose->CalcRect(this); 3096 } 3097 3098 AddToVSList(pose); 3099 AddToExtent(poseBounds); 3100 3101 if (viewBounds.Intersects(poseBounds)) 3102 Invalidate(poseBounds); 3103 if (viewBounds.Intersects(oldBounds)) 3104 Invalidate(oldBounds); 3105 } 3106 } 3107 } 3108 } 3109 3110 3111 void 3112 BPoseView::PlacePose(BPose *pose, BRect &viewBounds) 3113 { 3114 // move pose to probable location 3115 pose->SetLocation(fHintLocation); 3116 BRect rect(pose->CalcRect(this)); 3117 BPoint deltaFromBounds(fHintLocation - rect.LeftTop()); 3118 3119 // make pose rect a little bigger to ensure space between poses 3120 rect.InsetBy(-3, 0); 3121 3122 BRect deskbarFrame; 3123 bool checkDeskbarFrame = false; 3124 if (IsDesktopWindow() && get_deskbar_frame(&deskbarFrame) == B_OK) { 3125 checkDeskbarFrame = true; 3126 deskbarFrame.InsetBy(-10, -10); 3127 } 3128 3129 // find an empty slot to put pose into 3130 if (fVSPoseList->CountItems() > 0) 3131 while (SlotOccupied(rect, viewBounds) 3132 // avoid Deskbar 3133 || (checkDeskbarFrame && deskbarFrame.Intersects(rect))) 3134 NextSlot(pose, rect, viewBounds); 3135 3136 rect.InsetBy(3, 0); 3137 3138 fHintLocation = pose->Location() + BPoint(fGrid.x, 0); 3139 3140 pose->SetLocation(rect.LeftTop() + deltaFromBounds); 3141 pose->SetSaveLocation(); 3142 } 3143 3144 3145 void 3146 BPoseView::CheckAutoPlacedPoses() 3147 { 3148 BRect viewBounds(Bounds()); 3149 3150 int32 count = fPoseList->CountItems(); 3151 for (int32 index = 0; index < count; index++) { 3152 BPose *pose = fPoseList->ItemAt(index); 3153 if (pose->WasAutoPlaced()) { 3154 RemoveFromVSList(pose); 3155 fHintLocation = pose->Location(); 3156 BRect oldBounds(pose->CalcRect(this)); 3157 PlacePose(pose, viewBounds); 3158 3159 BRect newBounds(pose->CalcRect(this)); 3160 AddToVSList(pose); 3161 pose->SetAutoPlaced(false); 3162 AddToExtent(newBounds); 3163 3164 Invalidate(oldBounds); 3165 Invalidate(newBounds); 3166 } 3167 } 3168 } 3169 3170 3171 void 3172 BPoseView::CheckPoseVisibility(BRect *newFrame) 3173 { 3174 bool desktop = IsDesktopWindow() && newFrame != 0; 3175 3176 BRect deskFrame; 3177 if (desktop) { 3178 ASSERT(newFrame); 3179 deskFrame = *newFrame; 3180 } 3181 3182 ASSERT(ViewMode() != kListMode); 3183 3184 BRect bounds(Bounds()); 3185 bounds.InsetBy(20, 20); 3186 3187 int32 count = fPoseList->CountItems(); 3188 for (int32 index = 0; index < count; index++) { 3189 BPose *pose = fPoseList->ItemAt(index); 3190 BPoint newLocation(pose->Location()); 3191 bool locationNeedsUpdating = false; 3192 3193 if (desktop) { 3194 // we just switched screen resolution, pick up the right 3195 // icon locations for the new resolution 3196 Model *model = pose->TargetModel(); 3197 ExtendedPoseInfo *info = ReadExtendedPoseInfo(model); 3198 if (info && info->HasLocationForFrame(deskFrame)) { 3199 BPoint locationForFrame = info->LocationForFrame(deskFrame); 3200 if (locationForFrame != newLocation) { 3201 // found one and it is different from the current 3202 newLocation = locationForFrame; 3203 locationNeedsUpdating = true; 3204 Invalidate(pose->CalcRect(this)); 3205 // make sure the old icon gets erased 3206 RemoveFromVSList(pose); 3207 pose->SetLocation(newLocation); 3208 // set the new location 3209 } 3210 } 3211 delete [] (char *)info; 3212 // ToDo: 3213 // fix up this mess 3214 } 3215 3216 BRect rect(pose->CalcRect(this)); 3217 if (!rect.Intersects(bounds)) { 3218 // pose doesn't fit on screen 3219 if (!locationNeedsUpdating) { 3220 // didn't already invalidate and remove in the desktop case 3221 Invalidate(rect); 3222 RemoveFromVSList(pose); 3223 } 3224 BPoint loc(pose->Location()); 3225 loc.ConstrainTo(bounds); 3226 // place it onscreen 3227 3228 pose->SetLocation(loc); 3229 // set the new location 3230 locationNeedsUpdating = true; 3231 } 3232 3233 if (locationNeedsUpdating) { 3234 // pose got reposition by one or both of the above 3235 pose->SetSaveLocation(); 3236 AddToVSList(pose); 3237 // add it at the new location 3238 Invalidate(pose->CalcRect(this)); 3239 // make sure the new pose location updates properly 3240 } 3241 } 3242 } 3243 3244 3245 bool 3246 BPoseView::SlotOccupied(BRect poseRect, BRect viewBounds) const 3247 { 3248 if (fVSPoseList->IsEmpty()) 3249 return false; 3250 3251 // ## be sure to keep this code in sync with calls to NextSlot 3252 // ## in terms of the comparison of fHintLocation and PinToGrid 3253 if (poseRect.right >= viewBounds.right) { 3254 BPoint point(viewBounds.left + fOffset.x, 0); 3255 point = PinToGrid(point, fGrid, fOffset); 3256 if (fHintLocation.x != point.x) 3257 return true; 3258 } 3259 3260 // search only nearby poses (vertically) 3261 int32 index = FirstIndexAtOrBelow((int32)(poseRect.top - IconPoseHeight())); 3262 int32 numPoses = fVSPoseList->CountItems(); 3263 3264 while (index < numPoses && fVSPoseList->ItemAt(index)->Location().y 3265 < poseRect.bottom) { 3266 3267 BRect rect(fVSPoseList->ItemAt(index)->CalcRect(this)); 3268 if (poseRect.Intersects(rect)) 3269 return true; 3270 3271 index++; 3272 } 3273 3274 return false; 3275 } 3276 3277 3278 void 3279 BPoseView::NextSlot(BPose *pose, BRect &poseRect, BRect viewBounds) 3280 { 3281 // move to next slot 3282 poseRect.OffsetBy(fGrid.x, 0); 3283 3284 // if we reached the end of row go down to next row 3285 if (poseRect.right > viewBounds.right) { 3286 fHintLocation.y += fGrid.y; 3287 fHintLocation.x = viewBounds.left + fOffset.x; 3288 fHintLocation = PinToGrid(fHintLocation, fGrid, fOffset); 3289 pose->SetLocation(fHintLocation); 3290 poseRect = pose->CalcRect(this); 3291 poseRect.InsetBy(-3, 0); 3292 } 3293 } 3294 3295 3296 int32 3297 BPoseView::FirstIndexAtOrBelow(int32 y, bool constrainIndex) const 3298 { 3299 // This method performs a binary search on the vertically sorted pose list 3300 // and returns either the index of the first pose at a given y location or 3301 // the proper index to insert a new pose into the list. 3302 3303 int32 index = 0; 3304 int32 l = 0; 3305 int32 r = fVSPoseList->CountItems() - 1; 3306 3307 while (l <= r) { 3308 index = (l + r) >> 1; 3309 int32 result = (int32)(y - fVSPoseList->ItemAt(index)->Location().y); 3310 3311 if (result < 0) 3312 r = index - 1; 3313 else if (result > 0) 3314 l = index + 1; 3315 else { 3316 // compare turned out equal, find first pose 3317 while (index > 0 3318 && y == fVSPoseList->ItemAt(index - 1)->Location().y) 3319 index--; 3320 return index; 3321 } 3322 } 3323 3324 // didn't find pose AT location y - bump index to proper insert point 3325 while (index < fVSPoseList->CountItems() 3326 && fVSPoseList->ItemAt(index)->Location().y <= y) 3327 index++; 3328 3329 // if flag is true then constrain index to legal value since this 3330 // method returns the proper insertion point which could be outside 3331 // the current bounds of the list 3332 if (constrainIndex && index >= fVSPoseList->CountItems()) 3333 index = fVSPoseList->CountItems() - 1; 3334 3335 return index; 3336 } 3337 3338 3339 void 3340 BPoseView::AddToVSList(BPose *pose) 3341 { 3342 int32 index = FirstIndexAtOrBelow((int32)pose->Location().y, false); 3343 fVSPoseList->AddItem(pose, index); 3344 } 3345 3346 3347 int32 3348 BPoseView::RemoveFromVSList(const BPose *pose) 3349 { 3350 int32 index = FirstIndexAtOrBelow((int32)pose->Location().y); 3351 3352 int32 count = fVSPoseList->CountItems(); 3353 for (; index < count; index++) { 3354 BPose *matchingPose = fVSPoseList->ItemAt(index); 3355 ASSERT(matchingPose); 3356 if (!matchingPose) 3357 return -1; 3358 3359 if (pose == matchingPose) { 3360 fVSPoseList->RemoveItemAt(index); 3361 return index; 3362 } 3363 } 3364 3365 return -1; 3366 } 3367 3368 3369 BPoint 3370 BPoseView::PinToGrid(BPoint point, BPoint grid, BPoint offset) const 3371 { 3372 if (grid.x == 0 || grid.y == 0) 3373 return point; 3374 3375 point -= offset; 3376 BPoint gridLoc(point); 3377 3378 if (point.x >= 0) 3379 gridLoc.x = floorf((point.x / grid.x) + 0.5f) * grid.x; 3380 else 3381 gridLoc.x = ceilf((point.x / grid.x) - 0.5f) * grid.x; 3382 3383 if (point.y >= 0) 3384 gridLoc.y = floorf((point.y / grid.y) + 0.5f) * grid.y; 3385 else 3386 gridLoc.y = ceilf((point.y / grid.y) - 0.5f) * grid.y; 3387 3388 gridLoc += offset; 3389 return gridLoc; 3390 } 3391 3392 3393 void 3394 BPoseView::ResetPosePlacementHint() 3395 { 3396 fHintLocation = PinToGrid(BPoint(LeftTop().x + fOffset.x, 3397 LeftTop().y + fOffset.y), fGrid, fOffset); 3398 } 3399 3400 3401 void 3402 BPoseView::SelectPoses(int32 start, int32 end) 3403 { 3404 BPoint loc(0, 0); 3405 BRect bounds(Bounds()); 3406 3407 // clear selection list 3408 fSelectionList->MakeEmpty(); 3409 fMimeTypesInSelectionCache.MakeEmpty(); 3410 fSelectionPivotPose = NULL; 3411 fRealPivotPose = NULL; 3412 3413 bool iconMode = ViewMode() != kListMode; 3414 3415 int32 count = fPoseList->CountItems(); 3416 for (int32 index = start; index < end && index < count; index++) { 3417 BPose *pose = fPoseList->ItemAt(index); 3418 fSelectionList->AddItem(pose); 3419 if (index == start) 3420 fSelectionPivotPose = pose; 3421 if (!pose->IsSelected()) { 3422 pose->Select(true); 3423 BRect poseRect; 3424 if (iconMode) 3425 poseRect = pose->CalcRect(this); 3426 else 3427 poseRect = pose->CalcRect(loc, this); 3428 3429 if (bounds.Intersects(poseRect)) { 3430 if (EraseWidgetTextBackground()) 3431 Invalidate(poseRect); 3432 else 3433 pose->Draw(poseRect, this, false); 3434 Flush(); 3435 } 3436 } 3437 3438 loc.y += fListElemHeight; 3439 } 3440 } 3441 3442 3443 void 3444 BPoseView::ScrollIntoView(BPose *pose, int32 index, bool drawOnly) 3445 { 3446 BRect poseRect; 3447 3448 if (ViewMode() == kListMode) 3449 poseRect = CalcPoseRect(pose, index); 3450 else 3451 poseRect = pose->CalcRect(this); 3452 3453 if (!IsDesktopWindow() && !drawOnly) { 3454 BRect testRect(poseRect); 3455 3456 if (ViewMode() == kListMode) { 3457 // if we're in list view then we only care that the entire 3458 // pose is visible vertically, not horizontally 3459 testRect.left = 0; 3460 testRect.right = testRect.left + 1; 3461 } 3462 if (!Bounds().Contains(testRect)) 3463 SetScrollBarsTo(testRect.LeftTop()); 3464 } 3465 3466 if (Bounds().Intersects(poseRect)) 3467 pose->Draw(poseRect, this, false); 3468 } 3469 3470 3471 void 3472 BPoseView::SelectPose(BPose *pose, int32 index, bool scrollIntoView) 3473 { 3474 if (!pose || fSelectionList->CountItems() > 1 || !pose->IsSelected()) 3475 ClearSelection(); 3476 3477 AddPoseToSelection(pose, index, scrollIntoView); 3478 } 3479 3480 3481 void 3482 BPoseView::AddPoseToSelection(BPose *pose, int32 index, bool scrollIntoView) 3483 { 3484 // ToDo: 3485 // need to check if pose is member of selection list 3486 if (pose && !pose->IsSelected()) { 3487 pose->Select(true); 3488 fSelectionList->AddItem(pose); 3489 3490 ScrollIntoView(pose, index, !scrollIntoView); 3491 3492 if (fSelectionChangedHook) 3493 ContainerWindow()->SelectionChanged(); 3494 } 3495 } 3496 3497 3498 void 3499 BPoseView::RemovePoseFromSelection(BPose *pose) 3500 { 3501 if (fSelectionPivotPose == pose) 3502 fSelectionPivotPose = NULL; 3503 if (fRealPivotPose == pose) 3504 fRealPivotPose = NULL; 3505 3506 if (!fSelectionList->RemoveItem(pose)) 3507 // wasn't selected to begin with 3508 return; 3509 3510 pose->Select(false); 3511 if (ViewMode() == kListMode) { 3512 // ToDo: 3513 // need a simple call to CalcRect that works both in listView and icon view modes 3514 // without the need for an index/pos 3515 int32 count = fPoseList->CountItems(); 3516 BPoint loc(0, 0); 3517 for (int32 index = 0; index < count; index++) { 3518 if (pose == fPoseList->ItemAt(index)) { 3519 Invalidate(pose->CalcRect(loc, this)); 3520 break; 3521 } 3522 loc.y += fListElemHeight; 3523 } 3524 } else 3525 Invalidate(pose->CalcRect(this)); 3526 3527 if (fSelectionChangedHook) 3528 ContainerWindow()->SelectionChanged(); 3529 } 3530 3531 3532 bool 3533 BPoseView::EachItemInDraggedSelection(const BMessage *message, 3534 bool (*func)(BPose *, BPoseView *, void *), BPoseView *poseView, void *passThru) 3535 { 3536 BContainerWindow *srcWindow; 3537 message->FindPointer("src_window", (void **)&srcWindow); 3538 3539 AutoLock<BWindow> lock(srcWindow); 3540 if (!lock) 3541 return false; 3542 3543 PoseList *selectionList = srcWindow->PoseView()->SelectionList(); 3544 int32 count = selectionList->CountItems(); 3545 3546 for (int32 index = 0; index < count; index++) { 3547 BPose *pose = selectionList->ItemAt(index); 3548 if (func(pose, poseView, passThru)) 3549 // early iteration termination 3550 return true; 3551 } 3552 return false; 3553 } 3554 3555 3556 static bool 3557 ContainsOne(BString *string, const char *matchString) 3558 { 3559 return strcmp(string->String(), matchString) == 0; 3560 } 3561 3562 3563 bool 3564 BPoseView::FindDragNDropAction(const BMessage *dragMessage, bool &canCopy, 3565 bool &canMove, bool &canLink, bool &canErase) 3566 { 3567 canCopy = false; 3568 canMove = false; 3569 canErase = false; 3570 canLink = false; 3571 if (!dragMessage->HasInt32("be:actions")) 3572 return false; 3573 3574 int32 action; 3575 for (int32 index = 0; 3576 dragMessage->FindInt32("be:actions", index, &action) == B_OK; index++) { 3577 switch (action) { 3578 case B_MOVE_TARGET: 3579 canMove = true; 3580 break; 3581 3582 case B_COPY_TARGET: 3583 canCopy = true; 3584 break; 3585 3586 case B_TRASH_TARGET: 3587 canErase = true; 3588 break; 3589 3590 case B_LINK_TARGET: 3591 canLink = true; 3592 break; 3593 } 3594 } 3595 return canCopy || canMove || canErase || canLink; 3596 } 3597 3598 3599 bool 3600 BPoseView::CanTrashForeignDrag(const Model *targetModel) 3601 { 3602 BEntry entry(targetModel->EntryRef()); 3603 return FSIsTrashDir(&entry); 3604 } 3605 3606 3607 bool 3608 BPoseView::CanCopyOrMoveForeignDrag(const Model *targetModel, 3609 const BMessage *dragMessage) 3610 { 3611 if (!targetModel->IsDirectory()) 3612 return false; 3613 3614 // in order to handle a clipping file, the drag initiator must be able 3615 // do deal with B_FILE_MIME_TYPE 3616 for (int32 index = 0; ; index++) { 3617 const char *type; 3618 if (dragMessage->FindString("be:types", index, &type) != B_OK) 3619 break; 3620 3621 if (strcasecmp(type, B_FILE_MIME_TYPE) == 0) 3622 return true; 3623 } 3624 3625 return false; 3626 } 3627 3628 3629 bool 3630 BPoseView::CanHandleDragSelection(const Model *target, const BMessage *dragMessage, 3631 bool ignoreTypes) 3632 { 3633 if (ignoreTypes) 3634 return target->IsDropTarget(); 3635 3636 ASSERT(dragMessage); 3637 3638 BContainerWindow *srcWindow; 3639 dragMessage->FindPointer("src_window", (void **)&srcWindow); 3640 if (!srcWindow) { 3641 // handle a foreign drag 3642 bool canCopy; 3643 bool canMove; 3644 bool canErase; 3645 bool canLink; 3646 FindDragNDropAction(dragMessage, canCopy, canMove, canLink, canErase); 3647 if (canErase && CanTrashForeignDrag(target)) 3648 return true; 3649 3650 if (canCopy || canMove) { 3651 if (CanCopyOrMoveForeignDrag(target, dragMessage)) 3652 return true; 3653 3654 // ToDo: 3655 // collect all mime types here and pass into 3656 // target->IsDropTargetForList(mimeTypeList); 3657 } 3658 3659 // handle an old style entry_refs only darg message 3660 if (dragMessage->HasRef("refs") && target->IsDirectory()) 3661 return true; 3662 3663 // handle simple text clipping drag&drop message 3664 if (dragMessage->HasData(kPlainTextMimeType, B_MIME_TYPE) && target->IsDirectory()) 3665 return true; 3666 3667 // handle simple bitmap clipping drag&drop message 3668 if (target->IsDirectory() 3669 && (dragMessage->HasData(kBitmapMimeType, B_MESSAGE_TYPE) 3670 || dragMessage->HasData(kLargeIconType, B_MESSAGE_TYPE) 3671 || dragMessage->HasData(kMiniIconType, B_MESSAGE_TYPE))) 3672 return true; 3673 3674 // ToDo: 3675 // check for a drag message full of refs, feed a list of their types to 3676 // target->IsDropTargetForList(mimeTypeList); 3677 return false; 3678 } 3679 3680 AutoLock<BWindow> lock(srcWindow); 3681 if (!lock) 3682 return false; 3683 BObjectList<BString> *mimeTypeList = srcWindow->PoseView()->MimeTypesInSelection(); 3684 if (mimeTypeList->IsEmpty()) { 3685 PoseList *selectionList = srcWindow->PoseView()->SelectionList(); 3686 if (!selectionList->IsEmpty()) { 3687 // no cached data yet, build the cache 3688 int32 count = selectionList->CountItems(); 3689 3690 for (int32 index = 0; index < count; index++) { 3691 // get the mime type of the model, following a possible symlink 3692 BEntry entry(selectionList->ItemAt(index)->TargetModel()->EntryRef(), true); 3693 if (entry.InitCheck() != B_OK) 3694 continue; 3695 3696 BFile file(&entry, O_RDONLY); 3697 BNodeInfo mime(&file); 3698 3699 if (mime.InitCheck() != B_OK) 3700 continue; 3701 3702 char mimeType[B_MIME_TYPE_LENGTH]; 3703 mime.GetType(mimeType); 3704 3705 // add unique type string 3706 if (!WhileEachListItem(mimeTypeList, ContainsOne, (const char *)mimeType)) { 3707 BString *newMimeString = new BString(mimeType); 3708 mimeTypeList->AddItem(newMimeString); 3709 } 3710 } 3711 } 3712 } 3713 3714 return target->IsDropTargetForList(mimeTypeList); 3715 } 3716 3717 3718 void 3719 BPoseView::TrySettingPoseLocation(BNode *node, BPoint point) 3720 { 3721 if (ViewMode() == kListMode) 3722 return; 3723 3724 if (modifiers() & B_COMMAND_KEY) 3725 // allign to grid if needed 3726 point = PinToGrid(point, fGrid, fOffset); 3727 3728 if (FSSetPoseLocation(TargetModel()->NodeRef()->node, node, point) == B_OK) 3729 // get rid of opposite endianness attribute 3730 node->RemoveAttr(kAttrPoseInfoForeign); 3731 } 3732 3733 3734 status_t 3735 BPoseView::CreateClippingFile(BPoseView *poseView, BFile &result, char *resultingName, 3736 BDirectory *dir, BMessage *message, const char *fallbackName, 3737 bool setLocation, BPoint dropPoint) 3738 { 3739 // build a file name 3740 // try picking it up from the message 3741 const char *suggestedName; 3742 if (message && message->FindString("be:clip_name", &suggestedName) == B_OK) 3743 strncpy(resultingName, suggestedName, B_FILE_NAME_LENGTH - 1); 3744 else 3745 strcpy(resultingName, fallbackName); 3746 3747 FSMakeOriginalName(resultingName, dir, ""); 3748 3749 // create a clipping file 3750 status_t error = dir->CreateFile(resultingName, &result, true); 3751 if (error != B_OK) 3752 return error; 3753 3754 if (setLocation && poseView) 3755 poseView->TrySettingPoseLocation(&result, dropPoint); 3756 3757 return B_OK; 3758 } 3759 3760 3761 static int32 3762 RunMimeTypeDestinationMenu(const char *actionText, const BObjectList<BString> *types, 3763 const BObjectList<BString> *specificItems, BPoint where) 3764 { 3765 int32 count; 3766 3767 if (types) 3768 count = types->CountItems(); 3769 else 3770 count = specificItems->CountItems(); 3771 3772 if (!count) 3773 return 0; 3774 3775 BPopUpMenu *menu = new BPopUpMenu("create clipping"); 3776 menu->SetFont(be_plain_font); 3777 3778 for (int32 index = 0; index < count; index++) { 3779 3780 const char *embedTypeAs = NULL; 3781 char buffer[256]; 3782 if (types) { 3783 types->ItemAt(index)->String(); 3784 BMimeType mimeType(embedTypeAs); 3785 3786 if (mimeType.GetShortDescription(buffer) == B_OK) 3787 embedTypeAs = buffer; 3788 } 3789 3790 BString description; 3791 if (specificItems->ItemAt(index)->Length()) { 3792 description << (const BString &)(*specificItems->ItemAt(index)); 3793 3794 if (embedTypeAs) 3795 description << " (" << embedTypeAs << ")"; 3796 3797 } else if (types) 3798 description = embedTypeAs; 3799 3800 const char *labelText; 3801 char text[1024]; 3802 if (actionText) { 3803 int32 length = 1024 - 1 - (int32)strlen(actionText); 3804 if (length > 0) { 3805 description.Truncate(length); 3806 sprintf(text, actionText, description.String()); 3807 labelText = text; 3808 } else 3809 labelText = "label too long"; 3810 } else 3811 labelText = description.String(); 3812 3813 menu->AddItem(new BMenuItem(labelText, 0)); 3814 } 3815 3816 menu->AddSeparatorItem(); 3817 menu->AddItem(new BMenuItem("Cancel", 0)); 3818 3819 int32 result = -1; 3820 BMenuItem *resultingItem = menu->Go(where, false, true); 3821 if (resultingItem) { 3822 int32 index = menu->IndexOf(resultingItem); 3823 if (index < count) 3824 result = index; 3825 } 3826 3827 delete menu; 3828 3829 return result; 3830 } 3831 3832 3833 bool 3834 BPoseView::HandleMessageDropped(BMessage *message) 3835 { 3836 ASSERT(message->WasDropped()); 3837 3838 if (!fDropEnabled) 3839 return false; 3840 3841 if (!dynamic_cast<BContainerWindow*>(Window())) 3842 return false; 3843 3844 if (message->HasData("RGBColor", 'RGBC')) { 3845 // do not handle roColor-style drops here, pass them on to the desktop 3846 if (dynamic_cast<BDeskWindow *>(Window())) 3847 BMessenger((BHandler *)Window()).SendMessage(message); 3848 3849 return true; 3850 } 3851 3852 if (fDropTarget) 3853 HiliteDropTarget(false); 3854 3855 fDropTarget = NULL; 3856 3857 ASSERT(TargetModel()); 3858 BPoint offset; 3859 BPoint dropPt(message->DropPoint(&offset)); 3860 ConvertFromScreen(&dropPt); 3861 3862 // tenatively figure out the pose we dropped the file onto 3863 int32 index; 3864 BPose *targetPose = FindPose(dropPt, &index); 3865 Model tmpTarget; 3866 Model *targetModel = NULL; 3867 if (targetPose) { 3868 targetModel = targetPose->TargetModel(); 3869 if (targetModel->IsSymLink() 3870 && tmpTarget.SetTo(targetPose->TargetModel()->EntryRef(), true, true) == B_OK) 3871 targetModel = &tmpTarget; 3872 } 3873 3874 return HandleDropCommon(message, targetModel, targetPose, this, dropPt); 3875 } 3876 3877 3878 bool 3879 BPoseView::HandleDropCommon(BMessage *message, Model *targetModel, BPose *targetPose, 3880 BView *view, BPoint dropPt) 3881 { 3882 uint32 buttons = (uint32)message->FindInt32("buttons"); 3883 3884 BContainerWindow *containerWindow = NULL; 3885 BPoseView *poseView = dynamic_cast<BPoseView*>(view); 3886 if (poseView) 3887 containerWindow = poseView->ContainerWindow(); 3888 3889 // look for srcWindow to determine whether drag was initiated in tracker 3890 BContainerWindow *srcWindow = NULL; 3891 message->FindPointer("src_window", (void **) &srcWindow); 3892 3893 if (!srcWindow) { 3894 // drag was from another app 3895 3896 if (targetModel == NULL) 3897 targetModel = poseView->TargetModel(); 3898 3899 // figure out if we dropped a file onto a directory and set the targetDirectory 3900 // to it, else set it to this pose view 3901 BDirectory targetDirectory; 3902 if (targetModel && targetModel->IsDirectory()) 3903 targetDirectory.SetTo(targetModel->EntryRef()); 3904 3905 if (targetModel->IsRoot()) 3906 // don't drop anyting into the root disk 3907 return false; 3908 3909 bool canCopy; 3910 bool canMove; 3911 bool canErase; 3912 bool canLink; 3913 if (FindDragNDropAction(message, canCopy, canMove, canLink, canErase)) { 3914 // new D&D protocol 3915 // what action can the drag initiator do? 3916 if (canErase && CanTrashForeignDrag(targetModel)) { 3917 BMessage reply(B_TRASH_TARGET); 3918 message->SendReply(&reply); 3919 return true; 3920 } 3921 3922 if ((canCopy || canMove) 3923 && CanCopyOrMoveForeignDrag(targetModel, message)) { 3924 // handle the promise style drag&drop 3925 3926 // fish for specification of specialized menu items 3927 BObjectList<BString> actionSpecifiers(10, true); 3928 for (int32 index = 0; ; index++) { 3929 const char *string; 3930 if (message->FindString("be:actionspecifier", index, &string) != B_OK) 3931 break; 3932 3933 ASSERT(string); 3934 actionSpecifiers.AddItem(new BString(string)); 3935 } 3936 3937 // build the list of types the drag originator offers 3938 BObjectList<BString> types(10, true); 3939 BObjectList<BString> typeNames(10, true); 3940 for (int32 index = 0; ; index++) { 3941 const char *string; 3942 if (message->FindString("be:filetypes", index, &string) != B_OK) 3943 break; 3944 3945 ASSERT(string); 3946 types.AddItem(new BString(string)); 3947 3948 const char *typeName = ""; 3949 message->FindString("be:type_descriptions", index, &typeName); 3950 typeNames.AddItem(new BString(typeName)); 3951 } 3952 3953 int32 specificTypeIndex = -1; 3954 int32 specificActionIndex = -1; 3955 3956 // if control down, run a popup menu 3957 if (canCopy 3958 && ((modifiers() & B_CONTROL_KEY) || (buttons & B_SECONDARY_MOUSE_BUTTON))) { 3959 3960 if (actionSpecifiers.CountItems() > 0) { 3961 specificActionIndex = RunMimeTypeDestinationMenu(NULL, 3962 NULL, &actionSpecifiers, view->ConvertToScreen(dropPt)); 3963 3964 if (specificActionIndex == -1) 3965 return false; 3966 } else if (types.CountItems() > 0) { 3967 specificTypeIndex = RunMimeTypeDestinationMenu("Create %s clipping", 3968 &types, &typeNames, view->ConvertToScreen(dropPt)); 3969 3970 if (specificTypeIndex == -1) 3971 return false; 3972 } 3973 } 3974 3975 char name[B_FILE_NAME_LENGTH]; 3976 BFile file; 3977 if (CreateClippingFile(poseView, file, name, &targetDirectory, message, 3978 "Untitled clipping", !targetPose, dropPt) != B_OK) 3979 return false; 3980 3981 // here is a file for the drag initiator, it is up to it now to stuff it 3982 // with the goods 3983 3984 // build the reply message 3985 BMessage reply(canCopy ? B_COPY_TARGET : B_MOVE_TARGET); 3986 reply.AddString("be:types", B_FILE_MIME_TYPE); 3987 if (specificTypeIndex != -1) { 3988 // we had the user pick a specific type from a menu, use it 3989 reply.AddString("be:filetypes", 3990 types.ItemAt(specificTypeIndex)->String()); 3991 3992 if (typeNames.ItemAt(specificTypeIndex)->Length()) 3993 reply.AddString("be:type_descriptions", 3994 typeNames.ItemAt(specificTypeIndex)->String()); 3995 } 3996 3997 if (specificActionIndex != -1) 3998 // we had the user pick a specific type from a menu, use it 3999 reply.AddString("be:actionspecifier", 4000 actionSpecifiers.ItemAt(specificActionIndex)->String()); 4001 4002 4003 reply.AddRef("directory", targetModel->EntryRef()); 4004 reply.AddString("name", name); 4005 4006 // Attach any data the originator may have tagged on 4007 BMessage data; 4008 if (message->FindMessage("be:originator-data", &data) == B_OK) 4009 reply.AddMessage("be:originator-data", &data); 4010 4011 // copy over all the file types the drag initiator claimed to 4012 // support 4013 for (int32 index = 0; ; index++) { 4014 const char *type; 4015 if (message->FindString("be:filetypes", index, &type) != B_OK) 4016 break; 4017 reply.AddString("be:filetypes", type); 4018 } 4019 4020 message->SendReply(&reply); 4021 return true; 4022 } 4023 } 4024 4025 if (message->HasRef("refs")) { 4026 // ToDo: 4027 // decide here on copy, move or create symlink 4028 // look for specific command or bring up popup 4029 // Unify this with local drag&drop 4030 4031 if (!targetModel->IsDirectory()) 4032 // bail if we are not a directory 4033 return false; 4034 4035 bool canRelativeLink = false; 4036 if (!canCopy && !canMove && !canLink && containerWindow) { 4037 if (((buttons & B_SECONDARY_MOUSE_BUTTON) 4038 || (modifiers() & B_CONTROL_KEY))) { 4039 switch (containerWindow->ShowDropContextMenu(dropPt)) { 4040 case kCreateRelativeLink: 4041 canRelativeLink = true; 4042 break; 4043 case kCreateLink: 4044 canLink = true; 4045 break; 4046 case kMoveSelectionTo: 4047 canMove = true; 4048 break; 4049 case kCopySelectionTo: 4050 canCopy = true; 4051 break; 4052 case kCancelButton: 4053 default: 4054 // user canceled context menu 4055 return true; 4056 } 4057 } else 4058 canCopy = true; 4059 } 4060 4061 uint32 moveMode; 4062 if (canCopy) 4063 moveMode = kCopySelectionTo; 4064 else if (canMove) 4065 moveMode = kMoveSelectionTo; 4066 else if (canLink) 4067 moveMode = kCreateLink; 4068 else if (canRelativeLink) 4069 moveMode = kCreateRelativeLink; 4070 else { 4071 TRESPASS(); 4072 return true; 4073 } 4074 4075 // handle refs by performing a copy 4076 BObjectList<entry_ref> *entryList = new BObjectList<entry_ref>(); 4077 4078 for (int32 index = 0; ; index++) { 4079 // copy all enclosed refs into a list 4080 entry_ref ref; 4081 if (message->FindRef("refs", index, &ref) != B_OK) 4082 break; 4083 entryList->AddItem(new entry_ref(ref)); 4084 } 4085 4086 int32 count = entryList->CountItems(); 4087 if (count) { 4088 BList *pointList = 0; 4089 if (poseView && !targetPose) { 4090 // calculate a pointList to make the icons land were we dropped them 4091 pointList = new BList(count); 4092 // force the the icons to lay out in 5 columns 4093 for (int32 index = 0; count; index++) { 4094 for (int32 j = 0; count && j < 4; j++, count--) { 4095 BPoint point(dropPt + BPoint(j * poseView->fGrid.x, index * 4096 poseView->fGrid.y)); 4097 pointList->AddItem(new BPoint(poseView->PinToGrid(point, 4098 poseView->fGrid, poseView->fOffset))); 4099 } 4100 } 4101 } 4102 4103 // perform asynchronous copy 4104 FSMoveToFolder(entryList, new BEntry(targetModel->EntryRef()), 4105 moveMode, pointList); 4106 4107 return true; 4108 } 4109 4110 // nothing to copy, list doesn't get consumed 4111 delete entryList; 4112 return true; 4113 } 4114 if (message->HasData(kPlainTextMimeType, B_MIME_TYPE)) { 4115 // text dropped, make into a clipping file 4116 if (!targetModel->IsDirectory()) 4117 // bail if we are not a directory 4118 return false; 4119 4120 // find the text 4121 int32 textLength; 4122 const char *text; 4123 if (message->FindData(kPlainTextMimeType, B_MIME_TYPE, (const void **)&text, 4124 &textLength) != B_OK) 4125 return false; 4126 4127 char name[B_FILE_NAME_LENGTH]; 4128 4129 BFile file; 4130 if (CreateClippingFile(poseView, file, name, &targetDirectory, message, 4131 "Untitled clipping", !targetPose, dropPt) != B_OK) 4132 return false; 4133 4134 // write out the file 4135 if (file.Seek(0, SEEK_SET) == B_ERROR 4136 || file.Write(text, (size_t)textLength) < 0 4137 || file.SetSize(textLength) != B_OK) { 4138 // failed to write file, remove file and bail 4139 file.Unset(); 4140 BEntry entry(&targetDirectory, name); 4141 entry.Remove(); 4142 PRINT(("error writing text into file %s\n", name)); 4143 } 4144 4145 // pick up TextView styles if available and save them with the file 4146 const text_run_array *textRuns = NULL; 4147 int32 dataSize = 0; 4148 if (message->FindData("application/x-vnd.Be-text_run_array", B_MIME_TYPE, 4149 (const void **)&textRuns, &dataSize) == B_OK && textRuns && dataSize) { 4150 // save styles the same way StyledEdit does 4151 void *data = BTextView::FlattenRunArray(textRuns, &dataSize); 4152 file.WriteAttr("styles", B_RAW_TYPE, 0, data, (size_t)dataSize); 4153 free(data); 4154 } 4155 4156 // mark as a clipping file 4157 int32 tmp; 4158 file.WriteAttr(kAttrClippingFile, B_RAW_TYPE, 0, &tmp, sizeof(int32)); 4159 4160 // set the file type 4161 BNodeInfo info(&file); 4162 info.SetType(kPlainTextMimeType); 4163 4164 return true; 4165 } 4166 if (message->HasData(kBitmapMimeType, B_MESSAGE_TYPE) 4167 || message->HasData(kLargeIconType, B_MESSAGE_TYPE) 4168 || message->HasData(kMiniIconType, B_MESSAGE_TYPE)) { 4169 // bitmap, make into a clipping file 4170 if (!targetModel->IsDirectory()) 4171 // bail if we are not a directory 4172 return false; 4173 4174 BMessage embeddedBitmap; 4175 if (message->FindMessage(kBitmapMimeType, &embeddedBitmap) != B_OK 4176 && message->FindMessage(kLargeIconType, &embeddedBitmap) != B_OK 4177 && message->FindMessage(kMiniIconType, &embeddedBitmap) != B_OK) 4178 return false; 4179 4180 char name[B_FILE_NAME_LENGTH]; 4181 4182 BFile file; 4183 if (CreateClippingFile(poseView, file, name, &targetDirectory, message, 4184 "Untitled bitmap", !targetPose, dropPt) != B_OK) 4185 return false; 4186 4187 int32 size = embeddedBitmap.FlattenedSize(); 4188 if (size > 1024*1024) 4189 // bail if too large 4190 return false; 4191 4192 char *buffer = new char [size]; 4193 embeddedBitmap.Flatten(buffer, size); 4194 4195 // write out the file 4196 if (file.Seek(0, SEEK_SET) == B_ERROR 4197 || file.Write(buffer, (size_t)size) < 0 4198 || file.SetSize(size) != B_OK) { 4199 // failed to write file, remove file and bail 4200 file.Unset(); 4201 BEntry entry(&targetDirectory, name); 4202 entry.Remove(); 4203 PRINT(("error writing bitmap into file %s\n", name)); 4204 } 4205 4206 // mark as a clipping file 4207 int32 tmp; 4208 file.WriteAttr(kAttrClippingFile, B_RAW_TYPE, 0, &tmp, sizeof(int32)); 4209 4210 // set the file type 4211 BNodeInfo info(&file); 4212 info.SetType(kBitmapMimeType); 4213 4214 return true; 4215 } 4216 return false; 4217 } 4218 4219 if (srcWindow == containerWindow) { 4220 // drag started in this window 4221 containerWindow->Activate(); 4222 containerWindow->UpdateIfNeeded(); 4223 poseView->ResetPosePlacementHint(); 4224 } 4225 4226 if (srcWindow == containerWindow && DragSelectionContains(targetPose, message)) { 4227 // drop on self 4228 targetModel = NULL; 4229 } 4230 4231 bool wasHandled = false; 4232 bool ignoreTypes = (modifiers() & B_CONTROL_KEY) != 0; 4233 4234 if (targetModel) { 4235 // ToDo: 4236 // pick files to drop/launch on a case by case basis 4237 if (targetModel->IsDirectory()) { 4238 MoveSelectionInto(targetModel, srcWindow, containerWindow, buttons, dropPt, 4239 false); 4240 wasHandled = true; 4241 } else if (CanHandleDragSelection(targetModel, message, ignoreTypes)) { 4242 LaunchAppWithSelection(targetModel, message, !ignoreTypes); 4243 wasHandled = true; 4244 } 4245 } 4246 4247 if (poseView && !wasHandled) { 4248 BPoint clickPt = message->FindPoint("click_pt"); 4249 // ToDo: 4250 // removed check for root here need to do that, possibly at a different 4251 // level 4252 poseView->MoveSelectionTo(dropPt, clickPt, srcWindow); 4253 } 4254 4255 if (poseView && poseView->fEnsurePosesVisible) 4256 poseView->CheckPoseVisibility(); 4257 4258 return true; 4259 } 4260 4261 4262 struct LaunchParams { 4263 Model *app; 4264 bool checkTypes; 4265 BMessage *refsMessage; 4266 }; 4267 4268 4269 static bool 4270 AddOneToLaunchMessage(BPose *pose, BPoseView *, void *castToParams) 4271 { 4272 LaunchParams *params = (LaunchParams *)castToParams; 4273 4274 ASSERT(pose->TargetModel()); 4275 if (params->app->IsDropTarget(params->checkTypes ? pose->TargetModel() : 0, true)) 4276 params->refsMessage->AddRef("refs", pose->TargetModel()->EntryRef()); 4277 4278 return false; 4279 } 4280 4281 4282 void 4283 BPoseView::LaunchAppWithSelection(Model *appModel, const BMessage *dragMessage, 4284 bool checkTypes) 4285 { 4286 // launch items from the current selection with <appModel>; only pass the same 4287 // files that we previously decided can be handled by <appModel> 4288 BMessage refs(B_REFS_RECEIVED); 4289 LaunchParams params; 4290 params.app = appModel; 4291 params.checkTypes = checkTypes; 4292 params.refsMessage = &refs; 4293 4294 // add Tracker token so that refs received recipients can script us 4295 BContainerWindow *srcWindow; 4296 dragMessage->FindPointer("src_window", (void **)&srcWindow); 4297 if (srcWindow) 4298 params.refsMessage->AddMessenger("TrackerViewToken", BMessenger( 4299 srcWindow->PoseView())); 4300 4301 EachItemInDraggedSelection(dragMessage, AddOneToLaunchMessage, 0, ¶ms); 4302 if (params.refsMessage->HasRef("refs")) 4303 TrackerLaunch(appModel->EntryRef(), params.refsMessage, true); 4304 } 4305 4306 4307 static bool 4308 OneMatches(BPose *pose, BPoseView *, void *castToPose) 4309 { 4310 return pose == (const BPose *)castToPose; 4311 } 4312 4313 4314 bool 4315 BPoseView::DragSelectionContains(const BPose *target, 4316 const BMessage *dragMessage) 4317 { 4318 return EachItemInDraggedSelection(dragMessage, OneMatches, 0, (void *)target); 4319 } 4320 4321 4322 static void 4323 CopySelectionListToBListAsEntryRefs(const PoseList *original, BObjectList<entry_ref> *copy) 4324 { 4325 int32 count = original->CountItems(); 4326 for (int32 index = 0; index < count; index++) 4327 copy->AddItem(new entry_ref(*(original->ItemAt(index)->TargetModel()->EntryRef()))); 4328 } 4329 4330 4331 void 4332 BPoseView::MoveSelectionInto(Model *destFolder, BContainerWindow *srcWindow, 4333 bool forceCopy, bool createLink, bool relativeLink) 4334 { 4335 uint32 buttons; 4336 BPoint loc; 4337 GetMouse(&loc, &buttons); 4338 MoveSelectionInto(destFolder, srcWindow, dynamic_cast<BContainerWindow*>(Window()), 4339 buttons, loc, forceCopy, createLink, relativeLink); 4340 } 4341 4342 4343 void 4344 BPoseView::MoveSelectionInto(Model *destFolder, BContainerWindow *srcWindow, 4345 BContainerWindow *destWindow, uint32 buttons, BPoint loc, bool forceCopy, 4346 bool createLink, bool relativeLink) 4347 { 4348 AutoLock<BWindow> lock(srcWindow); 4349 if (!lock) 4350 return; 4351 4352 ASSERT(srcWindow->PoseView()->TargetModel()); 4353 4354 // make sure source and destination folders are different 4355 if (!createLink && (*srcWindow->PoseView()->TargetModel()->NodeRef() 4356 == *destFolder->NodeRef())) 4357 return; 4358 4359 bool createRelativeLink = relativeLink; 4360 if (((buttons & B_SECONDARY_MOUSE_BUTTON) 4361 || (modifiers() & B_CONTROL_KEY)) && destWindow) { 4362 4363 switch (destWindow->ShowDropContextMenu(loc)) { 4364 case kCreateRelativeLink: 4365 createRelativeLink = true; 4366 break; 4367 4368 case kCreateLink: 4369 createLink = true; 4370 break; 4371 4372 case kMoveSelectionTo: 4373 break; 4374 4375 case kCopySelectionTo: 4376 forceCopy = true; 4377 break; 4378 4379 case kCancelButton: 4380 default: 4381 // user canceled context menu 4382 return; 4383 } 4384 } 4385 4386 BEntry *destEntry = new BEntry(destFolder->EntryRef()); 4387 bool destIsTrash = FSIsTrashDir(destEntry); 4388 4389 // perform asynchronous copy/move 4390 forceCopy = forceCopy || (modifiers() & B_OPTION_KEY); 4391 4392 bool okToMove = true; 4393 4394 if (destFolder->IsRoot()) { 4395 (new BAlert("", kNoCopyToRootStr, "Cancel", NULL, NULL, 4396 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 4397 okToMove = false; 4398 } 4399 4400 // can't copy items into the trash 4401 if (forceCopy && destIsTrash) { 4402 (new BAlert("", kNoCopyToTrashStr, "Cancel", NULL, NULL, 4403 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 4404 okToMove = false; 4405 } 4406 4407 // can't create symlinks into the trash 4408 if (createLink && destIsTrash) { 4409 (new BAlert("", kNoLinkToTrashStr, "Cancel", NULL, NULL, 4410 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 4411 okToMove = false; 4412 } 4413 4414 // prompt user if drag was from a query 4415 if (srcWindow->TargetModel()->IsQuery() 4416 && !forceCopy && !destIsTrash && !createLink) { 4417 srcWindow->UpdateIfNeeded(); 4418 okToMove = (new BAlert("", kOkToMoveStr, "Cancel", "Move", NULL, 4419 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go() == 1; 4420 } 4421 4422 if (okToMove) { 4423 PoseList *selectionList = srcWindow->PoseView()->SelectionList(); 4424 BObjectList<entry_ref> *srcList = new BObjectList<entry_ref>( 4425 selectionList->CountItems(), true); 4426 CopySelectionListToBListAsEntryRefs(selectionList, srcList); 4427 4428 uint32 moveMode; 4429 if (forceCopy) 4430 moveMode = kCopySelectionTo; 4431 else if (createRelativeLink) 4432 moveMode = kCreateRelativeLink; 4433 else if (createLink) 4434 moveMode = kCreateLink; 4435 else 4436 moveMode = kMoveSelectionTo; 4437 4438 FSMoveToFolder(srcList, destEntry, moveMode); 4439 return; 4440 } 4441 4442 delete destEntry; 4443 } 4444 4445 4446 void 4447 BPoseView::MoveSelectionTo(BPoint dropPt, BPoint clickPt, 4448 BContainerWindow* srcWindow) 4449 { 4450 // Moves selection from srcWindow into this window, copying if necessary. 4451 4452 BContainerWindow *window = ContainerWindow(); 4453 if (!window) 4454 return; 4455 4456 ASSERT(window->PoseView()); 4457 ASSERT(TargetModel()); 4458 4459 // make sure this window is a legal drop target 4460 if (srcWindow != window && !TargetModel()->IsDropTarget()) 4461 return; 4462 4463 // if drop was done with control key or secondary button 4464 // then we need to show a context menu for drop location 4465 uint32 buttons = (uint32)window->CurrentMessage()->FindInt32("buttons"); 4466 bool createLink = false; 4467 bool forceCopy = false; 4468 bool createRelativeLink = false; 4469 bool dropOnGrid = (modifiers() & B_COMMAND_KEY) != 0; 4470 4471 if ((buttons & B_SECONDARY_MOUSE_BUTTON) || (modifiers() & B_CONTROL_KEY)) { 4472 4473 switch (window->ShowDropContextMenu(dropPt)) { 4474 case kCreateRelativeLink: 4475 createRelativeLink = true; 4476 break; 4477 4478 case kCreateLink: 4479 createLink = true; 4480 break; 4481 4482 case kMoveSelectionTo: 4483 break; 4484 4485 case kCopySelectionTo: 4486 if (srcWindow == window) { 4487 DuplicateSelection(&clickPt, &dropPt); 4488 return; 4489 } 4490 forceCopy = true; 4491 break; 4492 4493 case kCancelButton: 4494 default: 4495 // user canceled context menu 4496 return; 4497 } 4498 } 4499 4500 if (!createLink && !createRelativeLink && srcWindow == window) { // dropped in same window 4501 if (ViewMode() == kListMode) // can't move in list view 4502 return; 4503 4504 BPoint delta(dropPt - clickPt); 4505 int32 count = fSelectionList->CountItems(); 4506 for (int32 index = 0; index < count; index++) { 4507 BPose *pose = fSelectionList->ItemAt(index); 4508 4509 // remove pose from VSlist before changing location 4510 // so that we "find" the correct pose to remove 4511 // need to do this because bsearch uses top of pose 4512 // to locate pose to remove 4513 RemoveFromVSList(pose); 4514 4515 BRect oldBounds(pose->CalcRect(this)); 4516 BPoint location(pose->Location() + delta); 4517 if (dropOnGrid) 4518 location = PinToGrid(location, fGrid, fOffset); 4519 4520 pose->MoveTo(location, this); 4521 4522 RemoveFromExtent(oldBounds); 4523 AddToExtent(pose->CalcRect(this)); 4524 4525 // remove and reinsert pose to keep VSlist sorted 4526 AddToVSList(pose); 4527 } 4528 } else { 4529 AutoLock<BWindow> lock(srcWindow); 4530 if (!lock) 4531 return; 4532 4533 // dropped from another window 4534 // CopyTask will delete pointList 4535 PoseList *selectionList = srcWindow->PoseView()->SelectionList(); 4536 int32 count = selectionList->CountItems(); 4537 BList *pointList = GetDropPointList(clickPt, dropPt, selectionList, 4538 srcWindow->PoseView()->ViewMode() == kListMode, dropOnGrid); 4539 4540 // perform asynch copy/move 4541 forceCopy = forceCopy || (modifiers() & B_OPTION_KEY); 4542 bool okToMove = true; 4543 BEntry *destEntry = new BEntry(TargetModel()->EntryRef()); 4544 bool destIsTrash = FSIsTrashDir(destEntry); 4545 4546 // don't prompt if we're going to end up copying anyway 4547 if (srcWindow->PoseView()->TargetModel()->IsQuery() 4548 && !forceCopy 4549 && !createLink 4550 && !destIsTrash) { 4551 srcWindow->UpdateIfNeeded(); 4552 okToMove = (new BAlert("", kOkToMoveStr, "Cancel", "Move", NULL, 4553 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go() == 1; 4554 } 4555 4556 // can't copy items into the trash 4557 if (forceCopy && destIsTrash) { 4558 (new BAlert("", kNoCopyToTrashStr, "Cancel", NULL, NULL, 4559 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 4560 okToMove = false; 4561 } 4562 4563 // can't create symlinks into the trash 4564 if ((createLink || createRelativeLink) && destIsTrash) { 4565 (new BAlert("", kNoLinkToTrashStr, "Cancel", NULL, NULL, 4566 B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 4567 okToMove = false; 4568 } 4569 4570 if (okToMove) { 4571 // create dup Model list, dest Model for CopyTask 4572 BObjectList<entry_ref> *srcList = new BObjectList<entry_ref>(count, true); 4573 CopySelectionListToBListAsEntryRefs(selectionList, srcList); 4574 uint32 moveMode; 4575 if (forceCopy) 4576 moveMode = kCopySelectionTo; 4577 else if (createRelativeLink) 4578 moveMode = kCreateRelativeLink; 4579 else if (createLink) 4580 moveMode = kCreateLink; 4581 else 4582 moveMode = kMoveSelectionTo; 4583 FSMoveToFolder(srcList, destEntry, moveMode, pointList); 4584 } else { 4585 if (pointList) { 4586 pointList->DoForEach(delete_point); 4587 delete pointList; 4588 } 4589 delete destEntry; 4590 } 4591 } 4592 } 4593 4594 4595 inline void 4596 UpdateWasBrokenSymlinkBinder(BPose *pose, Model *, BPoseView *poseView, 4597 BPoint *loc) 4598 { 4599 pose->UpdateWasBrokenSymlink(*loc, poseView); 4600 loc->y += poseView->ListElemHeight(); 4601 } 4602 4603 4604 void 4605 BPoseView::TryUpdatingBrokenLinks() 4606 { 4607 AutoLock<BWindow> lock(Window()); 4608 if (!lock) 4609 return; 4610 4611 // try fixing broken symlinks 4612 BPoint loc; 4613 EachPoseAndModel(fPoseList, &UpdateWasBrokenSymlinkBinder, this, &loc); 4614 } 4615 4616 4617 void 4618 BPoseView::RemoveNonBootDesktopModels(BPose *, Model *model, int32, 4619 BPoseView *poseView, dev_t) 4620 { 4621 BPath path; 4622 4623 model->GetPath(&path); 4624 4625 TrackerString pathString(path.Path()); 4626 4627 if (pathString.Contains("/home/Desktop") && !pathString.StartsWith("/boot")) 4628 poseView->DeletePose(model->NodeRef()); 4629 } 4630 4631 4632 void 4633 BPoseView::PoseHandleDeviceUnmounted(BPose *pose, Model *model, int32 index, 4634 BPoseView *poseView, dev_t device) 4635 { 4636 if (model->NodeRef()->device == device) 4637 poseView->DeletePose(model->NodeRef()); 4638 else if (model->IsSymLink() 4639 && model->LinkTo() 4640 && model->LinkTo()->NodeRef()->device == device) 4641 poseView->DeleteSymLinkPoseTarget(model->LinkTo()->NodeRef(), pose, index); 4642 } 4643 4644 4645 static void 4646 OneMetaMimeChanged(BPose *pose, Model *model, int32 index, 4647 BPoseView *poseView, const char *type) 4648 { 4649 ASSERT(model); 4650 if (model->IconFrom() != kNode 4651 && model->IconFrom() != kUnknownSource 4652 && model->IconFrom() != kUnknownNotFromNode 4653 // ToDo: 4654 // add supertype compare 4655 && strcasecmp(model->MimeType(), type) == 0) { 4656 // metamime change very likely affected the documents icon 4657 4658 BPoint poseLoc(0, index * poseView->ListElemHeight()); 4659 pose->UpdateIcon(poseLoc, poseView); 4660 } 4661 } 4662 4663 4664 void 4665 BPoseView::MetaMimeChanged(const char *type, const char *preferredApp) 4666 { 4667 IconCache::sIconCache->IconChanged(type, preferredApp); 4668 // wait for other windows to do the same before we start 4669 // updating poses which causes icon recaching 4670 snooze(200000); 4671 4672 EachPoseAndResolvedModel(fPoseList, &OneMetaMimeChanged, this, type); 4673 } 4674 4675 4676 class MetaMimeChangedAccumulator : public AccumulatingFunctionObject { 4677 // pools up matching metamime change notices, executing them as a single 4678 // update 4679 public: 4680 MetaMimeChangedAccumulator(void (BPoseView::*func)(const char *type, 4681 const char *preferredApp), 4682 BContainerWindow *window, const char *type, const char *preferredApp) 4683 : fCallOnThis(window), 4684 fFunc(func), 4685 fType(type), 4686 fPreferredApp(preferredApp) 4687 {} 4688 4689 virtual bool CanAccumulate(const AccumulatingFunctionObject *functor) const 4690 { 4691 return dynamic_cast<const MetaMimeChangedAccumulator *>(functor) 4692 && dynamic_cast<const MetaMimeChangedAccumulator *>(functor)->fType 4693 == fType 4694 && dynamic_cast<const MetaMimeChangedAccumulator *>(functor)-> 4695 fPreferredApp == fPreferredApp; 4696 } 4697 4698 virtual void Accumulate(AccumulatingFunctionObject *DEBUG_ONLY(functor)) 4699 { 4700 ASSERT(CanAccumulate(functor)); 4701 // do nothing, no further accumulating needed 4702 } 4703 4704 protected: 4705 virtual void operator()() 4706 { 4707 AutoLock<BWindow> lock(fCallOnThis); 4708 if (!lock) 4709 return; 4710 4711 (fCallOnThis->PoseView()->*fFunc)(fType.String(), fPreferredApp.String()); 4712 } 4713 4714 virtual ulong Size() const 4715 { 4716 return sizeof (*this); 4717 } 4718 4719 private: 4720 BContainerWindow *fCallOnThis; 4721 void (BPoseView::*fFunc)(const char *type, const char *preferredApp); 4722 BString fType; 4723 BString fPreferredApp; 4724 }; 4725 4726 4727 bool 4728 BPoseView::NoticeMetaMimeChanged(const BMessage *message) 4729 { 4730 int32 change; 4731 if (message->FindInt32("be:which", &change) != B_OK) 4732 return true; 4733 4734 bool iconChanged = (change & B_ICON_CHANGED) != 0; 4735 bool iconForTypeChanged = (change & B_ICON_FOR_TYPE_CHANGED) != 0; 4736 bool preferredAppChanged = (change & B_APP_HINT_CHANGED) 4737 || (change & B_PREFERRED_APP_CHANGED); 4738 4739 const char *type = NULL; 4740 const char *preferredApp = NULL; 4741 4742 if (iconChanged || preferredAppChanged) 4743 message->FindString("be:type", &type); 4744 4745 if (iconForTypeChanged) { 4746 message->FindString("be:extra_type", &type); 4747 message->FindString("be:type", &preferredApp); 4748 } 4749 4750 if (iconChanged || preferredAppChanged || iconForTypeChanged) { 4751 TaskLoop *taskLoop = ContainerWindow()->DelayedTaskLoop(); 4752 ASSERT(taskLoop); 4753 taskLoop->AccumulatedRunLater(new MetaMimeChangedAccumulator( 4754 &BPoseView::MetaMimeChanged, ContainerWindow(), type, preferredApp), 4755 200000, 5000000); 4756 } 4757 return true; 4758 } 4759 4760 4761 bool 4762 BPoseView::FSNotification(const BMessage *message) 4763 { 4764 node_ref itemNode; 4765 dev_t device; 4766 4767 switch (message->FindInt32("opcode")) { 4768 case B_ENTRY_CREATED: 4769 { 4770 message->FindInt32("device", &itemNode.device); 4771 node_ref dirNode; 4772 dirNode.device = itemNode.device; 4773 message->FindInt64("directory", (int64 *)&dirNode.node); 4774 message->FindInt64("node", (int64 *)&itemNode.node); 4775 4776 ASSERT(TargetModel()); 4777 4778 // Query windows can get notices on different dirNodes 4779 // The Disks window can too 4780 // So can the Desktop, as long as the integrate flag is on 4781 TrackerSettings settings; 4782 if (dirNode != *TargetModel()->NodeRef() 4783 && !TargetModel()->IsQuery() 4784 && !TargetModel()->IsRoot() 4785 && ((!settings.IntegrateNonBootBeOSDesktops() 4786 && !settings.ShowDisksIcon()) || !IsDesktopView())) 4787 // stray notification 4788 break; 4789 4790 const char *name; 4791 if (message->FindString("name", &name) == B_OK) 4792 EntryCreated(&dirNode, &itemNode, name); 4793 #if DEBUG 4794 else 4795 SERIAL_PRINT(("no name in entry creation message\n")); 4796 #endif 4797 break; 4798 } 4799 case B_ENTRY_MOVED: 4800 return EntryMoved(message); 4801 break; 4802 4803 case B_ENTRY_REMOVED: 4804 message->FindInt32("device", &itemNode.device); 4805 message->FindInt64("node", (int64 *)&itemNode.node); 4806 4807 // our window itself may be deleted 4808 // we must check to see if this comes as a query 4809 // notification or a node monitor notification because 4810 // if it's a query notification then we're just being told we 4811 // no longer match the query, so we don't want to close the window 4812 // but it's a node monitor notification then that means our query 4813 // file has been deleted so we close the window 4814 4815 if (message->what == B_NODE_MONITOR 4816 && TargetModel() && *(TargetModel()->NodeRef()) == itemNode) { 4817 if (!TargetModel()->IsRoot()) { 4818 // it is impossible to watch for ENTRY_REMOVED in "/" because the 4819 // notification is ambiguous - the vnode is that of the volume but 4820 // the device is of the parent not the same as the device of the volume 4821 // that way we may get aliasing for volumes with vnodes of 1 4822 // (currently the case for iso9660) 4823 DisableSaveLocation(); 4824 Window()->Close(); 4825 } 4826 } else { 4827 int32 index; 4828 BPose *pose = fPoseList->FindPose(&itemNode, &index); 4829 if (!pose) { 4830 // couldn't find pose, first check if the node might be 4831 // target of a symlink pose; 4832 // 4833 // What happens when a node and a symlink to it are in the 4834 // same window? 4835 // They get monitored twice, we get two notifications; the 4836 // first one will get caught by the first FindPose, the 4837 // second one by the DeepFindPose 4838 // 4839 pose = fPoseList->DeepFindPose(&itemNode, &index); 4840 if (pose) { 4841 DeleteSymLinkPoseTarget(&itemNode, pose, index); 4842 break; 4843 } 4844 } 4845 return DeletePose(&itemNode); 4846 } 4847 break; 4848 4849 case B_DEVICE_MOUNTED: 4850 { 4851 if (message->FindInt32("new device", &device) != B_OK) 4852 break; 4853 4854 if (TargetModel() != NULL && TargetModel()->IsRoot()) { 4855 BVolume volume(device); 4856 CreateVolumePose(&volume, false); 4857 } else if (ContainerWindow()->IsTrash()) { 4858 // add trash items from newly mounted volume 4859 4860 BDirectory trashDir; 4861 BEntry entry; 4862 BVolume volume(device); 4863 if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK 4864 && trashDir.GetEntry(&entry) == B_OK) { 4865 Model model(&entry); 4866 if (model.InitCheck() == B_OK) 4867 AddPoses(&model); 4868 } 4869 } 4870 TaskLoop *taskLoop = ContainerWindow()->DelayedTaskLoop(); 4871 ASSERT(taskLoop); 4872 taskLoop->RunLater(NewMemberFunctionObject( 4873 &BPoseView::TryUpdatingBrokenLinks, this), 500000); 4874 // delay of 500000: wait for volumes to properly finish mounting 4875 // without this in the Model::FinishSettingUpType a symlink 4876 // to a volume would get initialized as a symlink to a directory 4877 // because IsRootDirectory looks like returns false. Either there 4878 // is a race condition or I was doing something wrong. 4879 break; 4880 } 4881 case B_DEVICE_UNMOUNTED: 4882 if (message->FindInt32("device", &device) == B_OK) { 4883 if (TargetModel() && TargetModel()->NodeRef()->device == device) { 4884 // close the window from a volume that is gone 4885 DisableSaveLocation(); 4886 Window()->Close(); 4887 } else if (TargetModel()) 4888 EachPoseAndModel(fPoseList, &PoseHandleDeviceUnmounted, this, device); 4889 } 4890 break; 4891 4892 case B_STAT_CHANGED: 4893 case B_ATTR_CHANGED: 4894 return AttributeChanged(message); 4895 break; 4896 } 4897 return true; 4898 } 4899 4900 4901 bool 4902 BPoseView::CreateSymlinkPoseTarget(Model *symlink) 4903 { 4904 Model *newResolvedModel = NULL; 4905 Model *result = symlink->LinkTo(); 4906 4907 if (!result) { 4908 newResolvedModel = new Model(symlink->EntryRef(), true, true); 4909 WatchNewNode(newResolvedModel->NodeRef()); 4910 // this should be called before creating the model 4911 4912 if (newResolvedModel->InitCheck() != B_OK) { 4913 // broken link, still can show though, bail 4914 watch_node(newResolvedModel->NodeRef(), B_STOP_WATCHING, this); 4915 delete newResolvedModel; 4916 return true; 4917 } 4918 result = newResolvedModel; 4919 } 4920 4921 BModelOpener opener(result); 4922 // open the model 4923 4924 PoseInfo poseInfo; 4925 ReadPoseInfo(result, &poseInfo); 4926 4927 if (!ShouldShowPose(result, &poseInfo)) { 4928 // symlink target invisible, make the link to it the same 4929 watch_node(newResolvedModel->NodeRef(), B_STOP_WATCHING, this); 4930 delete newResolvedModel; 4931 // clean up what we allocated 4932 return false; 4933 } 4934 4935 symlink->SetLinkTo(result); 4936 // watch the link target too 4937 return true; 4938 } 4939 4940 4941 BPose * 4942 BPoseView::EntryCreated(const node_ref *dirNode, const node_ref *itemNode, 4943 const char *name, int32 *indexPtr) 4944 { 4945 // reject notification if pose already exists 4946 if (fPoseList->FindPose(itemNode) || FindZombie(itemNode)) 4947 return NULL; 4948 BPoseView::WatchNewNode(itemNode); 4949 // have to node monitor ahead of time because Model will 4950 // cache up the file type and preferred app 4951 Model *model = new Model(dirNode, itemNode, name, true); 4952 if (model->InitCheck() != B_OK) { 4953 // if we have trouble setting up model then we stuff it into 4954 // a zombie list in a half-alive state until we can properly awaken it 4955 PRINT(("2 adding model %s to zombie list, error %s\n", model->Name(), 4956 strerror(model->InitCheck()))); 4957 fZombieList->AddItem(model); 4958 return NULL; 4959 } 4960 4961 // get saved pose info out of attribute 4962 PoseInfo poseInfo; 4963 ReadPoseInfo(model, &poseInfo); 4964 4965 if (!ShouldShowPose(model, &poseInfo) 4966 // filter out undesired poses 4967 || (model->IsSymLink() && !CreateSymlinkPoseTarget(model))) { 4968 // model is a symlink, cache up the symlink target or scrap 4969 // everything if target is invisible 4970 watch_node(model->NodeRef(), B_STOP_WATCHING, this); 4971 delete model; 4972 return NULL; 4973 } 4974 4975 return CreatePose(model, &poseInfo, true, indexPtr); 4976 } 4977 4978 4979 bool 4980 BPoseView::EntryMoved(const BMessage *message) 4981 { 4982 ino_t oldDir; 4983 node_ref dirNode; 4984 node_ref itemNode; 4985 4986 message->FindInt32("device", &dirNode.device); 4987 itemNode.device = dirNode.device; 4988 message->FindInt64("to directory", (int64 *)&dirNode.node); 4989 message->FindInt64("node", (int64 *)&itemNode.node); 4990 message->FindInt64("from directory", (int64 *)&oldDir); 4991 4992 const char *name; 4993 if (message->FindString("name", &name) != B_OK) 4994 return true; 4995 // handle special case of notifying a name change for a volume 4996 // - the notification is not enough, because the volume's device 4997 // is different than that of the root directory; we have to do a 4998 // lookup using the new volume name and get the volume device from there 4999 StatStruct st; 5000 // get the inode of the root and check if we got a notification on it 5001 if (stat("/", &st) >= 0 5002 && st.st_dev == dirNode.device 5003 && st.st_ino == dirNode.node) { 5004 5005 BString buffer; 5006 buffer << "/" << name; 5007 if (stat(buffer.String(), &st) >= 0) { 5008 // point the dirNode to the actual volume 5009 itemNode.node = st.st_ino; 5010 itemNode.device = st.st_dev; 5011 } 5012 } 5013 5014 ASSERT(TargetModel()); 5015 5016 node_ref thisDirNode; 5017 if (ContainerWindow()->IsTrash()) { 5018 5019 BDirectory trashDir; 5020 if (FSGetTrashDir(&trashDir, itemNode.device) != B_OK) 5021 return true; 5022 5023 trashDir.GetNodeRef(&thisDirNode); 5024 } else 5025 thisDirNode = *TargetModel()->NodeRef(); 5026 5027 // see if we need to update window title (and folder itself) 5028 if (thisDirNode == itemNode) { 5029 5030 TargetModel()->UpdateEntryRef(&dirNode, name); 5031 assert_cast<BContainerWindow *>(Window())->UpdateTitle(); 5032 } 5033 if (oldDir == dirNode.node || TargetModel()->IsQuery()) { 5034 5035 // rename or move of entry in this directory (or query) 5036 5037 int32 index; 5038 BPose *pose = fPoseList->FindPose(&itemNode, &index); 5039 5040 if (pose) { 5041 pose->TargetModel()->UpdateEntryRef(&dirNode, name); 5042 // for queries we check for move to trash and remove item if so 5043 if (TargetModel()->IsQuery()) { 5044 PoseInfo poseInfo; 5045 ReadPoseInfo(pose->TargetModel(), &poseInfo); 5046 if (!ShouldShowPose(pose->TargetModel(), &poseInfo)) 5047 return DeletePose(&itemNode, pose, index); 5048 } 5049 5050 BPoint loc(0, index * fListElemHeight); 5051 // if we get a rename then we need to assume that we might 5052 // have missed some other attr changed notifications so we 5053 // recheck all widgets 5054 if (pose->TargetModel()->OpenNode() == B_OK) { 5055 pose->UpdateAllWidgets(index, loc, this); 5056 pose->TargetModel()->CloseNode(); 5057 CheckPoseSortOrder(pose, index); 5058 } 5059 } else { 5060 // also must watch for renames on zombies 5061 Model *zombie = FindZombie(&itemNode, &index); 5062 if (zombie) { 5063 PRINT(("converting model %s from a zombie\n", zombie->Name())); 5064 zombie->UpdateEntryRef(&dirNode, name); 5065 pose = ConvertZombieToPose(zombie, index); 5066 } else 5067 return false; 5068 } 5069 if (pose) 5070 pendingNodeMonitorCache.PoseCreatedOrMoved(this, pose); 5071 } else if (oldDir == thisDirNode.node) 5072 return DeletePose(&itemNode); 5073 else if (dirNode.node == thisDirNode.node) 5074 EntryCreated(&dirNode, &itemNode, name); 5075 else if (TrackerSettings().IntegrateNonBootBeOSDesktops() && IsDesktopView()) { 5076 // node entered/exited desktop view, we have more work to do 5077 5078 // if old dir node is a desktop folder, delete pose 5079 node_ref oldDirNode; 5080 oldDirNode.node = oldDir; 5081 oldDirNode.device = dirNode.device; 5082 BDirectory oldDirectory(&oldDirNode); 5083 BEntry oldDirectoryEntry; 5084 oldDirectory.GetEntry(&oldDirectoryEntry); 5085 if (oldDirectoryEntry.InitCheck() == B_OK 5086 && FSIsDeskDir(&oldDirectoryEntry) 5087 && !DeletePose(&itemNode)) 5088 return false; 5089 5090 // if new dir node is a desktop folder, create pose 5091 BDirectory newDirectory(&dirNode); 5092 BEntry newDirectoryEntry; 5093 newDirectory.GetEntry(&newDirectoryEntry); 5094 if (newDirectoryEntry.InitCheck() == B_OK && FSIsDeskDir(&newDirectoryEntry)) 5095 EntryCreated(&dirNode, &itemNode, name); 5096 } 5097 return true; 5098 } 5099 5100 5101 bool 5102 BPoseView::AttributeChanged(const BMessage *message) 5103 { 5104 node_ref itemNode; 5105 message->FindInt32("device", &itemNode.device); 5106 message->FindInt64("node", (int64 *)&itemNode.node); 5107 5108 const char *attrName; 5109 message->FindString("attr", &attrName); 5110 5111 int32 index; 5112 BPose *pose = fPoseList->DeepFindPose(&itemNode, &index); 5113 if (pose) { 5114 attr_info info; 5115 BPoint loc(0, index * fListElemHeight); 5116 5117 Model *model = pose->TargetModel(); 5118 if (model->IsSymLink() && *model->NodeRef() != itemNode) 5119 // change happened on symlink's target 5120 model = model->ResolveIfLink(); 5121 ASSERT(model); 5122 5123 status_t result = B_OK; 5124 for (int32 count = 0; count < 100; count++) { 5125 // if node is busy, wait a little, it may be in the 5126 // middle of mimeset and we wan't to pick up the changes 5127 result = model->OpenNode(); 5128 if (result == B_OK || result != B_BUSY) 5129 break; 5130 5131 PRINT(("model %s busy, retrying in a bit\n", model->Name())); 5132 snooze(10000); 5133 } 5134 5135 if (result == B_OK) { 5136 if (attrName && model->Node()) { 5137 info.type = 0; 5138 // the call below might fail if the attribute has been removed 5139 model->Node()->GetAttrInfo(attrName, &info); 5140 pose->UpdateWidgetAndModel(model, attrName, info.type, index, loc, this); 5141 } else 5142 pose->UpdateWidgetAndModel(model, 0, 0, index, loc, this); 5143 5144 model->CloseNode(); 5145 } else { 5146 PRINT(("Cache Error %s\n", strerror(result))); 5147 return false; 5148 } 5149 5150 uint32 attrHash; 5151 if (attrName) { 5152 // rebuild the MIME type list, if the MIME type has changed 5153 if (strcmp(attrName, kAttrMIMEType) == 0) 5154 RefreshMimeTypeList(); 5155 5156 // note: the following code is wrong, because this sort of hashing 5157 // may overlap and we get aliasing 5158 attrHash = AttrHashString(attrName, info.type); 5159 } 5160 if (!attrName || attrHash == PrimarySort() || attrHash == SecondarySort()) 5161 CheckPoseSortOrder(pose, index); 5162 } else { 5163 // pose might be in zombie state if we're copying... 5164 Model *zombie = FindZombie(&itemNode, &index); 5165 if (zombie) { 5166 PRINT(("converting model %s from a zombie\n", zombie->Name())); 5167 ConvertZombieToPose(zombie, index); 5168 } else { 5169 // did not find a pose, probably not entered yet 5170 // PRINT(("failed to deliver attr change node monitor - pose not found\n")); 5171 return false; 5172 } 5173 } 5174 5175 return true; 5176 } 5177 5178 5179 void 5180 BPoseView::UpdateVolumeIcon(dev_t device, bool forceUpdate) 5181 { 5182 int32 index; 5183 BPose *pose = fPoseList->FindVolumePose(device,&index); 5184 if (pose == NULL) 5185 return; 5186 5187 if (pose->UpdateVolumeSpaceBar(TrackerSettings().ShowVolumeSpaceBar()) || forceUpdate) { 5188 BPoint loc(0, index * fListElemHeight); 5189 pose->UpdateIcon(loc, this); 5190 } 5191 } 5192 5193 5194 void 5195 BPoseView::UpdateVolumeIcons() 5196 { 5197 BVolumeRoster roster; 5198 5199 BVolume volume; 5200 while(roster.GetNextVolume(&volume) == B_NO_ERROR) { 5201 BDirectory dir; 5202 volume.GetRootDirectory(&dir); 5203 node_ref nodeRef; 5204 dir.GetNodeRef(&nodeRef); 5205 5206 UpdateVolumeIcon(nodeRef.device, true); 5207 } 5208 } 5209 5210 5211 BPose * 5212 BPoseView::ConvertZombieToPose(Model *zombie, int32 index) 5213 { 5214 if (zombie->UpdateStatAndOpenNode() != B_OK) 5215 return NULL; 5216 5217 fZombieList->RemoveItemAt(index); 5218 5219 PoseInfo poseInfo; 5220 ReadPoseInfo(zombie, &poseInfo); 5221 5222 if (ShouldShowPose(zombie, &poseInfo)) 5223 // ToDo: 5224 // handle symlinks here 5225 return CreatePose(zombie, &poseInfo); 5226 5227 delete zombie; 5228 5229 return NULL; 5230 } 5231 5232 5233 BList * 5234 BPoseView::GetDropPointList(BPoint dropStart, BPoint dropEnd, const PoseList *poses, 5235 bool sourceInListMode, bool dropOnGrid) const 5236 { 5237 if (ViewMode() == kListMode) 5238 return NULL; 5239 5240 int32 count = poses->CountItems(); 5241 BList *pointList = new BList(count); 5242 for (int32 index = 0; index < count; index++) { 5243 BPose *pose = poses->ItemAt(index); 5244 BPoint poseLoc; 5245 if (sourceInListMode) 5246 poseLoc = dropEnd + BPoint(0, index * (IconPoseHeight() + 3)); 5247 else 5248 poseLoc = dropEnd + (pose->Location() - dropStart); 5249 5250 if (dropOnGrid) 5251 poseLoc = PinToGrid(poseLoc, fGrid, fOffset); 5252 5253 pointList->AddItem(new BPoint(poseLoc)); 5254 } 5255 5256 return pointList; 5257 } 5258 5259 5260 void 5261 BPoseView::DuplicateSelection(BPoint *dropStart, BPoint *dropEnd) 5262 { 5263 // If there is a volume or trash folder, remove them from the list 5264 // because they cannot get copied 5265 int32 selectionSize = fSelectionList->CountItems(); 5266 for (int32 index = 0; index < selectionSize; index++) { 5267 BPose *pose = (BPose*)fSelectionList->ItemAt(index); 5268 Model *model = pose->TargetModel(); 5269 5270 // can't duplicate a volume or the trash 5271 BEntry entry(model->EntryRef()); 5272 if (FSIsTrashDir(&entry) || model->IsVolume()) { 5273 fSelectionList->RemoveItemAt(index); 5274 index--; 5275 selectionSize--; 5276 if (fSelectionPivotPose == pose) 5277 fSelectionPivotPose = NULL; 5278 if (fRealPivotPose == pose) 5279 fRealPivotPose = NULL; 5280 continue; 5281 } 5282 } 5283 5284 // create entry_ref list from selection 5285 if (!fSelectionList->IsEmpty()) { 5286 BObjectList<entry_ref> *srcList = new BObjectList<entry_ref>( 5287 fSelectionList->CountItems(), true); 5288 CopySelectionListToBListAsEntryRefs(fSelectionList, srcList); 5289 5290 BList *dropPoints = NULL; 5291 if (dropStart) 5292 dropPoints = GetDropPointList(*dropStart, *dropEnd, fSelectionList, 5293 ViewMode() == kListMode, (modifiers() & B_COMMAND_KEY) != 0); 5294 5295 // perform asynchronous duplicate 5296 FSDuplicate(srcList, dropPoints); 5297 } 5298 } 5299 5300 5301 void 5302 BPoseView::SelectPoseAtLocation(BPoint point) 5303 { 5304 int32 index; 5305 BPose *pose = FindPose(point, &index); 5306 if (pose) 5307 SelectPose(pose, index); 5308 } 5309 5310 5311 void 5312 BPoseView::MoveListToTrash(BObjectList<entry_ref> *list, bool selectNext, 5313 bool deleteDirectly) 5314 { 5315 if (!list->CountItems()) 5316 return; 5317 5318 BObjectList<FunctionObject> *taskList = 5319 new BObjectList<FunctionObject>(2, true); 5320 // new owning list of tasks 5321 5322 // first move selection to trash, 5323 if (deleteDirectly) 5324 taskList->AddItem(NewFunctionObject(FSDeleteRefList, list, false, true)); 5325 else 5326 taskList->AddItem(NewFunctionObject(FSMoveToTrash, list, 5327 (BList *)NULL, false)); 5328 5329 if (selectNext && ViewMode() == kListMode) { 5330 // next, if in list view mode try selecting the next item after 5331 BPose *pose = fSelectionList->ItemAt(0); 5332 5333 // find a point in the pose 5334 BPoint pointInPose(kListOffset + 5, 5); 5335 int32 index = IndexOfPose(pose); 5336 pointInPose.y += fListElemHeight * index; 5337 5338 TTracker *tracker = dynamic_cast<TTracker *>(be_app); 5339 5340 ASSERT(TargetModel()); 5341 if (tracker) 5342 // add a function object to the list of tasks to run 5343 // that will select the next item after the one we just 5344 // deleted 5345 taskList->AddItem(NewMemberFunctionObject( 5346 &TTracker::SelectPoseAtLocationSoon, tracker, 5347 *TargetModel()->NodeRef(), pointInPose)); 5348 5349 } 5350 // execute the two tasks in order 5351 ThreadSequence::Launch(taskList, true); 5352 } 5353 5354 5355 inline void 5356 CopyOneTrashedRefAsEntry(const entry_ref *ref, BObjectList<entry_ref> *trashList, 5357 BObjectList<entry_ref> *noTrashList, std::map<int32, bool> *deviceHasTrash) 5358 { 5359 std::map<int32, bool> &deviceHasTrashTmp = *deviceHasTrash; 5360 // work around stupid binding problems with EachListItem 5361 5362 BDirectory entryDir(ref); 5363 bool isVolume = entryDir.IsRootDirectory(); 5364 // volumes will get unmounted 5365 5366 // see if pose's device has a trash 5367 int32 device = ref->device; 5368 BDirectory trashDir; 5369 5370 // cache up the result in a map so that we don't have to keep calling 5371 // FSGetTrashDir over and over 5372 if (!isVolume 5373 && deviceHasTrashTmp.find(device) == deviceHasTrashTmp.end()) 5374 deviceHasTrashTmp[device] = FSGetTrashDir(&trashDir, device) == B_OK; 5375 5376 if (isVolume || deviceHasTrashTmp[device]) 5377 trashList->AddItem(new entry_ref(*ref)); 5378 else 5379 noTrashList->AddItem(new entry_ref(*ref)); 5380 } 5381 5382 5383 static void 5384 CopyPoseOneAsEntry(BPose *pose, BObjectList<entry_ref> *trashList, 5385 BObjectList<entry_ref> *noTrashList, std::map<int32, bool> *deviceHasTrash) 5386 { 5387 CopyOneTrashedRefAsEntry(pose->TargetModel()->EntryRef(), trashList, 5388 noTrashList, deviceHasTrash); 5389 } 5390 5391 5392 void 5393 BPoseView::MoveSelectionOrEntryToTrash(const entry_ref *ref, bool selectNext) 5394 { 5395 BObjectList<entry_ref> *entriesToTrash = new 5396 BObjectList<entry_ref>(fSelectionList->CountItems()); 5397 BObjectList<entry_ref> *entriesToDeleteOnTheSpot = new 5398 BObjectList<entry_ref>(20, true); 5399 std::map<int32, bool> deviceHasTrash; 5400 5401 if (ref) { 5402 CopyOneTrashedRefAsEntry(ref, entriesToTrash, entriesToDeleteOnTheSpot, 5403 &deviceHasTrash); 5404 } else { 5405 EachListItem(fSelectionList, CopyPoseOneAsEntry, entriesToTrash, 5406 entriesToDeleteOnTheSpot, &deviceHasTrash); 5407 } 5408 5409 if (entriesToDeleteOnTheSpot->CountItems()) { 5410 const char *alertText; 5411 if (ref) { 5412 alertText = "The selected item cannot be moved to the Trash. " 5413 "Would you like to delete it instead? (This operation cannot " 5414 "be reverted.)"; 5415 } else { 5416 alertText = "Some of the selected items cannot be moved to the Trash. " 5417 "Would you like to delete them instead? (This operation cannot " 5418 "be reverted.)"; 5419 } 5420 5421 if ((new BAlert("", alertText, "Cancel", "Delete"))->Go() == 0) 5422 return; 5423 } 5424 5425 MoveListToTrash(entriesToTrash, selectNext, false); 5426 MoveListToTrash(entriesToDeleteOnTheSpot, selectNext, true); 5427 } 5428 5429 5430 void 5431 BPoseView::MoveSelectionToTrash(bool selectNext) 5432 { 5433 if (fSelectionList->IsEmpty()) 5434 return; 5435 5436 // create entry_ref list from selection 5437 // separate items that can be trashed from ones that cannot 5438 5439 MoveSelectionOrEntryToTrash(0, selectNext); 5440 } 5441 5442 5443 void 5444 BPoseView::MoveEntryToTrash(const entry_ref *ref, bool selectNext) 5445 { 5446 MoveSelectionOrEntryToTrash(ref, selectNext); 5447 } 5448 5449 5450 void 5451 BPoseView::DeleteSelection(bool selectNext, bool askUser) 5452 { 5453 int32 count = fSelectionList -> CountItems(); 5454 if (count <= 0) 5455 return; 5456 5457 BObjectList<entry_ref> *entriesToDelete = new BObjectList<entry_ref>(count, true); 5458 5459 for (int32 index = 0; index < count; index++) 5460 entriesToDelete->AddItem(new entry_ref((*fSelectionList->ItemAt(index) 5461 ->TargetModel()->EntryRef()))); 5462 5463 Delete(entriesToDelete, selectNext, askUser); 5464 } 5465 5466 5467 void 5468 BPoseView::RestoreSelectionFromTrash(bool selectNext) 5469 { 5470 int32 count = fSelectionList -> CountItems(); 5471 if (count <= 0) 5472 return; 5473 5474 BObjectList<entry_ref> *entriesToRestore = new BObjectList<entry_ref>(count, true); 5475 5476 for (int32 index = 0; index < count; index++) 5477 entriesToRestore->AddItem(new entry_ref((*fSelectionList->ItemAt(index) 5478 ->TargetModel()->EntryRef()))); 5479 5480 RestoreItemsFromTrash(entriesToRestore, selectNext); 5481 } 5482 5483 5484 void 5485 BPoseView::Delete(const entry_ref &ref, bool selectNext, bool askUser) 5486 { 5487 BObjectList<entry_ref> *entriesToDelete = new BObjectList<entry_ref>(1, true); 5488 entriesToDelete->AddItem(new entry_ref(ref)); 5489 5490 Delete(entriesToDelete, selectNext, askUser); 5491 } 5492 5493 5494 void 5495 BPoseView::Delete(BObjectList<entry_ref> *list, bool selectNext, bool askUser) 5496 { 5497 if (list->CountItems() == 0) { 5498 delete list; 5499 return; 5500 } 5501 5502 BObjectList<FunctionObject> *taskList = 5503 new BObjectList<FunctionObject>(2, true); 5504 5505 // first move selection to trash, 5506 taskList->AddItem(NewFunctionObject(FSDeleteRefList, list, false, askUser)); 5507 5508 if (selectNext && ViewMode() == kListMode) { 5509 // next, if in list view mode try selecting the next item after 5510 BPose *pose = fSelectionList->ItemAt(0); 5511 5512 // find a point in the pose 5513 BPoint pointInPose(kListOffset + 5, 5); 5514 int32 index = IndexOfPose(pose); 5515 pointInPose.y += fListElemHeight * index; 5516 5517 TTracker *tracker = dynamic_cast<TTracker *>(be_app); 5518 5519 ASSERT(TargetModel()); 5520 if (tracker) 5521 // add a function object to the list of tasks to run 5522 // that will select the next item after the one we just 5523 // deleted 5524 taskList->AddItem(NewMemberFunctionObject( 5525 &TTracker::SelectPoseAtLocationSoon, tracker, 5526 *TargetModel()->NodeRef(), pointInPose)); 5527 5528 } 5529 // execute the two tasks in order 5530 ThreadSequence::Launch(taskList, true); 5531 } 5532 5533 5534 void 5535 BPoseView::RestoreItemsFromTrash(BObjectList<entry_ref> *list, bool selectNext) 5536 { 5537 if (list->CountItems() == 0) { 5538 delete list; 5539 return; 5540 } 5541 5542 BObjectList<FunctionObject> *taskList = 5543 new BObjectList<FunctionObject>(2, true); 5544 5545 // first restoree selection 5546 taskList->AddItem(NewFunctionObject(FSRestoreRefList, list, false)); 5547 5548 if (selectNext && ViewMode() == kListMode) { 5549 // next, if in list view mode try selecting the next item after 5550 BPose *pose = fSelectionList->ItemAt(0); 5551 5552 // find a point in the pose 5553 BPoint pointInPose(kListOffset + 5, 5); 5554 int32 index = IndexOfPose(pose); 5555 pointInPose.y += fListElemHeight * index; 5556 5557 TTracker *tracker = dynamic_cast<TTracker *>(be_app); 5558 5559 ASSERT(TargetModel()); 5560 if (tracker) 5561 // add a function object to the list of tasks to run 5562 // that will select the next item after the one we just 5563 // restored 5564 taskList->AddItem(NewMemberFunctionObject( 5565 &TTracker::SelectPoseAtLocationSoon, tracker, 5566 *TargetModel()->NodeRef(), pointInPose)); 5567 5568 } 5569 // execute the two tasks in order 5570 ThreadSequence::Launch(taskList, true); 5571 } 5572 5573 5574 void 5575 BPoseView::SelectAll() 5576 { 5577 BRect bounds(Bounds()); 5578 5579 // clear selection list 5580 fSelectionList->MakeEmpty(); 5581 fMimeTypesInSelectionCache.MakeEmpty(); 5582 fSelectionPivotPose = NULL; 5583 fRealPivotPose = NULL; 5584 5585 int32 startIndex = 0; 5586 BPoint loc(0, 0); 5587 5588 bool iconMode = ViewMode() != kListMode; 5589 5590 int32 count = fPoseList->CountItems(); 5591 for (int32 index = startIndex; index < count; index++) { 5592 BPose *pose = fPoseList->ItemAt(index); 5593 fSelectionList->AddItem(pose); 5594 if (index == startIndex) 5595 fSelectionPivotPose = pose; 5596 5597 if (!pose->IsSelected()) { 5598 pose->Select(true); 5599 5600 BRect poseRect; 5601 if (iconMode) 5602 poseRect = pose->CalcRect(this); 5603 else 5604 poseRect = pose->CalcRect(loc, this); 5605 5606 if (bounds.Intersects(poseRect)) { 5607 pose->Draw(poseRect, this, false); 5608 Flush(); 5609 } 5610 } 5611 5612 loc.y += fListElemHeight; 5613 } 5614 5615 if (fSelectionChangedHook) 5616 ContainerWindow()->SelectionChanged(); 5617 } 5618 5619 5620 void 5621 BPoseView::InvertSelection() 5622 { 5623 // Since this function shares most code with 5624 // SelectAll(), we could make SelectAll() empty the selection, 5625 // then call InvertSelection() 5626 5627 BRect bounds(Bounds()); 5628 5629 int32 startIndex = 0; 5630 BPoint loc(0, 0); 5631 5632 fMimeTypesInSelectionCache.MakeEmpty(); 5633 fSelectionPivotPose = NULL; 5634 fRealPivotPose = NULL; 5635 5636 bool iconMode = ViewMode() != kListMode; 5637 5638 int32 count = fPoseList->CountItems(); 5639 for (int32 index = startIndex; index < count; index++) { 5640 BPose *pose = fPoseList->ItemAt(index); 5641 5642 if (pose->IsSelected()) { 5643 fSelectionList->RemoveItem(pose); 5644 pose->Select(false); 5645 } else { 5646 if (index == startIndex) 5647 fSelectionPivotPose = pose; 5648 fSelectionList->AddItem(pose); 5649 pose->Select(true); 5650 } 5651 5652 BRect poseRect; 5653 if (iconMode) 5654 poseRect = pose->CalcRect(this); 5655 else 5656 poseRect = pose->CalcRect(loc, this); 5657 5658 if (bounds.Intersects(poseRect)) 5659 Invalidate(); 5660 5661 loc.y += fListElemHeight; 5662 } 5663 5664 if (fSelectionChangedHook) 5665 ContainerWindow()->SelectionChanged(); 5666 } 5667 5668 5669 int32 5670 BPoseView::SelectMatchingEntries(const BMessage *message) 5671 { 5672 int32 matchCount = 0; 5673 SetMultipleSelection(true); 5674 5675 ClearSelection(); 5676 5677 TrackerStringExpressionType expressionType; 5678 BString expression; 5679 const char *expressionPointer; 5680 bool invertSelection; 5681 bool ignoreCase; 5682 5683 message->FindInt32("ExpressionType", (int32*)&expressionType); 5684 message->FindString("Expression", &expressionPointer); 5685 message->FindBool("InvertSelection", &invertSelection); 5686 message->FindBool("IgnoreCase", &ignoreCase); 5687 5688 expression = expressionPointer; 5689 5690 int32 count = fPoseList->CountItems(); 5691 TrackerString name; 5692 5693 RegExp regExpression; 5694 5695 // Make sure we don't have any errors in the expression 5696 // before we match the names: 5697 if (expressionType == kRegexpMatch) { 5698 regExpression.SetTo(expression); 5699 5700 if (regExpression.InitCheck() != B_OK) { 5701 BString message; 5702 message << "Error in regular expression:\n\n'"; 5703 message << regExpression.ErrorString() << "'"; 5704 (new BAlert("", message.String(), "OK", NULL, NULL, B_WIDTH_AS_USUAL, 5705 B_STOP_ALERT))->Go(); 5706 return 0; 5707 } 5708 } 5709 5710 // There is room for optimizations here: If regexp-type match, the Matches() 5711 // function compiles the expression for every entry. One could use 5712 // TrackerString::CompileRegExp and reuse the expression. However, then we have 5713 // to take care of the case sensitivity ourselves. 5714 for (int32 index = 0; index < count; index++) { 5715 BPose *pose = fPoseList->ItemAt(index); 5716 name = pose->TargetModel()->Name(); 5717 if (name.Matches(expression.String(), !ignoreCase, expressionType) ^ invertSelection) { 5718 matchCount++; 5719 AddPoseToSelection(pose, index); 5720 } 5721 } 5722 5723 Window()->Activate(); 5724 // Make sure the window is activated for 5725 // subsequent manipulations. Esp. needed 5726 // for the Desktop window. 5727 5728 return matchCount; 5729 } 5730 5731 5732 void 5733 BPoseView::ShowSelectionWindow() 5734 { 5735 Window()->PostMessage(kShowSelectionWindow); 5736 } 5737 5738 5739 void 5740 BPoseView::KeyDown(const char *bytes, int32 count) 5741 { 5742 char key = bytes[0]; 5743 5744 switch (key) { 5745 case B_LEFT_ARROW: 5746 case B_RIGHT_ARROW: 5747 case B_UP_ARROW: 5748 case B_DOWN_ARROW: 5749 { 5750 int32 index; 5751 BPose *pose = FindNearbyPose(key, &index); 5752 if (pose == NULL) 5753 break; 5754 5755 if (fMultipleSelection && modifiers() & B_SHIFT_KEY) { 5756 if (pose->IsSelected()) { 5757 RemovePoseFromSelection(fSelectionList->LastItem()); 5758 fSelectionPivotPose = pose; 5759 ScrollIntoView(pose, index, false); 5760 } else 5761 AddPoseToSelection(pose, index, true); 5762 } else 5763 SelectPose(pose, index); 5764 break; 5765 } 5766 5767 case B_RETURN: 5768 OpenSelection(); 5769 break; 5770 5771 case B_HOME: 5772 // select the first entry (if in listview mode), and 5773 // scroll to the top of the view 5774 if (ViewMode() == kListMode) { 5775 BPose *pose = fSelectionList->LastItem(); 5776 5777 if (pose != NULL && fMultipleSelection && (modifiers() & B_SHIFT_KEY) != 0) { 5778 int32 index = fPoseList->IndexOf(pose); 5779 5780 // select all items from the current one till the top 5781 for (int32 i = index; i-- > 0; ) { 5782 pose = fPoseList->ItemAt(i); 5783 if (pose == NULL) 5784 continue; 5785 5786 if (!pose->IsSelected()) 5787 AddPoseToSelection(pose, i, i == 0); 5788 } 5789 } else 5790 SelectPose(fPoseList->FirstItem(), 0); 5791 } else if (fVScrollBar) 5792 fVScrollBar->SetValue(0); 5793 break; 5794 5795 case B_END: 5796 // select the last entry (if in listview mode), and 5797 // scroll to the bottom of the view 5798 if (ViewMode() == kListMode) { 5799 BPose *pose = fSelectionList->FirstItem(); 5800 5801 if (pose != NULL && fMultipleSelection && (modifiers() & B_SHIFT_KEY) != 0) { 5802 int32 index = fPoseList->IndexOf(pose); 5803 int32 count = fPoseList->CountItems() - 1; 5804 5805 // select all items from the current one to the bottom 5806 for (int32 i = index; i <= count; i++) { 5807 pose = fPoseList->ItemAt(i); 5808 if (pose == NULL) 5809 continue; 5810 5811 if (!pose->IsSelected()) 5812 AddPoseToSelection(pose, i, i == count); 5813 } 5814 } else 5815 SelectPose(fPoseList->LastItem(), fPoseList->CountItems() - 1); 5816 } else if (fVScrollBar) { 5817 float max, min; 5818 fVScrollBar->GetRange(&min, &max); 5819 fVScrollBar->SetValue(max); 5820 } 5821 break; 5822 5823 case B_PAGE_UP: 5824 if (fVScrollBar) { 5825 float max, min; 5826 fVScrollBar->GetSteps(&min, &max); 5827 fVScrollBar->SetValue(fVScrollBar->Value() - max); 5828 } 5829 break; 5830 5831 case B_PAGE_DOWN: 5832 if (fVScrollBar) { 5833 float max, min; 5834 fVScrollBar->GetSteps(&min, &max); 5835 fVScrollBar->SetValue(fVScrollBar->Value() + max); 5836 } 5837 break; 5838 5839 case B_TAB: 5840 if (IsFilePanel()) 5841 _inherited::KeyDown(bytes, count); 5842 else { 5843 if (fSelectionList->IsEmpty()) 5844 fMatchString[0] = '\0'; 5845 else { 5846 BPose *pose = fSelectionList->FirstItem(); 5847 strncpy(fMatchString, pose->TargetModel()->Name(), B_FILE_NAME_LENGTH - 1); 5848 fMatchString[B_FILE_NAME_LENGTH - 1] = '\0'; 5849 } 5850 5851 bool reverse = (Window()->CurrentMessage()->FindInt32("modifiers") 5852 & B_SHIFT_KEY) != 0; 5853 int32 index; 5854 BPose *pose = FindNextMatch(&index, reverse); 5855 if (!pose) { // wrap around 5856 if (reverse) { 5857 fMatchString[0] = (char)0xff; 5858 fMatchString[1] = '\0'; 5859 } else 5860 fMatchString[0] = '\0'; 5861 pose = FindNextMatch(&index, reverse); 5862 } 5863 5864 SelectPose(pose, index); 5865 } 5866 break; 5867 5868 case B_DELETE: 5869 { 5870 // Make sure user can't trash something already in the trash. 5871 BEntry entry(TargetModel()->EntryRef()); 5872 if (FSIsTrashDir(&entry)) { 5873 // Delete without asking from the trash 5874 DeleteSelection(true, false); 5875 } else { 5876 TrackerSettings settings; 5877 5878 if ((modifiers() & B_SHIFT_KEY) != 0 || settings.DontMoveFilesToTrash()) 5879 DeleteSelection(true, settings.AskBeforeDeleteFile()); 5880 else 5881 MoveSelectionToTrash(); 5882 } 5883 break; 5884 } 5885 5886 case B_BACKSPACE: 5887 // remove last char from the typeahead buffer 5888 if (strcmp(fMatchString, "") != 0) { 5889 fMatchString[strlen(fMatchString) - 1] = '\0'; 5890 5891 fLastKeyTime = system_time(); 5892 5893 fCountView->SetTypeAhead(fMatchString); 5894 5895 // select our new string 5896 int32 index; 5897 BPose *pose = FindBestMatch(&index); 5898 if (!pose) { // wrap around 5899 fMatchString[0] = '\0'; 5900 pose = FindBestMatch(&index); 5901 } 5902 SelectPose(pose, index); 5903 } 5904 break; 5905 5906 default: 5907 { 5908 // handle typeahead selection 5909 5910 // create a null-terminated version of typed char 5911 char searchChar[4] = { key, 0 }; 5912 5913 bigtime_t doubleClickSpeed; 5914 get_click_speed(&doubleClickSpeed); 5915 5916 // start watching 5917 if (fKeyRunner == NULL) { 5918 fKeyRunner = new BMessageRunner(this, new BMessage(kCheckTypeahead), doubleClickSpeed); 5919 if (fKeyRunner->InitCheck() != B_OK) 5920 return; 5921 } 5922 5923 // add char to existing matchString or start new match string 5924 // make sure we don't overfill matchstring 5925 if (system_time() - fLastKeyTime < (doubleClickSpeed * 2)) { 5926 uint32 nchars = B_FILE_NAME_LENGTH - strlen(fMatchString); 5927 strncat(fMatchString, searchChar, nchars); 5928 } else { 5929 strncpy(fMatchString, searchChar, B_FILE_NAME_LENGTH - 1); 5930 } 5931 fMatchString[B_FILE_NAME_LENGTH - 1] = '\0'; 5932 fLastKeyTime = system_time(); 5933 5934 fCountView->SetTypeAhead(fMatchString); 5935 5936 int32 index; 5937 BPose *pose = FindBestMatch(&index); 5938 if (!pose) { // wrap around 5939 fMatchString[0] = '\0'; 5940 pose = FindBestMatch(&index); 5941 } 5942 SelectPose(pose, index); 5943 break; 5944 } 5945 } 5946 } 5947 5948 5949 BPose * 5950 BPoseView::FindNextMatch(int32 *matchingIndex, bool reverse) 5951 { 5952 char bestSoFar[B_FILE_NAME_LENGTH] = { 0 }; 5953 BPose *poseToSelect = NULL; 5954 5955 // loop through all poses to find match 5956 int32 count = fPoseList->CountItems(); 5957 for (int32 index = 0; index < count; index++) { 5958 BPose *pose = fPoseList->ItemAt(index); 5959 5960 if (reverse) { 5961 if (strcasecmp(pose->TargetModel()->Name(), fMatchString) < 0) 5962 if (strcasecmp(pose->TargetModel()->Name(), bestSoFar) >= 0 5963 || !bestSoFar[0]) { 5964 strcpy(bestSoFar, pose->TargetModel()->Name()); 5965 poseToSelect = pose; 5966 *matchingIndex = index; 5967 } 5968 } else if (strcasecmp(pose->TargetModel()->Name(), fMatchString) > 0) 5969 if (strcasecmp(pose->TargetModel()->Name(), bestSoFar) <= 0 5970 || !bestSoFar[0]) { 5971 strcpy(bestSoFar, pose->TargetModel()->Name()); 5972 poseToSelect = pose; 5973 *matchingIndex = index; 5974 } 5975 5976 } 5977 5978 return poseToSelect; 5979 } 5980 5981 5982 BPose * 5983 BPoseView::FindBestMatch(int32 *index) 5984 { 5985 char bestSoFar[B_FILE_NAME_LENGTH] = { 0 }; 5986 BPose *poseToSelect = NULL; 5987 5988 BColumn *firstColumn = FirstColumn(); 5989 5990 // loop through all poses to find match 5991 int32 count = fPoseList->CountItems(); 5992 for (int32 i = 0; i < count; i++) { 5993 BPose *pose = fPoseList->ItemAt(i); 5994 const char * text; 5995 if (ViewMode() == kListMode) 5996 text = pose->TargetModel()->Name(); 5997 else { 5998 ModelNodeLazyOpener modelOpener(pose->TargetModel()); 5999 BTextWidget *widget = pose->WidgetFor(firstColumn, this, modelOpener); 6000 if (widget) 6001 text = widget->Text(); 6002 else 6003 text = pose->TargetModel()->Name(); 6004 } 6005 6006 if (strcasecmp(text, fMatchString) >= 0) 6007 if (strcasecmp(text, bestSoFar) <= 0 || !bestSoFar[0]) { 6008 strcpy(bestSoFar, text); 6009 poseToSelect = pose; 6010 *index = i; 6011 } 6012 } 6013 6014 return poseToSelect; 6015 } 6016 6017 6018 static bool 6019 LinesIntersect(float s1, float e1, float s2, float e2) 6020 { 6021 return std::max(s1, s2) < std::min(e1, e2); 6022 } 6023 6024 6025 BPose * 6026 BPoseView::FindNearbyPose(char arrowKey, int32 *poseIndex) 6027 { 6028 int32 resultingIndex = -1; 6029 BPose *poseToSelect = NULL; 6030 BPose *selectedPose = fSelectionList->LastItem(); 6031 6032 if (ViewMode() == kListMode) { 6033 switch (arrowKey) { 6034 case B_UP_ARROW: 6035 case B_LEFT_ARROW: 6036 if (selectedPose) { 6037 resultingIndex = fPoseList->IndexOf(selectedPose) - 1; 6038 poseToSelect = fPoseList->ItemAt(resultingIndex); 6039 if (!poseToSelect && arrowKey == B_LEFT_ARROW) { 6040 resultingIndex = fPoseList->CountItems() - 1; 6041 poseToSelect = fPoseList->LastItem(); 6042 } 6043 } else { 6044 resultingIndex = fPoseList->CountItems() - 1; 6045 poseToSelect = fPoseList->LastItem(); 6046 } 6047 break; 6048 6049 case B_DOWN_ARROW: 6050 case B_RIGHT_ARROW: 6051 if (selectedPose) { 6052 resultingIndex = fPoseList->IndexOf(selectedPose) + 1; 6053 poseToSelect = fPoseList->ItemAt(resultingIndex); 6054 if (!poseToSelect && arrowKey == B_RIGHT_ARROW) { 6055 resultingIndex = 0; 6056 poseToSelect = fPoseList->FirstItem(); 6057 } 6058 } else { 6059 resultingIndex = 0; 6060 poseToSelect = fPoseList->FirstItem(); 6061 } 6062 break; 6063 } 6064 *poseIndex = resultingIndex; 6065 return poseToSelect; 6066 } 6067 6068 // must be in one of the icon modes 6069 6070 // handle case where there is no current selection 6071 if (fSelectionList->IsEmpty()) { 6072 // find the upper-left pose (I know it's ugly!) 6073 poseToSelect = fVSPoseList->FirstItem(); 6074 for (int32 index = 0; ;index++) { 6075 BPose *pose = fVSPoseList->ItemAt(++index); 6076 if (!pose) 6077 break; 6078 6079 BRect selectedBounds(poseToSelect->CalcRect(this)); 6080 BRect poseRect(pose->CalcRect(this)); 6081 6082 if (poseRect.top > selectedBounds.top) 6083 break; 6084 6085 if (poseRect.left < selectedBounds.left) 6086 poseToSelect = pose; 6087 } 6088 6089 return poseToSelect; 6090 } 6091 6092 BRect selectionRect(selectedPose->CalcRect(this)); 6093 BRect bestRect; 6094 6095 // we're not in list mode so scan visually for pose to select 6096 int32 count = fPoseList->CountItems(); 6097 for (int32 index = 0; index < count; index++) { 6098 BPose *pose = fPoseList->ItemAt(index); 6099 BRect poseRect(pose->CalcRect(this)); 6100 6101 switch (arrowKey) { 6102 case B_LEFT_ARROW: 6103 if (LinesIntersect(poseRect.top, poseRect.bottom, 6104 selectionRect.top, selectionRect.bottom)) 6105 if (poseRect.left < selectionRect.left) 6106 if (poseRect.left > bestRect.left 6107 || !bestRect.IsValid()) { 6108 bestRect = poseRect; 6109 poseToSelect = pose; 6110 } 6111 break; 6112 6113 case B_RIGHT_ARROW: 6114 if (LinesIntersect(poseRect.top, poseRect.bottom, 6115 selectionRect.top, selectionRect.bottom)) 6116 if (poseRect.right > selectionRect.right) 6117 if (poseRect.right < bestRect.right 6118 || !bestRect.IsValid()) { 6119 bestRect = poseRect; 6120 poseToSelect = pose; 6121 } 6122 break; 6123 6124 case B_UP_ARROW: 6125 if (LinesIntersect(poseRect.left, poseRect.right, 6126 selectionRect.left, selectionRect.right)) 6127 if (poseRect.top < selectionRect.top) 6128 if (poseRect.top > bestRect.top 6129 || !bestRect.IsValid()) { 6130 bestRect = poseRect; 6131 poseToSelect = pose; 6132 } 6133 break; 6134 6135 case B_DOWN_ARROW: 6136 if (LinesIntersect(poseRect.left, poseRect.right, 6137 selectionRect.left, selectionRect.right)) 6138 if (poseRect.bottom > selectionRect.bottom) 6139 if (poseRect.bottom < bestRect.bottom 6140 || !bestRect.IsValid()) { 6141 bestRect = poseRect; 6142 poseToSelect = pose; 6143 } 6144 break; 6145 } 6146 } 6147 6148 if (poseToSelect) 6149 return poseToSelect; 6150 6151 return selectedPose; 6152 } 6153 6154 6155 void 6156 BPoseView::ShowContextMenu(BPoint where) 6157 { 6158 BContainerWindow *window = ContainerWindow(); 6159 if (!window) 6160 return; 6161 6162 // handle pose selection 6163 int32 index; 6164 BPose *pose = FindPose(where, &index); 6165 if (pose) { 6166 if (!pose->IsSelected()) { 6167 ClearSelection(); 6168 pose->Select(true); 6169 fSelectionList->AddItem(pose); 6170 DrawPose(pose, index, false); 6171 } 6172 } else 6173 ClearSelection(); 6174 6175 window->Activate(); 6176 window->UpdateIfNeeded(); 6177 window->ShowContextMenu(where, pose ? pose->TargetModel()->EntryRef() : 0, this); 6178 6179 if (fSelectionChangedHook) 6180 window->SelectionChanged(); 6181 } 6182 6183 6184 void 6185 BPoseView::MouseDown(BPoint where) 6186 { 6187 // ToDo: 6188 // add asynch mouse tracking 6189 // 6190 // handle disposing of drag data lazily 6191 DragStop(); 6192 BContainerWindow *window = ContainerWindow(); 6193 if (!window) 6194 return; 6195 6196 if (IsDesktopWindow()) { 6197 BScreen screen(Window()); 6198 rgb_color color = screen.DesktopColor(); 6199 SetLowColor(color); 6200 SetViewColor(color); 6201 } 6202 6203 MakeFocus(); 6204 6205 // "right" mouse button handling for context-sensitive menus 6206 uint32 buttons = (uint32)window->CurrentMessage()->FindInt32("buttons"); 6207 uint32 modifs = modifiers(); 6208 6209 bool showContext = true; 6210 if ((buttons & B_SECONDARY_MOUSE_BUTTON) == 0) 6211 showContext = (modifs & B_CONTROL_KEY) != 0; 6212 6213 // if a pose was hit, delay context menu for a bit to see if user dragged 6214 if (showContext) { 6215 int32 index; 6216 BPose *pose = FindPose(where, &index); 6217 if (!pose) { 6218 ShowContextMenu(where); 6219 return; 6220 } 6221 if (!pose->IsSelected()) { 6222 ClearSelection(); 6223 pose->Select(true); 6224 fSelectionList->AddItem(pose); 6225 DrawPose(pose, index, false); 6226 } 6227 6228 bigtime_t clickTime = system_time(); 6229 BPoint loc; 6230 GetMouse(&loc, &buttons); 6231 for (;;) { 6232 if (fabs(loc.x - where.x) > 4 || fabs(loc.y - where.y) > 4) 6233 // moved the mouse, cancel showing the context menu 6234 break; 6235 6236 if (!buttons || (system_time() - clickTime) > 200000) { 6237 // let go of button or pressing for a while, show menu now 6238 ShowContextMenu(where); 6239 return; 6240 } 6241 6242 snooze(10000); 6243 GetMouse(&loc, &buttons); 6244 } 6245 } 6246 6247 bool extendSelection = (modifs & B_SHIFT_KEY) && fMultipleSelection; 6248 6249 CommitActivePose(); 6250 6251 // see if mouse down occurred within a pose 6252 int32 index; 6253 BPose *pose = FindPose(where, &index); 6254 if (pose) { 6255 AddRemoveSelectionRange(where, extendSelection, pose); 6256 6257 switch (WaitForMouseUpOrDrag(where)) { 6258 case kWasDragged: 6259 DragSelectedPoses(pose, where); 6260 break; 6261 6262 case kNotDragged: 6263 if (!extendSelection && WasDoubleClick(pose, where)) { 6264 // special handling for Path field double-clicks 6265 if (!WasClickInPath(pose, index, where)) 6266 OpenSelection(pose, &index); 6267 6268 } else if (fAllowPoseEditing) 6269 // mouse is up but no drag or double-click occurred 6270 pose->MouseUp(BPoint(0, index * fListElemHeight), this, where, index); 6271 6272 break; 6273 6274 default: 6275 // this is the CONTEXT_MENU case 6276 break; 6277 } 6278 } else { 6279 // click was not in any pose 6280 fLastClickedPose = NULL; 6281 6282 window->Activate(); 6283 window->UpdateIfNeeded(); 6284 DragSelectionRect(where, extendSelection); 6285 } 6286 6287 if (fSelectionChangedHook) 6288 window->SelectionChanged(); 6289 } 6290 6291 6292 bool 6293 BPoseView::WasClickInPath(const BPose *pose, int32 index, BPoint mouseLoc) const 6294 { 6295 if (!pose || (ViewMode() != kListMode)) 6296 return false; 6297 6298 BPoint loc(0, index * fListElemHeight); 6299 BTextWidget *widget; 6300 if (!pose->PointInPose(loc, this, mouseLoc, &widget) || !widget) 6301 return false; 6302 6303 // note: the following code is wrong, because this sort of hashing 6304 // may overlap and we get aliasing 6305 if (widget->AttrHash() != AttrHashString(kAttrPath, B_STRING_TYPE)) 6306 return false; 6307 6308 BEntry entry(widget->Text()); 6309 if (entry.InitCheck() != B_OK) 6310 return false; 6311 6312 entry_ref ref; 6313 if (entry.GetRef(&ref) == B_OK) { 6314 BMessage message(B_REFS_RECEIVED); 6315 message.AddRef("refs", &ref); 6316 be_app->PostMessage(&message); 6317 return true; 6318 } 6319 6320 return false; 6321 } 6322 6323 6324 bool 6325 BPoseView::WasDoubleClick(const BPose *pose, BPoint point) 6326 { 6327 // check time and proximity 6328 BPoint delta = point - fLastClickPt; 6329 6330 bigtime_t sysTime; 6331 Window()->CurrentMessage()->FindInt64("when", &sysTime); 6332 6333 bigtime_t timeDelta = sysTime - fLastClickTime; 6334 6335 bigtime_t doubleClickSpeed; 6336 get_click_speed(&doubleClickSpeed); 6337 6338 if (timeDelta < doubleClickSpeed 6339 && fabs(delta.x) < kDoubleClickTresh 6340 && fabs(delta.y) < kDoubleClickTresh 6341 && pose == fLastClickedPose) { 6342 fLastClickPt.Set(LONG_MAX, LONG_MAX); 6343 fLastClickedPose = NULL; 6344 fLastClickTime = 0; 6345 return true; 6346 } 6347 6348 fLastClickPt = point; 6349 fLastClickedPose = pose; 6350 fLastClickTime = sysTime; 6351 return false; 6352 } 6353 6354 6355 static void 6356 AddPoseRefToMessage(BPose *, Model *model, BMessage *message) 6357 { 6358 // Make sure that every file added to the message has its 6359 // MIME type set. 6360 BNode node(model->EntryRef()); 6361 if (node.InitCheck() == B_OK) { 6362 BNodeInfo info(&node); 6363 char type[B_MIME_TYPE_LENGTH]; 6364 type[0] = '\0'; 6365 if (info.GetType(type) != B_OK) { 6366 BPath path(model->EntryRef()); 6367 if (path.InitCheck() == B_OK) 6368 update_mime_info(path.Path(), false, false, false); 6369 } 6370 } 6371 message->AddRef("refs", model->EntryRef()); 6372 } 6373 6374 6375 void 6376 BPoseView::DragSelectedPoses(const BPose *pose, BPoint clickPoint) 6377 { 6378 if (!fDragEnabled) 6379 return; 6380 6381 ASSERT(pose); 6382 6383 // make sure pose is selected, it could have been deselected as part of 6384 // a click during selection extention 6385 if (!pose->IsSelected()) 6386 return; 6387 6388 // setup tracking rect by unioning all selected pose rects 6389 BMessage message(B_SIMPLE_DATA); 6390 message.AddPointer("src_window", Window()); 6391 message.AddPoint("click_pt", clickPoint); 6392 6393 // add Tracker token so that refs received recipients can script us 6394 message.AddMessenger("TrackerViewToken", BMessenger(this)); 6395 6396 EachPoseAndModel(fSelectionList, &AddPoseRefToMessage, &message); 6397 6398 // do any special drag&drop handling 6399 if (fSelectionList->CountItems() == 1) { 6400 // for now just recognize text clipping files 6401 6402 BFile file(fSelectionList->ItemAt(0)->TargetModel()->EntryRef(), O_RDONLY); 6403 if (file.InitCheck() == B_OK) { 6404 BNodeInfo info(&file); 6405 char type[B_MIME_TYPE_LENGTH]; 6406 type[0] = '\0'; 6407 6408 info.GetType(type); 6409 6410 int32 tmp; 6411 if (strcasecmp(type, kPlainTextMimeType) == 0 6412 // got a text file 6413 && file.ReadAttr(kAttrClippingFile, B_RAW_TYPE, 0, 6414 &tmp, sizeof(int32)) == sizeof(int32)) { 6415 // and a clipping file 6416 6417 file.Seek(0, SEEK_SET); 6418 off_t size = 0; 6419 file.GetSize(&size); 6420 if (size) { 6421 char *buffer = new char[size]; 6422 if (file.Read(buffer, (size_t)size) == size) { 6423 message.AddData(kPlainTextMimeType, B_MIME_TYPE, buffer, (ssize_t)size); 6424 // add text into drag message 6425 6426 attr_info attrInfo; 6427 if (file.GetAttrInfo("styles", &attrInfo) == B_OK 6428 && attrInfo.size > 0) { 6429 char *data = new char [attrInfo.size]; 6430 file.ReadAttr("styles", B_RAW_TYPE, 0, data, (size_t)attrInfo.size); 6431 int32 textRunSize; 6432 text_run_array *textRuns = BTextView::UnflattenRunArray(data, 6433 &textRunSize); 6434 delete [] data; 6435 message.AddData("application/x-vnd.Be-text_run_array", 6436 B_MIME_TYPE, textRuns, textRunSize); 6437 free(textRuns); 6438 } 6439 } 6440 delete [] buffer; 6441 } 6442 } else if (strcasecmp(type, kBitmapMimeType) == 0 6443 // got a text file 6444 && file.ReadAttr(kAttrClippingFile, B_RAW_TYPE, 0, 6445 &tmp, sizeof(int32)) == sizeof(int32)) { 6446 file.Seek(0, SEEK_SET); 6447 off_t size = 0; 6448 file.GetSize(&size); 6449 if (size) { 6450 char *buffer = new char[size]; 6451 if (file.Read(buffer, (size_t)size) == size) { 6452 BMessage embeddedBitmap; 6453 if (embeddedBitmap.Unflatten(buffer) == B_OK) 6454 message.AddMessage(kBitmapMimeType, &embeddedBitmap); 6455 // add bitmap into drag message 6456 } 6457 delete [] buffer; 6458 } 6459 } 6460 } 6461 } 6462 6463 // make sure button is still down 6464 uint32 button; 6465 BPoint tempLoc; 6466 GetMouse(&tempLoc, &button); 6467 if (button) { 6468 int32 index = fPoseList->IndexOf(pose); 6469 message.AddInt32("buttons", (int32)button); 6470 BRect dragRect(GetDragRect(index)); 6471 BBitmap *dragBitmap = NULL; 6472 BPoint offset; 6473 6474 // The bitmap is now always created (if DRAG_FRAME is not defined) 6475 6476 #ifdef DRAG_FRAME 6477 if (dragRect.Width() < kTransparentDragThreshold.x 6478 && dragRect.Height() < kTransparentDragThreshold.y) 6479 #endif 6480 dragBitmap = MakeDragBitmap(dragRect, clickPoint, index, offset); 6481 6482 if (dragBitmap) { 6483 DragMessage(&message, dragBitmap, B_OP_ALPHA, offset); 6484 // this DragMessage supports alpha blending 6485 } else 6486 DragMessage(&message, dragRect); 6487 6488 // turn on auto scrolling 6489 fAutoScrollState = kWaitForTransition; 6490 Window()->SetPulseRate(100000); 6491 } 6492 } 6493 6494 6495 BBitmap * 6496 BPoseView::MakeDragBitmap(BRect dragRect, BPoint clickedPoint, int32 clickedPoseIndex, BPoint &offset) 6497 { 6498 BRect inner(clickedPoint.x - kTransparentDragThreshold.x / 2, 6499 clickedPoint.y - kTransparentDragThreshold.x / 2, 6500 clickedPoint.x + kTransparentDragThreshold.x / 2, 6501 clickedPoint.y + kTransparentDragThreshold.x / 2); 6502 6503 // (BRect & BRect) doesn't work correctly if the rectangles don't intersect 6504 // this catches a bug that is produced somewhere before this function is called 6505 if (inner.right < dragRect.left || inner.bottom < dragRect.top 6506 || inner.left > dragRect.right || inner.top > dragRect.bottom) 6507 return NULL; 6508 6509 inner = inner & dragRect; 6510 6511 // If the selection is bigger than the specified limit, the 6512 // contents will fade out when they come near the borders 6513 bool fadeTop = false, fadeBottom = false, fadeLeft = false, fadeRight = false, fade = false; 6514 if (inner.left > dragRect.left) { 6515 inner.left = max(inner.left - 32, dragRect.left); 6516 fade = fadeLeft = true; 6517 } 6518 if (inner.right < dragRect.right) { 6519 inner.right = min(inner.right + 32, dragRect.right); 6520 fade = fadeRight = true; 6521 } 6522 if (inner.top > dragRect.top) { 6523 inner.top = max(inner.top - 32, dragRect.top); 6524 fade = fadeTop = true; 6525 } 6526 if (inner.bottom < dragRect.bottom) { 6527 inner.bottom = min(inner.bottom + 32, dragRect.bottom); 6528 fade = fadeBottom = true; 6529 } 6530 6531 // set the offset for the dragged bitmap (for the BView::DragMessage() call) 6532 offset = clickedPoint - inner.LeftTop(); 6533 6534 BRect rect(inner); 6535 rect.OffsetTo(B_ORIGIN); 6536 6537 BBitmap *bitmap = new BBitmap(rect, B_RGBA32, true); 6538 bitmap->Lock(); 6539 BView *view = new BView(bitmap->Bounds(), "", B_FOLLOW_NONE, 0); 6540 bitmap->AddChild(view); 6541 6542 view->SetOrigin(0, 0); 6543 6544 BRect clipRect(view->Bounds()); 6545 BRegion newClip; 6546 newClip.Set(clipRect); 6547 view->ConstrainClippingRegion(&newClip); 6548 6549 // Transparent draw magic 6550 view->SetHighColor(0, 0, 0, uint8(fade ? 10 : 0)); 6551 view->FillRect(view->Bounds()); 6552 view->Sync(); 6553 6554 if (fade) { 6555 // If we fade out any border of the selection, the background 6556 // will be slightly darker, and we will also fade out the 6557 // edges so that everything looks smooth 6558 uint32 *bits = (uint32 *)bitmap->Bits(); 6559 int32 width = bitmap->BytesPerRow() / 4; 6560 6561 FadeRGBA32Horizontal(bits, width, int32(rect.bottom), 6562 int32(rect.right), int32(rect.right) - 16); 6563 FadeRGBA32Horizontal(bits, width, int32(rect.bottom), 0, 16); 6564 6565 FadeRGBA32Vertical(bits, width, int32(rect.bottom), 6566 int32(rect.bottom), int32(rect.bottom) - 16); 6567 FadeRGBA32Vertical(bits, width, int32(rect.bottom), 0, 16); 6568 } 6569 6570 view->SetDrawingMode(B_OP_ALPHA); 6571 view->SetHighColor(0, 0, 0, uint8(fade ? 164 : 128)); 6572 // set the level of transparency by value 6573 view->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE); 6574 6575 BRect bounds(Bounds()); 6576 6577 BPose *pose = fPoseList->ItemAt(clickedPoseIndex); 6578 if (ViewMode() == kListMode) { 6579 int32 count = fPoseList->CountItems(); 6580 int32 startIndex = (int32)(bounds.top / fListElemHeight); 6581 BPoint loc(0, startIndex * fListElemHeight); 6582 6583 for (int32 index = startIndex; index < count; index++) { 6584 pose = fPoseList->ItemAt(index); 6585 if (pose->IsSelected()) { 6586 BRect poseRect(pose->CalcRect(loc, this, true)); 6587 if (poseRect.Intersects(inner)) { 6588 BPoint offsetBy(-inner.LeftTop().x, -inner.LeftTop().y); 6589 pose->Draw(poseRect, this, view, true, 0, offsetBy, false); 6590 } 6591 } 6592 loc.y += fListElemHeight; 6593 if (loc.y > bounds.bottom) 6594 break; 6595 } 6596 } else { 6597 // add rects for visible poses only (uses VSList!!) 6598 int32 startIndex = FirstIndexAtOrBelow((int32)(bounds.top - IconPoseHeight())); 6599 int32 count = fVSPoseList->CountItems(); 6600 6601 for (int32 index = startIndex; index < count; index++) { 6602 pose = fVSPoseList->ItemAt(index); 6603 if (pose && pose->IsSelected()) { 6604 BRect poseRect(pose->CalcRect(this)); 6605 if (!poseRect.Intersects(inner)) 6606 continue; 6607 6608 BPoint offsetBy(-inner.LeftTop().x, -inner.LeftTop().y); 6609 pose->Draw(poseRect, this, view, true, 0, offsetBy, false); 6610 } 6611 } 6612 } 6613 6614 view->Sync(); 6615 6616 // Fade out the contents if necessary 6617 if (fade) { 6618 uint32 *bits = (uint32 *)bitmap->Bits(); 6619 int32 width = bitmap->BytesPerRow() / 4; 6620 6621 if (fadeLeft) 6622 FadeRGBA32Horizontal(bits, width, int32(rect.bottom), 0, 64); 6623 if (fadeRight) 6624 FadeRGBA32Horizontal(bits, width, int32(rect.bottom), 6625 int32(rect.right), int32(rect.right) - 64); 6626 6627 if (fadeTop) 6628 FadeRGBA32Vertical(bits, width, int32(rect.bottom), 0, 64); 6629 if (fadeBottom) 6630 FadeRGBA32Vertical(bits, width, int32(rect.bottom), 6631 int32(rect.bottom), int32(rect.bottom) - 64); 6632 } 6633 6634 bitmap->Unlock(); 6635 return bitmap; 6636 } 6637 6638 6639 BRect 6640 BPoseView::GetDragRect(int32 clickedPoseIndex) 6641 { 6642 BRect result; 6643 BRect bounds(Bounds()); 6644 6645 BPose *pose = fPoseList->ItemAt(clickedPoseIndex); 6646 if (ViewMode() == kListMode) { 6647 // get starting rect of clicked pose 6648 result = CalcPoseRect(pose, clickedPoseIndex, true); 6649 6650 // add rects for visible poses only 6651 int32 count = fPoseList->CountItems(); 6652 int32 startIndex = (int32)(bounds.top / fListElemHeight); 6653 BPoint loc(0, startIndex * fListElemHeight); 6654 6655 for (int32 index = startIndex; index < count; index++) { 6656 pose = fPoseList->ItemAt(index); 6657 if (pose->IsSelected()) 6658 result = result | pose->CalcRect(loc, this, true); 6659 6660 loc.y += fListElemHeight; 6661 if (loc.y > bounds.bottom) 6662 break; 6663 } 6664 } else { 6665 // get starting rect of clicked pose 6666 result = pose->CalcRect(this); 6667 6668 // add rects for visible poses only (uses VSList!!) 6669 int32 count = fVSPoseList->CountItems(); 6670 for (int32 index = FirstIndexAtOrBelow((int32)(bounds.top - IconPoseHeight())); 6671 index < count; index++) { 6672 BPose *pose = fVSPoseList->ItemAt(index); 6673 if (pose) { 6674 if (pose->IsSelected()) 6675 result = result | pose->CalcRect(this); 6676 6677 if (pose->Location().y > bounds.bottom) 6678 break; 6679 } 6680 } 6681 } 6682 6683 return result; 6684 } 6685 6686 6687 static void 6688 AddIfPoseSelected(BPose *pose, PoseList *list) 6689 { 6690 if (pose->IsSelected()) 6691 list->AddItem(pose); 6692 } 6693 6694 6695 void 6696 BPoseView::DragSelectionRect(BPoint startPoint, bool shouldExtend) 6697 { 6698 // only clear selection if we are not extending it 6699 if (!shouldExtend) 6700 ClearSelection(); 6701 6702 if (WaitForMouseUpOrDrag(startPoint) != kWasDragged) { 6703 if (!shouldExtend) 6704 ClearSelection(); 6705 return; 6706 } 6707 6708 if (!fSelectionRectEnabled || !fMultipleSelection) { 6709 ClearSelection(); 6710 return; 6711 } 6712 6713 // clearing the selection could take a while so poll the mouse again 6714 BPoint newMousePoint; 6715 uint32 button; 6716 GetMouse(&newMousePoint, &button); 6717 6718 // draw initial empty selection rectangle 6719 BRect lastselectionRect; 6720 fSelectionRect = lastselectionRect = BRect(startPoint, startPoint - BPoint(1, 1)); 6721 6722 if (!fTransparentSelection) { 6723 SetDrawingMode(B_OP_INVERT); 6724 StrokeRect(fSelectionRect, B_MIXED_COLORS); 6725 SetDrawingMode(B_OP_OVER); 6726 } 6727 6728 BList *selectionList = new BList; 6729 6730 BPoint oldMousePoint(startPoint); 6731 while (button) { 6732 GetMouse(&newMousePoint, &button); 6733 if (newMousePoint != oldMousePoint) { 6734 oldMousePoint = newMousePoint; 6735 BRect oldRect = fSelectionRect; 6736 fSelectionRect.top = std::min(newMousePoint.y, startPoint.y); 6737 fSelectionRect.left = std::min(newMousePoint.x, startPoint.x); 6738 fSelectionRect.bottom = std::max(newMousePoint.y, startPoint.y); 6739 fSelectionRect.right = std::max(newMousePoint.x, startPoint.x); 6740 6741 // erase old rect 6742 if (!fTransparentSelection) { 6743 SetDrawingMode(B_OP_INVERT); 6744 StrokeRect(oldRect, B_MIXED_COLORS); 6745 SetDrawingMode(B_OP_OVER); 6746 } 6747 6748 fIsDrawingSelectionRect = true; 6749 6750 CheckAutoScroll(newMousePoint, true, true); 6751 6752 // use current selection rectangle to scan poses 6753 if (ViewMode() == kListMode) 6754 SelectPosesListMode(fSelectionRect, &selectionList); 6755 else 6756 SelectPosesIconMode(fSelectionRect, &selectionList); 6757 6758 Window()->UpdateIfNeeded(); 6759 6760 // draw new selected rect 6761 if (!fTransparentSelection) { 6762 SetDrawingMode(B_OP_INVERT); 6763 StrokeRect(fSelectionRect, B_MIXED_COLORS); 6764 SetDrawingMode(B_OP_OVER); 6765 } else { 6766 BRegion updateRegion1; 6767 BRegion updateRegion2; 6768 6769 bool samewidth = fSelectionRect.Width() == lastselectionRect.Width(); 6770 bool sameheight = fSelectionRect.Height() == lastselectionRect.Height(); 6771 6772 updateRegion1.Include(fSelectionRect); 6773 updateRegion1.Exclude(lastselectionRect.InsetByCopy(samewidth ? 0 : 1, sameheight ? 0 : 1)); 6774 updateRegion2.Include(lastselectionRect); 6775 updateRegion2.Exclude(fSelectionRect.InsetByCopy(samewidth ? 0 : 1, sameheight ? 0 : 1)); 6776 updateRegion1.Include(&updateRegion2); 6777 BRect unionRect = fSelectionRect & lastselectionRect; 6778 updateRegion1.Exclude(unionRect & BRect(-2000, startPoint.y, 2000, startPoint.y)); 6779 updateRegion1.Exclude(unionRect & BRect(startPoint.x, -2000, startPoint.x, 2000)); 6780 6781 lastselectionRect = fSelectionRect; 6782 6783 Invalidate(&updateRegion1); 6784 Window()->UpdateIfNeeded(); 6785 } 6786 6787 Flush(); 6788 } 6789 6790 snooze(20000); 6791 } 6792 6793 delete selectionList; 6794 6795 fIsDrawingSelectionRect = false; 6796 6797 // do final erase of selection rect 6798 if (!fTransparentSelection) { 6799 SetDrawingMode(B_OP_INVERT); 6800 StrokeRect(fSelectionRect, B_MIXED_COLORS); 6801 SetDrawingMode(B_OP_COPY); 6802 } else { 6803 Invalidate(fSelectionRect); 6804 fSelectionRect.Set(0, 0, -1, -1); 6805 Window()->UpdateIfNeeded(); 6806 } 6807 6808 // we now need to update the pose view's selection list by clearing it 6809 // and then polling each pose for selection state and rebuilding list 6810 fSelectionList->MakeEmpty(); 6811 fMimeTypesInSelectionCache.MakeEmpty(); 6812 6813 EachListItem(fPoseList, AddIfPoseSelected, fSelectionList); 6814 6815 // and now make sure that the pivot point is in sync 6816 if (fSelectionPivotPose && !fSelectionList->HasItem(fSelectionPivotPose)) 6817 fSelectionPivotPose = NULL; 6818 if (fRealPivotPose && !fSelectionList->HasItem(fRealPivotPose)) 6819 fRealPivotPose = NULL; 6820 } 6821 6822 // ToDo: 6823 // SelectPosesListMode and SelectPosesIconMode are terrible and share most code 6824 6825 void 6826 BPoseView::SelectPosesListMode(BRect selectionRect, BList **oldList) 6827 { 6828 ASSERT(ViewMode() == kListMode); 6829 6830 // collect all the poses which are enclosed inside the selection rect 6831 BList *newList = new BList; 6832 BRect bounds(Bounds()); 6833 SetDrawingMode(B_OP_COPY); 6834 6835 int32 startIndex = (int32)(selectionRect.top / fListElemHeight); 6836 if (startIndex < 0) 6837 startIndex = 0; 6838 6839 BPoint loc(0, startIndex * fListElemHeight); 6840 6841 int32 count = fPoseList->CountItems(); 6842 for (int32 index = startIndex; index < count; index++) { 6843 BPose *pose = fPoseList->ItemAt(index); 6844 BRect poseRect(pose->CalcRect(loc, this)); 6845 6846 if (selectionRect.Intersects(poseRect)) { 6847 bool selected = pose->IsSelected(); 6848 pose->Select(!fSelectionList->HasItem(pose)); 6849 newList->AddItem((void *)index); // this sucks, need to clean up 6850 // using a vector class instead of BList 6851 6852 if ((selected != pose->IsSelected()) && poseRect.Intersects(bounds)) 6853 pose->Draw(poseRect, this, false); 6854 6855 // First Pose selected gets to be the pivot. 6856 if ((fSelectionPivotPose == NULL) && (selected == false)) 6857 fSelectionPivotPose = pose; 6858 } 6859 6860 loc.y += fListElemHeight; 6861 if (loc.y > selectionRect.bottom) 6862 break; 6863 } 6864 6865 // take the old set of enclosed poses and invert selection state 6866 // on those which are no longer enclosed 6867 count = (*oldList)->CountItems(); 6868 for (int32 index = 0; index < count; index++) { 6869 int32 oldIndex = (int32)(*oldList)->ItemAt(index); 6870 6871 if (!newList->HasItem((void *)oldIndex)) { 6872 BPose *pose = fPoseList->ItemAt(oldIndex); 6873 pose->Select(!pose->IsSelected()); 6874 loc.Set(0, oldIndex * fListElemHeight); 6875 BRect poseRect(pose->CalcRect(loc, this)); 6876 6877 if (poseRect.Intersects(bounds)) 6878 pose->Draw(poseRect, this, false); 6879 } 6880 } 6881 6882 delete *oldList; 6883 *oldList = newList; 6884 } 6885 6886 6887 void 6888 BPoseView::SelectPosesIconMode(BRect selectionRect, BList **oldList) 6889 { 6890 ASSERT(ViewMode() != kListMode); 6891 6892 // collect all the poses which are enclosed inside the selection rect 6893 BList *newList = new BList; 6894 BRect bounds(Bounds()); 6895 SetDrawingMode(B_OP_COPY); 6896 6897 int32 startIndex = FirstIndexAtOrBelow((int32)(selectionRect.top - IconPoseHeight()), true); 6898 if (startIndex < 0) 6899 startIndex = 0; 6900 6901 int32 count = fPoseList->CountItems(); 6902 for (int32 index = startIndex; index < count; index++) { 6903 BPose *pose = fVSPoseList->ItemAt(index); 6904 if (pose) { 6905 BRect poseRect(pose->CalcRect(this)); 6906 6907 if (selectionRect.Intersects(poseRect)) { 6908 bool selected = pose->IsSelected(); 6909 pose->Select(!fSelectionList->HasItem(pose)); 6910 newList->AddItem((void *)index); 6911 6912 if ((selected != pose->IsSelected()) && poseRect.Intersects(bounds)) 6913 if (pose->IsSelected() || EraseWidgetTextBackground()) 6914 pose->Draw(poseRect, this, false); 6915 else 6916 Invalidate(poseRect); 6917 6918 // First Pose selected gets to be the pivot. 6919 if ((fSelectionPivotPose == NULL) && (selected == false)) 6920 fSelectionPivotPose = pose; 6921 } 6922 6923 if (pose->Location().y > selectionRect.bottom) 6924 break; 6925 } 6926 } 6927 6928 // take the old set of enclosed poses and invert selection state 6929 // on those which are no longer enclosed 6930 count = (*oldList)->CountItems(); 6931 for (int32 index = 0; index < count; index++) { 6932 int32 oldIndex = (int32)(*oldList)->ItemAt(index); 6933 6934 if (!newList->HasItem((void *)oldIndex)) { 6935 BPose *pose = fVSPoseList->ItemAt(oldIndex); 6936 pose->Select(!pose->IsSelected()); 6937 BRect poseRect(pose->CalcRect(this)); 6938 6939 if (poseRect.Intersects(bounds)) { 6940 if (pose->IsSelected() || EraseWidgetTextBackground()) 6941 pose->Draw(poseRect, this, false); 6942 else 6943 Invalidate(poseRect); 6944 } 6945 } 6946 } 6947 6948 delete *oldList; 6949 *oldList = newList; 6950 } 6951 6952 6953 void 6954 BPoseView::AddRemoveSelectionRange(BPoint where, bool extendSelection, BPose *pose) 6955 { 6956 ASSERT(pose); 6957 6958 if ((pose == fSelectionPivotPose) && !extendSelection) 6959 return; 6960 6961 if ((modifiers() & B_COMMAND_KEY) && fSelectionPivotPose) { 6962 // Multi Pose extend/shrink current selection 6963 bool select = !pose->IsSelected() || !extendSelection; 6964 // This weird bit of logic causes the selection to always 6965 // center around the pivot point, unless you choose to hold 6966 // down SHIFT, which will unselect between the pivot and 6967 // the most recently selected Pose. 6968 6969 if (!extendSelection) { 6970 // Remember fSelectionPivotPose because ClearSelection() NULLs it 6971 // and we need it to be preserved. 6972 const BPose *savedPivotPose = fSelectionPivotPose; 6973 ClearSelection(); 6974 fSelectionPivotPose = savedPivotPose; 6975 } 6976 6977 if (ViewMode() == kListMode) { 6978 int32 currSelIndex = fPoseList->IndexOf(pose); 6979 int32 lastSelIndex = fPoseList->IndexOf(fSelectionPivotPose); 6980 6981 int32 startRange; 6982 int32 endRange; 6983 6984 if (lastSelIndex < currSelIndex) { 6985 startRange = lastSelIndex; 6986 endRange = currSelIndex; 6987 } else { 6988 startRange = currSelIndex; 6989 endRange = lastSelIndex; 6990 } 6991 6992 for (int32 i = startRange; i <= endRange; i++) 6993 AddRemovePoseFromSelection(fPoseList->ItemAt(i), i, select); 6994 6995 } else { 6996 BRect selection(where, fSelectionPivotPose->Location()); 6997 6998 // Things will get odd if we don't 'fix' the selection rect. 6999 if (selection.left > selection.right) { 7000 float temp = selection.right; 7001 selection.right = selection.left; 7002 selection.left = temp; 7003 } 7004 7005 if (selection.top > selection.bottom) { 7006 float temp = selection.top; 7007 selection.top = selection.bottom; 7008 selection.bottom = temp; 7009 } 7010 7011 // If the selection rect is not at least 1 pixel high/wide, things 7012 // are also not going to work out. 7013 if (selection.IntegerWidth() < 1) 7014 selection.right = selection.left + 1.0f; 7015 7016 if (selection.IntegerHeight() < 1) 7017 selection.bottom = selection.top + 1.0f; 7018 7019 ASSERT(selection.IsValid()); 7020 7021 int32 count = fPoseList->CountItems(); 7022 for (int32 index = count - 1; index >= 0; index--) { 7023 BPose *currPose = fPoseList->ItemAt(index); 7024 if (selection.Intersects(currPose->CalcRect(this))) 7025 AddRemovePoseFromSelection(currPose, index, select); 7026 } 7027 } 7028 } else { 7029 int32 index = fPoseList->IndexOf(pose); 7030 if (!extendSelection) { 7031 if (!pose->IsSelected()) { 7032 // create new selection 7033 ClearSelection(); 7034 AddRemovePoseFromSelection(pose, index, true); 7035 fSelectionPivotPose = pose; 7036 } 7037 } else { 7038 fMimeTypesInSelectionCache.MakeEmpty(); 7039 AddRemovePoseFromSelection(pose, index, !pose->IsSelected()); 7040 } 7041 } 7042 7043 // If the list is empty, there cannot be a pivot pose, 7044 // however if the list is not empty there must be a pivot 7045 // pose. 7046 if (fSelectionList->IsEmpty()) { 7047 fSelectionPivotPose = NULL; 7048 fRealPivotPose = NULL; 7049 } else if (fSelectionPivotPose == NULL) { 7050 fSelectionPivotPose = pose; 7051 fRealPivotPose = pose; 7052 } 7053 } 7054 7055 7056 int32 7057 BPoseView::WaitForMouseUpOrDrag(BPoint start) 7058 { 7059 bigtime_t start_time = system_time(); 7060 bigtime_t doubleClickSpeed; 7061 get_click_speed(&doubleClickSpeed); 7062 7063 // use double the doubleClickSpeed as a treshold 7064 doubleClickSpeed *= 2; 7065 7066 // loop until mouse has been dragged at least 2 pixels 7067 uint32 button; 7068 BPoint loc; 7069 GetMouse(&loc, &button, false); 7070 7071 while (button) { 7072 GetMouse(&loc, &button, false); 7073 if (fabs(loc.x - start.x) > 2 || fabs(loc.y - start.y) > 2) 7074 return kWasDragged; 7075 7076 if ((system_time() - start_time) > doubleClickSpeed) { 7077 ShowContextMenu(start); 7078 return kContextMenuShown; 7079 } 7080 7081 snooze(15000); 7082 } 7083 7084 // user let up on mouse button without dragging 7085 Window()->Activate(); 7086 Window()->UpdateIfNeeded(); 7087 return kNotDragged; 7088 } 7089 7090 7091 void 7092 BPoseView::DeleteSymLinkPoseTarget(const node_ref *itemNode, BPose *pose, 7093 int32 index) 7094 { 7095 ASSERT(pose->TargetModel()->IsSymLink()); 7096 watch_node(itemNode, B_STOP_WATCHING, this); 7097 BPoint loc(0, index * fListElemHeight); 7098 pose->TargetModel()->SetLinkTo(0); 7099 pose->UpdateBrokenSymLink(loc, this); 7100 } 7101 7102 7103 bool 7104 BPoseView::DeletePose(const node_ref *itemNode, BPose *pose, int32 index) 7105 { 7106 watch_node(itemNode, B_STOP_WATCHING, this); 7107 7108 if (!pose) 7109 pose = fPoseList->FindPose(itemNode, &index); 7110 7111 if (pose) { 7112 if (TargetModel()->IsSymLink()) { 7113 Model *target = pose->TargetModel()->LinkTo(); 7114 if (target) 7115 watch_node(target->NodeRef(), B_STOP_WATCHING, this); 7116 } 7117 7118 ASSERT(TargetModel()); 7119 7120 if (pose == fDropTarget) 7121 fDropTarget = NULL; 7122 7123 if (pose == ActivePose()) 7124 CommitActivePose(); 7125 7126 Window()->UpdateIfNeeded(); 7127 7128 // remove it from list no matter what since it might be in list 7129 // but not "selected" since selection is hidden 7130 fSelectionList->RemoveItem(pose); 7131 if (fSelectionPivotPose == pose) 7132 fSelectionPivotPose = NULL; 7133 if (fRealPivotPose == pose) 7134 fRealPivotPose = NULL; 7135 7136 if (pose->IsSelected() && fSelectionChangedHook) 7137 ContainerWindow()->SelectionChanged(); 7138 7139 fPoseList->RemoveItemAt(index); 7140 fMimeTypeListIsDirty = true; 7141 7142 if (pose->HasLocation()) 7143 RemoveFromVSList(pose); 7144 7145 BRect invalidRect; 7146 if (ViewMode() == kListMode) 7147 invalidRect = CalcPoseRect(pose, index); 7148 else 7149 invalidRect = pose->CalcRect(this); 7150 7151 if (ViewMode() == kListMode) 7152 CloseGapInList(&invalidRect); 7153 else 7154 RemoveFromExtent(invalidRect); 7155 7156 Invalidate(invalidRect); 7157 UpdateCount(); 7158 UpdateScrollRange(); 7159 ResetPosePlacementHint(); 7160 7161 if (ViewMode() == kListMode) { 7162 BRect bounds(Bounds()); 7163 int32 index = (int32)(bounds.bottom / fListElemHeight); 7164 BPose *pose = fPoseList->ItemAt(index); 7165 if (!pose && bounds.top > 0) // scroll up a little 7166 ScrollTo(bounds.left, max_c(bounds.top - fListElemHeight, 0)); 7167 } 7168 7169 delete pose; 7170 7171 } else { 7172 // we might be getting a delete for an item in the zombie list 7173 Model *zombie = FindZombie(itemNode, &index); 7174 if (zombie) { 7175 PRINT(("deleting zombie model %s\n", zombie->Name())); 7176 fZombieList->RemoveItemAt(index); 7177 delete zombie; 7178 } else 7179 return false; 7180 } 7181 return true; 7182 } 7183 7184 7185 Model * 7186 BPoseView::FindZombie(const node_ref *itemNode, int32 *resultingIndex) 7187 { 7188 int32 count = fZombieList->CountItems(); 7189 for (int32 index = 0; index < count; index++) { 7190 Model *zombie = fZombieList->ItemAt(index); 7191 if (*zombie->NodeRef() == *itemNode) { 7192 if (resultingIndex) 7193 *resultingIndex = index; 7194 return zombie; 7195 } 7196 } 7197 7198 return NULL; 7199 } 7200 7201 // return pose at location h,v (search list starting from bottom so 7202 // drawing and hit detection reflect the same pose ordering) 7203 7204 BPose * 7205 BPoseView::FindPose(BPoint point, int32 *poseIndex) const 7206 { 7207 if (ViewMode() == kListMode) { 7208 int32 index = (int32)(point.y / fListElemHeight); 7209 if (poseIndex) 7210 *poseIndex = index; 7211 7212 BPoint loc(0, index * fListElemHeight); 7213 BPose *pose = fPoseList->ItemAt(index); 7214 if (pose && pose->PointInPose(loc, this, point)) 7215 return pose; 7216 } else { 7217 int32 count = fPoseList->CountItems(); 7218 for (int32 index = count - 1; index >= 0; index--) { 7219 BPose *pose = fPoseList->ItemAt(index); 7220 if (pose->PointInPose(this, point)) { 7221 if (poseIndex) 7222 *poseIndex = index; 7223 return pose; 7224 } 7225 } 7226 } 7227 7228 return NULL; 7229 } 7230 7231 7232 void 7233 BPoseView::OpenSelection(BPose *clickedPose, int32 *index) 7234 { 7235 BPose *singleWindowBrowsePose = clickedPose; 7236 TrackerSettings settings; 7237 7238 // Get first selected pose in selection if none was clicked 7239 if (settings.SingleWindowBrowse() 7240 && !singleWindowBrowsePose 7241 && fSelectionList->CountItems() == 1 7242 && !IsFilePanel()) 7243 singleWindowBrowsePose = fSelectionList->ItemAt(0); 7244 7245 // check if we can use the single window mode 7246 if (settings.SingleWindowBrowse() 7247 && !IsDesktopWindow() 7248 && !IsFilePanel() 7249 && !(modifiers() & B_OPTION_KEY) 7250 && TargetModel()->IsDirectory() 7251 && singleWindowBrowsePose 7252 && singleWindowBrowsePose->ResolvedModel() 7253 && singleWindowBrowsePose->ResolvedModel()->IsDirectory()) { 7254 // Switch to new directory 7255 BMessage msg(kSwitchDirectory); 7256 msg.AddRef("refs", singleWindowBrowsePose->ResolvedModel()->EntryRef()); 7257 Window()->PostMessage(&msg); 7258 } else 7259 // Otherwise use standard method 7260 OpenSelectionCommon(clickedPose, index, false); 7261 7262 } 7263 7264 7265 void 7266 BPoseView::OpenSelectionUsing(BPose *clickedPose, int32 *index) 7267 { 7268 OpenSelectionCommon(clickedPose, index, true); 7269 } 7270 7271 7272 void 7273 BPoseView::OpenSelectionCommon(BPose *clickedPose, int32 *poseIndex, 7274 bool openWith) 7275 { 7276 int32 count = fSelectionList->CountItems(); 7277 if (!count) 7278 return; 7279 7280 TTracker *tracker = dynamic_cast<TTracker *>(be_app); 7281 7282 BMessage message(B_REFS_RECEIVED); 7283 7284 for (int32 index = 0; index < count; index++) { 7285 BPose *pose = fSelectionList->ItemAt(index); 7286 7287 message.AddRef("refs", pose->TargetModel()->EntryRef()); 7288 7289 // close parent window if option down and we're not the desktop 7290 // and we're not in single window mode 7291 if (!tracker 7292 || (modifiers() & B_OPTION_KEY) == 0 7293 || IsFilePanel() 7294 || IsDesktopWindow() 7295 || TrackerSettings().SingleWindowBrowse()) 7296 continue; 7297 7298 ASSERT(TargetModel()); 7299 message.AddData("nodeRefsToClose", B_RAW_TYPE, TargetModel()->NodeRef(), 7300 sizeof (node_ref)); 7301 } 7302 7303 if (openWith) 7304 message.AddInt32("launchUsingSelector", 0); 7305 7306 // add a messenger to the launch message that will be used to 7307 // dispatch scripting calls from apps to the PoseView 7308 message.AddMessenger("TrackerViewToken", BMessenger(this)); 7309 7310 if (fSelectionHandler) 7311 fSelectionHandler->PostMessage(&message); 7312 7313 if (clickedPose) { 7314 ASSERT(poseIndex); 7315 if (ViewMode() == kListMode) 7316 DrawOpenAnimation(CalcPoseRect(clickedPose, *poseIndex, true)); 7317 else 7318 DrawOpenAnimation(clickedPose->CalcRect(this)); 7319 } 7320 } 7321 7322 7323 void 7324 BPoseView::DrawOpenAnimation(BRect rect) 7325 { 7326 SetDrawingMode(B_OP_INVERT); 7327 7328 BRect box1(rect); 7329 box1.InsetBy(rect.Width() / 2 - 2, rect.Height() / 2 - 2); 7330 BRect box2(box1); 7331 7332 for (int32 index = 0; index < 7; index++) { 7333 box2 = box1; 7334 box2.InsetBy(-2, -2); 7335 StrokeRect(box1, B_MIXED_COLORS); 7336 Sync(); 7337 StrokeRect(box2, B_MIXED_COLORS); 7338 Sync(); 7339 snooze(10000); 7340 StrokeRect(box1, B_MIXED_COLORS); 7341 StrokeRect(box2, B_MIXED_COLORS); 7342 Sync(); 7343 box1 = box2; 7344 } 7345 7346 SetDrawingMode(B_OP_OVER); 7347 } 7348 7349 7350 void 7351 BPoseView::UnmountSelectedVolumes() 7352 { 7353 BVolume boot; 7354 BVolumeRoster().GetBootVolume(&boot); 7355 7356 int32 select_count = fSelectionList->CountItems(); 7357 for (int32 index = 0; index < select_count; index++) { 7358 Model *model = fSelectionList->ItemAt(index)->TargetModel(); 7359 if (model->IsVolume()) { 7360 BVolume volume(model->NodeRef()->device); 7361 if (volume != boot) { 7362 dynamic_cast<TTracker*>(be_app)->SaveAllPoseLocations(); 7363 7364 BMessage message(kUnmountVolume); 7365 message.AddInt32("device_id", volume.Device()); 7366 be_app->PostMessage(&message); 7367 } 7368 } 7369 } 7370 } 7371 7372 7373 void 7374 BPoseView::ClearPoses() 7375 { 7376 CommitActivePose(); 7377 SavePoseLocations(); 7378 7379 // clear all pose lists 7380 fPoseList->MakeEmpty(); 7381 fMimeTypeListIsDirty = true; 7382 fVSPoseList->MakeEmpty(); 7383 fZombieList->MakeEmpty(); 7384 fSelectionList->MakeEmpty(); 7385 fSelectionPivotPose = NULL; 7386 fRealPivotPose = NULL; 7387 fMimeTypesInSelectionCache.MakeEmpty(); 7388 7389 DisableScrollBars(); 7390 ScrollTo(BPoint(0, 0)); 7391 UpdateScrollRange(); 7392 SetScrollBarsTo(BPoint(0, 0)); 7393 EnableScrollBars(); 7394 ResetPosePlacementHint(); 7395 ClearExtent(); 7396 7397 if (fSelectionChangedHook) 7398 ContainerWindow()->SelectionChanged(); 7399 } 7400 7401 7402 void 7403 BPoseView::SwitchDir(const entry_ref *newDirRef, AttributeStreamNode *node) 7404 { 7405 ASSERT(TargetModel()); 7406 if (*newDirRef == *TargetModel()->EntryRef()) 7407 // no change 7408 return; 7409 7410 Model *model = new Model(newDirRef, true); 7411 if (model->InitCheck() != B_OK || !model->IsDirectory()) { 7412 delete model; 7413 return; 7414 } 7415 7416 CommitActivePose(); 7417 7418 // before clearing and adding new poses, we reset "blessed" async 7419 // thread id to prevent old add_poses thread from adding any more icons 7420 // the new add_poses thread will then set fAddPosesThread to its ID and it 7421 // will be allowed to add icons 7422 fAddPosesThreads.clear(); 7423 7424 delete fModel; 7425 fModel = model; 7426 7427 // check if model is a trash dir, if so 7428 // update ContainerWindow's fIsTrash, etc. 7429 // variables to indicate new state 7430 ContainerWindow()->UpdateIfTrash(model); 7431 7432 StopWatching(); 7433 ClearPoses(); 7434 7435 // Restore state if requested 7436 if (node) { 7437 uint32 oldMode = ViewMode(); 7438 7439 // Get new state 7440 RestoreState(node); 7441 7442 // Make sure the title view reset its items 7443 fTitleView->Reset(); 7444 7445 if (ViewMode() == kListMode && oldMode != kListMode) { 7446 7447 MoveBy(0, kTitleViewHeight + 1); 7448 ResizeBy(0, -(kTitleViewHeight + 1)); 7449 7450 if (ContainerWindow()) 7451 ContainerWindow()->ShowAttributeMenu(); 7452 7453 fTitleView->ResizeTo(Frame().Width(), fTitleView->Frame().Height()); 7454 fTitleView->MoveTo(Frame().left, Frame().top - (kTitleViewHeight + 1)); 7455 if (Parent()) 7456 Parent()->AddChild(fTitleView); 7457 else 7458 Window()->AddChild(fTitleView); 7459 } else if (ViewMode() != kListMode && oldMode == kListMode) { 7460 fTitleView->RemoveSelf(); 7461 7462 if (ContainerWindow()) 7463 ContainerWindow()->HideAttributeMenu(); 7464 7465 MoveBy(0, -(kTitleViewHeight + 1)); 7466 ResizeBy(0, kTitleViewHeight + 1); 7467 } else if (ViewMode() == kListMode && oldMode == kListMode && fTitleView != NULL) 7468 fTitleView->Invalidate(); 7469 7470 BPoint origin; 7471 if (ViewMode() == kListMode) 7472 origin = fViewState->ListOrigin(); 7473 else 7474 origin = fViewState->IconOrigin(); 7475 7476 PinPointToValidRange(origin); 7477 7478 SetIconPoseHeight(); 7479 GetLayoutInfo(ViewMode(), &fGrid, &fOffset); 7480 ResetPosePlacementHint(); 7481 7482 DisableScrollBars(); 7483 ScrollTo(origin); 7484 UpdateScrollRange(); 7485 SetScrollBarsTo(origin); 7486 EnableScrollBars(); 7487 } 7488 7489 StartWatching(); 7490 7491 // be sure this happens after origin is set and window is sized 7492 // properly for proper icon caching! 7493 7494 if (ContainerWindow()->IsTrash()) 7495 AddTrashPoses(); 7496 else AddPoses(TargetModel()); 7497 TargetModel()->CloseNode(); 7498 7499 Invalidate(); 7500 ResetOrigin(); 7501 ResetPosePlacementHint(); 7502 7503 fLastKeyTime = 0; 7504 } 7505 7506 7507 void 7508 BPoseView::Refresh() 7509 { 7510 BEntry entry; 7511 7512 ASSERT(TargetModel()); 7513 if (TargetModel()->OpenNode() != B_OK) 7514 return; 7515 7516 StopWatching(); 7517 ClearPoses(); 7518 StartWatching(); 7519 7520 // be sure this happens after origin is set and window is sized 7521 // properly for proper icon caching! 7522 AddPoses(TargetModel()); 7523 TargetModel()->CloseNode(); 7524 7525 Invalidate(); 7526 ResetOrigin(); 7527 ResetPosePlacementHint(); 7528 } 7529 7530 7531 void 7532 BPoseView::ResetOrigin() 7533 { 7534 DisableScrollBars(); 7535 ScrollTo(B_ORIGIN); 7536 UpdateScrollRange(); 7537 SetScrollBarsTo(B_ORIGIN); 7538 EnableScrollBars(); 7539 } 7540 7541 7542 void 7543 BPoseView::EditQueries() 7544 { 7545 // edit selected queries 7546 SendSelectionAsRefs(kEditQuery, true); 7547 } 7548 7549 7550 void 7551 BPoseView::SendSelectionAsRefs(uint32 what, bool onlyQueries) 7552 { 7553 // fix this by having a proper selection iterator 7554 7555 int32 numItems = fSelectionList->CountItems(); 7556 if (!numItems) 7557 return; 7558 7559 bool haveRef = false; 7560 BMessage message; 7561 message.what = what; 7562 7563 for (int32 index = 0; index < numItems; index++) { 7564 BPose *pose = fSelectionList->ItemAt(index); 7565 if (onlyQueries) { 7566 // to check if pose is a query, follow any symlink first 7567 BEntry resolvedEntry(pose->TargetModel()->EntryRef(), true); 7568 if (resolvedEntry.InitCheck() != B_OK) 7569 continue; 7570 7571 Model model(&resolvedEntry); 7572 if (!model.IsQuery() && !model.IsQueryTemplate()) 7573 continue; 7574 } 7575 haveRef = true; 7576 message.AddRef("refs", pose->TargetModel()->EntryRef()); 7577 } 7578 if (!haveRef) 7579 return; 7580 7581 if (onlyQueries) 7582 // this is used to make query templates come up in a special edit window 7583 message.AddBool("editQueryOnPose", &onlyQueries); 7584 7585 BMessenger(kTrackerSignature).SendMessage(&message); 7586 } 7587 7588 7589 void 7590 BPoseView::OpenInfoWindows() 7591 { 7592 BMessenger tracker(kTrackerSignature); 7593 if (!tracker.IsValid()) { 7594 (new BAlert("", "The Tracker must be running to see Info windows.", 7595 "Cancel", NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 7596 return; 7597 } 7598 SendSelectionAsRefs(kGetInfo); 7599 } 7600 7601 7602 void 7603 BPoseView::SetDefaultPrinter() 7604 { 7605 BMessenger tracker(kTrackerSignature); 7606 if (!tracker.IsValid()) { 7607 (new BAlert("", "The Tracker must be running to see set the default printer.", 7608 "Cancel", NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 7609 return; 7610 } 7611 SendSelectionAsRefs(kMakeActivePrinter); 7612 } 7613 7614 7615 void 7616 BPoseView::OpenParent() 7617 { 7618 if (!TargetModel() || TargetModel()->IsRoot() || IsDesktopWindow()) 7619 return; 7620 7621 BEntry entry(TargetModel()->EntryRef()); 7622 BDirectory parent; 7623 entry_ref ref; 7624 7625 if (entry.GetParent(&parent) != B_OK 7626 || parent.GetEntry(&entry) != B_OK 7627 || entry.GetRef(&ref) != B_OK) 7628 return; 7629 7630 BEntry root("/"); 7631 if (!TrackerSettings().ShowDisksIcon() && entry == root 7632 && (modifiers() & B_CONTROL_KEY) == 0) 7633 return; 7634 7635 Model parentModel(&ref); 7636 7637 BMessage message(B_REFS_RECEIVED); 7638 message.AddRef("refs", &ref); 7639 7640 if (dynamic_cast<TTracker *>(be_app)) { 7641 // add information about the child, so that we can select it 7642 // in the parent view 7643 message.AddData("nodeRefToSelect", B_RAW_TYPE, TargetModel()->NodeRef(), 7644 sizeof (node_ref)); 7645 7646 if ((modifiers() & B_OPTION_KEY) != 0 && !IsFilePanel()) 7647 // if option down, add instructions to close the parent 7648 message.AddData("nodeRefsToClose", B_RAW_TYPE, TargetModel()->NodeRef(), 7649 sizeof (node_ref)); 7650 } 7651 7652 be_app->PostMessage(&message); 7653 } 7654 7655 7656 void 7657 BPoseView::IdentifySelection() 7658 { 7659 bool force = (modifiers() & B_OPTION_KEY) != 0; 7660 int32 count = fSelectionList->CountItems(); 7661 for (int32 index = 0; index < count; index++) { 7662 BPose *pose = fSelectionList->ItemAt(index); 7663 BEntry entry(pose->TargetModel()->EntryRef()); 7664 if (entry.InitCheck() == B_OK) { 7665 BPath path; 7666 if (entry.GetPath(&path) == B_OK) 7667 update_mime_info(path.Path(), true, false, force ? 2 : 1); 7668 } 7669 } 7670 } 7671 7672 7673 void 7674 BPoseView::ClearSelection() 7675 { 7676 CommitActivePose(); 7677 fSelectionPivotPose = NULL; 7678 fRealPivotPose = NULL; 7679 7680 if (fSelectionList->CountItems()) { 7681 7682 // scan all visible poses first 7683 BRect bounds(Bounds()); 7684 7685 if (ViewMode() == kListMode) { 7686 int32 startIndex = (int32)(bounds.top / fListElemHeight); 7687 BPoint loc(0, startIndex * fListElemHeight); 7688 int32 count = fPoseList->CountItems(); 7689 for (int32 index = startIndex; index < count; index++) { 7690 BPose *pose = fPoseList->ItemAt(index); 7691 if (pose->IsSelected()) { 7692 pose->Select(false); 7693 pose->Draw(pose->CalcRect(loc, this, false), this, false); 7694 } 7695 7696 loc.y += fListElemHeight; 7697 if (loc.y > bounds.bottom) 7698 break; 7699 } 7700 } else { 7701 int32 startIndex = FirstIndexAtOrBelow((int32)(bounds.top - IconPoseHeight()), true); 7702 int32 count = fVSPoseList->CountItems(); 7703 for (int32 index = startIndex; index < count; index++) { 7704 BPose *pose = fVSPoseList->ItemAt(index); 7705 if (pose) { 7706 if (pose->IsSelected()) { 7707 pose->Select(false); 7708 BRect poseRect(pose->CalcRect(this)); 7709 if (EraseWidgetTextBackground()) 7710 pose->Draw(poseRect, this, false); 7711 else 7712 Invalidate(poseRect); 7713 } 7714 7715 if (pose->Location().y > bounds.bottom) 7716 break; 7717 } 7718 } 7719 } 7720 7721 // clear selection state in all poses 7722 int32 count = fSelectionList->CountItems(); 7723 for (int32 index = 0; index < count; index++) 7724 fSelectionList->ItemAt(index)->Select(false); 7725 7726 fSelectionList->MakeEmpty(); 7727 } 7728 fMimeTypesInSelectionCache.MakeEmpty(); 7729 } 7730 7731 7732 void 7733 BPoseView::ShowSelection(bool show) 7734 { 7735 if (fSelectionVisible == show) 7736 return; 7737 7738 fSelectionVisible = show; 7739 7740 if (fSelectionList->CountItems()) { 7741 7742 // scan all visible poses first 7743 BRect bounds(Bounds()); 7744 7745 if (ViewMode() == kListMode) { 7746 int32 startIndex = (int32)(bounds.top / fListElemHeight); 7747 BPoint loc(0, startIndex * fListElemHeight); 7748 int32 count = fPoseList->CountItems(); 7749 for (int32 index = startIndex; index < count; index++) { 7750 BPose *pose = fPoseList->ItemAt(index); 7751 if (fSelectionList->HasItem(pose)) 7752 if (pose->IsSelected() != show || fShowSelectionWhenInactive) { 7753 if (!fShowSelectionWhenInactive) 7754 pose->Select(show); 7755 pose->Draw(BRect(pose->CalcRect(loc, this, false)), this, false); 7756 } 7757 7758 loc.y += fListElemHeight; 7759 if (loc.y > bounds.bottom) 7760 break; 7761 } 7762 } else { 7763 int32 startIndex = FirstIndexAtOrBelow((int32)(bounds.top - IconPoseHeight()), true); 7764 int32 count = fVSPoseList->CountItems(); 7765 for (int32 index = startIndex; index < count; index++) { 7766 BPose *pose = fVSPoseList->ItemAt(index); 7767 if (pose) { 7768 if (fSelectionList->HasItem(pose)) 7769 if (pose->IsSelected() != show || fShowSelectionWhenInactive) { 7770 if (!fShowSelectionWhenInactive) 7771 pose->Select(show); 7772 if (show && EraseWidgetTextBackground()) 7773 pose->Draw(pose->CalcRect(this), this, false); 7774 else 7775 Invalidate(pose->CalcRect(this)); 7776 } 7777 7778 if (pose->Location().y > bounds.bottom) 7779 break; 7780 } 7781 } 7782 } 7783 7784 // now set all other poses 7785 int32 count = fSelectionList->CountItems(); 7786 for (int32 index = 0; index < count; index++) { 7787 BPose *pose = fSelectionList->ItemAt(index); 7788 if (pose->IsSelected() != show && !fShowSelectionWhenInactive) 7789 pose->Select(show); 7790 } 7791 7792 // finally update fRealPivotPose/fSelectionPivotPose 7793 if (!show) { 7794 fRealPivotPose = fSelectionPivotPose; 7795 fSelectionPivotPose = NULL; 7796 } else { 7797 if (fRealPivotPose) 7798 fSelectionPivotPose = fRealPivotPose; 7799 fRealPivotPose = NULL; 7800 } 7801 } 7802 } 7803 7804 7805 void 7806 BPoseView::AddRemovePoseFromSelection(BPose *pose, int32 index, bool select) 7807 { 7808 // Do not allow double selection/deselection. 7809 if (select == pose->IsSelected()) 7810 return; 7811 7812 pose->Select(select); 7813 7814 // update display 7815 if (EraseWidgetTextBackground()) 7816 DrawPose(pose, index, false); 7817 else 7818 Invalidate(pose->CalcRect(this)); 7819 7820 if (select) 7821 fSelectionList->AddItem(pose); 7822 else { 7823 fSelectionList->RemoveItem(pose); 7824 if (fSelectionPivotPose == pose) 7825 fSelectionPivotPose = NULL; 7826 if (fRealPivotPose == pose) 7827 fRealPivotPose = NULL; 7828 } 7829 } 7830 7831 7832 void 7833 BPoseView::RemoveFromExtent(const BRect &rect) 7834 { 7835 ASSERT(ViewMode() != kListMode); 7836 7837 if (rect.left <= fExtent.left || rect.right >= fExtent.right 7838 || rect.top <= fExtent.top || rect.bottom >= fExtent.bottom) 7839 RecalcExtent(); 7840 } 7841 7842 7843 void 7844 BPoseView::RecalcExtent() 7845 { 7846 ASSERT(ViewMode() != kListMode); 7847 7848 ClearExtent(); 7849 int32 count = fPoseList->CountItems(); 7850 for (int32 index = 0; index < count; index++) 7851 AddToExtent(fPoseList->ItemAt(index)->CalcRect(this)); 7852 } 7853 7854 7855 BRect 7856 BPoseView::Extent() const 7857 { 7858 BRect rect; 7859 7860 if (ViewMode() == kListMode) { 7861 BColumn *column = fColumnList->LastItem(); 7862 if (column) { 7863 rect.left = rect.top = 0; 7864 rect.right = column->Offset() + column->Width(); 7865 rect.bottom = fListElemHeight * fPoseList->CountItems(); 7866 } else 7867 rect.Set(LeftTop().x, LeftTop().y, LeftTop().x, LeftTop().y); 7868 7869 } else { 7870 rect = fExtent; 7871 rect.left -= fOffset.x; 7872 rect.top -= fOffset.y; 7873 rect.right += fOffset.x; 7874 rect.bottom += fOffset.y; 7875 if (!rect.IsValid()) 7876 rect.Set(LeftTop().x, LeftTop().y, LeftTop().x, LeftTop().y); 7877 } 7878 7879 return rect; 7880 } 7881 7882 7883 void 7884 BPoseView::SetScrollBarsTo(BPoint point) 7885 { 7886 BPoint origin; 7887 7888 if (fHScrollBar && fVScrollBar) { 7889 fHScrollBar->SetValue(point.x); 7890 fVScrollBar->SetValue(point.y); 7891 } else { 7892 origin = LeftTop(); 7893 ScrollTo(BPoint(point.x, origin.y)); 7894 ScrollTo(BPoint(origin.x, point.y)); 7895 } 7896 } 7897 7898 7899 void 7900 BPoseView::PinPointToValidRange(BPoint& origin) 7901 { 7902 // !NaN and valid range 7903 // the following checks are not broken even they look like they are 7904 if (!(origin.x >= 0) && !(origin.x <= 0)) 7905 origin.x = 0; 7906 else if (origin.x < -40000.0 || origin.x > 40000.0) 7907 origin.x = 0; 7908 7909 if (!(origin.y >= 0) && !(origin.y <= 0)) 7910 origin.y = 0; 7911 else if (origin.y < -40000.0 || origin.y > 40000.0) 7912 origin.y = 0; 7913 } 7914 7915 7916 void 7917 BPoseView::UpdateScrollRange() 7918 { 7919 // ToDo: 7920 // some calls to UpdateScrollRange don't do the right thing because 7921 // Extent doesn't return the right value (too early in PoseView lifetime??) 7922 // 7923 // This happened most with file panels, when opening a parent - added 7924 // an extra call to UpdateScrollRange in SelectChildInParent to work 7925 // around this 7926 7927 AutoLock<BWindow> lock(Window()); 7928 if (!lock) 7929 return; 7930 7931 BRect bounds(Bounds()); 7932 7933 BPoint origin(LeftTop()); 7934 BRect extent(Extent()); 7935 7936 lock.Unlock(); 7937 7938 BPoint minVal(std::min(extent.left, origin.x), std::min(extent.top, origin.y)); 7939 7940 BPoint maxVal((extent.right - bounds.right) + origin.x, 7941 (extent.bottom - bounds.bottom) + origin.y); 7942 7943 maxVal.x = std::max(maxVal.x, origin.x); 7944 maxVal.y = std::max(maxVal.y, origin.y); 7945 7946 if (fHScrollBar) { 7947 float scrollMin; 7948 float scrollMax; 7949 fHScrollBar->GetRange(&scrollMin, &scrollMax); 7950 if (minVal.x != scrollMin || maxVal.x != scrollMax) { 7951 fHScrollBar->SetRange(minVal.x, maxVal.x); 7952 fHScrollBar->SetSteps(kSmallStep, bounds.Width()); 7953 } 7954 } 7955 7956 if (fVScrollBar) { 7957 float scrollMin; 7958 float scrollMax; 7959 fVScrollBar->GetRange(&scrollMin, &scrollMax); 7960 7961 if (minVal.y != scrollMin || maxVal.y != scrollMax) { 7962 fVScrollBar->SetRange(minVal.y, maxVal.y); 7963 fVScrollBar->SetSteps(kSmallStep, bounds.Height()); 7964 } 7965 } 7966 7967 // set proportions for bars 7968 BRect visibleExtent(extent & bounds); 7969 BRect totalExtent(extent | bounds); 7970 7971 if (fHScrollBar) { 7972 float proportion = visibleExtent.Width() / totalExtent.Width(); 7973 if (fHScrollBar->Proportion() != proportion) 7974 fHScrollBar->SetProportion(proportion); 7975 } 7976 7977 if (fVScrollBar) { 7978 float proportion = visibleExtent.Height() / totalExtent.Height(); 7979 if (fVScrollBar->Proportion() != proportion) 7980 fVScrollBar->SetProportion(proportion); 7981 } 7982 } 7983 7984 7985 void 7986 BPoseView::DrawPose(BPose *pose, int32 index, bool fullDraw) 7987 { 7988 BRect rect; 7989 if (ViewMode() == kListMode) 7990 rect = pose->CalcRect(BPoint(0, index * fListElemHeight), this, fullDraw); 7991 else 7992 rect = pose->CalcRect(this); 7993 7994 if (TrackerSettings().ShowVolumeSpaceBar() && pose->TargetModel()->IsVolume()) 7995 Invalidate(rect); 7996 else 7997 pose->Draw(rect, this, fullDraw); 7998 } 7999 8000 8001 rgb_color 8002 BPoseView::DeskTextColor() const 8003 { 8004 rgb_color color = ViewColor(); 8005 float thresh = color.red + (color.green * 1.5f) + (color.blue * .50f); 8006 8007 if (thresh >= 300) { 8008 color.red = 0; 8009 color.green = 0; 8010 color.blue = 0; 8011 } else { 8012 color.red = 255; 8013 color.green = 255; 8014 color.blue = 255; 8015 } 8016 8017 return color; 8018 } 8019 8020 8021 rgb_color 8022 BPoseView::DeskTextBackColor() const 8023 { 8024 // returns black or white color depending on the desktop background 8025 int32 thresh = 0; 8026 rgb_color color = LowColor(); 8027 8028 if (color.red > 150) 8029 thresh++; 8030 if (color.green > 150) 8031 thresh++; 8032 if (color.blue > 150) 8033 thresh++; 8034 8035 if (thresh > 1) { 8036 color.red = 255; 8037 color.green = 255; 8038 color.blue = 255; 8039 } else { 8040 color.red = 0; 8041 color.green = 0; 8042 color.blue = 0; 8043 } 8044 8045 return color; 8046 } 8047 8048 8049 void 8050 BPoseView::Draw(BRect updateRect) 8051 { 8052 if (IsDesktopWindow()) { 8053 BScreen screen(Window()); 8054 rgb_color color = screen.DesktopColor(); 8055 SetLowColor(color); 8056 SetViewColor(color); 8057 } 8058 DrawViewCommon(updateRect); 8059 8060 if (fTransparentSelection && fSelectionRect.IsValid()) { 8061 SetDrawingMode(B_OP_ALPHA); 8062 SetHighColor(255, 255, 255, 128); 8063 if (fSelectionRect.Width() == 0 || fSelectionRect.Height() == 0) 8064 StrokeLine(fSelectionRect.LeftTop(), fSelectionRect.RightBottom()); 8065 else { 8066 StrokeRect(fSelectionRect); 8067 BRect interior = fSelectionRect; 8068 interior.InsetBy(1, 1); 8069 if (interior.IsValid()) { 8070 SetHighColor(80, 80, 80, 90); 8071 FillRect(interior); 8072 } 8073 } 8074 SetDrawingMode(B_OP_OVER); 8075 } 8076 } 8077 8078 8079 void 8080 BPoseView::SynchronousUpdate(BRect updateRect, bool clip) 8081 { 8082 if (clip) { 8083 BRegion updateRegion; 8084 updateRegion.Set(updateRect); 8085 ConstrainClippingRegion(&updateRegion); 8086 } 8087 8088 FillRect(updateRect, B_SOLID_LOW); 8089 DrawViewCommon(updateRect); 8090 8091 if (clip) 8092 ConstrainClippingRegion(0); 8093 } 8094 8095 8096 void 8097 BPoseView::DrawViewCommon(BRect updateRect, bool recalculateText) 8098 { 8099 GetClippingRegion(fUpdateRegion); 8100 8101 int32 count = fPoseList->CountItems(); 8102 if (ViewMode() == kListMode) { 8103 int32 startIndex = (int32)((updateRect.top - fListElemHeight) / fListElemHeight); 8104 if (startIndex < 0) 8105 startIndex = 0; 8106 8107 BPoint loc(0, startIndex * fListElemHeight); 8108 8109 for (int32 index = startIndex; index < count; index++) { 8110 BPose *pose = fPoseList->ItemAt(index); 8111 BRect poseRect(pose->CalcRect(loc, this, true)); 8112 pose->Draw(poseRect, this, true, fUpdateRegion, recalculateText); 8113 loc.y += fListElemHeight; 8114 if (loc.y >= updateRect.bottom) 8115 break; 8116 } 8117 } else { 8118 for (int32 index = 0; index < count; index++) { 8119 BPose *pose = fPoseList->ItemAt(index); 8120 BRect poseRect(pose->CalcRect(this)); 8121 if (fUpdateRegion->Intersects(poseRect)) 8122 pose->Draw(poseRect, this, true, fUpdateRegion); 8123 } 8124 } 8125 } 8126 8127 8128 void 8129 BPoseView::ColumnRedraw(BRect updateRect) 8130 { 8131 // used for dynamic column resizing using an offscreen draw buffer 8132 ASSERT(ViewMode() == kListMode); 8133 8134 if (IsDesktopWindow()) { 8135 BScreen screen(Window()); 8136 rgb_color d = screen.DesktopColor(); 8137 SetLowColor(d); 8138 SetViewColor(d); 8139 } 8140 8141 int32 startIndex = (int32)((updateRect.top - fListElemHeight) / fListElemHeight); 8142 if (startIndex < 0) 8143 startIndex = 0; 8144 8145 int32 count = fPoseList->CountItems(); 8146 if (!count) 8147 return; 8148 8149 BPoint loc(0, startIndex * fListElemHeight); 8150 BRect srcRect = fPoseList->ItemAt(0)->CalcRect(BPoint(0, 0), this, false); 8151 srcRect.right += 1024; // need this to erase correctly 8152 fOffscreen->BeginUsing(srcRect); 8153 BView *offscreenView = fOffscreen->View(); 8154 8155 BRegion updateRegion; 8156 updateRegion.Set(updateRect); 8157 ConstrainClippingRegion(&updateRegion); 8158 8159 for (int32 index = startIndex; index < count; index++) { 8160 BPose *pose = fPoseList->ItemAt(index); 8161 8162 offscreenView->SetDrawingMode(B_OP_COPY); 8163 offscreenView->SetLowColor(LowColor()); 8164 offscreenView->FillRect(offscreenView->Bounds(), B_SOLID_LOW); 8165 8166 BRect dstRect = srcRect; 8167 dstRect.OffsetTo(loc); 8168 8169 BPoint offsetBy(0, -(index * ListElemHeight())); 8170 pose->Draw(dstRect, this, offscreenView, true, &updateRegion, 8171 offsetBy, pose->IsSelected()); 8172 8173 offscreenView->Sync(); 8174 SetDrawingMode(B_OP_COPY); 8175 DrawBitmap(fOffscreen->Bitmap(), srcRect, dstRect); 8176 loc.y += fListElemHeight; 8177 if (loc.y > updateRect.bottom) 8178 break; 8179 } 8180 fOffscreen->DoneUsing(); 8181 ConstrainClippingRegion(0); 8182 } 8183 8184 8185 void 8186 BPoseView::CloseGapInList(BRect *invalidRect) 8187 { 8188 (*invalidRect).bottom = Extent().bottom + fListElemHeight; 8189 BRect bounds(Bounds()); 8190 8191 if (bounds.Intersects(*invalidRect)) { 8192 BRect destRect(*invalidRect); 8193 destRect = destRect & bounds; 8194 destRect.bottom -= fListElemHeight; 8195 8196 BRect srcRect(destRect); 8197 srcRect.OffsetBy(0, fListElemHeight); 8198 8199 if (srcRect.Intersects(bounds) || destRect.Intersects(bounds)) 8200 CopyBits(srcRect, destRect); 8201 8202 *invalidRect = srcRect; 8203 (*invalidRect).top = destRect.bottom; 8204 } 8205 } 8206 8207 8208 void 8209 BPoseView::CheckPoseSortOrder(BPose *pose, int32 oldIndex) 8210 { 8211 if (ViewMode() != kListMode) 8212 return; 8213 8214 Window()->UpdateIfNeeded(); 8215 8216 // take pose out of list for BSearch 8217 fPoseList->RemoveItemAt(oldIndex); 8218 int32 afterIndex; 8219 int32 orientation = BSearchList(pose, &afterIndex); 8220 8221 int32 newIndex; 8222 if (orientation == kInsertAtFront) 8223 newIndex = 0; 8224 else 8225 newIndex = afterIndex + 1; 8226 8227 if (newIndex == oldIndex) { 8228 fPoseList->AddItem(pose, oldIndex); 8229 return; 8230 } 8231 8232 BRect invalidRect(CalcPoseRect(pose, oldIndex)); 8233 CloseGapInList(&invalidRect); 8234 Invalidate(invalidRect); 8235 // need to invalidate for the last item in the list 8236 InsertPoseAfter(pose, &afterIndex, orientation, &invalidRect); 8237 fPoseList->AddItem(pose, newIndex); 8238 Invalidate(invalidRect); 8239 } 8240 8241 8242 static int 8243 PoseCompareAddWidget(const BPose *p1, const BPose *p2, BPoseView *view) 8244 { 8245 // pose comparison and lazy text widget adding 8246 8247 uint32 sort = view->PrimarySort(); 8248 BColumn *column = view->ColumnFor(sort); 8249 if (!column) 8250 return 0; 8251 8252 BPose *primary; 8253 BPose *secondary; 8254 if (!view->ReverseSort()) { 8255 primary = const_cast<BPose *>(p1); 8256 secondary = const_cast<BPose *>(p2); 8257 } else { 8258 primary = const_cast<BPose *>(p2); 8259 secondary = const_cast<BPose *>(p1); 8260 } 8261 8262 int32 result = 0; 8263 for (int32 count = 0; ; count++) { 8264 8265 BTextWidget *widget1 = primary->WidgetFor(sort); 8266 if (!widget1) 8267 widget1 = primary->AddWidget(view, column); 8268 8269 BTextWidget *widget2 = secondary->WidgetFor(sort); 8270 if (!widget2) 8271 widget2 = secondary->AddWidget(view, column); 8272 8273 if (!widget1 || !widget2) 8274 return result; 8275 8276 result = widget1->Compare(*widget2, view); 8277 8278 if (count) 8279 return result; 8280 8281 // do we need to sort by secondary attribute? 8282 if (result == 0) { 8283 sort = view->SecondarySort(); 8284 if (!sort) 8285 return result; 8286 8287 column = view->ColumnFor(sort); 8288 if (!column) 8289 return result; 8290 } 8291 } 8292 8293 return result; 8294 } 8295 8296 8297 static BPose * 8298 BSearch(PoseList *table, const BPose* key, BPoseView *view, 8299 int (*cmp)(const BPose *, const BPose *, BPoseView *)) 8300 { 8301 int32 r = table->CountItems(); 8302 BPose *result = 0; 8303 8304 for (int32 l = 1; l <= r;) { 8305 int32 m = (l + r) / 2; 8306 8307 result = table->ItemAt(m - 1); 8308 int32 compareResult = (cmp)(result, key, view); 8309 if (compareResult == 0) 8310 return result; 8311 else if (compareResult < 0) 8312 l = m + 1; 8313 else 8314 r = m - 1; 8315 } 8316 8317 return result; 8318 } 8319 8320 8321 int32 8322 BPoseView::BSearchList(const BPose *pose, int32 *resultingIndex) 8323 { 8324 // check to see if insertion should be at beginning of list 8325 const BPose *firstPose = fPoseList->FirstItem(); 8326 if (!firstPose) 8327 return kInsertAtFront; 8328 8329 if (PoseCompareAddWidget(pose, firstPose, this) <= 0) { 8330 *resultingIndex = 0; 8331 return kInsertAtFront; 8332 } 8333 8334 int32 count = fPoseList->CountItems(); 8335 *resultingIndex = count - 1; 8336 8337 const BPose *searchResult = BSearch(fPoseList, pose, this, PoseCompareAddWidget); 8338 8339 if (searchResult) { 8340 // what are we doing here?? 8341 // looks like we are skipping poses with identical search results or 8342 // something 8343 int32 index = fPoseList->IndexOf(searchResult); 8344 for (; index < count; index++) { 8345 int32 result = PoseCompareAddWidget(pose, fPoseList->ItemAt(index), this); 8346 if (result <= 0) { 8347 --index; 8348 break; 8349 } 8350 } 8351 8352 if (index != count) 8353 *resultingIndex = index; 8354 } 8355 8356 return kInsertAfter; 8357 } 8358 8359 8360 void 8361 BPoseView::SetPrimarySort(uint32 attrHash) 8362 { 8363 BColumn *column = ColumnFor(attrHash); 8364 8365 if (column) { 8366 fViewState->SetPrimarySort(attrHash); 8367 fViewState->SetPrimarySortType(column->AttrType()); 8368 } 8369 } 8370 8371 8372 void 8373 BPoseView::SetSecondarySort(uint32 attrHash) 8374 { 8375 BColumn *column = ColumnFor(attrHash); 8376 8377 if (column) { 8378 fViewState->SetSecondarySort(attrHash); 8379 fViewState->SetSecondarySortType(column->AttrType()); 8380 } else { 8381 fViewState->SetSecondarySort(0); 8382 fViewState->SetSecondarySortType(0); 8383 } 8384 } 8385 8386 8387 void 8388 BPoseView::SetReverseSort(bool reverse) 8389 { 8390 fViewState->SetReverseSort(reverse); 8391 } 8392 8393 8394 inline int 8395 PoseCompareAddWidgetBinder(const BPose *p1, const BPose *p2, void *castToPoseView) 8396 { 8397 return PoseCompareAddWidget(p1, p2, (BPoseView *)castToPoseView); 8398 } 8399 8400 8401 #if xDEBUG 8402 static BPose * 8403 DumpOne(BPose *pose, void *) 8404 { 8405 pose->TargetModel()->PrintToStream(0); 8406 return 0; 8407 } 8408 #endif 8409 8410 8411 void 8412 BPoseView::SortPoses() 8413 { 8414 CommitActivePose(); 8415 // PRINT(("pose list count %d\n", fPoseList->CountItems())); 8416 #if xDEBUG 8417 fPoseList->EachElement(DumpOne, 0); 8418 PRINT(("===================\n")); 8419 #endif 8420 8421 fPoseList->SortItems(PoseCompareAddWidgetBinder, this); 8422 } 8423 8424 8425 BColumn * 8426 BPoseView::ColumnFor(uint32 attr) const 8427 { 8428 int32 count = fColumnList->CountItems(); 8429 for (int32 index = 0; index < count; index++) { 8430 BColumn *column = ColumnAt(index); 8431 if (column->AttrHash() == attr) 8432 return column; 8433 } 8434 8435 return NULL; 8436 } 8437 8438 8439 bool // returns true if actually resized 8440 BPoseView::ResizeColumnToWidest(BColumn *column) 8441 { 8442 ASSERT(ViewMode() == kListMode); 8443 8444 float maxWidth = 0; 8445 8446 int32 count = fPoseList->CountItems(); 8447 for (int32 i = 0; i < count; ++i) { 8448 BTextWidget *widget = fPoseList->ItemAt(i)->WidgetFor(column->AttrHash()); 8449 if (widget) { 8450 float width = widget->PreferredWidth(this); 8451 if (width > maxWidth) 8452 maxWidth = width; 8453 } 8454 } 8455 8456 if (maxWidth > 0) { 8457 ResizeColumn(column, maxWidth); 8458 return true; 8459 } 8460 8461 return false; 8462 } 8463 8464 8465 const int32 kRoomForLine = 2; 8466 8467 BPoint 8468 BPoseView::ResizeColumn(BColumn *column, float newSize, 8469 float *lastLineDrawPos, 8470 void (*drawLineFunc)(BPoseView *, BPoint, BPoint), 8471 void (*undrawLineFunc)(BPoseView *, BPoint, BPoint)) 8472 { 8473 BRect sourceRect(Bounds()); 8474 BPoint result(sourceRect.RightBottom()); 8475 8476 BRect destRect(sourceRect); 8477 // we will use sourceRect and destRect for copyBits 8478 BRect invalidateRect(sourceRect); 8479 // this will serve to clean up after the invalidate 8480 BRect columnDrawRect(sourceRect); 8481 // we will use columnDrawRect to draw the actual resized column 8482 8483 8484 bool shrinking = newSize < column->Width(); 8485 columnDrawRect.left = column->Offset(); 8486 columnDrawRect.right = column->Offset() + kTitleColumnRightExtraMargin 8487 - kRoomForLine + newSize; 8488 sourceRect.left = column->Offset() + kTitleColumnRightExtraMargin 8489 - kRoomForLine + column->Width(); 8490 destRect.left = columnDrawRect.right; 8491 destRect.right = destRect.left + sourceRect.Width(); 8492 invalidateRect.left = destRect.right; 8493 invalidateRect.right = sourceRect.right; 8494 8495 column->SetWidth(newSize); 8496 8497 float offset = kColumnStart; 8498 BColumn *last = fColumnList->FirstItem(); 8499 8500 8501 int32 count = fColumnList->CountItems(); 8502 for (int32 index = 0; index < count; index++) { 8503 column = fColumnList->ItemAt(index); 8504 column->SetOffset(offset); 8505 last = column; 8506 offset = last->Offset() + last->Width() + kTitleColumnExtraMargin; 8507 } 8508 8509 if (shrinking) { 8510 ColumnRedraw(columnDrawRect); 8511 // dont have to undraw when shrinking 8512 CopyBits(sourceRect, destRect); 8513 if (drawLineFunc) { 8514 ASSERT(lastLineDrawPos); 8515 (drawLineFunc)(this, BPoint(destRect.left + kRoomForLine, destRect.top), 8516 BPoint(destRect.left + kRoomForLine, destRect.bottom)); 8517 *lastLineDrawPos = destRect.left + kRoomForLine; 8518 } 8519 } else { 8520 CopyBits(sourceRect, destRect); 8521 if (undrawLineFunc) { 8522 ASSERT(lastLineDrawPos); 8523 (undrawLineFunc)(this, BPoint(*lastLineDrawPos, sourceRect.top), 8524 BPoint(*lastLineDrawPos, sourceRect.bottom)); 8525 } 8526 if (drawLineFunc) { 8527 ASSERT(lastLineDrawPos); 8528 #if 0 8529 (drawLineFunc)(this, BPoint(destRect.left + kRoomForLine, destRect.top), 8530 BPoint(destRect.left + kRoomForLine, destRect.bottom)); 8531 #endif 8532 *lastLineDrawPos = destRect.left + kRoomForLine; 8533 } 8534 ColumnRedraw(columnDrawRect); 8535 } 8536 if (invalidateRect.left < invalidateRect.right) 8537 SynchronousUpdate(invalidateRect, true); 8538 8539 fStateNeedsSaving = true; 8540 8541 return result; 8542 } 8543 8544 8545 void 8546 BPoseView::MoveColumnTo(BColumn *src, BColumn *dest) 8547 { 8548 // find the leftmost boundary of columns we are about to reshuffle 8549 float miny = src->Offset(); 8550 if (miny > dest->Offset()) 8551 miny = dest->Offset(); 8552 8553 // ensure columns are in proper order in list 8554 int32 index = fColumnList->IndexOf(dest); 8555 fColumnList->RemoveItem(src, false); 8556 fColumnList->AddItem(src, index); 8557 8558 float offset = kColumnStart; 8559 BColumn *last = fColumnList->FirstItem(); 8560 int32 count = fColumnList->CountItems(); 8561 8562 for (int32 index = 0; index < count; index++) { 8563 BColumn *column = fColumnList->ItemAt(index); 8564 column->SetOffset(offset); 8565 last = column; 8566 offset = last->Offset() + last->Width() + kTitleColumnExtraMargin; 8567 } 8568 8569 // invalidate everything to the right of miny 8570 BRect bounds(Bounds()); 8571 bounds.left = miny; 8572 Invalidate(bounds); 8573 8574 fStateNeedsSaving = true; 8575 } 8576 8577 8578 void 8579 BPoseView::MouseMoved(BPoint mouseLoc, uint32 moveCode, const BMessage *message) 8580 { 8581 if (!fDropEnabled || !message) 8582 return; 8583 8584 BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window()); 8585 if (!window) 8586 return; 8587 8588 switch (moveCode) { 8589 case B_INSIDE_VIEW: 8590 case B_ENTERED_VIEW: 8591 UpdateDropTarget(mouseLoc, message, window->ContextMenu()); 8592 if (fDropTarget) { 8593 bigtime_t dropMenuDelay; 8594 get_click_speed(&dropMenuDelay); 8595 dropMenuDelay *= 3; 8596 8597 BContainerWindow *window = ContainerWindow(); 8598 if (!window || !message || window->ContextMenu()) 8599 break; 8600 8601 bigtime_t clickTime = system_time(); 8602 BPoint loc; 8603 uint32 buttons; 8604 GetMouse(&loc, &buttons); 8605 for (;;) { 8606 if (buttons == 0 8607 || fabs(loc.x - mouseLoc.x) > 4 || fabs(loc.y - mouseLoc.y) > 4) 8608 // only loop if mouse buttons are down 8609 // moved the mouse, cancel showing the context menu 8610 break; 8611 8612 // handle drag and drop 8613 bigtime_t now = system_time(); 8614 // use shift key to get around over-loading of Control key 8615 // for context menus and auto-dnd menu 8616 if (((modifiers() & B_SHIFT_KEY) 8617 && (now - clickTime) > 200000) 8618 || now - clickTime > dropMenuDelay) { 8619 // let go of button or pressing for a while, show menu now 8620 window->DragStart(message); 8621 FrameForPose(fDropTarget, true, &fStartFrame); 8622 ShowContextMenu(mouseLoc); 8623 break; 8624 } 8625 8626 snooze(10000); 8627 GetMouse(&loc, &buttons); 8628 } 8629 8630 } 8631 break; 8632 8633 case B_EXITED_VIEW: 8634 // ToDo: 8635 // autoscroll here 8636 if (!window->ContextMenu()) { 8637 HiliteDropTarget(false); 8638 fDropTarget = NULL; 8639 } 8640 break; 8641 } 8642 } 8643 8644 8645 bool 8646 BPoseView::UpdateDropTarget(BPoint mouseLoc, const BMessage *dragMessage, 8647 bool trackingContextMenu) 8648 { 8649 ASSERT(dragMessage); 8650 8651 int32 index; 8652 BPose *targetPose = FindPose(mouseLoc, &index); 8653 8654 if (targetPose == fDropTarget 8655 || (trackingContextMenu && !targetPose)) 8656 // no change 8657 return false; 8658 8659 if (fDropTarget) 8660 HiliteDropTarget(false); 8661 8662 fDropTarget = targetPose; 8663 8664 // dereference if symlink 8665 Model *targetModel = NULL; 8666 if (targetPose) 8667 targetModel = targetPose->TargetModel(); 8668 Model tmpTarget; 8669 if (targetModel && targetModel->IsSymLink() 8670 && tmpTarget.SetTo(targetPose->TargetModel()->EntryRef(), true, true) == B_OK) 8671 targetModel = &tmpTarget; 8672 8673 bool ignoreTypes = (modifiers() & B_CONTROL_KEY) != 0; 8674 if (targetPose && CanHandleDragSelection(targetModel, dragMessage, ignoreTypes)) { 8675 // new target is valid, select it 8676 HiliteDropTarget(true); 8677 } else 8678 fDropTarget = NULL; 8679 8680 return true; 8681 } 8682 8683 8684 bool 8685 BPoseView::FrameForPose(BPose *targetpose, bool convert, BRect *poseRect) 8686 { 8687 bool returnvalue = false; 8688 BRect bounds(Bounds()); 8689 8690 if (ViewMode() == kListMode) { 8691 int32 count = fPoseList->CountItems(); 8692 int32 startIndex = (int32)(bounds.top / fListElemHeight); 8693 8694 BPoint loc(0, startIndex * fListElemHeight); 8695 8696 for (int32 index = startIndex; index < count; index++) { 8697 if (targetpose == fPoseList->ItemAt(index)) { 8698 *poseRect = fDropTarget->CalcRect(loc, this, false); 8699 returnvalue = true; 8700 } 8701 8702 loc.y += fListElemHeight; 8703 if (loc.y > bounds.bottom) 8704 returnvalue = false; 8705 } 8706 } else { 8707 int32 startIndex = FirstIndexAtOrBelow((int32)(bounds.top - IconPoseHeight()), true); 8708 int32 count = fVSPoseList->CountItems(); 8709 8710 for (int32 index = startIndex; index < count; index++) { 8711 BPose *pose = fVSPoseList->ItemAt(index); 8712 if (pose) { 8713 if (pose == fDropTarget) { 8714 *poseRect = pose->CalcRect(this); 8715 returnvalue = true; 8716 break; 8717 } 8718 8719 if (pose->Location().y > bounds.bottom) { 8720 returnvalue = false; 8721 break; 8722 } 8723 } 8724 } 8725 } 8726 8727 if (convert) 8728 ConvertToScreen(poseRect); 8729 8730 return returnvalue; 8731 } 8732 8733 8734 const int32 kMenuTrackMargin = 20; 8735 bool 8736 BPoseView::MenuTrackingHook(BMenu *menu, void *) 8737 { 8738 // return true if the menu should go away 8739 if (!menu->LockLooper()) 8740 return false; 8741 8742 uint32 buttons; 8743 BPoint location; 8744 menu->GetMouse(&location, &buttons); 8745 8746 bool returnvalue = true; 8747 // don't test for buttons up here and try to circumvent messaging 8748 // lest you miss an invoke that will happen after the window goes away 8749 8750 BRect bounds(menu->Bounds()); 8751 bounds.InsetBy(-kMenuTrackMargin, -kMenuTrackMargin); 8752 if (bounds.Contains(location)) 8753 // still in menu 8754 returnvalue = false; 8755 8756 8757 if (returnvalue) { 8758 menu->ConvertToScreen(&location); 8759 int32 count = menu->CountItems(); 8760 for (int32 index = 0 ; index < count; index++) { 8761 // iterate through all of the items in the menu 8762 // if the submenu is showing, 8763 // see if the mouse is in the submenu 8764 BMenuItem *item = menu->ItemAt(index); 8765 if (item && item->Submenu()) { 8766 BWindow *window = item->Submenu()->Window(); 8767 bool inSubmenu = false; 8768 if (window && window->Lock()) { 8769 if (!window->IsHidden()) { 8770 BRect frame(window->Frame()); 8771 8772 frame.InsetBy(-kMenuTrackMargin, -kMenuTrackMargin); 8773 inSubmenu = frame.Contains(location); 8774 } 8775 window->Unlock(); 8776 if (inSubmenu) { 8777 // only one menu can have its window open 8778 // bail now 8779 returnvalue = false; 8780 break; 8781 } 8782 } 8783 } 8784 } 8785 } 8786 8787 menu->UnlockLooper(); 8788 8789 return returnvalue; 8790 } 8791 8792 8793 void 8794 BPoseView::DragStop() 8795 { 8796 fStartFrame.Set(0, 0, 0, 0); 8797 BContainerWindow *window = ContainerWindow(); 8798 if (window) 8799 window->DragStop(); 8800 } 8801 8802 8803 void 8804 BPoseView::HiliteDropTarget(bool hiliteState) 8805 { 8806 // hilites current drop target while dragging, does not modify selection list 8807 if (!fDropTarget) 8808 return; 8809 8810 // drop target already has the desired state 8811 if (fDropTarget->IsSelected() == hiliteState || (!hiliteState && fDropTargetWasSelected)) { 8812 fDropTargetWasSelected = hiliteState; 8813 return; 8814 } 8815 8816 fDropTarget->Select(hiliteState); 8817 8818 // scan all visible poses 8819 BRect bounds(Bounds()); 8820 8821 if (ViewMode() == kListMode) { 8822 int32 count = fPoseList->CountItems(); 8823 int32 startIndex = (int32)(bounds.top / fListElemHeight); 8824 8825 BPoint loc(0, startIndex * fListElemHeight); 8826 8827 for (int32 index = startIndex; index < count; index++) { 8828 if (fDropTarget == fPoseList->ItemAt(index)) { 8829 BRect poseRect = fDropTarget->CalcRect(loc, this, false); 8830 fDropTarget->Draw(poseRect, this, false); 8831 break; 8832 } 8833 8834 loc.y += fListElemHeight; 8835 if (loc.y > bounds.bottom) 8836 break; 8837 } 8838 } else { 8839 int32 startIndex = FirstIndexAtOrBelow((int32)(bounds.top - IconPoseHeight()), true); 8840 int32 count = fVSPoseList->CountItems(); 8841 8842 for (int32 index = startIndex; index < count; index++) { 8843 BPose *pose = fVSPoseList->ItemAt(index); 8844 if (pose) { 8845 if (pose == fDropTarget) { 8846 if (!hiliteState && !EraseWidgetTextBackground()) 8847 // deselecting an icon with widget drawn over background 8848 // have to be a little tricky here - draw just the icon, 8849 // invalidate the widget 8850 pose->DeselectWithoutErasingBackground(pose->CalcRect(this), this); 8851 else 8852 pose->Draw(pose->CalcRect(this), this, false); 8853 break; 8854 } 8855 8856 if (pose->Location().y > bounds.bottom) 8857 break; 8858 } 8859 } 8860 } 8861 } 8862 8863 8864 bool 8865 BPoseView::CheckAutoScroll(BPoint mouseLoc, bool shouldScroll, 8866 bool selectionScrolling) 8867 { 8868 if (!fShouldAutoScroll) 8869 return false; 8870 8871 // make sure window is in front before attempting scrolling 8872 BContainerWindow *window = ContainerWindow(); 8873 if (!window) 8874 return false; 8875 8876 // selection scrolling will also work if the window is inactive 8877 if (!selectionScrolling && !window->IsActive()) 8878 return false; 8879 8880 BRect bounds(Bounds()); 8881 BRect extent(Extent()); 8882 8883 bool wouldScroll = false; 8884 bool keepGoing; 8885 float scrollIncrement; 8886 8887 BRect border(bounds); 8888 border.bottom = border.top; 8889 border.top -= kBorderHeight; 8890 if (ViewMode() == kListMode) 8891 border.top -= kTitleViewHeight; 8892 8893 if (bounds.top > extent.top) { 8894 if (selectionScrolling) { 8895 keepGoing = mouseLoc.y < bounds.top; 8896 if (fabs(bounds.top - mouseLoc.y) > kSlowScrollBucket) 8897 scrollIncrement = fAutoScrollInc / 1.5f; 8898 else 8899 scrollIncrement = fAutoScrollInc / 4; 8900 } else { 8901 keepGoing = border.Contains(mouseLoc); 8902 scrollIncrement = fAutoScrollInc; 8903 } 8904 8905 if (keepGoing) { 8906 wouldScroll = true; 8907 if (shouldScroll) 8908 if (fVScrollBar) 8909 fVScrollBar->SetValue(fVScrollBar->Value() - scrollIncrement); 8910 else 8911 ScrollBy(0, -scrollIncrement); 8912 } 8913 } 8914 8915 border = bounds; 8916 border.top = border.bottom; 8917 border.bottom += (float)B_H_SCROLL_BAR_HEIGHT; 8918 if (bounds.bottom < extent.bottom) { 8919 if (selectionScrolling) { 8920 keepGoing = mouseLoc.y > bounds.bottom; 8921 if (fabs(bounds.bottom - mouseLoc.y) > kSlowScrollBucket) 8922 scrollIncrement = fAutoScrollInc / 1.5f; 8923 else 8924 scrollIncrement = fAutoScrollInc / 4; 8925 } else { 8926 keepGoing = border.Contains(mouseLoc); 8927 scrollIncrement = fAutoScrollInc; 8928 } 8929 8930 if (keepGoing) { 8931 wouldScroll = true; 8932 if (shouldScroll) 8933 if (fVScrollBar) 8934 fVScrollBar->SetValue(fVScrollBar->Value() + scrollIncrement); 8935 else 8936 ScrollBy(0, scrollIncrement); 8937 } 8938 } 8939 8940 border = bounds; 8941 border.right = border.left; 8942 border.left -= 6; 8943 if (bounds.left > extent.left) { 8944 if (selectionScrolling) { 8945 keepGoing = mouseLoc.x < bounds.left; 8946 if (fabs(bounds.left - mouseLoc.x) > kSlowScrollBucket) 8947 scrollIncrement = fAutoScrollInc / 1.5f; 8948 else 8949 scrollIncrement = fAutoScrollInc / 4; 8950 } else { 8951 keepGoing = border.Contains(mouseLoc); 8952 scrollIncrement = fAutoScrollInc; 8953 } 8954 8955 if (keepGoing) { 8956 wouldScroll = true; 8957 if (shouldScroll) 8958 if (fHScrollBar) 8959 fHScrollBar->SetValue(fHScrollBar->Value() - scrollIncrement); 8960 else 8961 ScrollBy(-scrollIncrement, 0); 8962 } 8963 } 8964 8965 border = bounds; 8966 border.left = border.right; 8967 border.right += (float)B_V_SCROLL_BAR_WIDTH; 8968 if (bounds.right < extent.right) { 8969 if (selectionScrolling) { 8970 keepGoing = mouseLoc.x > bounds.right; 8971 if (fabs(bounds.right - mouseLoc.x) > kSlowScrollBucket) 8972 scrollIncrement = fAutoScrollInc / 1.5f; 8973 else 8974 scrollIncrement = fAutoScrollInc / 4; 8975 } else { 8976 keepGoing = border.Contains(mouseLoc); 8977 scrollIncrement = fAutoScrollInc; 8978 } 8979 8980 if (keepGoing) { 8981 wouldScroll = true; 8982 if (shouldScroll) 8983 if (fHScrollBar) 8984 fHScrollBar->SetValue(fHScrollBar->Value() + scrollIncrement); 8985 else 8986 ScrollBy(scrollIncrement, 0); 8987 } 8988 } 8989 8990 return wouldScroll; 8991 } 8992 8993 8994 void 8995 BPoseView::HandleAutoScroll() 8996 { 8997 if (!fShouldAutoScroll) 8998 return; 8999 9000 uint32 button; 9001 BPoint mouseLoc; 9002 GetMouse(&mouseLoc, &button); 9003 9004 if (!button) { 9005 fAutoScrollState = kAutoScrollOff; 9006 Window()->SetPulseRate(500000); 9007 return; 9008 } 9009 9010 switch (fAutoScrollState) { 9011 case kWaitForTransition: 9012 if (CheckAutoScroll(mouseLoc, false) == false) 9013 fAutoScrollState = kDelayAutoScroll; 9014 break; 9015 9016 case kDelayAutoScroll: 9017 if (CheckAutoScroll(mouseLoc, false) == true) { 9018 snooze(600000); 9019 GetMouse(&mouseLoc, &button); 9020 if (CheckAutoScroll(mouseLoc, false) == true) 9021 fAutoScrollState = kAutoScrollOn; 9022 } 9023 break; 9024 9025 case kAutoScrollOn: 9026 CheckAutoScroll(mouseLoc, true); 9027 break; 9028 } 9029 } 9030 9031 9032 BRect 9033 BPoseView::CalcPoseRect(BPose *pose, int32 index, bool min) const 9034 { 9035 return pose->CalcRect(BPoint(0, index * fListElemHeight), 9036 this, min); 9037 } 9038 9039 9040 bool 9041 BPoseView::Represents(const node_ref *node) const 9042 { 9043 return *(fModel->NodeRef()) == *node; 9044 } 9045 9046 9047 bool 9048 BPoseView::Represents(const entry_ref *ref) const 9049 { 9050 return *fModel->EntryRef() == *ref; 9051 } 9052 9053 9054 void 9055 BPoseView::ShowBarberPole() 9056 { 9057 if (fCountView) { 9058 AutoLock<BWindow> lock(Window()); 9059 if (!lock) 9060 return; 9061 fCountView->StartBarberPole(); 9062 } 9063 } 9064 9065 9066 void 9067 BPoseView::HideBarberPole() 9068 { 9069 if (fCountView) { 9070 AutoLock<BWindow> lock(Window()); 9071 if (!lock) 9072 return; 9073 fCountView->EndBarberPole(); 9074 } 9075 } 9076 9077 9078 bool 9079 BPoseView::IsWatchingDateFormatChange() 9080 { 9081 return fIsWatchingDateFormatChange; 9082 } 9083 9084 9085 void 9086 BPoseView::StartWatchDateFormatChange() 9087 { 9088 if (IsFilePanel()) { 9089 BMessenger tracker(kTrackerSignature); 9090 BHandler::StartWatching(tracker, kDateFormatChanged); 9091 } else { 9092 be_app->LockLooper(); 9093 be_app->StartWatching(this, kDateFormatChanged); 9094 be_app->UnlockLooper(); 9095 } 9096 9097 fIsWatchingDateFormatChange = true; 9098 } 9099 9100 9101 void 9102 BPoseView::StopWatchDateFormatChange() 9103 { 9104 if (IsFilePanel()) { 9105 BMessenger tracker(kTrackerSignature); 9106 BHandler::StopWatching(tracker, kDateFormatChanged); 9107 } else { 9108 be_app->LockLooper(); 9109 be_app->StopWatching(this, kDateFormatChanged); 9110 be_app->UnlockLooper(); 9111 } 9112 9113 fIsWatchingDateFormatChange = false; 9114 } 9115 9116 9117 void 9118 BPoseView::UpdateDateColumns(BMessage *message) 9119 { 9120 int32 columnCount = CountColumns(); 9121 9122 BRect columnRect(Bounds()); 9123 9124 if (IsFilePanel()) { 9125 FormatSeparator separator; 9126 DateOrder format; 9127 bool clock; 9128 9129 message->FindInt32("TimeFormatSeparator", (int32*)&separator); 9130 message->FindInt32("DateOrderFormat", (int32*)&format); 9131 message->FindBool("24HrClock", &clock); 9132 9133 TrackerSettings settings; 9134 settings.SetTimeFormatSeparator(separator); 9135 settings.SetDateOrderFormat(format); 9136 settings.SetClockTo24Hr(clock); 9137 } 9138 9139 for (int32 i = 0; i < columnCount; i++) { 9140 BColumn *col = ColumnAt(i); 9141 if (col && col->AttrType() == B_TIME_TYPE) { 9142 columnRect.left = col->Offset(); 9143 columnRect.right = columnRect.left + col->Width(); 9144 DrawViewCommon(columnRect, true); // true means recalculate texts. 9145 } 9146 } 9147 } 9148 9149 9150 void 9151 BPoseView::AdaptToVolumeChange(BMessage *) 9152 { 9153 } 9154 9155 9156 void 9157 BPoseView::AdaptToDesktopIntegrationChange(BMessage *) 9158 { 9159 } 9160 9161 9162 bool 9163 BPoseView::EraseWidgetTextBackground() const 9164 { 9165 return fEraseWidgetBackground; 9166 } 9167 9168 9169 void 9170 BPoseView::SetEraseWidgetTextBackground(bool on) 9171 { 9172 fEraseWidgetBackground = on; 9173 } 9174 9175 9176 /* static */ 9177 bool 9178 BPoseView::ShouldIntegrateDesktop(const BVolume &volume) 9179 { 9180 if (!volume.IsPersistent()) 9181 return false; 9182 9183 TrackerSettings settings; 9184 if (settings.IntegrateAllNonBootDesktops()) 9185 return true; 9186 9187 if (!settings.IntegrateNonBootBeOSDesktops()) 9188 return false; 9189 9190 // That's obviously what makes a BeOS desktop :-) 9191 return volume.KnowsQuery() && volume.KnowsAttr() && volume.KnowsMime(); 9192 } 9193 9194 9195 // #pragma mark - 9196 9197 9198 BHScrollBar::BHScrollBar(BRect bounds, const char *name, BView *target) 9199 : BScrollBar(bounds, name, target, 0, 1, B_HORIZONTAL), 9200 fTitleView(0) 9201 { 9202 } 9203 9204 9205 void 9206 BHScrollBar::ValueChanged(float value) 9207 { 9208 if (fTitleView) { 9209 BPoint origin = fTitleView->LeftTop(); 9210 fTitleView->ScrollTo(BPoint(value, origin.y)); 9211 } 9212 9213 _inherited::ValueChanged(value); 9214 } 9215 9216 9217 TPoseViewFilter::TPoseViewFilter(BPoseView *pose) 9218 : BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE), 9219 fPoseView(pose) 9220 { 9221 } 9222 9223 9224 TPoseViewFilter::~TPoseViewFilter() 9225 { 9226 } 9227 9228 9229 filter_result 9230 TPoseViewFilter::Filter(BMessage *message, BHandler **) 9231 { 9232 filter_result result = B_DISPATCH_MESSAGE; 9233 9234 switch (message->what) { 9235 case B_ARCHIVED_OBJECT: 9236 bool handled = fPoseView->HandleMessageDropped(message); 9237 if (handled) 9238 result = B_SKIP_MESSAGE; 9239 break; 9240 } 9241 9242 return result; 9243 } 9244 9245 9246 // static member initializations 9247 9248 float BPoseView::fFontHeight = -1; 9249 font_height BPoseView::fFontInfo = { 0, 0, 0 }; 9250 bigtime_t BPoseView::fLastKeyTime = 0; 9251 _BWidthBuffer_* BPoseView::fWidthBuf = new _BWidthBuffer_; 9252 BFont BPoseView::fCurrentFont; 9253 OffscreenBitmap *BPoseView::fOffscreen = new OffscreenBitmap; 9254 char BPoseView::fMatchString[] = ""; 9255 9256