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