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