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