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