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