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