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