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