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( 6821 sameWidth ? 0 : 1, sameHeight ? 0 : 1)); 6822 updateRegion2.Include(lastSelectionRect); 6823 updateRegion2.Exclude(fSelectionRect.InsetByCopy( 6824 sameWidth ? 0 : 1, sameHeight ? 0 : 1)); 6825 updateRegion1.Include(&updateRegion2); 6826 BRect unionRect = fSelectionRect & lastSelectionRect; 6827 updateRegion1.Exclude(unionRect 6828 & BRect(-2000, startPoint.y, 2000, startPoint.y)); 6829 updateRegion1.Exclude(unionRect 6830 & BRect(startPoint.x, -2000, startPoint.x, 2000)); 6831 6832 lastSelectionRect = fSelectionRect; 6833 6834 Invalidate(&updateRegion1); 6835 Window()->UpdateIfNeeded(); 6836 } 6837 6838 Flush(); 6839 } 6840 6841 snooze(20000); 6842 } 6843 6844 delete selectionList; 6845 6846 fIsDrawingSelectionRect = false; 6847 6848 // do final erase of selection rect 6849 if (!fTransparentSelection) { 6850 SetDrawingMode(B_OP_INVERT); 6851 StrokeRect(fSelectionRect, B_MIXED_COLORS); 6852 SetDrawingMode(B_OP_COPY); 6853 } else { 6854 Invalidate(fSelectionRect); 6855 fSelectionRect.Set(0, 0, -1, -1); 6856 Window()->UpdateIfNeeded(); 6857 } 6858 6859 // we now need to update the pose view's selection list by clearing it 6860 // and then polling each pose for selection state and rebuilding list 6861 fSelectionList->MakeEmpty(); 6862 fMimeTypesInSelectionCache.MakeEmpty(); 6863 6864 EachListItem(fPoseList, AddIfPoseSelected, fSelectionList); 6865 6866 // and now make sure that the pivot point is in sync 6867 if (fSelectionPivotPose && !fSelectionList->HasItem(fSelectionPivotPose)) 6868 fSelectionPivotPose = NULL; 6869 if (fRealPivotPose && !fSelectionList->HasItem(fRealPivotPose)) 6870 fRealPivotPose = NULL; 6871 } 6872 6873 // ToDo: 6874 // SelectPosesListMode and SelectPosesIconMode are terrible and share most code 6875 6876 void 6877 BPoseView::SelectPosesListMode(BRect selectionRect, BList **oldList) 6878 { 6879 ASSERT(ViewMode() == kListMode); 6880 6881 // collect all the poses which are enclosed inside the selection rect 6882 BList *newList = new BList; 6883 BRect bounds(Bounds()); 6884 SetDrawingMode(B_OP_COPY); 6885 6886 int32 startIndex = (int32)(selectionRect.top / fListElemHeight); 6887 if (startIndex < 0) 6888 startIndex = 0; 6889 6890 BPoint loc(0, startIndex * fListElemHeight); 6891 6892 int32 count = fPoseList->CountItems(); 6893 for (int32 index = startIndex; index < count; index++) { 6894 BPose *pose = fPoseList->ItemAt(index); 6895 BRect poseRect(pose->CalcRect(loc, this)); 6896 6897 if (selectionRect.Intersects(poseRect)) { 6898 bool selected = pose->IsSelected(); 6899 pose->Select(!fSelectionList->HasItem(pose)); 6900 newList->AddItem((void *)index); // this sucks, need to clean up 6901 // using a vector class instead of BList 6902 6903 if ((selected != pose->IsSelected()) && poseRect.Intersects(bounds)) { 6904 if (pose->IsSelected() || EraseWidgetTextBackground()) 6905 pose->Draw(poseRect, this, false); 6906 else 6907 Invalidate(poseRect); 6908 } 6909 6910 // First Pose selected gets to be the pivot. 6911 if ((fSelectionPivotPose == NULL) && (selected == false)) 6912 fSelectionPivotPose = pose; 6913 } 6914 6915 loc.y += fListElemHeight; 6916 if (loc.y > selectionRect.bottom) 6917 break; 6918 } 6919 6920 // take the old set of enclosed poses and invert selection state 6921 // on those which are no longer enclosed 6922 count = (*oldList)->CountItems(); 6923 for (int32 index = 0; index < count; index++) { 6924 int32 oldIndex = (int32)(*oldList)->ItemAt(index); 6925 6926 if (!newList->HasItem((void *)oldIndex)) { 6927 BPose *pose = fPoseList->ItemAt(oldIndex); 6928 pose->Select(!pose->IsSelected()); 6929 loc.Set(0, oldIndex * fListElemHeight); 6930 BRect poseRect(pose->CalcRect(loc, this)); 6931 6932 if (poseRect.Intersects(bounds)) { 6933 if (pose->IsSelected() || EraseWidgetTextBackground()) 6934 pose->Draw(poseRect, this, false); 6935 else 6936 Invalidate(poseRect); 6937 } 6938 } 6939 } 6940 6941 delete *oldList; 6942 *oldList = newList; 6943 } 6944 6945 6946 void 6947 BPoseView::SelectPosesIconMode(BRect selectionRect, BList **oldList) 6948 { 6949 ASSERT(ViewMode() != kListMode); 6950 6951 // collect all the poses which are enclosed inside the selection rect 6952 BList *newList = new BList; 6953 BRect bounds(Bounds()); 6954 SetDrawingMode(B_OP_COPY); 6955 6956 int32 startIndex = FirstIndexAtOrBelow((int32)(selectionRect.top - IconPoseHeight()), true); 6957 if (startIndex < 0) 6958 startIndex = 0; 6959 6960 int32 count = fPoseList->CountItems(); 6961 for (int32 index = startIndex; index < count; index++) { 6962 BPose *pose = fVSPoseList->ItemAt(index); 6963 if (pose) { 6964 BRect poseRect(pose->CalcRect(this)); 6965 6966 if (selectionRect.Intersects(poseRect)) { 6967 bool selected = pose->IsSelected(); 6968 pose->Select(!fSelectionList->HasItem(pose)); 6969 newList->AddItem((void *)index); 6970 6971 if ((selected != pose->IsSelected()) && poseRect.Intersects(bounds)) { 6972 if (pose->IsSelected() || EraseWidgetTextBackground()) 6973 pose->Draw(poseRect, this, false); 6974 else 6975 Invalidate(poseRect); 6976 } 6977 6978 // First Pose selected gets to be the pivot. 6979 if ((fSelectionPivotPose == NULL) && (selected == false)) 6980 fSelectionPivotPose = pose; 6981 } 6982 6983 if (pose->Location().y > selectionRect.bottom) 6984 break; 6985 } 6986 } 6987 6988 // take the old set of enclosed poses and invert selection state 6989 // on those which are no longer enclosed 6990 count = (*oldList)->CountItems(); 6991 for (int32 index = 0; index < count; index++) { 6992 int32 oldIndex = (int32)(*oldList)->ItemAt(index); 6993 6994 if (!newList->HasItem((void *)oldIndex)) { 6995 BPose *pose = fVSPoseList->ItemAt(oldIndex); 6996 pose->Select(!pose->IsSelected()); 6997 BRect poseRect(pose->CalcRect(this)); 6998 6999 if (poseRect.Intersects(bounds)) { 7000 if (pose->IsSelected() || EraseWidgetTextBackground()) 7001 pose->Draw(poseRect, this, false); 7002 else 7003 Invalidate(poseRect); 7004 } 7005 } 7006 } 7007 7008 delete *oldList; 7009 *oldList = newList; 7010 } 7011 7012 7013 void 7014 BPoseView::AddRemoveSelectionRange(BPoint where, bool extendSelection, BPose *pose) 7015 { 7016 ASSERT(pose); 7017 7018 if ((pose == fSelectionPivotPose) && !extendSelection) 7019 return; 7020 7021 if ((modifiers() & B_COMMAND_KEY) && fSelectionPivotPose) { 7022 // Multi Pose extend/shrink current selection 7023 bool select = !pose->IsSelected() || !extendSelection; 7024 // This weird bit of logic causes the selection to always 7025 // center around the pivot point, unless you choose to hold 7026 // down SHIFT, which will unselect between the pivot and 7027 // the most recently selected Pose. 7028 7029 if (!extendSelection) { 7030 // Remember fSelectionPivotPose because ClearSelection() NULLs it 7031 // and we need it to be preserved. 7032 const BPose *savedPivotPose = fSelectionPivotPose; 7033 ClearSelection(); 7034 fSelectionPivotPose = savedPivotPose; 7035 } 7036 7037 if (ViewMode() == kListMode) { 7038 int32 currSelIndex = fPoseList->IndexOf(pose); 7039 int32 lastSelIndex = fPoseList->IndexOf(fSelectionPivotPose); 7040 7041 int32 startRange; 7042 int32 endRange; 7043 7044 if (lastSelIndex < currSelIndex) { 7045 startRange = lastSelIndex; 7046 endRange = currSelIndex; 7047 } else { 7048 startRange = currSelIndex; 7049 endRange = lastSelIndex; 7050 } 7051 7052 for (int32 i = startRange; i <= endRange; i++) 7053 AddRemovePoseFromSelection(fPoseList->ItemAt(i), i, select); 7054 7055 } else { 7056 BRect selection(where, fSelectionPivotPose->Location()); 7057 7058 // Things will get odd if we don't 'fix' the selection rect. 7059 if (selection.left > selection.right) { 7060 float temp = selection.right; 7061 selection.right = selection.left; 7062 selection.left = temp; 7063 } 7064 7065 if (selection.top > selection.bottom) { 7066 float temp = selection.top; 7067 selection.top = selection.bottom; 7068 selection.bottom = temp; 7069 } 7070 7071 // If the selection rect is not at least 1 pixel high/wide, things 7072 // are also not going to work out. 7073 if (selection.IntegerWidth() < 1) 7074 selection.right = selection.left + 1.0f; 7075 7076 if (selection.IntegerHeight() < 1) 7077 selection.bottom = selection.top + 1.0f; 7078 7079 ASSERT(selection.IsValid()); 7080 7081 int32 count = fPoseList->CountItems(); 7082 for (int32 index = count - 1; index >= 0; index--) { 7083 BPose *currPose = fPoseList->ItemAt(index); 7084 if (selection.Intersects(currPose->CalcRect(this))) 7085 AddRemovePoseFromSelection(currPose, index, select); 7086 } 7087 } 7088 } else { 7089 int32 index = fPoseList->IndexOf(pose); 7090 if (!extendSelection) { 7091 if (!pose->IsSelected()) { 7092 // create new selection 7093 ClearSelection(); 7094 AddRemovePoseFromSelection(pose, index, true); 7095 fSelectionPivotPose = pose; 7096 } 7097 } else { 7098 fMimeTypesInSelectionCache.MakeEmpty(); 7099 AddRemovePoseFromSelection(pose, index, !pose->IsSelected()); 7100 } 7101 } 7102 7103 // If the list is empty, there cannot be a pivot pose, 7104 // however if the list is not empty there must be a pivot 7105 // pose. 7106 if (fSelectionList->IsEmpty()) { 7107 fSelectionPivotPose = NULL; 7108 fRealPivotPose = NULL; 7109 } else if (fSelectionPivotPose == NULL) { 7110 fSelectionPivotPose = pose; 7111 fRealPivotPose = pose; 7112 } 7113 } 7114 7115 7116 int32 7117 BPoseView::WaitForMouseUpOrDrag(BPoint start) 7118 { 7119 bigtime_t start_time = system_time(); 7120 bigtime_t doubleClickSpeed; 7121 get_click_speed(&doubleClickSpeed); 7122 7123 // use double the doubleClickSpeed as a treshold 7124 doubleClickSpeed *= 2; 7125 7126 // loop until mouse has been dragged at least 2 pixels 7127 uint32 button; 7128 BPoint loc; 7129 GetMouse(&loc, &button, false); 7130 7131 while (button) { 7132 GetMouse(&loc, &button, false); 7133 if (fabs(loc.x - start.x) > 2 || fabs(loc.y - start.y) > 2) 7134 return kWasDragged; 7135 7136 if ((system_time() - start_time) > doubleClickSpeed) { 7137 ShowContextMenu(start); 7138 return kContextMenuShown; 7139 } 7140 7141 snooze(15000); 7142 } 7143 7144 // user let up on mouse button without dragging 7145 Window()->Activate(); 7146 Window()->UpdateIfNeeded(); 7147 return kNotDragged; 7148 } 7149 7150 7151 void 7152 BPoseView::DeleteSymLinkPoseTarget(const node_ref *itemNode, BPose *pose, 7153 int32 index) 7154 { 7155 ASSERT(pose->TargetModel()->IsSymLink()); 7156 watch_node(itemNode, B_STOP_WATCHING, this); 7157 BPoint loc(0, index * fListElemHeight); 7158 pose->TargetModel()->SetLinkTo(0); 7159 pose->UpdateBrokenSymLink(loc, this); 7160 } 7161 7162 7163 bool 7164 BPoseView::DeletePose(const node_ref *itemNode, BPose *pose, int32 index) 7165 { 7166 watch_node(itemNode, B_STOP_WATCHING, this); 7167 7168 if (!pose) 7169 pose = fPoseList->FindPose(itemNode, &index); 7170 7171 if (pose) { 7172 if (TargetModel()->IsSymLink()) { 7173 Model *target = pose->TargetModel()->LinkTo(); 7174 if (target) 7175 watch_node(target->NodeRef(), B_STOP_WATCHING, this); 7176 } 7177 7178 ASSERT(TargetModel()); 7179 7180 if (pose == fDropTarget) 7181 fDropTarget = NULL; 7182 7183 if (pose == ActivePose()) 7184 CommitActivePose(); 7185 7186 Window()->UpdateIfNeeded(); 7187 7188 // remove it from list no matter what since it might be in list 7189 // but not "selected" since selection is hidden 7190 fSelectionList->RemoveItem(pose); 7191 if (fSelectionPivotPose == pose) 7192 fSelectionPivotPose = NULL; 7193 if (fRealPivotPose == pose) 7194 fRealPivotPose = NULL; 7195 7196 if (pose->IsSelected() && fSelectionChangedHook) 7197 ContainerWindow()->SelectionChanged(); 7198 7199 fPoseList->RemoveItemAt(index); 7200 fMimeTypeListIsDirty = true; 7201 7202 if (pose->HasLocation()) 7203 RemoveFromVSList(pose); 7204 7205 BRect invalidRect; 7206 if (ViewMode() == kListMode) 7207 invalidRect = CalcPoseRect(pose, index); 7208 else 7209 invalidRect = pose->CalcRect(this); 7210 7211 if (ViewMode() == kListMode) 7212 CloseGapInList(&invalidRect); 7213 else 7214 RemoveFromExtent(invalidRect); 7215 7216 Invalidate(invalidRect); 7217 UpdateCount(); 7218 UpdateScrollRange(); 7219 ResetPosePlacementHint(); 7220 7221 if (ViewMode() == kListMode) { 7222 BRect bounds(Bounds()); 7223 int32 index = (int32)(bounds.bottom / fListElemHeight); 7224 BPose *pose = fPoseList->ItemAt(index); 7225 if (!pose && bounds.top > 0) // scroll up a little 7226 ScrollTo(bounds.left, max_c(bounds.top - fListElemHeight, 0)); 7227 } 7228 7229 delete pose; 7230 7231 } else { 7232 // we might be getting a delete for an item in the zombie list 7233 Model *zombie = FindZombie(itemNode, &index); 7234 if (zombie) { 7235 PRINT(("deleting zombie model %s\n", zombie->Name())); 7236 fZombieList->RemoveItemAt(index); 7237 delete zombie; 7238 } else 7239 return false; 7240 } 7241 return true; 7242 } 7243 7244 7245 Model * 7246 BPoseView::FindZombie(const node_ref *itemNode, int32 *resultingIndex) 7247 { 7248 int32 count = fZombieList->CountItems(); 7249 for (int32 index = 0; index < count; index++) { 7250 Model *zombie = fZombieList->ItemAt(index); 7251 if (*zombie->NodeRef() == *itemNode) { 7252 if (resultingIndex) 7253 *resultingIndex = index; 7254 return zombie; 7255 } 7256 } 7257 7258 return NULL; 7259 } 7260 7261 // return pose at location h,v (search list starting from bottom so 7262 // drawing and hit detection reflect the same pose ordering) 7263 7264 BPose * 7265 BPoseView::FindPose(BPoint point, int32 *poseIndex) const 7266 { 7267 if (ViewMode() == kListMode) { 7268 int32 index = (int32)(point.y / fListElemHeight); 7269 if (poseIndex) 7270 *poseIndex = index; 7271 7272 BPoint loc(0, index * fListElemHeight); 7273 BPose *pose = fPoseList->ItemAt(index); 7274 if (pose && pose->PointInPose(loc, this, point)) 7275 return pose; 7276 } else { 7277 int32 count = fPoseList->CountItems(); 7278 for (int32 index = count - 1; index >= 0; index--) { 7279 BPose *pose = fPoseList->ItemAt(index); 7280 if (pose->PointInPose(this, point)) { 7281 if (poseIndex) 7282 *poseIndex = index; 7283 return pose; 7284 } 7285 } 7286 } 7287 7288 return NULL; 7289 } 7290 7291 7292 void 7293 BPoseView::OpenSelection(BPose *clickedPose, int32 *index) 7294 { 7295 BPose *singleWindowBrowsePose = clickedPose; 7296 TrackerSettings settings; 7297 7298 // Get first selected pose in selection if none was clicked 7299 if (settings.SingleWindowBrowse() 7300 && !singleWindowBrowsePose 7301 && fSelectionList->CountItems() == 1 7302 && !IsFilePanel()) 7303 singleWindowBrowsePose = fSelectionList->ItemAt(0); 7304 7305 // check if we can use the single window mode 7306 if (settings.SingleWindowBrowse() 7307 && !IsDesktopWindow() 7308 && !IsFilePanel() 7309 && !(modifiers() & B_OPTION_KEY) 7310 && TargetModel()->IsDirectory() 7311 && singleWindowBrowsePose 7312 && singleWindowBrowsePose->ResolvedModel() 7313 && singleWindowBrowsePose->ResolvedModel()->IsDirectory()) { 7314 // Switch to new directory 7315 BMessage msg(kSwitchDirectory); 7316 msg.AddRef("refs", singleWindowBrowsePose->ResolvedModel()->EntryRef()); 7317 Window()->PostMessage(&msg); 7318 } else 7319 // Otherwise use standard method 7320 OpenSelectionCommon(clickedPose, index, false); 7321 7322 } 7323 7324 7325 void 7326 BPoseView::OpenSelectionUsing(BPose *clickedPose, int32 *index) 7327 { 7328 OpenSelectionCommon(clickedPose, index, true); 7329 } 7330 7331 7332 void 7333 BPoseView::OpenSelectionCommon(BPose *clickedPose, int32 *poseIndex, 7334 bool openWith) 7335 { 7336 int32 count = fSelectionList->CountItems(); 7337 if (!count) 7338 return; 7339 7340 TTracker *tracker = dynamic_cast<TTracker *>(be_app); 7341 7342 BMessage message(B_REFS_RECEIVED); 7343 7344 for (int32 index = 0; index < count; index++) { 7345 BPose *pose = fSelectionList->ItemAt(index); 7346 7347 message.AddRef("refs", pose->TargetModel()->EntryRef()); 7348 7349 // close parent window if option down and we're not the desktop 7350 // and we're not in single window mode 7351 if (!tracker 7352 || (modifiers() & B_OPTION_KEY) == 0 7353 || IsFilePanel() 7354 || IsDesktopWindow() 7355 || TrackerSettings().SingleWindowBrowse()) 7356 continue; 7357 7358 ASSERT(TargetModel()); 7359 message.AddData("nodeRefsToClose", B_RAW_TYPE, TargetModel()->NodeRef(), 7360 sizeof (node_ref)); 7361 } 7362 7363 if (openWith) 7364 message.AddInt32("launchUsingSelector", 0); 7365 7366 // add a messenger to the launch message that will be used to 7367 // dispatch scripting calls from apps to the PoseView 7368 message.AddMessenger("TrackerViewToken", BMessenger(this)); 7369 7370 if (fSelectionHandler) 7371 fSelectionHandler->PostMessage(&message); 7372 7373 if (clickedPose) { 7374 ASSERT(poseIndex); 7375 if (ViewMode() == kListMode) 7376 DrawOpenAnimation(CalcPoseRect(clickedPose, *poseIndex, true)); 7377 else 7378 DrawOpenAnimation(clickedPose->CalcRect(this)); 7379 } 7380 } 7381 7382 7383 void 7384 BPoseView::DrawOpenAnimation(BRect rect) 7385 { 7386 SetDrawingMode(B_OP_INVERT); 7387 7388 BRect box1(rect); 7389 box1.InsetBy(rect.Width() / 2 - 2, rect.Height() / 2 - 2); 7390 BRect box2(box1); 7391 7392 for (int32 index = 0; index < 7; index++) { 7393 box2 = box1; 7394 box2.InsetBy(-2, -2); 7395 StrokeRect(box1, B_MIXED_COLORS); 7396 Sync(); 7397 StrokeRect(box2, B_MIXED_COLORS); 7398 Sync(); 7399 snooze(10000); 7400 StrokeRect(box1, B_MIXED_COLORS); 7401 StrokeRect(box2, B_MIXED_COLORS); 7402 Sync(); 7403 box1 = box2; 7404 } 7405 7406 SetDrawingMode(B_OP_OVER); 7407 } 7408 7409 7410 void 7411 BPoseView::UnmountSelectedVolumes() 7412 { 7413 BVolume boot; 7414 BVolumeRoster().GetBootVolume(&boot); 7415 7416 int32 select_count = fSelectionList->CountItems(); 7417 for (int32 index = 0; index < select_count; index++) { 7418 BPose *pose = fSelectionList->ItemAt(index); 7419 if (!pose) 7420 continue; 7421 7422 Model *model = pose->TargetModel(); 7423 if (model->IsVolume()) { 7424 BVolume volume(model->NodeRef()->device); 7425 if (volume != boot) { 7426 dynamic_cast<TTracker*>(be_app)->SaveAllPoseLocations(); 7427 7428 BMessage message(kUnmountVolume); 7429 message.AddInt32("device_id", volume.Device()); 7430 be_app->PostMessage(&message); 7431 } 7432 } 7433 } 7434 } 7435 7436 7437 void 7438 BPoseView::ClearPoses() 7439 { 7440 CommitActivePose(); 7441 SavePoseLocations(); 7442 7443 // clear all pose lists 7444 fPoseList->MakeEmpty(); 7445 fMimeTypeListIsDirty = true; 7446 fVSPoseList->MakeEmpty(); 7447 fZombieList->MakeEmpty(); 7448 fSelectionList->MakeEmpty(); 7449 fSelectionPivotPose = NULL; 7450 fRealPivotPose = NULL; 7451 fMimeTypesInSelectionCache.MakeEmpty(); 7452 7453 DisableScrollBars(); 7454 ScrollTo(BPoint(0, 0)); 7455 UpdateScrollRange(); 7456 SetScrollBarsTo(BPoint(0, 0)); 7457 EnableScrollBars(); 7458 ResetPosePlacementHint(); 7459 ClearExtent(); 7460 7461 if (fSelectionChangedHook) 7462 ContainerWindow()->SelectionChanged(); 7463 } 7464 7465 7466 void 7467 BPoseView::SwitchDir(const entry_ref *newDirRef, AttributeStreamNode *node) 7468 { 7469 ASSERT(TargetModel()); 7470 if (*newDirRef == *TargetModel()->EntryRef()) 7471 // no change 7472 return; 7473 7474 Model *model = new Model(newDirRef, true); 7475 if (model->InitCheck() != B_OK || !model->IsDirectory()) { 7476 delete model; 7477 return; 7478 } 7479 7480 CommitActivePose(); 7481 7482 // before clearing and adding new poses, we reset "blessed" async 7483 // thread id to prevent old add_poses thread from adding any more icons 7484 // the new add_poses thread will then set fAddPosesThread to its ID and it 7485 // will be allowed to add icons 7486 fAddPosesThreads.clear(); 7487 7488 delete fModel; 7489 fModel = model; 7490 7491 // check if model is a trash dir, if so 7492 // update ContainerWindow's fIsTrash, etc. 7493 // variables to indicate new state 7494 ContainerWindow()->UpdateIfTrash(model); 7495 7496 StopWatching(); 7497 ClearPoses(); 7498 7499 // Restore state if requested 7500 if (node) { 7501 uint32 oldMode = ViewMode(); 7502 7503 // Get new state 7504 RestoreState(node); 7505 7506 // Make sure the title view reset its items 7507 fTitleView->Reset(); 7508 7509 if (ViewMode() == kListMode && oldMode != kListMode) { 7510 7511 MoveBy(0, kTitleViewHeight + 1); 7512 ResizeBy(0, -(kTitleViewHeight + 1)); 7513 7514 if (ContainerWindow()) 7515 ContainerWindow()->ShowAttributeMenu(); 7516 7517 fTitleView->ResizeTo(Frame().Width(), fTitleView->Frame().Height()); 7518 fTitleView->MoveTo(Frame().left, Frame().top - (kTitleViewHeight + 1)); 7519 if (Parent()) 7520 Parent()->AddChild(fTitleView); 7521 else 7522 Window()->AddChild(fTitleView); 7523 } else if (ViewMode() != kListMode && oldMode == kListMode) { 7524 fTitleView->RemoveSelf(); 7525 7526 if (ContainerWindow()) 7527 ContainerWindow()->HideAttributeMenu(); 7528 7529 MoveBy(0, -(kTitleViewHeight + 1)); 7530 ResizeBy(0, kTitleViewHeight + 1); 7531 } else if (ViewMode() == kListMode && oldMode == kListMode && fTitleView != NULL) 7532 fTitleView->Invalidate(); 7533 7534 BPoint origin; 7535 if (ViewMode() == kListMode) 7536 origin = fViewState->ListOrigin(); 7537 else 7538 origin = fViewState->IconOrigin(); 7539 7540 PinPointToValidRange(origin); 7541 7542 SetIconPoseHeight(); 7543 GetLayoutInfo(ViewMode(), &fGrid, &fOffset); 7544 ResetPosePlacementHint(); 7545 7546 DisableScrollBars(); 7547 ScrollTo(origin); 7548 UpdateScrollRange(); 7549 SetScrollBarsTo(origin); 7550 EnableScrollBars(); 7551 } 7552 7553 StartWatching(); 7554 7555 // be sure this happens after origin is set and window is sized 7556 // properly for proper icon caching! 7557 7558 if (ContainerWindow()->IsTrash()) 7559 AddTrashPoses(); 7560 else AddPoses(TargetModel()); 7561 TargetModel()->CloseNode(); 7562 7563 Invalidate(); 7564 ResetOrigin(); 7565 ResetPosePlacementHint(); 7566 7567 fLastKeyTime = 0; 7568 } 7569 7570 7571 void 7572 BPoseView::Refresh() 7573 { 7574 BEntry entry; 7575 7576 ASSERT(TargetModel()); 7577 if (TargetModel()->OpenNode() != B_OK) 7578 return; 7579 7580 StopWatching(); 7581 ClearPoses(); 7582 StartWatching(); 7583 7584 // be sure this happens after origin is set and window is sized 7585 // properly for proper icon caching! 7586 AddPoses(TargetModel()); 7587 TargetModel()->CloseNode(); 7588 7589 Invalidate(); 7590 ResetOrigin(); 7591 ResetPosePlacementHint(); 7592 } 7593 7594 7595 void 7596 BPoseView::ResetOrigin() 7597 { 7598 DisableScrollBars(); 7599 ScrollTo(B_ORIGIN); 7600 UpdateScrollRange(); 7601 SetScrollBarsTo(B_ORIGIN); 7602 EnableScrollBars(); 7603 } 7604 7605 7606 void 7607 BPoseView::EditQueries() 7608 { 7609 // edit selected queries 7610 SendSelectionAsRefs(kEditQuery, true); 7611 } 7612 7613 7614 void 7615 BPoseView::SendSelectionAsRefs(uint32 what, bool onlyQueries) 7616 { 7617 // fix this by having a proper selection iterator 7618 7619 int32 numItems = fSelectionList->CountItems(); 7620 if (!numItems) 7621 return; 7622 7623 bool haveRef = false; 7624 BMessage message; 7625 message.what = what; 7626 7627 for (int32 index = 0; index < numItems; index++) { 7628 BPose *pose = fSelectionList->ItemAt(index); 7629 if (onlyQueries) { 7630 // to check if pose is a query, follow any symlink first 7631 BEntry resolvedEntry(pose->TargetModel()->EntryRef(), true); 7632 if (resolvedEntry.InitCheck() != B_OK) 7633 continue; 7634 7635 Model model(&resolvedEntry); 7636 if (!model.IsQuery() && !model.IsQueryTemplate()) 7637 continue; 7638 } 7639 haveRef = true; 7640 message.AddRef("refs", pose->TargetModel()->EntryRef()); 7641 } 7642 if (!haveRef) 7643 return; 7644 7645 if (onlyQueries) 7646 // this is used to make query templates come up in a special edit window 7647 message.AddBool("editQueryOnPose", &onlyQueries); 7648 7649 BMessenger(kTrackerSignature).SendMessage(&message); 7650 } 7651 7652 7653 void 7654 BPoseView::OpenInfoWindows() 7655 { 7656 BMessenger tracker(kTrackerSignature); 7657 if (!tracker.IsValid()) { 7658 (new BAlert("", "The Tracker must be running to see Info windows.", 7659 "Cancel", NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 7660 return; 7661 } 7662 SendSelectionAsRefs(kGetInfo); 7663 } 7664 7665 7666 void 7667 BPoseView::SetDefaultPrinter() 7668 { 7669 BMessenger tracker(kTrackerSignature); 7670 if (!tracker.IsValid()) { 7671 (new BAlert("", "The Tracker must be running to see set the default printer.", 7672 "Cancel", NULL, NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT))->Go(); 7673 return; 7674 } 7675 SendSelectionAsRefs(kMakeActivePrinter); 7676 } 7677 7678 7679 void 7680 BPoseView::OpenParent() 7681 { 7682 if (!TargetModel() || TargetModel()->IsRoot() || IsDesktopWindow()) 7683 return; 7684 7685 BEntry entry(TargetModel()->EntryRef()); 7686 BDirectory parent; 7687 entry_ref ref; 7688 7689 if (entry.GetParent(&parent) != B_OK 7690 || parent.GetEntry(&entry) != B_OK 7691 || entry.GetRef(&ref) != B_OK) 7692 return; 7693 7694 BEntry root("/"); 7695 if (!TrackerSettings().ShowDisksIcon() && entry == root 7696 && (modifiers() & B_CONTROL_KEY) == 0) 7697 return; 7698 7699 Model parentModel(&ref); 7700 7701 BMessage message(B_REFS_RECEIVED); 7702 message.AddRef("refs", &ref); 7703 7704 if (dynamic_cast<TTracker *>(be_app)) { 7705 // add information about the child, so that we can select it 7706 // in the parent view 7707 message.AddData("nodeRefToSelect", B_RAW_TYPE, TargetModel()->NodeRef(), 7708 sizeof (node_ref)); 7709 7710 if ((modifiers() & B_OPTION_KEY) != 0 && !IsFilePanel()) 7711 // if option down, add instructions to close the parent 7712 message.AddData("nodeRefsToClose", B_RAW_TYPE, TargetModel()->NodeRef(), 7713 sizeof (node_ref)); 7714 } 7715 7716 be_app->PostMessage(&message); 7717 } 7718 7719 7720 void 7721 BPoseView::IdentifySelection() 7722 { 7723 bool force = (modifiers() & B_OPTION_KEY) != 0; 7724 int32 count = fSelectionList->CountItems(); 7725 for (int32 index = 0; index < count; index++) { 7726 BPose *pose = fSelectionList->ItemAt(index); 7727 BEntry entry(pose->TargetModel()->EntryRef()); 7728 if (entry.InitCheck() == B_OK) { 7729 BPath path; 7730 if (entry.GetPath(&path) == B_OK) 7731 update_mime_info(path.Path(), true, false, force ? 2 : 1); 7732 } 7733 } 7734 } 7735 7736 7737 void 7738 BPoseView::ClearSelection() 7739 { 7740 CommitActivePose(); 7741 fSelectionPivotPose = NULL; 7742 fRealPivotPose = NULL; 7743 7744 if (fSelectionList->CountItems()) { 7745 7746 // scan all visible poses first 7747 BRect bounds(Bounds()); 7748 7749 if (ViewMode() == kListMode) { 7750 int32 startIndex = (int32)(bounds.top / fListElemHeight); 7751 BPoint loc(0, startIndex * fListElemHeight); 7752 int32 count = fPoseList->CountItems(); 7753 for (int32 index = startIndex; index < count; index++) { 7754 BPose *pose = fPoseList->ItemAt(index); 7755 if (pose->IsSelected()) { 7756 pose->Select(false); 7757 BRect poseRect(pose->CalcRect(loc, this, false)); 7758 if (EraseWidgetTextBackground()) 7759 pose->Draw(poseRect, this, false); 7760 else 7761 Invalidate(poseRect); 7762 } 7763 7764 loc.y += fListElemHeight; 7765 if (loc.y > bounds.bottom) 7766 break; 7767 } 7768 } else { 7769 int32 startIndex = FirstIndexAtOrBelow((int32)(bounds.top - IconPoseHeight()), true); 7770 int32 count = fVSPoseList->CountItems(); 7771 for (int32 index = startIndex; index < count; index++) { 7772 BPose *pose = fVSPoseList->ItemAt(index); 7773 if (pose) { 7774 if (pose->IsSelected()) { 7775 pose->Select(false); 7776 BRect poseRect(pose->CalcRect(this)); 7777 if (EraseWidgetTextBackground()) 7778 pose->Draw(poseRect, this, false); 7779 else 7780 Invalidate(poseRect); 7781 } 7782 7783 if (pose->Location().y > bounds.bottom) 7784 break; 7785 } 7786 } 7787 } 7788 7789 // clear selection state in all poses 7790 int32 count = fSelectionList->CountItems(); 7791 for (int32 index = 0; index < count; index++) 7792 fSelectionList->ItemAt(index)->Select(false); 7793 7794 fSelectionList->MakeEmpty(); 7795 } 7796 fMimeTypesInSelectionCache.MakeEmpty(); 7797 } 7798 7799 7800 void 7801 BPoseView::ShowSelection(bool show) 7802 { 7803 if (fSelectionVisible == show) 7804 return; 7805 7806 fSelectionVisible = show; 7807 7808 if (fSelectionList->CountItems()) { 7809 7810 // scan all visible poses first 7811 BRect bounds(Bounds()); 7812 7813 if (ViewMode() == kListMode) { 7814 int32 startIndex = (int32)(bounds.top / fListElemHeight); 7815 BPoint loc(0, startIndex * fListElemHeight); 7816 int32 count = fPoseList->CountItems(); 7817 for (int32 index = startIndex; index < count; index++) { 7818 BPose *pose = fPoseList->ItemAt(index); 7819 if (fSelectionList->HasItem(pose)) 7820 if (pose->IsSelected() != show || fShowSelectionWhenInactive) { 7821 if (!fShowSelectionWhenInactive) 7822 pose->Select(show); 7823 pose->Draw(BRect(pose->CalcRect(loc, this, false)), this, false); 7824 } 7825 7826 loc.y += fListElemHeight; 7827 if (loc.y > bounds.bottom) 7828 break; 7829 } 7830 } else { 7831 int32 startIndex = FirstIndexAtOrBelow((int32)(bounds.top - IconPoseHeight()), true); 7832 int32 count = fVSPoseList->CountItems(); 7833 for (int32 index = startIndex; index < count; index++) { 7834 BPose *pose = fVSPoseList->ItemAt(index); 7835 if (pose) { 7836 if (fSelectionList->HasItem(pose)) 7837 if (pose->IsSelected() != show || fShowSelectionWhenInactive) { 7838 if (!fShowSelectionWhenInactive) 7839 pose->Select(show); 7840 if (show && EraseWidgetTextBackground()) 7841 pose->Draw(pose->CalcRect(this), this, false); 7842 else 7843 Invalidate(pose->CalcRect(this)); 7844 } 7845 7846 if (pose->Location().y > bounds.bottom) 7847 break; 7848 } 7849 } 7850 } 7851 7852 // now set all other poses 7853 int32 count = fSelectionList->CountItems(); 7854 for (int32 index = 0; index < count; index++) { 7855 BPose *pose = fSelectionList->ItemAt(index); 7856 if (pose->IsSelected() != show && !fShowSelectionWhenInactive) 7857 pose->Select(show); 7858 } 7859 7860 // finally update fRealPivotPose/fSelectionPivotPose 7861 if (!show) { 7862 fRealPivotPose = fSelectionPivotPose; 7863 fSelectionPivotPose = NULL; 7864 } else { 7865 if (fRealPivotPose) 7866 fSelectionPivotPose = fRealPivotPose; 7867 fRealPivotPose = NULL; 7868 } 7869 } 7870 } 7871 7872 7873 void 7874 BPoseView::AddRemovePoseFromSelection(BPose *pose, int32 index, bool select) 7875 { 7876 // Do not allow double selection/deselection. 7877 if (select == pose->IsSelected()) 7878 return; 7879 7880 pose->Select(select); 7881 7882 // update display 7883 if (EraseWidgetTextBackground()) 7884 DrawPose(pose, index, false); 7885 else 7886 Invalidate(pose->CalcRect(this)); 7887 7888 if (select) 7889 fSelectionList->AddItem(pose); 7890 else { 7891 fSelectionList->RemoveItem(pose); 7892 if (fSelectionPivotPose == pose) 7893 fSelectionPivotPose = NULL; 7894 if (fRealPivotPose == pose) 7895 fRealPivotPose = NULL; 7896 } 7897 } 7898 7899 7900 void 7901 BPoseView::RemoveFromExtent(const BRect &rect) 7902 { 7903 ASSERT(ViewMode() != kListMode); 7904 7905 if (rect.left <= fExtent.left || rect.right >= fExtent.right 7906 || rect.top <= fExtent.top || rect.bottom >= fExtent.bottom) 7907 RecalcExtent(); 7908 } 7909 7910 7911 void 7912 BPoseView::RecalcExtent() 7913 { 7914 ASSERT(ViewMode() != kListMode); 7915 7916 ClearExtent(); 7917 int32 count = fPoseList->CountItems(); 7918 for (int32 index = 0; index < count; index++) 7919 AddToExtent(fPoseList->ItemAt(index)->CalcRect(this)); 7920 } 7921 7922 7923 BRect 7924 BPoseView::Extent() const 7925 { 7926 BRect rect; 7927 7928 if (ViewMode() == kListMode) { 7929 BColumn *column = fColumnList->LastItem(); 7930 if (column) { 7931 rect.left = rect.top = 0; 7932 rect.right = column->Offset() + column->Width(); 7933 rect.bottom = fListElemHeight * fPoseList->CountItems(); 7934 } else 7935 rect.Set(LeftTop().x, LeftTop().y, LeftTop().x, LeftTop().y); 7936 7937 } else { 7938 rect = fExtent; 7939 rect.left -= fOffset.x; 7940 rect.top -= fOffset.y; 7941 rect.right += fOffset.x; 7942 rect.bottom += fOffset.y; 7943 if (!rect.IsValid()) 7944 rect.Set(LeftTop().x, LeftTop().y, LeftTop().x, LeftTop().y); 7945 } 7946 7947 return rect; 7948 } 7949 7950 7951 void 7952 BPoseView::SetScrollBarsTo(BPoint point) 7953 { 7954 BPoint origin; 7955 7956 if (fHScrollBar && fVScrollBar) { 7957 fHScrollBar->SetValue(point.x); 7958 fVScrollBar->SetValue(point.y); 7959 } else { 7960 origin = LeftTop(); 7961 ScrollTo(BPoint(point.x, origin.y)); 7962 ScrollTo(BPoint(origin.x, point.y)); 7963 } 7964 } 7965 7966 7967 void 7968 BPoseView::PinPointToValidRange(BPoint& origin) 7969 { 7970 // !NaN and valid range 7971 // the following checks are not broken even they look like they are 7972 if (!(origin.x >= 0) && !(origin.x <= 0)) 7973 origin.x = 0; 7974 else if (origin.x < -40000.0 || origin.x > 40000.0) 7975 origin.x = 0; 7976 7977 if (!(origin.y >= 0) && !(origin.y <= 0)) 7978 origin.y = 0; 7979 else if (origin.y < -40000.0 || origin.y > 40000.0) 7980 origin.y = 0; 7981 } 7982 7983 7984 void 7985 BPoseView::UpdateScrollRange() 7986 { 7987 // ToDo: 7988 // some calls to UpdateScrollRange don't do the right thing because 7989 // Extent doesn't return the right value (too early in PoseView lifetime??) 7990 // 7991 // This happened most with file panels, when opening a parent - added 7992 // an extra call to UpdateScrollRange in SelectChildInParent to work 7993 // around this 7994 7995 AutoLock<BWindow> lock(Window()); 7996 if (!lock) 7997 return; 7998 7999 BRect bounds(Bounds()); 8000 8001 BPoint origin(LeftTop()); 8002 BRect extent(Extent()); 8003 8004 lock.Unlock(); 8005 8006 BPoint minVal(std::min(extent.left, origin.x), std::min(extent.top, origin.y)); 8007 8008 BPoint maxVal((extent.right - bounds.right) + origin.x, 8009 (extent.bottom - bounds.bottom) + origin.y); 8010 8011 maxVal.x = std::max(maxVal.x, origin.x); 8012 maxVal.y = std::max(maxVal.y, origin.y); 8013 8014 if (fHScrollBar) { 8015 float scrollMin; 8016 float scrollMax; 8017 fHScrollBar->GetRange(&scrollMin, &scrollMax); 8018 if (minVal.x != scrollMin || maxVal.x != scrollMax) { 8019 fHScrollBar->SetRange(minVal.x, maxVal.x); 8020 fHScrollBar->SetSteps(kSmallStep, bounds.Width()); 8021 } 8022 } 8023 8024 if (fVScrollBar) { 8025 float scrollMin; 8026 float scrollMax; 8027 fVScrollBar->GetRange(&scrollMin, &scrollMax); 8028 8029 if (minVal.y != scrollMin || maxVal.y != scrollMax) { 8030 fVScrollBar->SetRange(minVal.y, maxVal.y); 8031 fVScrollBar->SetSteps(kSmallStep, bounds.Height()); 8032 } 8033 } 8034 8035 // set proportions for bars 8036 BRect visibleExtent(extent & bounds); 8037 BRect totalExtent(extent | bounds); 8038 8039 if (fHScrollBar) { 8040 float proportion = visibleExtent.Width() / totalExtent.Width(); 8041 if (fHScrollBar->Proportion() != proportion) 8042 fHScrollBar->SetProportion(proportion); 8043 } 8044 8045 if (fVScrollBar) { 8046 float proportion = visibleExtent.Height() / totalExtent.Height(); 8047 if (fVScrollBar->Proportion() != proportion) 8048 fVScrollBar->SetProportion(proportion); 8049 } 8050 } 8051 8052 8053 void 8054 BPoseView::DrawPose(BPose *pose, int32 index, bool fullDraw) 8055 { 8056 BRect rect; 8057 if (ViewMode() == kListMode) 8058 rect = pose->CalcRect(BPoint(0, index * fListElemHeight), this, fullDraw); 8059 else 8060 rect = pose->CalcRect(this); 8061 8062 if (TrackerSettings().ShowVolumeSpaceBar() && pose->TargetModel()->IsVolume()) 8063 Invalidate(rect); 8064 else 8065 pose->Draw(rect, this, fullDraw); 8066 } 8067 8068 8069 rgb_color 8070 BPoseView::DeskTextColor() const 8071 { 8072 rgb_color color = ViewColor(); 8073 float thresh = color.red + (color.green * 1.5f) + (color.blue * .50f); 8074 8075 if (thresh >= 300) { 8076 color.red = 0; 8077 color.green = 0; 8078 color.blue = 0; 8079 } else { 8080 color.red = 255; 8081 color.green = 255; 8082 color.blue = 255; 8083 } 8084 8085 return color; 8086 } 8087 8088 8089 rgb_color 8090 BPoseView::DeskTextBackColor() const 8091 { 8092 // returns black or white color depending on the desktop background 8093 int32 thresh = 0; 8094 rgb_color color = LowColor(); 8095 8096 if (color.red > 150) 8097 thresh++; 8098 if (color.green > 150) 8099 thresh++; 8100 if (color.blue > 150) 8101 thresh++; 8102 8103 if (thresh > 1) { 8104 color.red = 255; 8105 color.green = 255; 8106 color.blue = 255; 8107 } else { 8108 color.red = 0; 8109 color.green = 0; 8110 color.blue = 0; 8111 } 8112 8113 return color; 8114 } 8115 8116 8117 void 8118 BPoseView::Draw(BRect updateRect) 8119 { 8120 if (IsDesktopWindow()) { 8121 BScreen screen(Window()); 8122 rgb_color color = screen.DesktopColor(); 8123 SetLowColor(color); 8124 SetViewColor(color); 8125 } 8126 DrawViewCommon(updateRect); 8127 8128 if (fTransparentSelection && fSelectionRect.IsValid()) { 8129 SetDrawingMode(B_OP_ALPHA); 8130 SetHighColor(255, 255, 255, 128); 8131 if (fSelectionRect.Width() == 0 || fSelectionRect.Height() == 0) 8132 StrokeLine(fSelectionRect.LeftTop(), fSelectionRect.RightBottom()); 8133 else { 8134 StrokeRect(fSelectionRect); 8135 BRect interior = fSelectionRect; 8136 interior.InsetBy(1, 1); 8137 if (interior.IsValid()) { 8138 SetHighColor(80, 80, 80, 90); 8139 FillRect(interior); 8140 } 8141 } 8142 SetDrawingMode(B_OP_OVER); 8143 } 8144 } 8145 8146 8147 void 8148 BPoseView::SynchronousUpdate(BRect updateRect, bool clip) 8149 { 8150 if (clip) { 8151 BRegion updateRegion; 8152 updateRegion.Set(updateRect); 8153 ConstrainClippingRegion(&updateRegion); 8154 } 8155 8156 FillRect(updateRect, B_SOLID_LOW); 8157 DrawViewCommon(updateRect); 8158 8159 if (clip) 8160 ConstrainClippingRegion(0); 8161 } 8162 8163 8164 void 8165 BPoseView::DrawViewCommon(BRect updateRect, bool recalculateText) 8166 { 8167 GetClippingRegion(fUpdateRegion); 8168 8169 int32 count = fPoseList->CountItems(); 8170 if (ViewMode() == kListMode) { 8171 int32 startIndex = (int32)((updateRect.top - fListElemHeight) / fListElemHeight); 8172 if (startIndex < 0) 8173 startIndex = 0; 8174 8175 BPoint loc(0, startIndex * fListElemHeight); 8176 8177 for (int32 index = startIndex; index < count; index++) { 8178 BPose *pose = fPoseList->ItemAt(index); 8179 BRect poseRect(pose->CalcRect(loc, this, true)); 8180 pose->Draw(poseRect, this, true, fUpdateRegion, recalculateText); 8181 loc.y += fListElemHeight; 8182 if (loc.y >= updateRect.bottom) 8183 break; 8184 } 8185 } else { 8186 for (int32 index = 0; index < count; index++) { 8187 BPose *pose = fPoseList->ItemAt(index); 8188 BRect poseRect(pose->CalcRect(this)); 8189 if (fUpdateRegion->Intersects(poseRect)) 8190 pose->Draw(poseRect, this, true, fUpdateRegion); 8191 } 8192 } 8193 } 8194 8195 8196 void 8197 BPoseView::ColumnRedraw(BRect updateRect) 8198 { 8199 // used for dynamic column resizing using an offscreen draw buffer 8200 ASSERT(ViewMode() == kListMode); 8201 8202 if (IsDesktopWindow()) { 8203 BScreen screen(Window()); 8204 rgb_color d = screen.DesktopColor(); 8205 SetLowColor(d); 8206 SetViewColor(d); 8207 } 8208 8209 int32 startIndex = (int32)((updateRect.top - fListElemHeight) / fListElemHeight); 8210 if (startIndex < 0) 8211 startIndex = 0; 8212 8213 int32 count = fPoseList->CountItems(); 8214 if (!count) 8215 return; 8216 8217 BPoint loc(0, startIndex * fListElemHeight); 8218 BRect srcRect = fPoseList->ItemAt(0)->CalcRect(BPoint(0, 0), this, false); 8219 srcRect.right += 1024; // need this to erase correctly 8220 fOffscreen->BeginUsing(srcRect); 8221 BView *offscreenView = fOffscreen->View(); 8222 8223 BRegion updateRegion; 8224 updateRegion.Set(updateRect); 8225 ConstrainClippingRegion(&updateRegion); 8226 8227 for (int32 index = startIndex; index < count; index++) { 8228 BPose *pose = fPoseList->ItemAt(index); 8229 8230 offscreenView->SetDrawingMode(B_OP_COPY); 8231 offscreenView->SetLowColor(LowColor()); 8232 offscreenView->FillRect(offscreenView->Bounds(), B_SOLID_LOW); 8233 8234 BRect dstRect = srcRect; 8235 dstRect.OffsetTo(loc); 8236 8237 BPoint offsetBy(0, -(index * ListElemHeight())); 8238 pose->Draw(dstRect, this, offscreenView, true, &updateRegion, 8239 offsetBy, pose->IsSelected()); 8240 8241 offscreenView->Sync(); 8242 SetDrawingMode(B_OP_COPY); 8243 DrawBitmap(fOffscreen->Bitmap(), srcRect, dstRect); 8244 loc.y += fListElemHeight; 8245 if (loc.y > updateRect.bottom) 8246 break; 8247 } 8248 fOffscreen->DoneUsing(); 8249 ConstrainClippingRegion(0); 8250 } 8251 8252 8253 void 8254 BPoseView::CloseGapInList(BRect *invalidRect) 8255 { 8256 (*invalidRect).bottom = Extent().bottom + fListElemHeight; 8257 BRect bounds(Bounds()); 8258 8259 if (bounds.Intersects(*invalidRect)) { 8260 BRect destRect(*invalidRect); 8261 destRect = destRect & bounds; 8262 destRect.bottom -= fListElemHeight; 8263 8264 BRect srcRect(destRect); 8265 srcRect.OffsetBy(0, fListElemHeight); 8266 8267 if (srcRect.Intersects(bounds) || destRect.Intersects(bounds)) 8268 CopyBits(srcRect, destRect); 8269 8270 *invalidRect = srcRect; 8271 (*invalidRect).top = destRect.bottom; 8272 } 8273 } 8274 8275 8276 void 8277 BPoseView::CheckPoseSortOrder(BPose *pose, int32 oldIndex) 8278 { 8279 if (ViewMode() != kListMode) 8280 return; 8281 8282 Window()->UpdateIfNeeded(); 8283 8284 // take pose out of list for BSearch 8285 fPoseList->RemoveItemAt(oldIndex); 8286 int32 afterIndex; 8287 int32 orientation = BSearchList(pose, &afterIndex); 8288 8289 int32 newIndex; 8290 if (orientation == kInsertAtFront) 8291 newIndex = 0; 8292 else 8293 newIndex = afterIndex + 1; 8294 8295 if (newIndex == oldIndex) { 8296 fPoseList->AddItem(pose, oldIndex); 8297 return; 8298 } 8299 8300 BRect invalidRect(CalcPoseRect(pose, oldIndex)); 8301 CloseGapInList(&invalidRect); 8302 Invalidate(invalidRect); 8303 // need to invalidate for the last item in the list 8304 InsertPoseAfter(pose, &afterIndex, orientation, &invalidRect); 8305 fPoseList->AddItem(pose, newIndex); 8306 Invalidate(invalidRect); 8307 } 8308 8309 8310 static int 8311 PoseCompareAddWidget(const BPose *p1, const BPose *p2, BPoseView *view) 8312 { 8313 // pose comparison and lazy text widget adding 8314 8315 uint32 sort = view->PrimarySort(); 8316 BColumn *column = view->ColumnFor(sort); 8317 if (!column) 8318 return 0; 8319 8320 BPose *primary; 8321 BPose *secondary; 8322 if (!view->ReverseSort()) { 8323 primary = const_cast<BPose *>(p1); 8324 secondary = const_cast<BPose *>(p2); 8325 } else { 8326 primary = const_cast<BPose *>(p2); 8327 secondary = const_cast<BPose *>(p1); 8328 } 8329 8330 int32 result = 0; 8331 for (int32 count = 0; ; count++) { 8332 8333 BTextWidget *widget1 = primary->WidgetFor(sort); 8334 if (!widget1) 8335 widget1 = primary->AddWidget(view, column); 8336 8337 BTextWidget *widget2 = secondary->WidgetFor(sort); 8338 if (!widget2) 8339 widget2 = secondary->AddWidget(view, column); 8340 8341 if (!widget1 || !widget2) 8342 return result; 8343 8344 result = widget1->Compare(*widget2, view); 8345 8346 if (count) 8347 return result; 8348 8349 // do we need to sort by secondary attribute? 8350 if (result == 0) { 8351 sort = view->SecondarySort(); 8352 if (!sort) 8353 return result; 8354 8355 column = view->ColumnFor(sort); 8356 if (!column) 8357 return result; 8358 } 8359 } 8360 8361 return result; 8362 } 8363 8364 8365 static BPose * 8366 BSearch(PoseList *table, const BPose* key, BPoseView *view, 8367 int (*cmp)(const BPose *, const BPose *, BPoseView *)) 8368 { 8369 int32 r = table->CountItems(); 8370 BPose *result = 0; 8371 8372 for (int32 l = 1; l <= r;) { 8373 int32 m = (l + r) / 2; 8374 8375 result = table->ItemAt(m - 1); 8376 int32 compareResult = (cmp)(result, key, view); 8377 if (compareResult == 0) 8378 return result; 8379 else if (compareResult < 0) 8380 l = m + 1; 8381 else 8382 r = m - 1; 8383 } 8384 8385 return result; 8386 } 8387 8388 8389 int32 8390 BPoseView::BSearchList(const BPose *pose, int32 *resultingIndex) 8391 { 8392 // check to see if insertion should be at beginning of list 8393 const BPose *firstPose = fPoseList->FirstItem(); 8394 if (!firstPose) 8395 return kInsertAtFront; 8396 8397 if (PoseCompareAddWidget(pose, firstPose, this) <= 0) { 8398 *resultingIndex = 0; 8399 return kInsertAtFront; 8400 } 8401 8402 int32 count = fPoseList->CountItems(); 8403 *resultingIndex = count - 1; 8404 8405 const BPose *searchResult = BSearch(fPoseList, pose, this, PoseCompareAddWidget); 8406 8407 if (searchResult) { 8408 // what are we doing here?? 8409 // looks like we are skipping poses with identical search results or 8410 // something 8411 int32 index = fPoseList->IndexOf(searchResult); 8412 for (; index < count; index++) { 8413 int32 result = PoseCompareAddWidget(pose, fPoseList->ItemAt(index), this); 8414 if (result <= 0) { 8415 --index; 8416 break; 8417 } 8418 } 8419 8420 if (index != count) 8421 *resultingIndex = index; 8422 } 8423 8424 return kInsertAfter; 8425 } 8426 8427 8428 void 8429 BPoseView::SetPrimarySort(uint32 attrHash) 8430 { 8431 BColumn *column = ColumnFor(attrHash); 8432 8433 if (column) { 8434 fViewState->SetPrimarySort(attrHash); 8435 fViewState->SetPrimarySortType(column->AttrType()); 8436 } 8437 } 8438 8439 8440 void 8441 BPoseView::SetSecondarySort(uint32 attrHash) 8442 { 8443 BColumn *column = ColumnFor(attrHash); 8444 8445 if (column) { 8446 fViewState->SetSecondarySort(attrHash); 8447 fViewState->SetSecondarySortType(column->AttrType()); 8448 } else { 8449 fViewState->SetSecondarySort(0); 8450 fViewState->SetSecondarySortType(0); 8451 } 8452 } 8453 8454 8455 void 8456 BPoseView::SetReverseSort(bool reverse) 8457 { 8458 fViewState->SetReverseSort(reverse); 8459 } 8460 8461 8462 inline int 8463 PoseCompareAddWidgetBinder(const BPose *p1, const BPose *p2, void *castToPoseView) 8464 { 8465 return PoseCompareAddWidget(p1, p2, (BPoseView *)castToPoseView); 8466 } 8467 8468 8469 #if xDEBUG 8470 static BPose * 8471 DumpOne(BPose *pose, void *) 8472 { 8473 pose->TargetModel()->PrintToStream(0); 8474 return 0; 8475 } 8476 #endif 8477 8478 8479 void 8480 BPoseView::SortPoses() 8481 { 8482 CommitActivePose(); 8483 // PRINT(("pose list count %d\n", fPoseList->CountItems())); 8484 #if xDEBUG 8485 fPoseList->EachElement(DumpOne, 0); 8486 PRINT(("===================\n")); 8487 #endif 8488 8489 fPoseList->SortItems(PoseCompareAddWidgetBinder, this); 8490 } 8491 8492 8493 BColumn * 8494 BPoseView::ColumnFor(uint32 attr) const 8495 { 8496 int32 count = fColumnList->CountItems(); 8497 for (int32 index = 0; index < count; index++) { 8498 BColumn *column = ColumnAt(index); 8499 if (column->AttrHash() == attr) 8500 return column; 8501 } 8502 8503 return NULL; 8504 } 8505 8506 8507 bool // returns true if actually resized 8508 BPoseView::ResizeColumnToWidest(BColumn *column) 8509 { 8510 ASSERT(ViewMode() == kListMode); 8511 8512 float maxWidth = 0; 8513 8514 int32 count = fPoseList->CountItems(); 8515 for (int32 i = 0; i < count; ++i) { 8516 BTextWidget *widget = fPoseList->ItemAt(i)->WidgetFor(column->AttrHash()); 8517 if (widget) { 8518 float width = widget->PreferredWidth(this); 8519 if (width > maxWidth) 8520 maxWidth = width; 8521 } 8522 } 8523 8524 if (maxWidth > 0) { 8525 ResizeColumn(column, maxWidth); 8526 return true; 8527 } 8528 8529 return false; 8530 } 8531 8532 8533 const int32 kRoomForLine = 2; 8534 8535 BPoint 8536 BPoseView::ResizeColumn(BColumn *column, float newSize, 8537 float *lastLineDrawPos, 8538 void (*drawLineFunc)(BPoseView *, BPoint, BPoint), 8539 void (*undrawLineFunc)(BPoseView *, BPoint, BPoint)) 8540 { 8541 BRect sourceRect(Bounds()); 8542 BPoint result(sourceRect.RightBottom()); 8543 8544 BRect destRect(sourceRect); 8545 // we will use sourceRect and destRect for copyBits 8546 BRect invalidateRect(sourceRect); 8547 // this will serve to clean up after the invalidate 8548 BRect columnDrawRect(sourceRect); 8549 // we will use columnDrawRect to draw the actual resized column 8550 8551 8552 bool shrinking = newSize < column->Width(); 8553 columnDrawRect.left = column->Offset(); 8554 columnDrawRect.right = column->Offset() + kTitleColumnRightExtraMargin 8555 - kRoomForLine + newSize; 8556 sourceRect.left = column->Offset() + kTitleColumnRightExtraMargin 8557 - kRoomForLine + column->Width(); 8558 destRect.left = columnDrawRect.right; 8559 destRect.right = destRect.left + sourceRect.Width(); 8560 invalidateRect.left = destRect.right; 8561 invalidateRect.right = sourceRect.right; 8562 8563 column->SetWidth(newSize); 8564 8565 float offset = kColumnStart; 8566 BColumn *last = fColumnList->FirstItem(); 8567 8568 8569 int32 count = fColumnList->CountItems(); 8570 for (int32 index = 0; index < count; index++) { 8571 column = fColumnList->ItemAt(index); 8572 column->SetOffset(offset); 8573 last = column; 8574 offset = last->Offset() + last->Width() + kTitleColumnExtraMargin; 8575 } 8576 8577 if (shrinking) { 8578 ColumnRedraw(columnDrawRect); 8579 // dont have to undraw when shrinking 8580 CopyBits(sourceRect, destRect); 8581 if (drawLineFunc) { 8582 ASSERT(lastLineDrawPos); 8583 (drawLineFunc)(this, BPoint(destRect.left + kRoomForLine, destRect.top), 8584 BPoint(destRect.left + kRoomForLine, destRect.bottom)); 8585 *lastLineDrawPos = destRect.left + kRoomForLine; 8586 } 8587 } else { 8588 CopyBits(sourceRect, destRect); 8589 if (undrawLineFunc) { 8590 ASSERT(lastLineDrawPos); 8591 (undrawLineFunc)(this, BPoint(*lastLineDrawPos, sourceRect.top), 8592 BPoint(*lastLineDrawPos, sourceRect.bottom)); 8593 } 8594 if (drawLineFunc) { 8595 ASSERT(lastLineDrawPos); 8596 #if 0 8597 (drawLineFunc)(this, BPoint(destRect.left + kRoomForLine, destRect.top), 8598 BPoint(destRect.left + kRoomForLine, destRect.bottom)); 8599 #endif 8600 *lastLineDrawPos = destRect.left + kRoomForLine; 8601 } 8602 ColumnRedraw(columnDrawRect); 8603 } 8604 if (invalidateRect.left < invalidateRect.right) 8605 SynchronousUpdate(invalidateRect, true); 8606 8607 fStateNeedsSaving = true; 8608 8609 return result; 8610 } 8611 8612 8613 void 8614 BPoseView::MoveColumnTo(BColumn *src, BColumn *dest) 8615 { 8616 // find the leftmost boundary of columns we are about to reshuffle 8617 float miny = src->Offset(); 8618 if (miny > dest->Offset()) 8619 miny = dest->Offset(); 8620 8621 // ensure columns are in proper order in list 8622 int32 index = fColumnList->IndexOf(dest); 8623 fColumnList->RemoveItem(src, false); 8624 fColumnList->AddItem(src, index); 8625 8626 float offset = kColumnStart; 8627 BColumn *last = fColumnList->FirstItem(); 8628 int32 count = fColumnList->CountItems(); 8629 8630 for (int32 index = 0; index < count; index++) { 8631 BColumn *column = fColumnList->ItemAt(index); 8632 column->SetOffset(offset); 8633 last = column; 8634 offset = last->Offset() + last->Width() + kTitleColumnExtraMargin; 8635 } 8636 8637 // invalidate everything to the right of miny 8638 BRect bounds(Bounds()); 8639 bounds.left = miny; 8640 Invalidate(bounds); 8641 8642 fStateNeedsSaving = true; 8643 } 8644 8645 8646 void 8647 BPoseView::MouseMoved(BPoint mouseLoc, uint32 moveCode, const BMessage *message) 8648 { 8649 if (!fDropEnabled || !message) 8650 return; 8651 8652 BContainerWindow* window = ContainerWindow(); 8653 if (!window) 8654 return; 8655 8656 switch (moveCode) { 8657 case B_INSIDE_VIEW: 8658 case B_ENTERED_VIEW: 8659 { 8660 UpdateDropTarget(mouseLoc, message, window->ContextMenu()); 8661 if (fAutoScrollState == kAutoScrollOff) { 8662 // turn on auto scrolling if it's not yet on 8663 fAutoScrollState = kWaitForTransition; 8664 window->SetPulseRate(100000); 8665 } 8666 8667 bigtime_t dropActionDelay; 8668 get_click_speed(&dropActionDelay); 8669 dropActionDelay *= 3; 8670 8671 if (window->ContextMenu()) 8672 break; 8673 8674 bigtime_t clickTime = system_time(); 8675 BPoint loc; 8676 uint32 buttons; 8677 GetMouse(&loc, &buttons); 8678 for (;;) { 8679 if (buttons == 0 8680 || fabs(loc.x - mouseLoc.x) > 4 || fabs(loc.y - mouseLoc.y) > 4) { 8681 // only loop if mouse buttons are down 8682 // moved the mouse, cancel showing the context menu 8683 break; 8684 } 8685 8686 // handle drag and drop 8687 bigtime_t now = system_time(); 8688 // use shift key to get around over-loading of Control key 8689 // for context menus and auto-dnd menu 8690 if (((modifiers() & B_SHIFT_KEY) && (now - clickTime) > 200000) 8691 || now - clickTime > dropActionDelay) { 8692 // let go of button or pressing for a while, show menu now 8693 if (fDropTarget) { 8694 window->DragStart(message); 8695 FrameForPose(fDropTarget, true, &fStartFrame); 8696 ShowContextMenu(mouseLoc); 8697 } else 8698 window->Activate(); 8699 break; 8700 } 8701 8702 snooze(10000); 8703 GetMouse(&loc, &buttons); 8704 } 8705 break; 8706 } 8707 8708 case B_EXITED_VIEW: 8709 // ToDo: 8710 // autoscroll here 8711 if (!window->ContextMenu()) { 8712 HiliteDropTarget(false); 8713 fDropTarget = NULL; 8714 } 8715 break; 8716 } 8717 } 8718 8719 8720 bool 8721 BPoseView::UpdateDropTarget(BPoint mouseLoc, const BMessage *dragMessage, 8722 bool trackingContextMenu) 8723 { 8724 ASSERT(dragMessage); 8725 8726 int32 index; 8727 BPose *targetPose = FindPose(mouseLoc, &index); 8728 8729 if (targetPose == fDropTarget 8730 || (trackingContextMenu && !targetPose)) 8731 // no change 8732 return false; 8733 8734 if (fDropTarget && !DragSelectionContains(fDropTarget, dragMessage)) 8735 HiliteDropTarget(false); 8736 8737 fDropTarget = targetPose; 8738 8739 // dereference if symlink 8740 Model *targetModel = NULL; 8741 if (targetPose) 8742 targetModel = targetPose->TargetModel(); 8743 Model tmpTarget; 8744 if (targetModel && targetModel->IsSymLink() 8745 && tmpTarget.SetTo(targetPose->TargetModel()->EntryRef(), true, true) == B_OK) 8746 targetModel = &tmpTarget; 8747 8748 bool ignoreTypes = (modifiers() & B_CONTROL_KEY) != 0; 8749 if (targetPose && CanHandleDragSelection(targetModel, dragMessage, ignoreTypes)) { 8750 // new target is valid, select it 8751 HiliteDropTarget(true); 8752 } else 8753 fDropTarget = NULL; 8754 8755 return true; 8756 } 8757 8758 8759 bool 8760 BPoseView::FrameForPose(BPose *targetpose, bool convert, BRect *poseRect) 8761 { 8762 bool returnvalue = false; 8763 BRect bounds(Bounds()); 8764 8765 if (ViewMode() == kListMode) { 8766 int32 count = fPoseList->CountItems(); 8767 int32 startIndex = (int32)(bounds.top / fListElemHeight); 8768 8769 BPoint loc(0, startIndex * fListElemHeight); 8770 8771 for (int32 index = startIndex; index < count; index++) { 8772 if (targetpose == fPoseList->ItemAt(index)) { 8773 *poseRect = fDropTarget->CalcRect(loc, this, false); 8774 returnvalue = true; 8775 } 8776 8777 loc.y += fListElemHeight; 8778 if (loc.y > bounds.bottom) 8779 returnvalue = false; 8780 } 8781 } else { 8782 int32 startIndex = FirstIndexAtOrBelow((int32)(bounds.top - IconPoseHeight()), true); 8783 int32 count = fVSPoseList->CountItems(); 8784 8785 for (int32 index = startIndex; index < count; index++) { 8786 BPose *pose = fVSPoseList->ItemAt(index); 8787 if (pose) { 8788 if (pose == fDropTarget) { 8789 *poseRect = pose->CalcRect(this); 8790 returnvalue = true; 8791 break; 8792 } 8793 8794 if (pose->Location().y > bounds.bottom) { 8795 returnvalue = false; 8796 break; 8797 } 8798 } 8799 } 8800 } 8801 8802 if (convert) 8803 ConvertToScreen(poseRect); 8804 8805 return returnvalue; 8806 } 8807 8808 8809 const int32 kMenuTrackMargin = 20; 8810 bool 8811 BPoseView::MenuTrackingHook(BMenu *menu, void *) 8812 { 8813 // return true if the menu should go away 8814 if (!menu->LockLooper()) 8815 return false; 8816 8817 uint32 buttons; 8818 BPoint location; 8819 menu->GetMouse(&location, &buttons); 8820 8821 bool returnvalue = true; 8822 // don't test for buttons up here and try to circumvent messaging 8823 // lest you miss an invoke that will happen after the window goes away 8824 8825 BRect bounds(menu->Bounds()); 8826 bounds.InsetBy(-kMenuTrackMargin, -kMenuTrackMargin); 8827 if (bounds.Contains(location)) 8828 // still in menu 8829 returnvalue = false; 8830 8831 8832 if (returnvalue) { 8833 menu->ConvertToScreen(&location); 8834 int32 count = menu->CountItems(); 8835 for (int32 index = 0 ; index < count; index++) { 8836 // iterate through all of the items in the menu 8837 // if the submenu is showing, 8838 // see if the mouse is in the submenu 8839 BMenuItem *item = menu->ItemAt(index); 8840 if (item && item->Submenu()) { 8841 BWindow *window = item->Submenu()->Window(); 8842 bool inSubmenu = false; 8843 if (window && window->Lock()) { 8844 if (!window->IsHidden()) { 8845 BRect frame(window->Frame()); 8846 8847 frame.InsetBy(-kMenuTrackMargin, -kMenuTrackMargin); 8848 inSubmenu = frame.Contains(location); 8849 } 8850 window->Unlock(); 8851 if (inSubmenu) { 8852 // only one menu can have its window open 8853 // bail now 8854 returnvalue = false; 8855 break; 8856 } 8857 } 8858 } 8859 } 8860 } 8861 8862 menu->UnlockLooper(); 8863 8864 return returnvalue; 8865 } 8866 8867 8868 void 8869 BPoseView::DragStop() 8870 { 8871 fStartFrame.Set(0, 0, 0, 0); 8872 BContainerWindow *window = ContainerWindow(); 8873 if (window) 8874 window->DragStop(); 8875 } 8876 8877 8878 void 8879 BPoseView::HiliteDropTarget(bool hiliteState) 8880 { 8881 // hilites current drop target while dragging, does not modify selection list 8882 if (!fDropTarget) 8883 return; 8884 8885 // drop target already has the desired state 8886 if (fDropTarget->IsSelected() == hiliteState || (!hiliteState && fDropTargetWasSelected)) { 8887 fDropTargetWasSelected = hiliteState; 8888 return; 8889 } 8890 8891 fDropTarget->Select(hiliteState); 8892 8893 // scan all visible poses 8894 BRect bounds(Bounds()); 8895 8896 if (ViewMode() == kListMode) { 8897 int32 count = fPoseList->CountItems(); 8898 int32 startIndex = (int32)(bounds.top / fListElemHeight); 8899 8900 BPoint loc(0, startIndex * fListElemHeight); 8901 8902 for (int32 index = startIndex; index < count; index++) { 8903 if (fDropTarget == fPoseList->ItemAt(index)) { 8904 BRect poseRect = fDropTarget->CalcRect(loc, this, false); 8905 fDropTarget->Draw(poseRect, this, false); 8906 break; 8907 } 8908 8909 loc.y += fListElemHeight; 8910 if (loc.y > bounds.bottom) 8911 break; 8912 } 8913 } else { 8914 int32 startIndex = FirstIndexAtOrBelow((int32)(bounds.top - IconPoseHeight()), true); 8915 int32 count = fVSPoseList->CountItems(); 8916 8917 for (int32 index = startIndex; index < count; index++) { 8918 BPose *pose = fVSPoseList->ItemAt(index); 8919 if (pose) { 8920 if (pose == fDropTarget) { 8921 if (!hiliteState && !EraseWidgetTextBackground()) 8922 // deselecting an icon with widget drawn over background 8923 // have to be a little tricky here - draw just the icon, 8924 // invalidate the widget 8925 pose->DeselectWithoutErasingBackground(pose->CalcRect(this), this); 8926 else 8927 pose->Draw(pose->CalcRect(this), this, false); 8928 break; 8929 } 8930 8931 if (pose->Location().y > bounds.bottom) 8932 break; 8933 } 8934 } 8935 } 8936 } 8937 8938 8939 bool 8940 BPoseView::CheckAutoScroll(BPoint mouseLoc, bool shouldScroll, 8941 bool selectionScrolling) 8942 { 8943 if (!fShouldAutoScroll) 8944 return false; 8945 8946 // make sure window is in front before attempting scrolling 8947 BContainerWindow *window = ContainerWindow(); 8948 if (!window) 8949 return false; 8950 8951 // selection scrolling will also work if the window is inactive 8952 if (!selectionScrolling && !window->IsActive()) 8953 return false; 8954 8955 BRect bounds(Bounds()); 8956 BRect extent(Extent()); 8957 8958 bool wouldScroll = false; 8959 bool keepGoing; 8960 float scrollIncrement; 8961 8962 BRect border(bounds); 8963 border.bottom = border.top; 8964 border.top -= kBorderHeight; 8965 if (ViewMode() == kListMode) 8966 border.top -= kTitleViewHeight; 8967 8968 if (bounds.top > extent.top) { 8969 if (selectionScrolling) { 8970 keepGoing = mouseLoc.y < bounds.top; 8971 if (fabs(bounds.top - mouseLoc.y) > kSlowScrollBucket) 8972 scrollIncrement = fAutoScrollInc / 1.5f; 8973 else 8974 scrollIncrement = fAutoScrollInc / 4; 8975 } else { 8976 keepGoing = border.Contains(mouseLoc); 8977 scrollIncrement = fAutoScrollInc; 8978 } 8979 8980 if (keepGoing) { 8981 wouldScroll = true; 8982 if (shouldScroll) 8983 if (fVScrollBar) 8984 fVScrollBar->SetValue(fVScrollBar->Value() - scrollIncrement); 8985 else 8986 ScrollBy(0, -scrollIncrement); 8987 } 8988 } 8989 8990 border = bounds; 8991 border.top = border.bottom; 8992 border.bottom += (float)B_H_SCROLL_BAR_HEIGHT; 8993 if (bounds.bottom < extent.bottom) { 8994 if (selectionScrolling) { 8995 keepGoing = mouseLoc.y > bounds.bottom; 8996 if (fabs(bounds.bottom - mouseLoc.y) > kSlowScrollBucket) 8997 scrollIncrement = fAutoScrollInc / 1.5f; 8998 else 8999 scrollIncrement = fAutoScrollInc / 4; 9000 } else { 9001 keepGoing = border.Contains(mouseLoc); 9002 scrollIncrement = fAutoScrollInc; 9003 } 9004 9005 if (keepGoing) { 9006 wouldScroll = true; 9007 if (shouldScroll) 9008 if (fVScrollBar) 9009 fVScrollBar->SetValue(fVScrollBar->Value() + scrollIncrement); 9010 else 9011 ScrollBy(0, scrollIncrement); 9012 } 9013 } 9014 9015 border = bounds; 9016 border.right = border.left; 9017 border.left -= 6; 9018 if (bounds.left > extent.left) { 9019 if (selectionScrolling) { 9020 keepGoing = mouseLoc.x < bounds.left; 9021 if (fabs(bounds.left - mouseLoc.x) > kSlowScrollBucket) 9022 scrollIncrement = fAutoScrollInc / 1.5f; 9023 else 9024 scrollIncrement = fAutoScrollInc / 4; 9025 } else { 9026 keepGoing = border.Contains(mouseLoc); 9027 scrollIncrement = fAutoScrollInc; 9028 } 9029 9030 if (keepGoing) { 9031 wouldScroll = true; 9032 if (shouldScroll) 9033 if (fHScrollBar) 9034 fHScrollBar->SetValue(fHScrollBar->Value() - scrollIncrement); 9035 else 9036 ScrollBy(-scrollIncrement, 0); 9037 } 9038 } 9039 9040 border = bounds; 9041 border.left = border.right; 9042 border.right += (float)B_V_SCROLL_BAR_WIDTH; 9043 if (bounds.right < extent.right) { 9044 if (selectionScrolling) { 9045 keepGoing = mouseLoc.x > bounds.right; 9046 if (fabs(bounds.right - mouseLoc.x) > kSlowScrollBucket) 9047 scrollIncrement = fAutoScrollInc / 1.5f; 9048 else 9049 scrollIncrement = fAutoScrollInc / 4; 9050 } else { 9051 keepGoing = border.Contains(mouseLoc); 9052 scrollIncrement = fAutoScrollInc; 9053 } 9054 9055 if (keepGoing) { 9056 wouldScroll = true; 9057 if (shouldScroll) 9058 if (fHScrollBar) 9059 fHScrollBar->SetValue(fHScrollBar->Value() + scrollIncrement); 9060 else 9061 ScrollBy(scrollIncrement, 0); 9062 } 9063 } 9064 9065 return wouldScroll; 9066 } 9067 9068 9069 void 9070 BPoseView::HandleAutoScroll() 9071 { 9072 if (!fShouldAutoScroll) 9073 return; 9074 9075 uint32 button; 9076 BPoint mouseLoc; 9077 GetMouse(&mouseLoc, &button); 9078 9079 if (!button) { 9080 fAutoScrollState = kAutoScrollOff; 9081 Window()->SetPulseRate(500000); 9082 return; 9083 } 9084 9085 switch (fAutoScrollState) { 9086 case kWaitForTransition: 9087 if (CheckAutoScroll(mouseLoc, false) == false) 9088 fAutoScrollState = kDelayAutoScroll; 9089 break; 9090 9091 case kDelayAutoScroll: 9092 if (CheckAutoScroll(mouseLoc, false) == true) { 9093 snooze(600000); 9094 GetMouse(&mouseLoc, &button); 9095 if (CheckAutoScroll(mouseLoc, false) == true) 9096 fAutoScrollState = kAutoScrollOn; 9097 } 9098 break; 9099 9100 case kAutoScrollOn: 9101 CheckAutoScroll(mouseLoc, true); 9102 break; 9103 } 9104 } 9105 9106 9107 BRect 9108 BPoseView::CalcPoseRect(BPose *pose, int32 index, bool min) const 9109 { 9110 return pose->CalcRect(BPoint(0, index * fListElemHeight), 9111 this, min); 9112 } 9113 9114 9115 bool 9116 BPoseView::Represents(const node_ref *node) const 9117 { 9118 return *(fModel->NodeRef()) == *node; 9119 } 9120 9121 9122 bool 9123 BPoseView::Represents(const entry_ref *ref) const 9124 { 9125 return *fModel->EntryRef() == *ref; 9126 } 9127 9128 9129 void 9130 BPoseView::ShowBarberPole() 9131 { 9132 if (fCountView) { 9133 AutoLock<BWindow> lock(Window()); 9134 if (!lock) 9135 return; 9136 fCountView->StartBarberPole(); 9137 } 9138 } 9139 9140 9141 void 9142 BPoseView::HideBarberPole() 9143 { 9144 if (fCountView) { 9145 AutoLock<BWindow> lock(Window()); 9146 if (!lock) 9147 return; 9148 fCountView->EndBarberPole(); 9149 } 9150 } 9151 9152 9153 bool 9154 BPoseView::IsWatchingDateFormatChange() 9155 { 9156 return fIsWatchingDateFormatChange; 9157 } 9158 9159 9160 void 9161 BPoseView::StartWatchDateFormatChange() 9162 { 9163 // TODO: Workaround for R5 (overful message queue)! 9164 // Unfortunately, this causes a dead-lock under certain circumstances. 9165 #if !defined(HAIKU_TARGET_PLATFORM_HAIKU) && !defined(HAIKU_TARGET_PLATFORM_DANO) 9166 if (IsFilePanel()) { 9167 #endif 9168 BMessenger tracker(kTrackerSignature); 9169 BHandler::StartWatching(tracker, kDateFormatChanged); 9170 #if !defined(HAIKU_TARGET_PLATFORM_HAIKU) && !defined(HAIKU_TARGET_PLATFORM_DANO) 9171 } else { 9172 be_app->LockLooper(); 9173 be_app->StartWatching(this, kDateFormatChanged); 9174 be_app->UnlockLooper(); 9175 } 9176 #endif 9177 9178 fIsWatchingDateFormatChange = true; 9179 } 9180 9181 9182 void 9183 BPoseView::StopWatchDateFormatChange() 9184 { 9185 if (IsFilePanel()) { 9186 BMessenger tracker(kTrackerSignature); 9187 BHandler::StopWatching(tracker, kDateFormatChanged); 9188 } else { 9189 be_app->LockLooper(); 9190 be_app->StopWatching(this, kDateFormatChanged); 9191 be_app->UnlockLooper(); 9192 } 9193 9194 fIsWatchingDateFormatChange = false; 9195 } 9196 9197 9198 void 9199 BPoseView::UpdateDateColumns(BMessage *message) 9200 { 9201 int32 columnCount = CountColumns(); 9202 9203 BRect columnRect(Bounds()); 9204 9205 if (IsFilePanel()) { 9206 FormatSeparator separator; 9207 DateOrder format; 9208 bool clock; 9209 9210 message->FindInt32("TimeFormatSeparator", (int32*)&separator); 9211 message->FindInt32("DateOrderFormat", (int32*)&format); 9212 message->FindBool("24HrClock", &clock); 9213 9214 TrackerSettings settings; 9215 settings.SetTimeFormatSeparator(separator); 9216 settings.SetDateOrderFormat(format); 9217 settings.SetClockTo24Hr(clock); 9218 } 9219 9220 for (int32 i = 0; i < columnCount; i++) { 9221 BColumn *col = ColumnAt(i); 9222 if (col && col->AttrType() == B_TIME_TYPE) { 9223 columnRect.left = col->Offset(); 9224 columnRect.right = columnRect.left + col->Width(); 9225 DrawViewCommon(columnRect, true); // true means recalculate texts. 9226 } 9227 } 9228 } 9229 9230 9231 void 9232 BPoseView::AdaptToVolumeChange(BMessage *) 9233 { 9234 } 9235 9236 9237 void 9238 BPoseView::AdaptToDesktopIntegrationChange(BMessage *) 9239 { 9240 } 9241 9242 9243 bool 9244 BPoseView::EraseWidgetTextBackground() const 9245 { 9246 return fEraseWidgetBackground; 9247 } 9248 9249 9250 void 9251 BPoseView::SetEraseWidgetTextBackground(bool on) 9252 { 9253 fEraseWidgetBackground = on; 9254 } 9255 9256 9257 /* static */ 9258 bool 9259 BPoseView::ShouldIntegrateDesktop(const BVolume &volume) 9260 { 9261 if (!volume.IsPersistent()) 9262 return false; 9263 9264 TrackerSettings settings; 9265 if (settings.IntegrateAllNonBootDesktops()) 9266 return true; 9267 9268 if (!settings.IntegrateNonBootBeOSDesktops()) 9269 return false; 9270 9271 // Only removable read-only volumes should have their desktops integrated 9272 return volume.IsRemovable() && volume.IsReadOnly() 9273 && volume.KnowsQuery() && volume.KnowsAttr() && volume.KnowsMime(); 9274 } 9275 9276 9277 // #pragma mark - 9278 9279 9280 BHScrollBar::BHScrollBar(BRect bounds, const char *name, BView *target) 9281 : BScrollBar(bounds, name, target, 0, 1, B_HORIZONTAL), 9282 fTitleView(0) 9283 { 9284 } 9285 9286 9287 void 9288 BHScrollBar::ValueChanged(float value) 9289 { 9290 if (fTitleView) { 9291 BPoint origin = fTitleView->LeftTop(); 9292 fTitleView->ScrollTo(BPoint(value, origin.y)); 9293 } 9294 9295 _inherited::ValueChanged(value); 9296 } 9297 9298 9299 TPoseViewFilter::TPoseViewFilter(BPoseView *pose) 9300 : BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE), 9301 fPoseView(pose) 9302 { 9303 } 9304 9305 9306 TPoseViewFilter::~TPoseViewFilter() 9307 { 9308 } 9309 9310 9311 filter_result 9312 TPoseViewFilter::Filter(BMessage *message, BHandler **) 9313 { 9314 filter_result result = B_DISPATCH_MESSAGE; 9315 9316 switch (message->what) { 9317 case B_ARCHIVED_OBJECT: 9318 bool handled = fPoseView->HandleMessageDropped(message); 9319 if (handled) 9320 result = B_SKIP_MESSAGE; 9321 break; 9322 } 9323 9324 return result; 9325 } 9326 9327 9328 // static member initializations 9329 9330 float BPoseView::fFontHeight = -1; 9331 font_height BPoseView::fFontInfo = { 0, 0, 0 }; 9332 bigtime_t BPoseView::fLastKeyTime = 0; 9333 _BWidthBuffer_* BPoseView::fWidthBuf = new _BWidthBuffer_; 9334 BFont BPoseView::fCurrentFont; 9335 OffscreenBitmap *BPoseView::fOffscreen = new OffscreenBitmap; 9336 char BPoseView::fMatchString[] = ""; 9337 9338