1/*
2Open Tracker License
3
4Terms and Conditions
5
6Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7
8Permission is hereby granted, free of charge, to any person obtaining a copy of
9this software and associated documentation files (the "Software"), to deal in
10the Software without restriction, including without limitation the rights to
11use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12of the Software, and to permit persons to whom the Software is furnished to do
13so, subject to the following conditions:
14
15The above copyright notice and this permission notice applies to all licensees
16and shall be included in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25Except as contained in this notice, the name of Be Incorporated shall not be
26used in advertising or otherwise to promote the sale, use or other dealings in
27this Software without prior written authorization from Be Incorporated.
28
29Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30of Be Incorporated in the United States and other countries. Other brand product
31names are registered trademarks or trademarks of their respective holders.
32All rights reserved.
33*/
34
35
36#include "PoseView.h"
37
38#include <algorithm>
39#include <functional>
40#include <map>
41
42#include <ctype.h>
43#include <errno.h>
44#include <float.h>
45#include <stdlib.h>
46#include <strings.h>
47
48#include <compat/sys/stat.h>
49
50#include <Alert.h>
51#include <Application.h>
52#include <Catalog.h>
53#include <Clipboard.h>
54#include <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#include <PathMonitor.h>
76
77#include "Attributes.h"
78#include "AutoLock.h"
79#include "BackgroundImage.h"
80#include "Bitmaps.h"
81#include "Commands.h"
82#include "CountView.h"
83#include "DeskWindow.h"
84#include "DesktopPoseView.h"
85#include "FilePanelPriv.h"
86#include "FSClipboard.h"
87#include "FSUtils.h"
88#include "FunctionObject.h"
89#include "MimeTypes.h"
90#include "Navigator.h"
91#include "Pose.h"
92#include "InfoWindow.h"
93#include "Tests.h"
94#include "Thread.h"
95#include "Tracker.h"
96#include "TrackerString.h"
97#include "WidgetAttributeText.h"
98#include "WidthBuffer.h"
99
100
101#undef B_TRANSLATION_CONTEXT
102#define B_TRANSLATION_CONTEXT "PoseView"
103
104
105const float kDoubleClickTresh = 6;
106
107const uint32 kAddNewPoses = 'Tanp';
108const uint32 kAddPosesCompleted = 'Tapc';
109const int32 kMaxAddPosesChunk = 50;
110const uint32 kMsgMouseDragged = 'Mdrg';
111const uint32 kMsgMouseLongDown = 'Mold';
112
113const int32 kRoomForLine = 2;
114
115const int32 kMenuTrackMargin = 20;
116
117const float kSlowScrollBucket = 30;
118const float kBorderHeight = 20;
119
120enum {
121	kAutoScrollOff,
122	kWaitForTransition,
123	kDelayAutoScroll,
124	kAutoScrollOn
125};
126
127enum {
128	kWasDragged,
129	kContextMenuShown,
130	kNotDragged
131};
132
133enum {
134	kInsertAtFront,
135	kInsertAfter
136};
137
138const BPoint kTransparentDragThreshold(256, 192);
139	// maximum size of the transparent drag bitmap, use a drag rect
140	// if larger in any direction
141
142struct attr_column_relation {
143	uint32	attrHash;
144	int32	fieldMask;
145};
146
147static struct attr_column_relation sAttrColumnMap[] = {
148	{ AttrHashString(kAttrStatModified, B_TIME_TYPE),
149		B_STAT_MODIFICATION_TIME },
150	{ AttrHashString(kAttrStatSize, B_OFF_T_TYPE),
151		B_STAT_SIZE },
152	{ AttrHashString(kAttrStatCreated, B_TIME_TYPE),
153		B_STAT_CREATION_TIME },
154	{ AttrHashString(kAttrStatMode, B_STRING_TYPE),
155		B_STAT_MODE }
156};
157
158struct AddPosesResult {
159	~AddPosesResult();
160	void ReleaseModels();
161
162	Model* fModels[kMaxAddPosesChunk];
163	PoseInfo fPoseInfos[kMaxAddPosesChunk];
164	int32 fCount;
165};
166
167
168AddPosesResult::~AddPosesResult(void)
169{
170	for (int32 i = 0; i < fCount; i++)
171		delete fModels[i];
172}
173
174
175void
176AddPosesResult::ReleaseModels(void)
177{
178	for (int32 i = 0; i < kMaxAddPosesChunk; i++)
179		fModels[i] = NULL;
180}
181
182
183static BPose*
184BSearch(PoseList* table, const BPose* key, BPoseView* view,
185	int (*cmp)(const BPose*, const BPose*, BPoseView*),
186	bool returnClosest = true);
187
188static int
189PoseCompareAddWidget(const BPose* p1, const BPose* p2, BPoseView* view);
190
191
192static bool
193OneMatches(BPose* pose, BPoseView*, void* castToPose)
194{
195	return pose == (const BPose*)castToPose;
196}
197
198
199static void
200CopySelectionListToEntryRefList(const PoseList* original,
201	BObjectList<entry_ref>* copy)
202{
203	int32 count = original->CountItems();
204	for (int32 index = 0; index < count; index++) {
205		copy->AddItem(new entry_ref(*(original->ItemAt(
206			index)->TargetModel()->EntryRef())));
207	}
208}
209
210
211//	#pragma mark - BPoseView
212
213
214BPoseView::BPoseView(Model* model, uint32 viewMode)
215	:
216	BView("PoseView", B_WILL_DRAW | B_PULSE_NEEDED),
217	fIsDrawingSelectionRect(false),
218	fHScrollBar(NULL),
219	fVScrollBar(NULL),
220	fModel(model),
221	fActivePose(NULL),
222	fExtent(INT32_MAX, INT32_MAX, INT32_MIN, INT32_MIN),
223	fPoseList(new PoseList(40, true)),
224	fFilteredPoseList(new PoseList()),
225	fVSPoseList(new PoseList()),
226	fSelectionList(new PoseList()),
227	fMimeTypesInSelectionCache(20, true),
228	fZombieList(new BObjectList<Model>(10, true)),
229	fColumnList(new BObjectList<BColumn>(4, true)),
230	fMimeTypeList(new BObjectList<BString>(10, true)),
231	fBrokenLinks(new BObjectList<Model>(10, false)),
232	fMimeTypeListIsDirty(false),
233	fViewState(new BViewState),
234	fStateNeedsSaving(false),
235	fCountView(NULL),
236	fListElemHeight(0.0f),
237	fIconPoseHeight(0.0f),
238	fDropTarget(NULL),
239	fAlreadySelectedDropTarget(NULL),
240	fSelectionHandler(be_app),
241	fLastClickPoint(INT32_MAX, INT32_MAX),
242	fLastClickButtons(0),
243	fLastClickedPose(NULL),
244	fLastExtent(INT32_MAX, INT32_MAX, INT32_MIN, INT32_MIN),
245	fTitleView(NULL),
246	fRefFilter(NULL),
247	fAutoScrollInc(20),
248	fAutoScrollState(kAutoScrollOff),
249	fWidgetTextOutline(false),
250	fSelectionPivotPose(NULL),
251	fRealPivotPose(NULL),
252	fKeyRunner(NULL),
253	fTrackRightMouseUp(false),
254	fSelectionVisible(true),
255	fMultipleSelection(true),
256	fDragEnabled(true),
257	fDropEnabled(true),
258	fSelectionRectEnabled(true),
259	fAlwaysAutoPlace(false),
260	fAllowPoseEditing(true),
261	fSelectionChangedHook(false),
262	fSavePoseLocations(true),
263	fShowHideSelection(true),
264	fOkToMapIcons(false),
265	fEnsurePosesVisible(false),
266	fShouldAutoScroll(true),
267	fIsDesktopWindow(false),
268	fIsWatchingDateFormatChange(false),
269	fHasPosesInClipboard(false),
270	fCursorCheck(false),
271	fFiltering(false),
272	fFilterStrings(4, true),
273	fLastFilterStringCount(1),
274	fLastFilterStringLength(0),
275	fLastKeyTime(0),
276	fLastDeskbarFrameCheckTime(LONGLONG_MIN),
277	fDeskbarFrame(0, 0, -1, -1),
278	fTextWidgetToCheck(NULL)
279{
280	fListElemHeight = std::fmax(ListIconSize(),
281		ceilf(sFontHeight) < 20 ? 20 : ceilf(sFontHeight * 1.1f));
282
283	fViewState->SetViewMode(viewMode);
284	fShowSelectionWhenInactive
285		= TrackerSettings().ShowSelectionWhenInactive();
286	fTransparentSelection = TrackerSettings().TransparentSelection();
287	fFilterStrings.AddItem(new BString(""));
288}
289
290
291BPoseView::~BPoseView()
292{
293	delete fPoseList;
294	delete fFilteredPoseList;
295	delete fVSPoseList;
296	delete fColumnList;
297	delete fSelectionList;
298	delete fMimeTypeList;
299	delete fZombieList;
300	delete fViewState;
301	delete fModel;
302	delete fKeyRunner;
303	delete fBrokenLinks;
304
305	IconCache::sIconCache->Deleting(this);
306}
307
308
309void
310BPoseView::Init(AttributeStreamNode* node)
311{
312	RestoreState(node);
313	InitCommon();
314}
315
316
317void
318BPoseView::Init(const BMessage &message)
319{
320	RestoreState(message);
321	InitCommon();
322}
323
324
325void
326BPoseView::InitCommon()
327{
328	BContainerWindow* window = ContainerWindow();
329
330	// Create the TitleView and CountView
331	fTitleView = new BTitleView(this);
332	if (ViewMode() != kListMode)
333		fTitleView->Hide();
334	if (fHScrollBar != NULL)
335		fHScrollBar->SetTitleView(fTitleView);
336
337	fCountView = new BCountView(this);
338
339	BPoint origin;
340	if (ViewMode() == kListMode)
341		origin = fViewState->ListOrigin();
342	else
343		origin = fViewState->IconOrigin();
344
345	PinPointToValidRange(origin);
346
347	// init things related to laying out items
348	SetIconPoseHeight();
349	GetLayoutInfo(ViewMode(), &fGrid, &fOffset);
350	ResetPosePlacementHint();
351
352	DisableScrollBars();
353	ScrollTo(origin);
354	UpdateScrollRange();
355	SetScrollBarsTo(origin);
356	EnableScrollBars();
357
358	StartWatching();
359		// turn on volume node monitor, metamime monitor, etc.
360
361	// populate the window
362	if (window != NULL && 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 != NULL) {
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 == 0) {
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 == NULL)
424						break;
425					tempSortedList.AddItem(column);
426				}
427				AddColumnList(&tempSortedList);
428			}
429			delete[] buffer;
430		}
431	}
432
433	_ResetStartOffset();
434	SetUpDefaultColumnsIfNeeded();
435	if (!ColumnFor(PrimarySort())) {
436		fViewState->SetPrimarySort(FirstColumn()->AttrHash());
437		fViewState->SetPrimarySortType(FirstColumn()->AttrType());
438	}
439
440	if (PrimarySort() == SecondarySort())
441		fViewState->SetSecondarySort(0);
442}
443
444
445void
446BPoseView::RestoreColumnState(const BMessage &message)
447{
448	fColumnList->MakeEmpty();
449
450	BObjectList<BColumn> tempSortedList;
451	for (int32 index = 0; ; index++) {
452		BColumn* column = BColumn::InstantiateFromMessage(message, index);
453		if (column == NULL)
454			break;
455
456		tempSortedList.AddItem(column);
457	}
458
459	AddColumnList(&tempSortedList);
460
461	_ResetStartOffset();
462	SetUpDefaultColumnsIfNeeded();
463	if (!ColumnFor(PrimarySort())) {
464		fViewState->SetPrimarySort(FirstColumn()->AttrHash());
465		fViewState->SetPrimarySortType(FirstColumn()->AttrType());
466	}
467
468	if (PrimarySort() == SecondarySort())
469		fViewState->SetSecondarySort(0);
470}
471
472
473void
474BPoseView::AddColumnList(BObjectList<BColumn>* list)
475{
476	list->SortItems(&CompareColumns);
477
478	float nextLeftEdge = 0;
479	for (int32 columIndex = 0; columIndex < list->CountItems();
480			columIndex++) {
481		BColumn* column = list->ItemAt(columIndex);
482
483		// Make sure that columns don't overlap
484		if (column->Offset() < nextLeftEdge) {
485			PRINT(("\t**Overlapped columns in archived column state\n"));
486			column->SetOffset(nextLeftEdge);
487		}
488
489		nextLeftEdge = column->Offset() + column->Width()
490			- kRoomForLine / 2.0f + kTitleColumnExtraMargin;
491		fColumnList->AddItem(column);
492
493		if (!IsWatchingDateFormatChange()
494			&& column->AttrType() == B_TIME_TYPE) {
495			StartWatchDateFormatChange();
496		}
497	}
498}
499
500
501void
502BPoseView::RestoreState(AttributeStreamNode* node)
503{
504	RestoreColumnState(node);
505
506	if (node != NULL) {
507		const char* viewStateAttr;
508		const char* viewStateAttrForeign;
509
510		if (TargetModel() && TargetModel()->IsRoot()) {
511			viewStateAttr = kAttrDisksViewState;
512			viewStateAttrForeign = kAttrDisksViewStateForeign;
513		} else {
514			viewStateAttr = ViewStateAttributeName();
515			viewStateAttrForeign = ForeignViewStateAttributeName();
516		}
517
518		bool wrongEndianness = false;
519		const char* name = viewStateAttr;
520		size_t size = (size_t)node->Contains(name, B_RAW_TYPE);
521		if (!size) {
522			name = viewStateAttrForeign;
523			wrongEndianness = true;
524			size = (size_t)node->Contains(name, B_RAW_TYPE);
525		}
526
527		if (size > 0 && size < 10000) {
528			// check for invalid sizes here to protect against
529			// munged attributes
530			char* buffer = new char[size];
531			off_t result = node->Read(name, 0, B_RAW_TYPE, size, buffer);
532			if (result) {
533				BMallocIO stream;
534				stream.WriteAt(0, buffer, size);
535				stream.Seek(0, SEEK_SET);
536				BViewState* viewstate
537					= BViewState::InstantiateFromStream(&stream,
538						wrongEndianness);
539				if (viewstate) {
540					delete fViewState;
541					fViewState = viewstate;
542				}
543			}
544			delete[] buffer;
545		}
546	}
547
548	if (IsDesktopWindow() && ViewMode() == kListMode) {
549		// recover if desktop window view state set wrong
550		fViewState->SetViewMode(kIconMode);
551	}
552}
553
554
555void
556BPoseView::RestoreState(const BMessage &message)
557{
558	RestoreColumnState(message);
559
560	BViewState* viewstate = BViewState::InstantiateFromMessage(message);
561	if (viewstate != NULL) {
562		delete fViewState;
563		fViewState = viewstate;
564	}
565
566	if (IsDesktopWindow() && ViewMode() == kListMode) {
567		// recover if desktop window view state set wrong
568		fViewState->SetViewMode(kIconMode);
569	}
570}
571
572
573namespace BPrivate {
574
575bool
576ClearViewOriginOne(const char* DEBUG_ONLY(name), uint32 type, off_t size,
577	void* viewStateArchive, void*)
578{
579	ASSERT(strcmp(name, kAttrViewState) == 0);
580
581	if (viewStateArchive == NULL)
582		return false;
583
584	if (type != B_RAW_TYPE)
585		return false;
586
587	BMallocIO stream;
588	stream.WriteAt(0, viewStateArchive, (size_t)size);
589	stream.Seek(0, SEEK_SET);
590	BViewState* viewstate = BViewState::InstantiateFromStream(&stream, false);
591	if (!viewstate)
592		return false;
593
594	// this is why we are here - zero out
595	viewstate->SetListOrigin(BPoint(0, 0));
596	viewstate->SetIconOrigin(BPoint(0, 0));
597
598	stream.Seek(0, SEEK_SET);
599	viewstate->ArchiveToStream(&stream);
600	stream.ReadAt(0, viewStateArchive, (size_t)size);
601
602	return true;
603}
604
605}	// namespace BPrivate
606
607
608void
609BPoseView::SetUpDefaultColumnsIfNeeded()
610{
611	// in case there were errors getting some columns
612	if (fColumnList->CountItems() != 0)
613		return;
614
615	fColumnList->AddItem(new BColumn(B_TRANSLATE("Name"), StartOffset(), 145,
616		B_ALIGN_LEFT, kAttrStatName, B_STRING_TYPE, true, true));
617	fColumnList->AddItem(new BColumn(B_TRANSLATE("Size"), 200, 80,
618		B_ALIGN_RIGHT, kAttrStatSize, B_OFF_T_TYPE, true, false));
619	fColumnList->AddItem(new BColumn(B_TRANSLATE("Modified"), 295, 150,
620		B_ALIGN_LEFT, kAttrStatModified, B_TIME_TYPE, true, false));
621
622	if (!IsWatchingDateFormatChange())
623		StartWatchDateFormatChange();
624}
625
626
627const char*
628BPoseView::ViewStateAttributeName() const
629{
630	return IsDesktopView() ? kAttrDesktopViewState : kAttrViewState;
631}
632
633
634const char*
635BPoseView::ForeignViewStateAttributeName() const
636{
637	return IsDesktopView() ? kAttrDesktopViewStateForeign
638		: kAttrViewStateForeign;
639}
640
641
642void
643BPoseView::SaveColumnState(AttributeStreamNode* node)
644{
645	BMallocIO stream;
646	for (int32 index = 0; ; index++) {
647		const BColumn* column = ColumnAt(index);
648		if (column == NULL)
649			break;
650		column->ArchiveToStream(&stream);
651	}
652
653	const char* columnsAttr;
654	const char* columnsAttrForeign;
655	if (TargetModel() && TargetModel()->IsRoot()) {
656		columnsAttr = kAttrDisksColumns;
657		columnsAttrForeign = kAttrDisksColumnsForeign;
658	} else {
659		columnsAttr = kAttrColumns;
660		columnsAttrForeign = kAttrColumnsForeign;
661	}
662
663	node->Write(columnsAttr, columnsAttrForeign, B_RAW_TYPE,
664		stream.Position(), stream.Buffer());
665}
666
667
668void
669BPoseView::SaveColumnState(BMessage& message) const
670{
671	for (int32 index = 0; ; index++) {
672		const BColumn* column = ColumnAt(index);
673		if (column == NULL)
674			break;
675
676		column->ArchiveToMessage(message);
677	}
678}
679
680
681void
682BPoseView::SaveState(AttributeStreamNode* node)
683{
684	SaveColumnState(node);
685
686	// save view state into object
687	BMallocIO stream;
688
689	stream.Seek(0, SEEK_SET);
690	fViewState->ArchiveToStream(&stream);
691
692	const char* viewStateAttr;
693	const char* viewStateAttrForeign;
694	if (TargetModel() != NULL && TargetModel()->IsRoot()) {
695		viewStateAttr = kAttrDisksViewState;
696		viewStateAttrForeign = kAttrDisksViewStateForeign;
697	} else {
698		viewStateAttr = ViewStateAttributeName();
699		viewStateAttrForeign = ForeignViewStateAttributeName();
700	}
701
702	node->Write(viewStateAttr, viewStateAttrForeign, B_RAW_TYPE,
703		stream.Position(), stream.Buffer());
704
705	fStateNeedsSaving = false;
706}
707
708
709void
710BPoseView::SaveState(BMessage& message) const
711{
712	SaveColumnState(message);
713	fViewState->ArchiveToMessage(message);
714}
715
716
717float
718BPoseView::StringWidth(const char* str) const
719{
720	return BPrivate::gWidthBuffer->StringWidth(str, 0, (int32)strlen(str),
721		&sCurrentFont);
722}
723
724
725float
726BPoseView::StringWidth(const char* str, int32 len) const
727{
728	ASSERT(strlen(str) == (uint32)len);
729
730	return BPrivate::gWidthBuffer->StringWidth(str, 0, len, &sCurrentFont);
731}
732
733
734void
735BPoseView::SavePoseLocations(BRect* frameIfDesktop)
736{
737	PoseInfo poseInfo;
738
739	if (!fSavePoseLocations)
740		return;
741
742	ASSERT(Window()->IsLocked());
743
744	Model* targetModel = TargetModel();
745	ThrowOnAssert(targetModel != NULL);
746
747	BVolume volume(TargetModel()->NodeRef()->device);
748	if (volume.InitCheck() != B_OK)
749		return;
750
751	if (!targetModel->IsRoot()
752		&& (volume.IsReadOnly() || !volume.KnowsAttr())) {
753		// check that we can write out attrs; Root should always work
754		// because it gets saved on the boot disk but the above checks
755		// will fail
756		return;
757	}
758
759	bool isDesktop = IsDesktopWindow() && (frameIfDesktop != NULL);
760
761	int32 count = fPoseList->CountItems();
762	for (int32 index = 0; index < count; index++) {
763		BPose* pose = fPoseList->ItemAt(index);
764		if (pose->NeedsSaveLocation() && pose->HasLocation()) {
765			Model* model = pose->TargetModel();
766			poseInfo.fInvisible = false;
767
768			if (model->IsRoot())
769				poseInfo.fInitedDirectory = targetModel->NodeRef()->node;
770			else
771				poseInfo.fInitedDirectory = model->EntryRef()->directory;
772
773			poseInfo.fLocation = pose->Location(this);
774
775			ExtendedPoseInfo* extendedPoseInfo = NULL;
776			size_t extendedPoseInfoSize = 0;
777			ModelNodeLazyOpener opener(model, true);
778
779			if (isDesktop) {
780				opener.OpenNode(true);
781				// if saving desktop icons, save an extended pose info too
782				extendedPoseInfo = ReadExtendedPoseInfo(model);
783					// read the pre-existing one
784
785				if (!extendedPoseInfo) {
786					// don't have one yet, allocate one
787					size_t size = ExtendedPoseInfo::Size(1);
788					extendedPoseInfo = (ExtendedPoseInfo*)
789						new char [size];
790
791					memset((void*)extendedPoseInfo, 0, size);
792					extendedPoseInfo->fWorkspaces = 0xffffffff;
793					extendedPoseInfo->fInvisible = false;
794					extendedPoseInfo->fShowFromBootOnly = false;
795					extendedPoseInfo->fNumFrames = 0;
796				}
797				ASSERT(extendedPoseInfo);
798
799				extendedPoseInfo->SetLocationForFrame(pose->Location(this),
800					*frameIfDesktop);
801				extendedPoseInfoSize = extendedPoseInfo->Size();
802			}
803
804			if (model->InitCheck() != B_OK) {
805				delete[] (char*)extendedPoseInfo;
806				continue;
807			}
808
809			ASSERT(model);
810			ASSERT(model->InitCheck() == B_OK);
811			// special handling for "root" disks icon
812			// and Trash pose on Desktop directory
813			bool isTrash = model->IsTrash() && IsDesktopView();
814			if (model->IsRoot() || isTrash) {
815				BDirectory deskDir;
816				if (FSGetDeskDir(&deskDir) == B_OK) {
817					const char* poseInfoAttr = isTrash ? kAttrTrashPoseInfo
818						: kAttrDisksPoseInfo;
819					const char* poseInfoAttrForeign = isTrash
820						? kAttrTrashPoseInfoForeign
821						: kAttrDisksPoseInfoForeign;
822					if (deskDir.WriteAttr(poseInfoAttr, B_RAW_TYPE, 0,
823						&poseInfo, sizeof(poseInfo)) == sizeof(poseInfo)) {
824						// nuke opposite endianness
825						deskDir.RemoveAttr(poseInfoAttrForeign);
826					}
827
828					if (!isTrash && isDesktop
829						&& deskDir.WriteAttr(kAttrExtendedDisksPoseInfo,
830						B_RAW_TYPE, 0, extendedPoseInfo, extendedPoseInfoSize)
831							== (ssize_t)extendedPoseInfoSize) {
832						// nuke opposite endianness
833						deskDir.RemoveAttr(kAttrExtendedDisksPoseInfoForegin);
834					}
835				}
836			} else {
837				model->WriteAttrKillForeign(kAttrPoseInfo,
838					kAttrPoseInfoForeign, B_RAW_TYPE, 0, &poseInfo,
839					sizeof(poseInfo));
840
841				if (isDesktop) {
842					model->WriteAttrKillForeign(kAttrExtendedPoseInfo,
843						kAttrExtendedPoseInfoForegin,
844						B_RAW_TYPE, 0, extendedPoseInfo,
845						extendedPoseInfoSize);
846				}
847			}
848
849			delete[] (char*)extendedPoseInfo;
850				// TODO: fix up this mess
851		}
852	}
853}
854
855
856void
857BPoseView::StartWatching()
858{
859	// watch volumes
860	TTracker::WatchNode(NULL, B_WATCH_MOUNT, this);
861
862	Model* targetModel = TargetModel();
863	if (targetModel != NULL)
864		TTracker::WatchNode(targetModel->NodeRef(), B_WATCH_ATTR, this);
865
866	BMimeType::StartWatching(BMessenger(this));
867}
868
869
870void
871BPoseView::StopWatching()
872{
873	stop_watching(this);
874	BMimeType::StopWatching(BMessenger(this));
875}
876
877
878void
879BPoseView::DetachedFromWindow()
880{
881	if (fTitleView && !fTitleView->Window())
882		delete fTitleView;
883
884	TTracker* tracker = dynamic_cast<TTracker*>(be_app);
885	if (tracker != NULL && tracker->Lock()) {
886		tracker->StopWatching(this, kShowSelectionWhenInactiveChanged);
887		tracker->StopWatching(this, kTransparentSelectionChanged);
888		tracker->StopWatching(this, kSortFolderNamesFirstChanged);
889		tracker->StopWatching(this, kHideDotFilesChanged);
890		tracker->StopWatching(this, kTypeAheadFilteringChanged);
891		tracker->Unlock();
892	}
893
894	std::set<thread_id> addPosesThreads(fAddPosesThreads);
895	fAddPosesThreads.clear();
896		// The threads check periodically if they are still valid,
897		// and clearing the list makes them all "invalid."
898	std::set<thread_id>::iterator it;
899	for (it = addPosesThreads.begin(); it != addPosesThreads.end(); it++) {
900		UnlockLooper();
901		wait_for_thread(*it, NULL);
902		LockLooper();
903	}
904
905	StopWatching();
906	CommitActivePose();
907	SavePoseLocations();
908
909	FSClipboardStopWatch(this);
910}
911
912
913void
914BPoseView::Pulse()
915{
916	BContainerWindow* window = ContainerWindow();
917	if (window == NULL)
918		return;
919
920	window->PulseTaskLoop();
921		// make sure task loop gets pulsed properly, if installed
922
923	// update item count view in window if necessary
924	UpdateCount();
925
926	if (fAutoScrollState != kAutoScrollOff)
927		HandleAutoScroll();
928
929	// do we need to update scrollbars?
930	BRect extent = Extent();
931	if ((fLastExtent != extent) || (fLastLeftTop != LeftTop())) {
932		uint32 buttons;
933		BPoint mouse;
934		GetMouse(&mouse, &buttons);
935		if (buttons == 0) {
936			UpdateScrollRange();
937			fLastExtent = extent;
938			fLastLeftTop = LeftTop();
939		}
940	}
941
942	// do we have a TextWidget waiting for expiracy of its double-click
943	// check?
944	if (fTextWidgetToCheck != NULL)
945		fTextWidgetToCheck->CheckExpiration();
946}
947
948
949void
950BPoseView::ScrollTo(BPoint where)
951{
952	_inherited::ScrollTo(where);
953
954	// keep the view state in sync
955	if (ViewMode() == kListMode)
956		fViewState->SetListOrigin(LeftTop());
957	else
958		fViewState->SetIconOrigin(LeftTop());
959}
960
961
962void
963BPoseView::AttachedToWindow()
964{
965	fIsDesktopWindow = dynamic_cast<BDeskWindow*>(Window()) != NULL;
966	if (fIsDesktopWindow)
967		AddFilter(new TPoseViewFilter(this));
968	else {
969		SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR);
970		SetLowUIColor(ViewUIColor());
971	}
972
973	AddFilter(new ShortcutFilter(B_RETURN, B_OPTION_KEY, kOpenSelection,
974		this));
975		// add Option-Return as a shortcut filter because AddShortcut
976		// doesn't allow us to have shortcuts without Command yet
977	AddFilter(new ShortcutFilter(B_ESCAPE, 0, B_CANCEL, this));
978		// Escape key, used to abort an on-going clipboard cut or filtering
979	AddFilter(new ShortcutFilter(B_ESCAPE, B_SHIFT_KEY,
980		kCancelSelectionToClipboard, this));
981		// Escape + SHIFT will remove current selection from clipboard,
982		// or all poses from current folder if 0 selected
983
984	AddFilter(new LongAndDragTrackingFilter(kMsgMouseLongDown,
985		kMsgMouseDragged));
986
987	fLastLeftTop = LeftTop();
988	BFont font(be_plain_font);
989	font.SetSpacing(B_BITMAP_SPACING);
990	SetFont(&font);
991	GetFont(&sCurrentFont);
992
993	// static - init just once
994	if (sFontHeight == -1) {
995		font.GetHeight(&sFontInfo);
996		sFontHeight = sFontInfo.ascent + sFontInfo.descent
997			+ sFontInfo.leading;
998	}
999
1000	TTracker* tracker = dynamic_cast<TTracker*>(be_app);
1001	if (tracker != NULL && tracker->Lock()) {
1002		tracker->StartWatching(this, kShowSelectionWhenInactiveChanged);
1003		tracker->StartWatching(this, kTransparentSelectionChanged);
1004		tracker->StartWatching(this, kSortFolderNamesFirstChanged);
1005		tracker->StartWatching(this, kHideDotFilesChanged);
1006		tracker->StartWatching(this, kTypeAheadFilteringChanged);
1007		tracker->Unlock();
1008	}
1009
1010	FSClipboardStartWatch(this);
1011}
1012
1013
1014void
1015BPoseView::SetIconPoseHeight()
1016{
1017	switch (ViewMode()) {
1018		case kIconMode:
1019			// IconSize should already be set in MessageReceived()
1020			fIconPoseHeight = ceilf(IconSizeInt() + sFontHeight + 1);
1021			break;
1022
1023		case kMiniIconMode:
1024			fViewState->SetIconSize(B_MINI_ICON);
1025			fIconPoseHeight = ceilf(sFontHeight <
1026				IconSizeInt() ? IconSizeInt() : sFontHeight + 1);
1027			break;
1028
1029		case kListMode:
1030		default:
1031		{
1032			fViewState->SetIconSize(ListIconSize());
1033			fIconPoseHeight = fListElemHeight;
1034			break;
1035		}
1036	}
1037}
1038
1039
1040void
1041BPoseView::GetLayoutInfo(uint32 mode, BPoint* grid, BPoint* offset) const
1042{
1043	switch (mode) {
1044		case kMiniIconMode:
1045			grid->Set(96, 20);
1046			offset->Set(10, 5);
1047			break;
1048
1049		case kIconMode:
1050			grid->Set(IconSizeInt() + 28, IconSizeInt() + 28);
1051			offset->Set(20, 20);
1052			break;
1053
1054		default:
1055			grid->Set(0, 0);
1056			offset->Set(5, 5);
1057			break;
1058	}
1059}
1060
1061
1062void
1063BPoseView::ScrollView(int32 type)
1064{
1065	if (fVScrollBar == NULL)
1066		return;
1067
1068	float max, min;
1069	fVScrollBar->GetSteps(&min, &max);
1070
1071	switch (type) {
1072		case B_HOME:
1073			fVScrollBar->SetValue(0);
1074			break;
1075		case B_END:
1076			fVScrollBar->SetValue(max);
1077			break;
1078		case B_PAGE_UP:
1079			fVScrollBar->SetValue(fVScrollBar->Value() - max);
1080			break;
1081		case B_PAGE_DOWN:
1082			fVScrollBar->SetValue(fVScrollBar->Value() + max);
1083			break;
1084	}
1085}
1086
1087
1088void
1089BPoseView::MakeFocus(bool focused)
1090{
1091	bool invalidate = false;
1092	if (focused != IsFocus())
1093		invalidate = true;
1094
1095	_inherited::MakeFocus(focused);
1096
1097	if (invalidate) {
1098		BorderedView* view = dynamic_cast<BorderedView*>(Parent());
1099		if (view != NULL)
1100			view->PoseViewFocused(focused);
1101	}
1102}
1103
1104
1105BSize
1106BPoseView::MinSize()
1107{
1108	// Between the BTitleView, BCountView, and scrollbars,
1109	// we don't need any extra room.
1110	return BSize(0, 0);
1111}
1112
1113
1114void
1115BPoseView::WindowActivated(bool active)
1116{
1117	if (!active)
1118		CommitActivePose();
1119
1120	if (fShowHideSelection)
1121		ShowSelection(active);
1122
1123	if (active && ActivePose() == NULL && !IsFilePanel())
1124		MakeFocus();
1125}
1126
1127
1128void
1129BPoseView::SetActivePose(BPose* pose)
1130{
1131	if (pose != ActivePose()) {
1132		CommitActivePose();
1133		fActivePose = pose;
1134	}
1135}
1136
1137
1138void
1139BPoseView::CommitActivePose(bool saveChanges)
1140{
1141	BPose* activePose = ActivePose();
1142	if (activePose != NULL) {
1143		int32 index = fPoseList->IndexOf(ActivePose());
1144		if (fFiltering)
1145			index = fFilteredPoseList->IndexOf(ActivePose());
1146
1147		BPoint loc(0, index * fListElemHeight);
1148		if (ViewMode() != kListMode)
1149			loc = ActivePose()->Location(this);
1150
1151		activePose->Commit(saveChanges, loc, this, index);
1152		BPose* pose = fActivePose;
1153		fActivePose = NULL;
1154		if (fFiltering && !FilterPose(pose))
1155			RemoveFilteredPose(pose, index);
1156	}
1157}
1158
1159
1160EntryListBase*
1161BPoseView::InitDirentIterator(const entry_ref* ref)
1162{
1163	// set up a directory iteration
1164	Model sourceModel(ref, false, true);
1165	if (sourceModel.InitCheck() != B_OK)
1166		return NULL;
1167
1168	ASSERT(!sourceModel.IsQuery());
1169	ASSERT(!sourceModel.IsVirtualDirectory());
1170	ASSERT(sourceModel.Node() != NULL);
1171
1172	BDirectory* directory = dynamic_cast<BDirectory*>(sourceModel.Node());
1173
1174	ASSERT(directory != NULL);
1175
1176	if (directory == NULL) {
1177		HideBarberPole();
1178		return NULL;
1179	}
1180
1181	EntryListBase* entryList = new CachedDirectoryEntryList(*directory);
1182	if (entryList->Rewind() != B_OK) {
1183		delete entryList;
1184		HideBarberPole();
1185		return NULL;
1186	}
1187
1188	TTracker::WatchNode(sourceModel.NodeRef(), B_WATCH_DIRECTORY
1189		| B_WATCH_NAME | B_WATCH_STAT | B_WATCH_ATTR, this);
1190
1191	return entryList;
1192}
1193
1194
1195void
1196BPoseView::ReturnDirentIterator(EntryListBase* iterator)
1197{
1198	delete iterator;
1199}
1200
1201
1202uint32
1203BPoseView::WatchNewNodeMask()
1204{
1205	return B_WATCH_STAT | B_WATCH_INTERIM_STAT | B_WATCH_ATTR;
1206}
1207
1208
1209status_t
1210BPoseView::WatchNewNode(const node_ref* item)
1211{
1212	return WatchNewNode(item, WatchNewNodeMask(), BMessenger(this));
1213}
1214
1215
1216status_t
1217BPoseView::WatchNewNode(const node_ref* item, uint32 mask, BMessenger messenger)
1218{
1219	status_t result = TTracker::WatchNode(item, mask, messenger);
1220
1221#if DEBUG
1222	if (result != B_OK)
1223		PRINT(("failed to watch node %s\n", strerror(result)));
1224#endif
1225
1226	return result;
1227}
1228
1229
1230struct AddPosesParams {
1231	BMessenger target;
1232	entry_ref ref;
1233};
1234
1235
1236bool
1237BPoseView::IsValidAddPosesThread(thread_id currentThread) const
1238{
1239	return fAddPosesThreads.find(currentThread) != fAddPosesThreads.end();
1240}
1241
1242
1243void
1244BPoseView::AddPoses(Model* model)
1245{
1246	// if model is zero, PoseView has other means of iterating through all
1247	// the entries that it adds
1248	if (model != NULL) {
1249		TrackerSettings settings;
1250		if (model->IsRoot()) {
1251			AddRootPoses(true, settings.MountSharedVolumesOntoDesktop());
1252			return;
1253		} else if (IsDesktopView()
1254			&& (settings.MountVolumesOntoDesktop() || settings.ShowDisksIcon()
1255				|| (IsFilePanel() && settings.DesktopFilePanelRoot())))
1256			AddRootPoses(true, settings.MountSharedVolumesOntoDesktop());
1257	}
1258
1259	ShowBarberPole();
1260
1261	AddPosesParams* params = new AddPosesParams();
1262	BMessenger tmp(this);
1263	params->target = tmp;
1264
1265	if (model != NULL)
1266		params->ref = *model->EntryRef();
1267
1268	thread_id addPosesThread = spawn_thread(&BPoseView::AddPosesTask,
1269		"add poses", B_DISPLAY_PRIORITY, params);
1270
1271	if (addPosesThread >= B_OK) {
1272		fAddPosesThreads.insert(addPosesThread);
1273		resume_thread(addPosesThread);
1274	} else
1275		delete params;
1276}
1277
1278
1279class AutoLockingMessenger {
1280	// Note:
1281	// this locker requires that you lock/unlock the messenger and associated
1282	// looper only through the autolocker interface, otherwise the hasLock
1283	// flag gets out of sync
1284	//
1285	// Also, this class represents the entire BMessenger, not just it's
1286	// autolocker (unlike MessengerAutoLocker)
1287	public:
1288		AutoLockingMessenger(const BMessenger &target, bool lockLater = false)
1289			:
1290			messenger(target),
1291			hasLock(false)
1292		{
1293			if (!lockLater)
1294				hasLock = messenger.LockTarget();
1295		}
1296
1297		~AutoLockingMessenger()
1298		{
1299			if (hasLock) {
1300				BLooper* looper;
1301				messenger.Target(&looper);
1302				ASSERT(looper->IsLocked());
1303				looper->Unlock();
1304			}
1305		}
1306
1307		bool Lock()
1308		{
1309			if (!hasLock)
1310				hasLock = messenger.LockTarget();
1311
1312			return hasLock;
1313		}
1314
1315		bool IsLocked() const
1316		{
1317			return hasLock;
1318		}
1319
1320		void Unlock()
1321		{
1322			if (hasLock) {
1323				BLooper* looper;
1324				messenger.Target(&looper);
1325				ASSERT(looper);
1326				looper->Unlock();
1327				hasLock = false;
1328			}
1329		}
1330
1331		BLooper* Looper() const
1332		{
1333			BLooper* looper;
1334			messenger.Target(&looper);
1335			return looper;
1336		}
1337
1338		BHandler* Handler() const
1339		{
1340			ASSERT(hasLock);
1341			return messenger.Target(0);
1342		}
1343
1344		BMessenger Target() const
1345		{
1346			return messenger;
1347		}
1348
1349	private:
1350		BMessenger messenger;
1351		bool hasLock;
1352};
1353
1354
1355class failToLock { /* exception in AddPoses */ };
1356
1357
1358status_t
1359BPoseView::AddPosesTask(void* castToParams)
1360{
1361	// AddPosesTask reads a bunch of models and passes them off to
1362	// the pose placing and drawing routine.
1363
1364	AddPosesParams* params = (AddPosesParams*)castToParams;
1365	BMessenger target(params->target);
1366	entry_ref ref(params->ref);
1367
1368	delete params;
1369
1370	AutoLockingMessenger lock(target);
1371
1372	if (!lock.IsLocked())
1373		return B_ERROR;
1374
1375	thread_id threadID = find_thread(NULL);
1376
1377	BPoseView* view = dynamic_cast<BPoseView*>(lock.Handler());
1378	ThrowOnAssert(view != NULL);
1379
1380	BWindow* window = dynamic_cast<BWindow*>(lock.Looper());
1381	ThrowOnAssert(window != NULL);
1382
1383	// allocate the iterator we will use for adding poses; this
1384	// can be a directory or any other collection of entry_refs, such
1385	// as results of a query; subclasses override this to provide
1386	// other than standard directory iterations
1387	EntryListBase* container = view->InitDirentIterator(&ref);
1388	if (container == NULL) {
1389		view->HideBarberPole();
1390		return B_ERROR;
1391	}
1392
1393	AddPosesResult* posesResult = new AddPosesResult;
1394	posesResult->fCount = 0;
1395	int32 modelChunkIndex = -1;
1396	bigtime_t nextChunkTime = 0;
1397	uint32 watchMask = view->WatchNewNodeMask();
1398
1399	bool hideDotFiles = TrackerSettings().HideDotFiles();
1400#if DEBUG
1401	for (int32 index = 0; index < kMaxAddPosesChunk; index++)
1402		posesResult->fModels[index] = (Model*)0xdeadbeef;
1403#endif
1404
1405	try {
1406		for (;;) {
1407			lock.Unlock();
1408
1409			status_t result = B_OK;
1410			char entBuf[1024];
1411			dirent* eptr = (dirent*)entBuf;
1412			Model* model = 0;
1413			node_ref dirNode;
1414			node_ref itemNode;
1415
1416			int32 count = container->GetNextDirents(eptr, 1024, 1);
1417			if (count <= 0 && modelChunkIndex == -1)
1418				break;
1419
1420			if (count > 0) {
1421				ASSERT(count == 1);
1422
1423				if ((!hideDotFiles && (!strcmp(eptr->d_name, ".")
1424					|| !strcmp(eptr->d_name, "..")))
1425					|| (hideDotFiles && eptr->d_name[0] == '.')) {
1426					continue;
1427				}
1428
1429				dirNode.device = eptr->d_pdev;
1430				dirNode.node = eptr->d_pino;
1431				itemNode.device = eptr->d_dev;
1432				itemNode.node = eptr->d_ino;
1433
1434				BPoseView::WatchNewNode(&itemNode, watchMask, lock.Target());
1435					// have to node monitor ahead of time because Model will
1436					// cache up the file type and preferred app
1437					// OK to call when poseView is not locked
1438				model = new Model(&dirNode, &itemNode, eptr->d_name, false);
1439				result = model->InitCheck();
1440				modelChunkIndex++;
1441				posesResult->fModels[modelChunkIndex] = model;
1442			}
1443
1444			// before we access the pose view, lock down the window
1445
1446			if (!lock.Lock()) {
1447				PRINT(("failed to lock\n"));
1448				posesResult->fCount = modelChunkIndex + 1;
1449				throw failToLock();
1450			}
1451
1452			if (!view->IsValidAddPosesThread(threadID)) {
1453				// this handles the case of a file panel when the directory is
1454				// switched and an old AddPosesTask needs to die.
1455				// we might no longer be the current async thread
1456				// for this view - if not then we're done
1457				view->HideBarberPole();
1458
1459				view->ReturnDirentIterator(container);
1460				container = NULL;
1461
1462				// for now use the same cleanup as failToLock does
1463				posesResult->fCount = modelChunkIndex + 1;
1464				throw failToLock();
1465			}
1466
1467			if (count > 0) {
1468				// try to watch the model, no matter what
1469
1470				if (result != B_OK) {
1471					// failed to init pose, model is a zombie, add to zombie
1472					// list
1473					PRINT(("1 adding model %s to zombie list, error %s\n",
1474						model->Name(), strerror(model->InitCheck())));
1475					view->fZombieList->AddItem(model);
1476					modelChunkIndex--;
1477					continue;
1478				}
1479
1480				view->ReadPoseInfo(model,
1481					&posesResult->fPoseInfos[modelChunkIndex]);
1482
1483				if (!PoseVisible(model,
1484					&posesResult->fPoseInfos[modelChunkIndex])) {
1485					modelChunkIndex--;
1486					continue;
1487				}
1488
1489				if (model->IsSymLink())
1490					view->CreateSymlinkPoseTarget(model);
1491			}
1492
1493			bigtime_t now = system_time();
1494
1495			if (count <= 0 || modelChunkIndex >= kMaxAddPosesChunk - 1
1496				|| now > nextChunkTime) {
1497				// keep getting models until we get <kMaxAddPosesChunk> of them
1498				// or until 300000 runs out
1499
1500				ASSERT(modelChunkIndex >= 0);
1501
1502				// send of the created poses
1503
1504				posesResult->fCount = modelChunkIndex + 1;
1505				BMessage creationData(kAddNewPoses);
1506				creationData.AddPointer("currentPoses", posesResult);
1507				creationData.AddRef("ref", &ref);
1508
1509				lock.Target().SendMessage(&creationData);
1510
1511				modelChunkIndex = -1;
1512				nextChunkTime = now + 300000;
1513
1514				posesResult = new AddPosesResult;
1515				posesResult->fCount = 0;
1516
1517				snooze(500);
1518					// be nice
1519			}
1520
1521			if (count <= 0)
1522				break;
1523		}
1524
1525		BMessage finishedSending(kAddPosesCompleted);
1526		lock.Target().SendMessage(&finishedSending);
1527
1528	} catch (failToLock) {
1529		// we are here because the window got closed or otherwise failed to
1530		// lock
1531
1532		PRINT(("add_poses cleanup \n"));
1533		// failed to lock window, bail
1534		delete posesResult;
1535		delete container;
1536
1537		return B_ERROR;
1538	}
1539
1540	ASSERT(modelChunkIndex == -1);
1541
1542	delete posesResult;
1543
1544	if (lock.Lock()) {
1545		view->ReturnDirentIterator(container);
1546		view->fAddPosesThreads.erase(threadID);
1547	} else
1548		delete container;
1549
1550	return B_OK;
1551}
1552
1553
1554void
1555BPoseView::AddRootPoses(bool watchIndividually, bool mountShared)
1556{
1557	BVolumeRoster roster;
1558	roster.Rewind();
1559	BVolume volume;
1560
1561	if (TrackerSettings().ShowDisksIcon() && !TargetModel()->IsRoot()) {
1562		BEntry entry("/");
1563		Model model(&entry);
1564		if (model.InitCheck() == B_OK) {
1565			BMessage monitorMsg;
1566			monitorMsg.what = B_NODE_MONITOR;
1567
1568			monitorMsg.AddInt32("opcode", B_ENTRY_CREATED);
1569
1570			monitorMsg.AddInt32("device", model.NodeRef()->device);
1571			monitorMsg.AddInt64("node", model.NodeRef()->node);
1572			monitorMsg.AddInt64("directory", model.EntryRef()->directory);
1573			monitorMsg.AddString("name", model.EntryRef()->name);
1574			if (Window())
1575				Window()->PostMessage(&monitorMsg, this);
1576		}
1577	} else {
1578		while (roster.GetNextVolume(&volume) == B_OK) {
1579			if (!volume.IsPersistent())
1580				continue;
1581
1582	 		if (volume.IsShared() && !mountShared)
1583				continue;
1584
1585			CreateVolumePose(&volume, watchIndividually);
1586		}
1587	}
1588
1589	SortPoses();
1590	UpdateCount();
1591	Invalidate();
1592}
1593
1594
1595void
1596BPoseView::RemoveRootPoses()
1597{
1598	int32 index;
1599	int32 count = fPoseList->CountItems();
1600	for (index = 0; index < count;) {
1601		BPose* pose = fPoseList->ItemAt(index);
1602		if (pose != NULL) {
1603			Model* model = pose->TargetModel();
1604			if (model != NULL) {
1605				if (model->IsVolume()) {
1606					DeletePose(model->NodeRef());
1607					count--;
1608				} else
1609					index++;
1610			}
1611		}
1612	}
1613
1614	SortPoses();
1615	UpdateCount();
1616	Invalidate();
1617}
1618
1619
1620void
1621BPoseView::AddTrashPoses()
1622{
1623	// the trash window needs to display a union of all the
1624	// trash folders from all the mounted volumes
1625	BVolumeRoster volRoster;
1626	volRoster.Rewind();
1627	BVolume volume;
1628	while (volRoster.GetNextVolume(&volume) == B_OK) {
1629		if (!volume.IsPersistent())
1630			continue;
1631
1632		BDirectory trashDir;
1633		BEntry entry;
1634		if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK
1635			&& trashDir.GetEntry(&entry) == B_OK) {
1636			Model model(&entry);
1637			if (model.InitCheck() == B_OK)
1638				AddPoses(&model);
1639		}
1640	}
1641}
1642
1643
1644void
1645BPoseView::AddPosesCompleted()
1646{
1647	BContainerWindow* containerWindow = ContainerWindow();
1648	if (containerWindow != NULL)
1649		containerWindow->AddMimeTypesToMenu();
1650
1651	// if we're not in icon mode then we need to check for poses that
1652	// were "auto" placed to see if they overlap with other icons
1653	if (ViewMode() != kListMode)
1654		CheckAutoPlacedPoses();
1655
1656	UpdateScrollRange();
1657	HideBarberPole();
1658
1659	// make sure that the last item in the list is not placed
1660	// above the top of the view (leaving you with an empty window)
1661	if (ViewMode() == kListMode) {
1662		BRect bounds(Bounds());
1663		float lastItemTop = (CurrentPoseList()->CountItems() - 1)
1664			* fListElemHeight;
1665		if (bounds.top > lastItemTop)
1666			BView::ScrollTo(bounds.left, std::max(lastItemTop, 0.0f));
1667	}
1668}
1669
1670
1671void
1672BPoseView::CreateVolumePose(BVolume* volume, bool watchIndividually)
1673{
1674	if (volume->InitCheck() != B_OK || !volume->IsPersistent()) {
1675		// We never want to create poses for those volumes; the file
1676		// system root, /pipe, /dev, etc. are all non-persistent
1677		return;
1678	}
1679
1680	BDirectory root;
1681	if (volume->GetRootDirectory(&root) != B_OK)
1682		return;
1683
1684	BEntry entry;
1685	root.GetEntry(&entry);
1686
1687	entry_ref ref;
1688	entry.GetRef(&ref);
1689
1690	// If the volume is mounted at a directory of a persistent volume, we don't
1691	// want it on the desktop or in the disks window.
1692	BVolume parentVolume(ref.device);
1693	if (parentVolume.InitCheck() == B_OK && parentVolume.IsPersistent())
1694		return;
1695
1696	node_ref itemNode;
1697	root.GetNodeRef(&itemNode);
1698
1699	node_ref dirNode;
1700	dirNode.device = ref.device;
1701	dirNode.node = ref.directory;
1702
1703	BPose* pose = EntryCreated(&dirNode, &itemNode, ref.name, 0);
1704	if (pose != NULL && watchIndividually) {
1705		// make sure volume names still get watched, even though
1706		// they are on the desktop which is not their physical parent
1707		pose->TargetModel()->WatchVolumeAndMountPoint(B_WATCH_NAME
1708			| B_WATCH_STAT | B_WATCH_ATTR, this);
1709	}
1710}
1711
1712
1713void
1714BPoseView::CreateTrashPose()
1715{
1716	BVolume volume;
1717	if (BVolumeRoster().GetBootVolume(&volume) == B_OK) {
1718		BDirectory trash;
1719		BEntry entry;
1720		node_ref ref;
1721		if (FSGetTrashDir(&trash, volume.Device()) == B_OK
1722			&& trash.GetEntry(&entry) == B_OK
1723			&& entry.GetNodeRef(&ref) == B_OK) {
1724			WatchNewNode(&ref);
1725			Model* model = new Model(&entry);
1726			PoseInfo info;
1727			ReadPoseInfo(model, &info);
1728			CreatePose(model, &info, false, NULL, NULL, true);
1729		}
1730	}
1731}
1732
1733
1734BPose*
1735BPoseView::CreatePose(Model* model, PoseInfo* poseInfo, bool insertionSort,
1736	int32* indexPtr, BRect* boundsPointer, bool forceDraw)
1737{
1738	BPose* result;
1739	CreatePoses(&model, poseInfo, 1, &result, insertionSort, indexPtr,
1740		boundsPointer, forceDraw);
1741
1742	return result;
1743}
1744
1745
1746void
1747BPoseView::FinishPendingScroll(float &listViewScrollBy, BRect srcRect)
1748{
1749	if (listViewScrollBy == 0.0)
1750		return;
1751
1752	// copy top contents to bottom and
1753	// redraw from top to top of part that could be copied
1754
1755	if (srcRect.Width() > listViewScrollBy) {
1756		BRect dstRect = srcRect;
1757		srcRect.bottom -= listViewScrollBy;
1758		dstRect.top += listViewScrollBy;
1759		CopyBits(srcRect, dstRect);
1760		listViewScrollBy = 0;
1761		srcRect.bottom = dstRect.top;
1762	}
1763	SynchronousUpdate(srcRect);
1764}
1765
1766
1767bool
1768BPoseView::AddPosesThreadValid(const entry_ref* ref) const
1769{
1770	return *(TargetModel()->EntryRef()) == *ref || ContainerWindow()->IsTrash();
1771}
1772
1773
1774void
1775BPoseView::AddPoseToList(PoseList* list, bool visibleList, bool insertionSort,
1776	BPose* pose, BRect &viewBounds, float &listViewScrollBy, bool forceDraw,
1777	int32* indexPtr)
1778{
1779	int32 poseIndex = list->CountItems();
1780
1781	BRect poseBounds;
1782	bool havePoseBounds = false;
1783	bool addedItem = false;
1784	bool needToDraw = true;
1785
1786	if (insertionSort && poseIndex > 0) {
1787		int32 orientation = BSearchList(list, pose, &poseIndex, poseIndex);
1788
1789		if (orientation == kInsertAfter)
1790			poseIndex++;
1791
1792		if (visibleList) {
1793			// we only care about the positions if this is a visible list
1794			poseBounds = CalcPoseRectList(pose, poseIndex);
1795			havePoseBounds = true;
1796
1797			// Simple optimization: if the new pose bounds is completely below
1798			// the current view bounds, we do not need to draw.
1799			if (poseBounds.top > viewBounds.bottom) {
1800				needToDraw = false;
1801			} else {
1802				// The new pose may need to be placed where another pose already
1803				// is. This code creates some rects where we either need to
1804				// slide some already drawn poses down, or at least update the
1805				// rect where the new pose is.
1806				BRect srcRect(Extent());
1807				srcRect.top = poseBounds.top;
1808				srcRect = srcRect & viewBounds;
1809				BRect destRect(srcRect);
1810				destRect.OffsetBy(0, fListElemHeight);
1811
1812				// special case the addition of a pose that scrolls
1813				// the extent into the view for the first time:
1814				if (destRect.bottom > viewBounds.top
1815					&& destRect.top > destRect.bottom) {
1816					// make destRect valid
1817					destRect.top = viewBounds.top;
1818				}
1819
1820				// TODO: As long as either srcRect or destRect are valid, this
1821				// will always be true because srcRect is built from viewBounds.
1822				// Many times they are not valid, but most of the time they are,
1823				// and in a folder with a lot of contents this causes a lot of
1824				// unnecessary drawing. Hence the optimization above. This all
1825				// just needs to be rethought completely. Similar code is in
1826				// BPoseView::InsertPoseAfter.
1827				if (srcRect.Intersects(viewBounds)
1828					|| destRect.Intersects(viewBounds)) {
1829					// The visual area is affected by the insertion.
1830					// If items have been added above the visual area,
1831					// delay the scrolling. srcRect.bottom holds the
1832					// current Extent(). So if the bottom is still above
1833					// the viewBounds top, it means the view is scrolled
1834					// to show the area below the items that have already
1835					// been added.
1836					if (srcRect.top == viewBounds.top
1837						&& srcRect.bottom >= viewBounds.top
1838						&& poseIndex != 0) {
1839						// if new pose above current view bounds, cache up
1840						// the draw and do it later
1841						listViewScrollBy += fListElemHeight;
1842						needToDraw = false;
1843					} else {
1844						FinishPendingScroll(listViewScrollBy, viewBounds);
1845						list->AddItem(pose, poseIndex);
1846
1847						fMimeTypeListIsDirty = true;
1848						addedItem = true;
1849						if (srcRect.IsValid()) {
1850							// Slide the already drawn bits down.
1851							CopyBits(srcRect, destRect);
1852							// Shrink the srcRect down to the just the part that
1853							// needs to be redrawn.
1854							srcRect.bottom = destRect.top;
1855							SynchronousUpdate(srcRect);
1856						} else {
1857							// This is probably the bottom of the view or just
1858							// got scrolled into view.
1859							SynchronousUpdate(destRect);
1860						}
1861						needToDraw = false;
1862					}
1863				}
1864			}
1865		}
1866	}
1867
1868	if (!addedItem) {
1869		list->AddItem(pose, poseIndex);
1870		fMimeTypeListIsDirty = true;
1871	}
1872
1873	if (visibleList && needToDraw && forceDraw) {
1874		if (!havePoseBounds)
1875			poseBounds = CalcPoseRectList(pose, poseIndex);
1876
1877		if (viewBounds.Intersects(poseBounds))
1878 			SynchronousUpdate(poseBounds);
1879	}
1880
1881	if (indexPtr)
1882		*indexPtr = poseIndex;
1883}
1884
1885
1886void
1887BPoseView::CreatePoses(Model** models, PoseInfo* poseInfoArray, int32 count,
1888	BPose** resultingPoses, bool insertionSort, int32* lastPoseIndexPointer,
1889	BRect* boundsPointer, bool forceDraw)
1890{
1891	// were we passed the bounds of the view?
1892	BRect viewBounds;
1893	if (boundsPointer != NULL)
1894		viewBounds = *boundsPointer;
1895	else
1896		viewBounds = Bounds();
1897
1898	bool clipboardLocked = be_clipboard->Lock();
1899
1900	int32 poseIndex = 0;
1901	uint32 clipboardMode = 0;
1902	float listViewScrollBy = 0;
1903	for (int32 modelIndex = 0; modelIndex < count; modelIndex++) {
1904		Model* model = models[modelIndex];
1905
1906		// pose adopts model and deletes it when done
1907		if (fInsertedNodes.find(*(model->NodeRef())) != fInsertedNodes.end()
1908			|| FindZombie(model->NodeRef())) {
1909			watch_node(model->NodeRef(), B_STOP_WATCHING, this);
1910			delete model;
1911			if (resultingPoses)
1912				resultingPoses[modelIndex] = NULL;
1913
1914			continue;
1915		} else
1916			fInsertedNodes.insert(*(model->NodeRef()));
1917
1918		if ((clipboardMode = FSClipboardFindNodeMode(model, !clipboardLocked,
1919				true)) != 0 && !HasPosesInClipboard()) {
1920			SetHasPosesInClipboard(true);
1921		}
1922
1923		model->OpenNode();
1924		ASSERT(model->IsNodeOpen());
1925		PoseInfo* poseInfo = &poseInfoArray[modelIndex];
1926		BPose* pose = new BPose(model, this, clipboardMode);
1927
1928		if (resultingPoses)
1929			resultingPoses[modelIndex] = pose;
1930
1931		// set location from poseinfo if saved loc was for this dir
1932		if (poseInfo->fInitedDirectory != -1LL) {
1933			PinPointToValidRange(poseInfo->fLocation);
1934			pose->SetLocation(poseInfo->fLocation, this);
1935			AddToVSList(pose);
1936		}
1937
1938		BRect poseBounds;
1939
1940		switch (ViewMode()) {
1941			case kListMode:
1942			{
1943				AddPoseToList(fPoseList, !fFiltering, insertionSort, pose,
1944					viewBounds, listViewScrollBy, forceDraw, &poseIndex);
1945
1946				if (fFiltering && FilterPose(pose)) {
1947					AddPoseToList(fFilteredPoseList, true, insertionSort, pose,
1948						viewBounds, listViewScrollBy, forceDraw, &poseIndex);
1949				}
1950
1951				break;
1952			}
1953
1954			case kIconMode:
1955			case kMiniIconMode:
1956				if (poseInfo->fInitedDirectory == -1LL || fAlwaysAutoPlace) {
1957					if (pose->HasLocation())
1958						RemoveFromVSList(pose);
1959
1960					PlacePose(pose, viewBounds);
1961
1962					// we set a flag in the pose here to signify that we were
1963					// auto placed - after adding all poses to window, we're
1964					// going to go back and make sure that the auto placed poses
1965					// don't overlap previously positioned icons. If so, we'll
1966					// move them to new positions.
1967					if (!fAlwaysAutoPlace)
1968						pose->SetAutoPlaced(true);
1969
1970					AddToVSList(pose);
1971				}
1972
1973				// add item to list and draw if necessary
1974				fPoseList->AddItem(pose);
1975				fMimeTypeListIsDirty = true;
1976
1977				poseBounds = pose->CalcRect(this);
1978
1979				if (fEnsurePosesVisible && !viewBounds.Intersects(poseBounds)) {
1980					viewBounds.InsetBy(20, 20);
1981					RemoveFromVSList(pose);
1982					BPoint loc(pose->Location(this));
1983					loc.ConstrainTo(viewBounds);
1984					pose->SetLocation(loc, this);
1985					pose->SetSaveLocation();
1986					AddToVSList(pose);
1987					poseBounds = pose->CalcRect(this);
1988					viewBounds.InsetBy(-20, -20);
1989				}
1990
1991				if (forceDraw && viewBounds.Intersects(poseBounds))
1992					Invalidate(poseBounds);
1993
1994				// if this is the first item then we set extent here
1995				if (!fExtent.IsValid())
1996					fExtent = poseBounds;
1997				else
1998					AddToExtent(poseBounds);
1999
2000				break;
2001		}
2002		if (model->IsSymLink())
2003			model->ResolveIfLink()->CloseNode();
2004
2005		model->CloseNode();
2006	}
2007
2008	if (clipboardLocked)
2009		be_clipboard->Unlock();
2010
2011	FinishPendingScroll(listViewScrollBy, viewBounds);
2012
2013	if (lastPoseIndexPointer != NULL)
2014		*lastPoseIndexPointer = poseIndex;
2015}
2016
2017
2018bool
2019BPoseView::PoseVisible(const Model* model, const PoseInfo* poseInfo)
2020{
2021	return !poseInfo->fInvisible;
2022}
2023
2024
2025bool
2026BPoseView::ShouldShowPose(const Model* model, const PoseInfo* poseInfo)
2027{
2028	if (!PoseVisible(model, poseInfo))
2029		return false;
2030
2031	// check filter before adding item
2032	if (!fRefFilter)
2033		return true;
2034
2035	struct stat_beos stat;
2036	convert_to_stat_beos(model->StatBuf(), &stat);
2037
2038	return fRefFilter->Filter(model->EntryRef(), model->Node(), &stat,
2039		model->MimeType());
2040}
2041
2042
2043const char*
2044BPoseView::MimeTypeAt(int32 index)
2045{
2046	if (fMimeTypeListIsDirty)
2047		RefreshMimeTypeList();
2048
2049	return fMimeTypeList->ItemAt(index)->String();
2050}
2051
2052
2053int32
2054BPoseView::CountMimeTypes()
2055{
2056	if (fMimeTypeListIsDirty)
2057		RefreshMimeTypeList();
2058
2059	return fMimeTypeList->CountItems();
2060}
2061
2062
2063void
2064BPoseView::AddMimeType(const char* mimeType)
2065{
2066	int32 count = fMimeTypeList->CountItems();
2067	for (int32 index = 0; index < count; index++) {
2068		if (*fMimeTypeList->ItemAt(index) == mimeType)
2069			return;
2070	}
2071
2072	fMimeTypeList->AddItem(new BString(mimeType));
2073}
2074
2075
2076void
2077BPoseView::RefreshMimeTypeList()
2078{
2079	fMimeTypeList->MakeEmpty();
2080	fMimeTypeListIsDirty = false;
2081
2082	for (int32 index = 0;; index++) {
2083		BPose* pose = PoseAtIndex(index);
2084		if (pose == NULL)
2085			break;
2086
2087		Model* targetModel = pose->TargetModel();
2088		if (targetModel != NULL)
2089			AddMimeType(targetModel->MimeType());
2090	}
2091}
2092
2093
2094void
2095BPoseView::InsertPoseAfter(BPose* pose, int32* index, int32 orientation,
2096	BRect* invalidRect)
2097{
2098	if (orientation == kInsertAfter) {
2099		// TODO: get rid of this
2100		(*index)++;
2101	}
2102
2103	BRect bounds(Bounds());
2104	// copy the good bits in the list
2105	BRect srcRect(Extent());
2106	srcRect.top = CalcPoseRectList(pose, *index).top;
2107	srcRect = srcRect & bounds;
2108	BRect destRect(srcRect);
2109	destRect.OffsetBy(0, fListElemHeight);
2110
2111	if (srcRect.Intersects(bounds) || destRect.Intersects(bounds))
2112		CopyBits(srcRect, destRect);
2113
2114	// this is the invalid rectangle
2115	srcRect.bottom = destRect.top;
2116	*invalidRect = srcRect;
2117}
2118
2119
2120void
2121BPoseView::DisableScrollBars()
2122{
2123	if (fHScrollBar != NULL)
2124		fHScrollBar->SetTarget((BView*)NULL);
2125
2126	if (fVScrollBar != NULL)
2127		fVScrollBar->SetTarget((BView*)NULL);
2128}
2129
2130
2131void
2132BPoseView::EnableScrollBars()
2133{
2134	if (fHScrollBar != NULL)
2135		fHScrollBar->SetTarget(this);
2136
2137	if (fVScrollBar != NULL)
2138		fVScrollBar->SetTarget(this);
2139}
2140
2141
2142void
2143BPoseView::AddScrollBars()
2144{
2145	fHScrollBar = new TScrollBar("HScrollBar", this, 0, 100);
2146	fVScrollBar = new BScrollBar("VScrollBar", this, 0, 100, B_VERTICAL);
2147}
2148
2149
2150void
2151BPoseView::UpdateCount()
2152{
2153	if (fCountView != NULL)
2154		fCountView->CheckCount();
2155}
2156
2157
2158void
2159BPoseView::MessageReceived(BMessage* message)
2160{
2161	if (message->WasDropped() && HandleMessageDropped(message))
2162		return;
2163
2164	if (HandleScriptingMessage(message))
2165		return;
2166
2167	switch (message->what) {
2168		case kAddNewPoses:
2169		{
2170			AddPosesResult* currentPoses;
2171			entry_ref ref;
2172			if (message->FindPointer("currentPoses",
2173					reinterpret_cast<void**>(&currentPoses)) == B_OK
2174				&& message->FindRef("ref", &ref) == B_OK) {
2175				// check if CreatePoses should be called
2176				// (abort if dir has been switched under normal
2177				// circumstances, ignore in several special cases)
2178				if (AddPosesThreadValid(&ref)) {
2179					CreatePoses(currentPoses->fModels,
2180						currentPoses->fPoseInfos,
2181						currentPoses->fCount, NULL, true, 0, 0, true);
2182					currentPoses->ReleaseModels();
2183				}
2184				delete currentPoses;
2185			}
2186			break;
2187		}
2188
2189		case kAddPosesCompleted:
2190			AddPosesCompleted();
2191			break;
2192
2193		case kRestoreBackgroundImage:
2194			ContainerWindow()->UpdateBackgroundImage();
2195			break;
2196
2197		case B_META_MIME_CHANGED:
2198			NoticeMetaMimeChanged(message);
2199			break;
2200
2201		case B_NODE_MONITOR:
2202		case B_PATH_MONITOR:
2203		case B_QUERY_UPDATE:
2204			if (!FSNotification(message))
2205				pendingNodeMonitorCache.Add(message);
2206			break;
2207
2208		case kIconMode: {
2209			int32 size;
2210			int32 scale;
2211			if (message->FindInt32("size", &size) == B_OK) {
2212				if (size != (int32)IconSizeInt())
2213					fViewState->SetIconSize(size);
2214			} else if (message->FindInt32("scale", &scale) == B_OK
2215				&& fViewState->ViewMode() == kIconMode) {
2216				if (scale == 0 && (int32)IconSizeInt() != 32) {
2217					switch ((int32)IconSizeInt()) {
2218						case 40:
2219							fViewState->SetIconSize(32);
2220							break;
2221
2222						case 48:
2223							fViewState->SetIconSize(40);
2224							break;
2225
2226						case 64:
2227							fViewState->SetIconSize(48);
2228							break;
2229
2230						case 96:
2231							fViewState->SetIconSize(64);
2232							break;
2233
2234						case 128:
2235							fViewState->SetIconSize(96);
2236							break;
2237					}
2238				} else if (scale == 1 && (int32)IconSizeInt() != 128) {
2239					switch ((int32)IconSizeInt()) {
2240						case 32:
2241							fViewState->SetIconSize(40);
2242							break;
2243
2244						case 40:
2245							fViewState->SetIconSize(48);
2246							break;
2247
2248						case 48:
2249							fViewState->SetIconSize(64);
2250							break;
2251
2252						case 64:
2253							fViewState->SetIconSize(96);
2254							break;
2255
2256						case 96:
2257							fViewState->SetIconSize(128);
2258							break;
2259					}
2260				}
2261			} else {
2262				int32 iconSize = fViewState->LastIconSize();
2263				if (iconSize < 32 || iconSize > 128) {
2264					// uninitialized last icon size?
2265					iconSize = 32;
2266				}
2267				fViewState->SetIconSize(iconSize);
2268			}
2269			SetViewMode(message->what);
2270			break;
2271		}
2272
2273		case kListMode:
2274		case kMiniIconMode:
2275			SetViewMode(message->what);
2276			break;
2277
2278		case kMsgMouseDragged:
2279			MouseDragged(message);
2280			break;
2281
2282		case kMsgMouseLongDown:
2283			MouseLongDown(message);
2284			break;
2285
2286		case B_MOUSE_IDLE:
2287			MouseIdle(message);
2288			break;
2289
2290		case B_SELECT_ALL:
2291		{
2292			// Select widget if there is an active one
2293			BTextWidget* widget;
2294			if (ActivePose() && ((widget = ActivePose()->ActiveWidget())) != 0)
2295				widget->SelectAll(this);
2296			else
2297				SelectAll();
2298			break;
2299		}
2300
2301		case B_CUT:
2302		{
2303			Model* targetModel = TargetModel();
2304			if (targetModel != NULL) {
2305				FSClipboardAddPoses(targetModel->NodeRef(), fSelectionList,
2306					kMoveSelectionTo, true);
2307			}
2308			break;
2309		}
2310
2311		case kCutMoreSelectionToClipboard:
2312		{
2313			Model* targetModel = TargetModel();
2314			if (targetModel != NULL) {
2315				FSClipboardAddPoses(targetModel->NodeRef(), fSelectionList,
2316					kMoveSelectionTo, false);
2317			}
2318			break;
2319		}
2320
2321		case B_COPY:
2322		{
2323			Model* targetModel = TargetModel();
2324			if (targetModel != NULL) {
2325				FSClipboardAddPoses(targetModel->NodeRef(), fSelectionList,
2326					kCopySelectionTo, true);
2327			}
2328			break;
2329		}
2330
2331		case kCopyMoreSelectionToClipboard:
2332		{
2333			Model* targetModel = TargetModel();
2334			if (targetModel != NULL) {
2335				FSClipboardAddPoses(targetModel->NodeRef(), fSelectionList,
2336					kCopySelectionTo, false);
2337			}
2338			break;
2339		}
2340
2341		case B_PASTE:
2342			FSClipboardPaste(TargetModel());
2343			break;
2344
2345		case kPasteLinksFromClipboard:
2346			FSClipboardPaste(TargetModel(), kCreateLink);
2347			break;
2348
2349		case B_CANCEL:
2350			if (FSClipboardHasRefs())
2351				FSClipboardClear();
2352			else if (fFiltering)
2353				StopFiltering();
2354			break;
2355
2356		case kCancelSelectionToClipboard:
2357		{
2358			Model* targetModel = TargetModel();
2359			if (targetModel != NULL) {
2360				FSClipboardRemovePoses(targetModel->NodeRef(),
2361					fSelectionList != NULL && fSelectionList->CountItems() > 0
2362						? fSelectionList : fPoseList);
2363			}
2364			break;
2365		}
2366
2367		case kFSClipboardChanges:
2368		{
2369			node_ref node;
2370			message->FindInt32("device", &node.device);
2371			message->FindInt64("directory", &node.node);
2372
2373			Model* targetModel = TargetModel();
2374			if (targetModel != NULL && *targetModel->NodeRef() == node)
2375				UpdatePosesClipboardModeFromClipboard(message);
2376			else if (message->FindBool("clearClipboard")
2377				&& HasPosesInClipboard()) {
2378				// just remove all poses from clipboard
2379				SetHasPosesInClipboard(false);
2380				SetPosesClipboardMode(0);
2381			}
2382			break;
2383		}
2384
2385		case kInvertSelection:
2386			InvertSelection();
2387			break;
2388
2389		case kShowSelectionWindow:
2390			ShowSelectionWindow();
2391			break;
2392
2393		case kDuplicateSelection:
2394			DuplicateSelection();
2395			break;
2396
2397		case kOpenSelection:
2398			OpenSelection();
2399			break;
2400
2401		case kOpenSelectionWith:
2402			OpenSelectionUsing();
2403			break;
2404
2405		case kRestoreFromTrash:
2406			RestoreSelectionFromTrash();
2407			break;
2408
2409		case kDelete:
2410			ExcludeTrashFromSelection();
2411			if (ContainerWindow()->IsTrash())
2412				// if trash delete instantly
2413				DeleteSelection(true, false);
2414			else
2415				DeleteSelection();
2416			break;
2417
2418		case kMoveToTrash:
2419		{
2420			ExcludeTrashFromSelection();
2421			TrackerSettings settings;
2422
2423			if ((modifiers() & B_SHIFT_KEY) != 0
2424				|| settings.DontMoveFilesToTrash()) {
2425				DeleteSelection(true, settings.AskBeforeDeleteFile());
2426			} else
2427				MoveSelectionToTrash();
2428
2429			break;
2430		}
2431
2432		case kCleanupAll:
2433			Cleanup(true);
2434			break;
2435
2436		case kCleanup:
2437			Cleanup();
2438			break;
2439
2440		case kEditQuery:
2441			EditQueries();
2442			break;
2443
2444		case kRunAutomounterSettings:
2445			be_app->PostMessage(message);
2446			break;
2447
2448		case kNewEntryFromTemplate:
2449			if (message->HasRef("refs_template"))
2450				NewFileFromTemplate(message);
2451			break;
2452
2453		case kNewFolder:
2454			NewFolder(message);
2455			break;
2456
2457		case kUnmountVolume:
2458			UnmountSelectedVolumes();
2459			break;
2460
2461		case kEmptyTrash:
2462			FSEmptyTrash();
2463			break;
2464
2465		case kGetInfo:
2466			OpenInfoWindows();
2467			break;
2468
2469		case kIdentifyEntry:
2470		{
2471			bool force;
2472			if (message->FindBool("force", &force) != B_OK)
2473				force = false;
2474
2475			IdentifySelection(force);
2476			break;
2477		}
2478
2479		case kEditItem:
2480		{
2481			if (ActivePose())
2482				break;
2483
2484			BPose* pose = fSelectionList->FirstItem();
2485			if (pose != NULL) {
2486				BPoint where(0,
2487					CurrentPoseList()->IndexOf(pose) * fListElemHeight);
2488				pose->EditFirstWidget(where, this);
2489			}
2490			break;
2491		}
2492
2493		case kOpenParentDir:
2494			OpenParent();
2495			break;
2496
2497		case kCopyAttributes:
2498			if (be_clipboard->Lock()) {
2499				be_clipboard->Clear();
2500				BMessage* data = be_clipboard->Data();
2501				if (data != NULL) {
2502					// copy attributes to the clipboard
2503					BMessage state;
2504					SaveState(state);
2505
2506					BMallocIO stream;
2507					ssize_t size;
2508					if (state.Flatten(&stream, &size) == B_OK) {
2509						data->AddData("application/tracker-columns",
2510							B_MIME_TYPE, stream.Buffer(), size);
2511						be_clipboard->Commit();
2512					}
2513				}
2514				be_clipboard->Unlock();
2515			}
2516			break;
2517
2518		case kPasteAttributes:
2519			if (be_clipboard->Lock()) {
2520				BMessage* data = be_clipboard->Data();
2521				if (data != NULL) {
2522					// find the attributes in the clipboard
2523					const void* buffer;
2524					ssize_t size;
2525					if (data->FindData("application/tracker-columns",
2526							B_MIME_TYPE, &buffer, &size) == B_OK) {
2527						BMessage state;
2528						if (state.Unflatten((const char*)buffer) == B_OK) {
2529							// remove all current columns (one always stays)
2530							BColumn* old;
2531							while ((old = ColumnAt(0)) != NULL) {
2532								if (!RemoveColumn(old, false))
2533									break;
2534							}
2535
2536							// add new columns
2537							for (int32 index = 0; ; index++) {
2538								BColumn* column
2539									= BColumn::InstantiateFromMessage(state,
2540										index);
2541								if (column == NULL)
2542									break;
2543
2544								AddColumn(column);
2545							}
2546
2547							// remove the last old one
2548							RemoveColumn(old, false);
2549
2550							// set sorting mode
2551							BViewState* viewState
2552								= BViewState::InstantiateFromMessage(state);
2553							if (viewState != NULL) {
2554								SetPrimarySort(viewState->PrimarySort());
2555								SetSecondarySort(viewState->SecondarySort());
2556								SetReverseSort(viewState->ReverseSort());
2557
2558								SortPoses();
2559								Invalidate();
2560							}
2561						}
2562					}
2563				}
2564				be_clipboard->Unlock();
2565			}
2566			break;
2567
2568		case kArrangeBy:
2569		{
2570			uint32 attrHash;
2571			if (message->FindInt32("attr_hash", (int32*)&attrHash) == B_OK) {
2572				if (ColumnFor(attrHash) == NULL)
2573					HandleAttrMenuItemSelected(message);
2574
2575				if (PrimarySort() == attrHash)
2576					attrHash = 0;
2577
2578				SetPrimarySort(attrHash);
2579				SetSecondarySort(0);
2580				Cleanup(true);
2581			}
2582			break;
2583		}
2584
2585		case kArrangeReverseOrder:
2586			SetReverseSort(!fViewState->ReverseSort());
2587			Cleanup(true);
2588			break;
2589
2590		case kAttributeItem:
2591			HandleAttrMenuItemSelected(message);
2592			break;
2593
2594		case kAddPrinter:
2595			be_app->PostMessage(message);
2596			break;
2597
2598		case kMakeActivePrinter:
2599			SetDefaultPrinter();
2600			break;
2601
2602#if DEBUG
2603		case kTestIconCache:
2604			RunIconCacheTests();
2605			break;
2606
2607		case 'dbug':
2608		{
2609			int32 count = fSelectionList->CountItems();
2610			for (int32 index = 0; index < count; index++)
2611				fSelectionList->ItemAt(index)->PrintToStream();
2612
2613			break;
2614		}
2615#ifdef CHECK_OPEN_MODEL_LEAKS
2616		case 'dpfl':
2617			DumpOpenModels(false);
2618			break;
2619
2620		case 'dpfL':
2621			DumpOpenModels(true);
2622			break;
2623#endif
2624#endif
2625
2626		case kCheckTypeahead:
2627		{
2628			bigtime_t doubleClickSpeed;
2629			get_click_speed(&doubleClickSpeed);
2630			if (system_time() - fLastKeyTime > (doubleClickSpeed * 2)) {
2631				fCountView->SetTypeAhead("");
2632				delete fKeyRunner;
2633				fKeyRunner = NULL;
2634			}
2635			break;
2636		}
2637
2638		case B_OBSERVER_NOTICE_CHANGE:
2639		{
2640			int32 observerWhat;
2641			if (message->FindInt32("be:observe_change_what", &observerWhat)
2642					== B_OK) {
2643				switch (observerWhat) {
2644					case kDateFormatChanged:
2645						UpdateDateColumns(message);
2646						break;
2647
2648					case kVolumesOnDesktopChanged:
2649						AdaptToVolumeChange(message);
2650						break;
2651
2652					case kDesktopIntegrationChanged:
2653						AdaptToDesktopIntegrationChange(message);
2654						break;
2655
2656					case kShowSelectionWhenInactiveChanged:
2657					{
2658						// Updating the settings here will propagate
2659						// setting changed from Tracker to all open
2660						// file panels as well
2661						bool showSelection;
2662						if (message->FindBool("ShowSelectionWhenInactive",
2663								&showSelection) == B_OK) {
2664							fShowSelectionWhenInactive = showSelection;
2665							TrackerSettings().SetShowSelectionWhenInactive(
2666								fShowSelectionWhenInactive);
2667						}
2668						Invalidate();
2669						break;
2670					}
2671
2672					case kTransparentSelectionChanged:
2673					{
2674						bool transparentSelection;
2675						if (message->FindBool("TransparentSelection",
2676								&transparentSelection) == B_OK) {
2677							fTransparentSelection = transparentSelection;
2678							TrackerSettings().SetTransparentSelection(
2679								fTransparentSelection);
2680						}
2681						break;
2682					}
2683
2684					case kSortFolderNamesFirstChanged:
2685						if (ViewMode() == kListMode) {
2686							TrackerSettings settings;
2687							bool sortFolderNamesFirst;
2688							if (message->FindBool("SortFolderNamesFirst",
2689								&sortFolderNamesFirst) == B_OK) {
2690								settings.SetSortFolderNamesFirst(
2691									sortFolderNamesFirst);
2692							}
2693							NameAttributeText::SetSortFolderNamesFirst(
2694								settings.SortFolderNamesFirst());
2695							RealNameAttributeText::SetSortFolderNamesFirst(
2696								settings.SortFolderNamesFirst());
2697							SortPoses();
2698							Invalidate();
2699						}
2700						break;
2701
2702					case kHideDotFilesChanged:
2703					{
2704						TrackerSettings settings;
2705						bool hideDotFiles;
2706						if (message->FindBool("HideDotFiles",
2707								&hideDotFiles) == B_OK) {
2708							settings.SetHideDotFiles(hideDotFiles);
2709						}
2710
2711						Refresh();
2712						break;
2713					}
2714
2715					case kTypeAheadFilteringChanged:
2716					{
2717						TrackerSettings settings;
2718						bool typeAheadFiltering;
2719						if (message->FindBool("TypeAheadFiltering",
2720								&typeAheadFiltering) == B_OK) {
2721							settings.SetTypeAheadFiltering(typeAheadFiltering);
2722						}
2723
2724						if (fFiltering && !typeAheadFiltering)
2725							StopFiltering();
2726						break;
2727					}
2728				}
2729			}
2730			break;
2731		}
2732
2733		default:
2734			_inherited::MessageReceived(message);
2735			break;
2736	}
2737}
2738
2739
2740bool
2741BPoseView::RemoveColumn(BColumn* columnToRemove, bool runAlert)
2742{
2743	// make sure last column is not removed
2744	if (CountColumns() == 1) {
2745		if (runAlert) {
2746			BAlert* alert = new BAlert("",
2747				B_TRANSLATE("You must have at least one attribute showing."),
2748				B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
2749			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2750			alert->Go();
2751		}
2752
2753		return false;
2754	}
2755
2756	// column exists so remove it from list
2757	int32 columnIndex = IndexOfColumn(columnToRemove);
2758	float offset = columnToRemove->Offset();
2759
2760	int32 count = fPoseList->CountItems();
2761	for (int32 index = 0; index < count; index++)
2762		fPoseList->ItemAt(index)->RemoveWidget(this, columnToRemove);
2763
2764	fColumnList->RemoveItem(columnToRemove, false);
2765	fTitleView->RemoveTitle(columnToRemove);
2766
2767	float attrWidth = columnToRemove->Width();
2768	delete columnToRemove;
2769
2770	count = CountColumns();
2771	for (int32 index = columnIndex; index < count; index++) {
2772		BColumn* column = ColumnAt(index);
2773		column->SetOffset(column->Offset()
2774			- (attrWidth + kTitleColumnExtraMargin));
2775	}
2776
2777	BRect rect(Bounds());
2778	rect.left = offset;
2779	Invalidate(rect);
2780
2781	ContainerWindow()->MarkAttributeMenu();
2782
2783	if (IsWatchingDateFormatChange()) {
2784		int32 columnCount = CountColumns();
2785		bool anyDateAttributesLeft = false;
2786
2787		for (int32 i = 0; i < columnCount; i++) {
2788			BColumn* column = ColumnAt(i);
2789			if (column->AttrType() == B_TIME_TYPE)
2790				anyDateAttributesLeft = true;
2791
2792			if (anyDateAttributesLeft)
2793				break;
2794		}
2795
2796		if (!anyDateAttributesLeft)
2797			StopWatchDateFormatChange();
2798	}
2799
2800	fStateNeedsSaving = true;
2801
2802	if (fFiltering) {
2803		// the column we removed might just be the one that was used to filter
2804		int32 count = fFilteredPoseList->CountItems();
2805		for (int32 i = count - 1; i >= 0; i--) {
2806			BPose* pose = fFilteredPoseList->ItemAt(i);
2807			if (!FilterPose(pose))
2808				RemoveFilteredPose(pose, i);
2809		}
2810	}
2811
2812	return true;
2813}
2814
2815
2816bool
2817BPoseView::AddColumn(BColumn* newColumn, const BColumn* after)
2818{
2819	if (after == NULL)
2820		after = LastColumn();
2821
2822	// add new column after last column
2823	float offset;
2824	int32 afterColumnIndex;
2825	if (after != NULL) {
2826		offset = after->Offset() + after->Width() + kTitleColumnExtraMargin;
2827		afterColumnIndex = IndexOfColumn(after);
2828	} else {
2829		offset = StartOffset();
2830		afterColumnIndex = CountColumns() - 1;
2831	}
2832
2833	// add the new column
2834	fColumnList->AddItem(newColumn, afterColumnIndex + 1);
2835	fTitleView->AddTitle(newColumn);
2836
2837	BRect rect(Bounds());
2838
2839	// add widget for all visible poses
2840	PoseList* poseList = CurrentPoseList();
2841	int32 count = poseList->CountItems();
2842	int32 startIndex = (int32)(rect.top / fListElemHeight);
2843	BPoint loc(0, startIndex * fListElemHeight);
2844
2845	for (int32 index = startIndex; index < count; index++) {
2846		BPose* pose = poseList->ItemAt(index);
2847		if (pose->WidgetFor(newColumn->AttrHash()) == NULL)
2848			pose->AddWidget(this, newColumn);
2849
2850		loc.y += fListElemHeight;
2851		if (loc.y > rect.bottom)
2852			break;
2853	}
2854
2855	// rearrange column titles to fit new column
2856	newColumn->SetOffset(offset);
2857	float attrWidth = newColumn->Width();
2858
2859	count = CountColumns();
2860	for (int32 index = afterColumnIndex + 2; index < count; index++) {
2861		BColumn* column = ColumnAt(index);
2862		ASSERT(newColumn != column);
2863		column->SetOffset(column->Offset() + (attrWidth
2864			+ kTitleColumnExtraMargin));
2865	}
2866
2867	rect.left = offset;
2868	Invalidate(rect);
2869	ContainerWindow()->MarkAttributeMenu();
2870
2871	// Check if this is a time attribute and if so,
2872	// start watching for changed in time/date format:
2873	if (!IsWatchingDateFormatChange() && newColumn->AttrType() == B_TIME_TYPE)
2874		StartWatchDateFormatChange();
2875
2876	fStateNeedsSaving =  true;
2877
2878	if (fFiltering) {
2879		// the column we added might just add new poses to be showed
2880		fFilteredPoseList->MakeEmpty();
2881		fFiltering = false;
2882		StartFiltering();
2883	}
2884
2885	return true;
2886}
2887
2888
2889void
2890BPoseView::HandleAttrMenuItemSelected(BMessage* message)
2891{
2892	// see if source was a menu item
2893	BMenuItem* item;
2894	if (message->FindPointer("source", (void**)&item) != B_OK)
2895		item = NULL;
2896
2897	// find out which column was selected
2898	uint32 attrHash;
2899	if (message->FindInt32("attr_hash", (int32*)&attrHash) != B_OK)
2900		return;
2901
2902	BColumn* column = ColumnFor(attrHash);
2903	if (column != NULL) {
2904		RemoveColumn(column, true);
2905		return;
2906	} else {
2907		// collect info about selected attribute
2908		const char* attrName;
2909		if (message->FindString("attr_name", &attrName) != B_OK)
2910			return;
2911
2912		uint32 attrType;
2913		if (message->FindInt32("attr_type", (int32*)&attrType) != B_OK)
2914			return;
2915
2916		float attrWidth;
2917		if (message->FindFloat("attr_width", &attrWidth) != B_OK)
2918			return;
2919
2920		alignment attrAlign;
2921		if (message->FindInt32("attr_align", (int32*)&attrAlign) != B_OK)
2922			return;
2923
2924		bool isEditable;
2925		if (message->FindBool("attr_editable", &isEditable) != B_OK)
2926			return;
2927
2928		bool isStatfield;
2929		if (message->FindBool("attr_statfield", &isStatfield) != B_OK)
2930			return;
2931
2932		const char* displayAs;
2933		message->FindString("attr_display_as", &displayAs);
2934
2935		column = new BColumn(item->Label(), 0, attrWidth, attrAlign,
2936			attrName, attrType, displayAs, isStatfield, isEditable);
2937		AddColumn(column);
2938		if (item->Menu()->Supermenu() == NULL)
2939			delete item->Menu();
2940	}
2941}
2942
2943
2944const int32 kSanePoseLocation = 50000;
2945
2946
2947void
2948BPoseView::ReadPoseInfo(Model* model, PoseInfo* poseInfo)
2949{
2950	BModelOpener opener(model);
2951	if (model->Node() == NULL)
2952		return;
2953
2954	ReadAttrResult result = kReadAttrFailed;
2955	BEntry entry;
2956	model->GetEntry(&entry);
2957	bool isTrash = model->IsTrash() && IsDesktopView();
2958
2959	// special case the "root" disks icon
2960	// as well as the trash on desktop
2961	if (model->IsRoot() || isTrash) {
2962		BDirectory dir;
2963		if (FSGetDeskDir(&dir) == B_OK) {
2964			const char* poseInfoAttr = isTrash
2965				? kAttrTrashPoseInfo
2966				: kAttrDisksPoseInfo;
2967			const char* poseInfoAttrForeign = isTrash
2968				? kAttrTrashPoseInfoForeign
2969				: kAttrDisksPoseInfoForeign;
2970			result = ReadAttr(&dir, poseInfoAttr, poseInfoAttrForeign,
2971				B_RAW_TYPE, 0, poseInfo, sizeof(*poseInfo),
2972				&PoseInfo::EndianSwap);
2973		}
2974	} else {
2975		ASSERT(model->IsNodeOpen());
2976		time_t now = time(NULL);
2977
2978		for (int32 count = 10; count >= 0; count--) {
2979			if (model->Node() == NULL)
2980				break;
2981
2982			result = ReadAttr(model->Node(), kAttrPoseInfo,
2983				kAttrPoseInfoForeign, B_RAW_TYPE, 0, poseInfo,
2984				sizeof(*poseInfo), &PoseInfo::EndianSwap);
2985
2986			if (result != kReadAttrFailed) {
2987				// got it, bail
2988				break;
2989			}
2990
2991			// if we're in one of the icon modes and it's a newly created item
2992			// then we're going to retry a few times to see if we can get some
2993			// pose info to properly place the icon
2994			if (ViewMode() == kListMode)
2995				break;
2996
2997			const StatStruct* stat = model->StatBuf();
2998			if (stat->st_crtime < now - 5 || stat->st_crtime > now)
2999				break;
3000
3001			//PRINT(("retrying to read pose info for %s, %d\n",
3002			//	model->Name(), count));
3003
3004			snooze(10000);
3005		}
3006	}
3007
3008	if (result == kReadAttrFailed) {
3009		poseInfo->fInitedDirectory = -1LL;
3010		poseInfo->fInvisible = false;
3011	} else if (TargetModel() == NULL
3012		|| (poseInfo->fInitedDirectory != model->EntryRef()->directory
3013			&& (poseInfo->fInitedDirectory
3014				!= TargetModel()->NodeRef()->node))) {
3015		// info was read properly but it's not for this directory
3016		poseInfo->fInitedDirectory = -1LL;
3017	} else if (poseInfo->fLocation.x < -kSanePoseLocation
3018		|| poseInfo->fLocation.x > kSanePoseLocation
3019		|| poseInfo->fLocation.y < -kSanePoseLocation
3020		|| poseInfo->fLocation.y > kSanePoseLocation) {
3021		// location values not realistic, probably screwed up, force reset
3022		poseInfo->fInitedDirectory = -1LL;
3023	}
3024}
3025
3026
3027ExtendedPoseInfo*
3028BPoseView::ReadExtendedPoseInfo(Model* model)
3029{
3030	BModelOpener opener(model);
3031	if (model->Node() == NULL)
3032		return NULL;
3033
3034	ReadAttrResult result = kReadAttrFailed;
3035
3036	const char* extendedPoseInfoAttrName;
3037	const char* extendedPoseInfoAttrForeignName;
3038
3039	// special case the "root" disks icon
3040	if (model->IsRoot()) {
3041		BDirectory dir;
3042		if (FSGetDeskDir(&dir) == B_OK) {
3043			extendedPoseInfoAttrName = kAttrExtendedDisksPoseInfo;
3044			extendedPoseInfoAttrForeignName = kAttrExtendedDisksPoseInfoForegin;
3045		} else
3046			return NULL;
3047	} else {
3048		extendedPoseInfoAttrName = kAttrExtendedPoseInfo;
3049		extendedPoseInfoAttrForeignName = kAttrExtendedPoseInfoForegin;
3050	}
3051
3052	type_code type;
3053	size_t size;
3054	result = GetAttrInfo(model->Node(), extendedPoseInfoAttrName,
3055		extendedPoseInfoAttrForeignName, &type, &size);
3056
3057	if (result == kReadAttrFailed)
3058		return NULL;
3059
3060	char* buffer = new char[ExtendedPoseInfo::SizeWithHeadroom(size)];
3061	ExtendedPoseInfo* poseInfo = reinterpret_cast<ExtendedPoseInfo*>(buffer);
3062	result = ReadAttr(model->Node(), extendedPoseInfoAttrName,
3063		extendedPoseInfoAttrForeignName,
3064		B_RAW_TYPE, 0, buffer, size, &ExtendedPoseInfo::EndianSwap);
3065
3066	// check that read worked, and data is sane
3067	if (result == kReadAttrFailed
3068		|| size > poseInfo->SizeWithHeadroom()
3069		|| size < poseInfo->Size()) {
3070		delete[] buffer;
3071		return NULL;
3072	}
3073
3074	return poseInfo;
3075}
3076
3077
3078void
3079BPoseView::SetViewMode(uint32 newMode)
3080{
3081	uint32 oldMode = ViewMode();
3082	uint32 lastIconSize = fViewState->LastIconSize();
3083
3084	if (newMode == oldMode
3085		&& (newMode != kIconMode || lastIconSize == fViewState->IconSize())) {
3086		return;
3087	}
3088
3089	ASSERT(!IsFilePanel());
3090
3091	uint32 lastIconMode = fViewState->LastIconMode();
3092	if (newMode != kListMode) {
3093		fViewState->SetLastIconMode(newMode);
3094		if (oldMode == kIconMode)
3095			fViewState->SetLastIconSize(fViewState->IconSize());
3096	}
3097
3098	fViewState->SetViewMode(newMode);
3099
3100	// Try to lock the center of the pose view when scaling icons, but not
3101	// if we are the desktop.
3102	BPoint scaleOffset(0, 0);
3103	bool iconSizeChanged = newMode == kIconMode && oldMode == kIconMode;
3104	if (!IsDesktopWindow() && iconSizeChanged) {
3105		// definitely changing the icon size, so we will need to scroll
3106		BRect bounds(Bounds());
3107		BPoint center(bounds.LeftTop());
3108		center.x += bounds.Width() / 2.0;
3109		center.y += bounds.Height() / 2.0;
3110		// convert the center into "unscaled icon placement" space
3111		float oldScale = lastIconSize / 32.0;
3112		BPoint unscaledCenter(center.x / oldScale, center.y / oldScale);
3113		// get the new center in "scaled icon placement" place
3114		float newScale = fViewState->IconSize() / 32.0;
3115		BPoint newCenter(unscaledCenter.x * newScale,
3116			unscaledCenter.y * newScale);
3117		scaleOffset = newCenter - center;
3118	}
3119
3120	// toggle view layout between listmode and non-listmode, if necessary
3121	BContainerWindow* window = ContainerWindow();
3122	if (oldMode == kListMode) {
3123		if (fFiltering)
3124			ClearFilter();
3125
3126		if (window != NULL)
3127			window->HideAttributeMenu();
3128
3129		fTitleView->Hide();
3130	} else if (newMode == kListMode) {
3131		if (window != NULL)
3132			window->ShowAttributeMenu();
3133
3134		fTitleView->Show();
3135	}
3136
3137	CommitActivePose();
3138	SetIconPoseHeight();
3139	GetLayoutInfo(newMode, &fGrid, &fOffset);
3140
3141	// see if we need to map icons into new mode
3142	bool mapIcons = false;
3143	if (fOkToMapIcons) {
3144		mapIcons = (newMode != kListMode) && (newMode != lastIconMode
3145			|| fViewState->IconSize() != lastIconSize);
3146	}
3147
3148	// check if we need to re-place poses when they are out of view
3149	bool checkLocations = IsDesktopWindow() && iconSizeChanged;
3150
3151	BPoint oldOffset;
3152	BPoint oldGrid;
3153	if (mapIcons)
3154		GetLayoutInfo(lastIconMode, &oldGrid, &oldOffset);
3155
3156	BRect bounds(Bounds());
3157	PoseList newPoseList(30);
3158
3159	if (newMode != kListMode) {
3160		int32 count = fPoseList->CountItems();
3161		for (int32 index = 0; index < count; index++) {
3162			BPose* pose = fPoseList->ItemAt(index);
3163			if (pose->HasLocation() == false) {
3164				newPoseList.AddItem(pose);
3165			} else if (checkLocations && !IsValidLocation(pose)) {
3166				// this icon has a location, but needs to be remapped, because
3167				// it is going out of view for example
3168				RemoveFromVSList(pose);
3169				newPoseList.AddItem(pose);
3170			} else if (iconSizeChanged) {
3171				// The pose location is still changed in view coordinates,
3172				// so it needs to be changed anyways!
3173				pose->SetSaveLocation();
3174			} else if (mapIcons) {
3175				MapToNewIconMode(pose, oldGrid, oldOffset);
3176			}
3177		}
3178	}
3179
3180	// invalidate before anything else to avoid flickering, especially when
3181	// scrolling is also performed (invalidating before scrolling will cause
3182	// app_server to scroll silently, ie not visibly)
3183	Invalidate();
3184
3185	// update origin in case of a list <-> icon mode transition
3186	BPoint newOrigin;
3187	if (newMode == kListMode)
3188		newOrigin = fViewState->ListOrigin();
3189	else
3190		newOrigin = fViewState->IconOrigin() + scaleOffset;
3191
3192	PinPointToValidRange(newOrigin);
3193
3194	DisableScrollBars();
3195	ScrollTo(newOrigin);
3196
3197	// reset hint and arrange poses which DO NOT have a location yet
3198	ResetPosePlacementHint();
3199	int32 count = newPoseList.CountItems();
3200	for (int32 index = 0; index < count; index++) {
3201		BPose* pose = newPoseList.ItemAt(index);
3202		PlacePose(pose, bounds);
3203		AddToVSList(pose);
3204	}
3205
3206	SortPoses();
3207	if (newMode != kListMode)
3208		RecalcExtent();
3209
3210	UpdateScrollRange();
3211	SetScrollBarsTo(newOrigin);
3212	EnableScrollBars();
3213	ContainerWindow()->ViewModeChanged(oldMode, newMode);
3214}
3215
3216
3217void
3218BPoseView::MapToNewIconMode(BPose* pose, BPoint oldGrid, BPoint oldOffset)
3219{
3220	BPoint delta;
3221	BPoint poseLoc;
3222
3223	poseLoc = PinToGrid(pose->Location(this), oldGrid, oldOffset);
3224	delta = pose->Location(this) - poseLoc;
3225	poseLoc -= oldOffset;
3226
3227	if (poseLoc.x >= 0)
3228		poseLoc.x = floorf(poseLoc.x / oldGrid.x) * fGrid.x;
3229	else
3230		poseLoc.x = ceilf(poseLoc.x / oldGrid.x) * fGrid.x;
3231
3232	if (poseLoc.y >= 0)
3233		poseLoc.y = floorf(poseLoc.y / oldGrid.y) * fGrid.y;
3234	else
3235		poseLoc.y = ceilf(poseLoc.y / oldGrid.y) * fGrid.y;
3236
3237	if ((delta.x != 0) || (delta.y != 0)) {
3238		if (delta.x >= 0)
3239			delta.x = fGrid.x * floorf(delta.x / oldGrid.x);
3240		else
3241			delta.x = fGrid.x * ceilf(delta.x / oldGrid.x);
3242
3243		if (delta.y >= 0)
3244			delta.y = fGrid.y * floorf(delta.y / oldGrid.y);
3245		else
3246			delta.y = fGrid.y * ceilf(delta.y / oldGrid.y);
3247
3248		poseLoc += delta;
3249	}
3250
3251	poseLoc += fOffset;
3252	pose->SetLocation(poseLoc, this);
3253	pose->SetSaveLocation();
3254}
3255
3256
3257void
3258BPoseView::SetPosesClipboardMode(uint32 clipboardMode)
3259{
3260	if (ViewMode() == kListMode) {
3261		PoseList* poseList = CurrentPoseList();
3262		int32 count = poseList->CountItems();
3263
3264		BPoint loc(0,0);
3265		for (int32 index = 0; index < count; index++) {
3266			BPose* pose = poseList->ItemAt(index);
3267			if (pose->ClipboardMode() != clipboardMode) {
3268				pose->SetClipboardMode(clipboardMode);
3269				Invalidate(pose->CalcRect(loc, this, false));
3270			}
3271			loc.y += fListElemHeight;
3272		}
3273	} else {
3274		int32 count = fPoseList->CountItems();
3275		for (int32 index = 0; index < count; index++) {
3276			BPose* pose = fPoseList->ItemAt(index);
3277			if (pose->ClipboardMode() != clipboardMode) {
3278				pose->SetClipboardMode(clipboardMode);
3279				BRect poseRect(pose->CalcRect(this));
3280				Invalidate(poseRect);
3281			}
3282		}
3283	}
3284}
3285
3286
3287void
3288BPoseView::UpdatePosesClipboardModeFromClipboard(BMessage* clipboardReport)
3289{
3290	CommitActivePose();
3291	fSelectionPivotPose = NULL;
3292	fRealPivotPose = NULL;
3293	bool fullInvalidateNeeded = false;
3294
3295	node_ref node;
3296	clipboardReport->FindInt32("device", &node.device);
3297	clipboardReport->FindInt64("directory", &node.node);
3298
3299	bool clearClipboard = false;
3300	clipboardReport->FindBool("clearClipboard", &clearClipboard);
3301
3302	if (clearClipboard && fHasPosesInClipboard) {
3303		// clear all poses
3304		int32 count = fPoseList->CountItems();
3305		for (int32 index = 0; index < count; index++) {
3306			BPose* pose = fPoseList->ItemAt(index);
3307			pose->Select(false);
3308			pose->SetClipboardMode(0);
3309		}
3310		SetHasPosesInClipboard(false);
3311		fullInvalidateNeeded = true;
3312		fHasPosesInClipboard = false;
3313	}
3314
3315	BRect bounds(Bounds());
3316	BPoint loc(0, 0);
3317	bool hasPosesInClipboard = false;
3318	int32 foundNodeIndex = 0;
3319
3320	TClipboardNodeRef* clipNode = NULL;
3321	ssize_t size;
3322	for (int32 index = 0; clipboardReport->FindData("tcnode", T_CLIPBOARD_NODE,
3323			index, (const void**)&clipNode, &size) == B_OK; index++) {
3324		BPose* pose = fPoseList->FindPose(&clipNode->node, &foundNodeIndex);
3325		if (pose == NULL)
3326			continue;
3327
3328		if (clipNode->moveMode != pose->ClipboardMode() || pose->IsSelected()) {
3329			pose->SetClipboardMode(clipNode->moveMode);
3330			pose->Select(false);
3331
3332			if (!fullInvalidateNeeded) {
3333				if (ViewMode() == kListMode) {
3334					if (fFiltering) {
3335						pose = fFilteredPoseList->FindPose(&clipNode->node,
3336							&foundNodeIndex);
3337					}
3338
3339					if (pose != NULL) {
3340						loc.y = foundNodeIndex * fListElemHeight;
3341						if (loc.y <= bounds.bottom && loc.y >= bounds.top)
3342							Invalidate(pose->CalcRect(loc, this, false));
3343					}
3344				} else {
3345					BRect poseRect(pose->CalcRect(this));
3346					if (bounds.Contains(poseRect.LeftTop())
3347						|| bounds.Contains(poseRect.LeftBottom())
3348						|| bounds.Contains(poseRect.RightBottom())
3349						|| bounds.Contains(poseRect.RightTop())) {
3350						Invalidate(poseRect);
3351					}
3352				}
3353			}
3354			if (clipNode->moveMode)
3355				hasPosesInClipboard = true;
3356		}
3357	}
3358
3359	fSelectionList->MakeEmpty();
3360	fMimeTypesInSelectionCache.MakeEmpty();
3361
3362	SetHasPosesInClipboard(hasPosesInClipboard || fHasPosesInClipboard);
3363
3364	if (fullInvalidateNeeded)
3365		Invalidate();
3366}
3367
3368
3369void
3370BPoseView::PlaceFolder(const entry_ref* ref, const BMessage* message)
3371{
3372	BNode node(ref);
3373	BPoint location;
3374	bool setPosition = false;
3375
3376	if (message->FindPoint("be:invoke_origin", &location) == B_OK) {
3377		// new folder created from popup, place on click point
3378		setPosition = true;
3379		location = ConvertFromScreen(location);
3380	} else if (ViewMode() != kListMode) {
3381		// new folder created by keyboard shortcut
3382		uint32 buttons;
3383		GetMouse(&location, &buttons);
3384		BPoint globalLocation(location);
3385		ConvertToScreen(&globalLocation);
3386		// check if mouse over window
3387		if (Window()->Frame().Contains(globalLocation))
3388			// create folder under mouse
3389			setPosition = true;
3390	}
3391
3392	if (setPosition) {
3393		Model* targetModel = TargetModel();
3394		if (targetModel != NULL && targetModel->NodeRef() != NULL) {
3395			FSSetPoseLocation(targetModel->NodeRef()->node, &node,
3396				location);
3397		}
3398	}
3399}
3400
3401
3402void
3403BPoseView::NewFileFromTemplate(const BMessage* message)
3404{
3405	Model* targetModel = TargetModel();
3406	ThrowOnAssert(targetModel != NULL);
3407
3408	entry_ref destEntryRef;
3409	node_ref destNodeRef;
3410
3411	BDirectory destDir(targetModel->NodeRef());
3412	if (destDir.InitCheck() != B_OK)
3413		return;
3414
3415	// TODO: Localise this
3416	char fileName[B_FILE_NAME_LENGTH] = "New ";
3417	strlcat(fileName, message->FindString("name"), sizeof(fileName));
3418	FSMakeOriginalName(fileName, &destDir, " copy");
3419
3420	entry_ref srcRef;
3421	message->FindRef("refs_template", &srcRef);
3422
3423	BDirectory dir(&srcRef);
3424
3425	if (dir.InitCheck() == B_OK) {
3426		// special handling of directories
3427		if (FSCreateNewFolderIn(targetModel->NodeRef(), &destEntryRef,
3428				&destNodeRef) == B_OK) {
3429			BEntry destEntry(&destEntryRef);
3430			destEntry.Rename(fileName);
3431		}
3432	} else {
3433		BFile srcFile(&srcRef, B_READ_ONLY);
3434		BFile destFile(&destDir, fileName, B_READ_WRITE | B_CREATE_FILE);
3435
3436		// copy the data from the template file
3437		char* buffer = new char[1024];
3438		ssize_t result;
3439		do {
3440			result = srcFile.Read(buffer, 1024);
3441
3442			if (result > 0) {
3443				ssize_t written = destFile.Write(buffer, (size_t)result);
3444				if (written != result)
3445					result = written < B_OK ? written : B_ERROR;
3446			}
3447		} while (result > 0);
3448		delete[] buffer;
3449	}
3450
3451	// todo: create an UndoItem
3452
3453	// copy the attributes from the template file
3454	BNode srcNode(&srcRef);
3455	BNode destNode(&destDir, fileName);
3456	FSCopyAttributesAndStats(&srcNode, &destNode, false);
3457
3458	BEntry entry(&destDir, fileName);
3459	entry.GetRef(&destEntryRef);
3460
3461	// try to place new item at click point or under mouse if possible
3462	PlaceFolder(&destEntryRef, message);
3463
3464	// start renaming the entry
3465	int32 index;
3466	BPose* pose = EntryCreated(targetModel->NodeRef(), &destNodeRef,
3467		destEntryRef.name, &index);
3468
3469	if (pose != NULL) {
3470		WatchNewNode(pose->TargetModel()->NodeRef());
3471		UpdateScrollRange();
3472		CommitActivePose();
3473		SelectPose(pose, index);
3474		pose->EditFirstWidget(BPoint(0, index * fListElemHeight), this);
3475	}
3476}
3477
3478
3479void
3480BPoseView::NewFolder(const BMessage* message)
3481{
3482	Model* targetModel = TargetModel();
3483	ThrowOnAssert(targetModel != NULL);
3484
3485	entry_ref ref;
3486	node_ref nodeRef;
3487
3488	if (FSCreateNewFolderIn(targetModel->NodeRef(), &ref, &nodeRef) == B_OK) {
3489		// try to place new folder at click point or under mouse if possible
3490
3491		PlaceFolder(&ref, message);
3492
3493		int32 index;
3494		BPose* pose = EntryCreated(targetModel->NodeRef(), &nodeRef, ref.name,
3495			&index);
3496
3497		if (fFiltering) {
3498			if (fFilteredPoseList->FindPose(&nodeRef, &index) == NULL) {
3499				float scrollBy = 0;
3500				BRect bounds = Bounds();
3501				AddPoseToList(fFilteredPoseList, true, true, pose, bounds,
3502					scrollBy, true, &index);
3503			}
3504		}
3505
3506		if (pose != NULL) {
3507			UpdateScrollRange();
3508			CommitActivePose();
3509			SelectPose(pose, index);
3510			pose->EditFirstWidget(BPoint(0, index * fListElemHeight), this);
3511		}
3512	}
3513}
3514
3515
3516void
3517BPoseView::Cleanup(bool doAll)
3518{
3519	if (ViewMode() == kListMode)
3520		return;
3521
3522	BContainerWindow* window = ContainerWindow();
3523	if (window == NULL)
3524		return;
3525
3526	// replace all icons from the top
3527	if (doAll) {
3528		// sort by sort field
3529		SortPoses();
3530
3531		DisableScrollBars();
3532		ClearExtent();
3533		ClearSelection();
3534		ScrollTo(B_ORIGIN);
3535		UpdateScrollRange();
3536		SetScrollBarsTo(B_ORIGIN);
3537		ResetPosePlacementHint();
3538
3539		BRect viewBounds(Bounds());
3540
3541		// relocate all poses in list (reset vs list)
3542		fVSPoseList->MakeEmpty();
3543		int32 count = fPoseList->CountItems();
3544		for (int32 index = 0; index < count; index++) {
3545			BPose* pose = fPoseList->ItemAt(index);
3546			PlacePose(pose, viewBounds);
3547			AddToVSList(pose);
3548		}
3549
3550		RecalcExtent();
3551
3552		// scroll icons into view so that leftmost icon is "fOffset" from left
3553		UpdateScrollRange();
3554		EnableScrollBars();
3555
3556		if (HScrollBar()) {
3557			float min;
3558			float max;
3559			HScrollBar()->GetRange(&min, &max);
3560			HScrollBar()->SetValue(min);
3561		}
3562
3563		UpdateScrollRange();
3564		Invalidate(viewBounds);
3565	} else {
3566		// clean up items to nearest locations
3567		BRect viewBounds(Bounds());
3568		int32 count = fPoseList->CountItems();
3569		for (int32 index = 0; index < count; index++) {
3570			BPose* pose = fPoseList->ItemAt(index);
3571			BPoint location(pose->Location(this));
3572			BPoint newLocation(PinToGrid(location, fGrid, fOffset));
3573
3574			bool intersectsDesktopElements = !IsValidLocation(pose);
3575
3576			// do we need to move pose to a grid location?
3577			if (newLocation != location || intersectsDesktopElements) {
3578				// remove pose from VSlist so it doesn't "bump" into itself
3579				RemoveFromVSList(pose);
3580
3581				// try new grid location
3582				BRect oldBounds(pose->CalcRect(this));
3583				BRect poseBounds(oldBounds);
3584				pose->MoveTo(newLocation, this);
3585				if (SlotOccupied(oldBounds, viewBounds)
3586					|| intersectsDesktopElements) {
3587					ResetPosePlacementHint();
3588					PlacePose(pose, viewBounds);
3589					poseBounds = pose->CalcRect(this);
3590				}
3591
3592				AddToVSList(pose);
3593				AddToExtent(poseBounds);
3594
3595 				if (viewBounds.Intersects(poseBounds))
3596					Invalidate(poseBounds);
3597 				if (viewBounds.Intersects(oldBounds))
3598					Invalidate(oldBounds);
3599			}
3600		}
3601	}
3602}
3603
3604
3605void
3606BPoseView::PlacePose(BPose* pose, BRect &viewBounds)
3607{
3608	// move pose to probable location
3609	pose->SetLocation(fHintLocation, this);
3610	BRect rect(pose->CalcRect(this));
3611	BPoint deltaFromBounds(fHintLocation - rect.LeftTop());
3612
3613	// make pose rect a little bigger to ensure space between poses
3614	rect.InsetBy(-3, 0);
3615
3616	bool checkValidLocation = IsDesktopWindow();
3617
3618	// find an empty slot to put pose into
3619	while (SlotOccupied(rect, viewBounds)
3620		// check good location on the desktop
3621		|| (checkValidLocation && !IsValidLocation(rect))) {
3622		NextSlot(pose, rect, viewBounds);
3623		// we've scanned the entire desktop without finding an available
3624		// position, give up and simply place it towards the top left.
3625		if (checkValidLocation && !rect.Intersects(viewBounds)) {
3626			fHintLocation = PinToGrid(BPoint(0.0, 0.0), fGrid, fOffset);
3627			pose->SetLocation(fHintLocation, this);
3628			rect = pose->CalcRect(this);
3629			break;
3630		}
3631	}
3632
3633	rect.InsetBy(3, 0);
3634
3635	fHintLocation = pose->Location(this) + BPoint(fGrid.x, 0);
3636
3637	pose->SetLocation(rect.LeftTop() + deltaFromBounds, this);
3638	pose->SetSaveLocation();
3639}
3640
3641
3642bool
3643BPoseView::IsValidLocation(const BPose* pose)
3644{
3645	if (!IsDesktopWindow())
3646		return true;
3647
3648	BRect rect(pose->CalcRect(this));
3649	rect.InsetBy(-3, 0);
3650	return IsValidLocation(rect);
3651}
3652
3653
3654bool
3655BPoseView::IsValidLocation(const BRect& rect)
3656{
3657	if (!IsDesktopWindow())
3658		return true;
3659
3660	// on the desktop, don't allow icons outside of the view bounds
3661	if (!Bounds().Contains(rect))
3662		return false;
3663
3664	// also check the deskbar frame
3665	BRect deskbarFrame;
3666	if (GetDeskbarFrame(&deskbarFrame) == B_OK) {
3667		deskbarFrame.InsetBy(-10, -10);
3668		if (deskbarFrame.Intersects(rect))
3669			return false;
3670	}
3671
3672	// check replicants
3673	for (int32 i = 0; BView* child = ChildAt(i); i++) {
3674		BRect childFrame = child->Frame();
3675		childFrame.InsetBy(-5, -5);
3676		if (childFrame.Intersects(rect))
3677			return false;
3678	}
3679
3680	// location is ok
3681	return true;
3682}
3683
3684
3685status_t
3686BPoseView::GetDeskbarFrame(BRect* frame)
3687{
3688	// only really check the Deskbar frame every half a second,
3689	// use a cached value otherwise
3690	status_t result = B_OK;
3691	bigtime_t now = system_time();
3692	if (fLastDeskbarFrameCheckTime + 500000 < now) {
3693		// it's time to check the Deskbar frame again
3694		result = get_deskbar_frame(&fDeskbarFrame);
3695		fLastDeskbarFrameCheckTime = now;
3696	}
3697	*frame = fDeskbarFrame;
3698
3699	return result;
3700}
3701
3702
3703void
3704BPoseView::CheckAutoPlacedPoses()
3705{
3706	if (ViewMode() == kListMode)
3707		return;
3708
3709	BRect viewBounds(Bounds());
3710
3711	int32 count = fPoseList->CountItems();
3712	for (int32 index = 0; index < count; index++) {
3713		BPose* pose = fPoseList->ItemAt(index);
3714		if (pose->WasAutoPlaced()) {
3715			RemoveFromVSList(pose);
3716			fHintLocation = pose->Location(this);
3717			BRect oldBounds(pose->CalcRect(this));
3718			PlacePose(pose, viewBounds);
3719
3720			BRect newBounds(pose->CalcRect(this));
3721			AddToVSList(pose);
3722			pose->SetAutoPlaced(false);
3723			AddToExtent(newBounds);
3724
3725			Invalidate(oldBounds);
3726			Invalidate(newBounds);
3727		}
3728	}
3729}
3730
3731
3732void
3733BPoseView::CheckPoseVisibility(BRect* newFrame)
3734{
3735	bool desktop = IsDesktopWindow() && newFrame != 0;
3736
3737	BRect deskFrame;
3738	if (desktop) {
3739		ASSERT(newFrame);
3740		deskFrame = *newFrame;
3741	}
3742
3743	ASSERT(ViewMode() != kListMode);
3744
3745	BRect bounds(Bounds());
3746	bounds.InsetBy(20, 20);
3747
3748	int32 count = fPoseList->CountItems();
3749	for (int32 index = 0; index < count; index++) {
3750		BPose* pose = fPoseList->ItemAt(index);
3751		BPoint newLocation(pose->Location(this));
3752		bool locationNeedsUpdating = false;
3753
3754		if (desktop) {
3755			// we just switched screen resolution, pick up the right
3756			// icon locations for the new resolution
3757			Model* model = pose->TargetModel();
3758			ExtendedPoseInfo* info = ReadExtendedPoseInfo(model);
3759			if (info && info->HasLocationForFrame(deskFrame)) {
3760				BPoint locationForFrame = info->LocationForFrame(deskFrame);
3761				if (locationForFrame != newLocation) {
3762					// found one and it is different from the current
3763					newLocation = locationForFrame;
3764					locationNeedsUpdating = true;
3765					Invalidate(pose->CalcRect(this));
3766						// make sure the old icon gets erased
3767					RemoveFromVSList(pose);
3768					pose->SetLocation(newLocation, this);
3769						// set the new location
3770				}
3771			}
3772			delete[] (char*)info;
3773				// TODO: fix up this mess
3774		}
3775
3776		BRect rect(pose->CalcRect(this));
3777		if (!rect.Intersects(bounds)) {
3778			// pose doesn't fit on screen
3779			if (!locationNeedsUpdating) {
3780				// didn't already invalidate and remove in the desktop case
3781				Invalidate(rect);
3782				RemoveFromVSList(pose);
3783			}
3784			BPoint loc(pose->Location(this));
3785			loc.ConstrainTo(bounds);
3786				// place it onscreen
3787
3788			pose->SetLocation(loc, this);
3789				// set the new location
3790			locationNeedsUpdating = true;
3791		}
3792
3793		if (locationNeedsUpdating) {
3794			// pose got reposition by one or both of the above
3795			pose->SetSaveLocation();
3796			AddToVSList(pose);
3797				// add it at the new location
3798			Invalidate(pose->CalcRect(this));
3799				// make sure the new pose location updates properly
3800		}
3801	}
3802}
3803
3804
3805bool
3806BPoseView::SlotOccupied(BRect poseRect, BRect viewBounds) const
3807{
3808	if (fVSPoseList->IsEmpty())
3809		return false;
3810
3811	// ## be sure to keep this code in sync with calls to NextSlot
3812	// ## in terms of the comparison of fHintLocation and PinToGrid
3813	if (poseRect.right >= viewBounds.right) {
3814		BPoint point(viewBounds.left + fOffset.x, 0);
3815		point = PinToGrid(point, fGrid, fOffset);
3816		if (fHintLocation.x != point.x)
3817			return true;
3818	}
3819
3820	// search only nearby poses (vertically)
3821	int32 index = FirstIndexAtOrBelow((int32)(poseRect.top - IconPoseHeight()));
3822	int32 numPoses = fVSPoseList->CountItems();
3823
3824	while (index < numPoses && fVSPoseList->ItemAt(index)->Location(this).y
3825			< poseRect.bottom) {
3826		BRect rect(fVSPoseList->ItemAt(index)->CalcRect(this));
3827		if (poseRect.Intersects(rect))
3828			return true;
3829
3830		index++;
3831	}
3832
3833	return false;
3834}
3835
3836
3837void
3838BPoseView::NextSlot(BPose* pose, BRect &poseRect, BRect viewBounds)
3839{
3840	// move to next slot
3841	poseRect.OffsetBy(fGrid.x, 0);
3842
3843	// if we reached the end of row go down to next row
3844	if (poseRect.right > viewBounds.right) {
3845		fHintLocation.y += fGrid.y;
3846		fHintLocation.x = viewBounds.left + fOffset.x;
3847		fHintLocation = PinToGrid(fHintLocation, fGrid, fOffset);
3848		pose->SetLocation(fHintLocation, this);
3849		poseRect = pose->CalcRect(this);
3850		poseRect.InsetBy(-3, 0);
3851	}
3852}
3853
3854
3855int32
3856BPoseView::FirstIndexAtOrBelow(int32 y, bool constrainIndex) const
3857{
3858// This method performs a binary search on the vertically sorted pose list
3859// and returns either the index of the first pose at a given y location or
3860// the proper index to insert a new pose into the list.
3861
3862	int32 index = 0;
3863	int32 l = 0;
3864	int32 r = fVSPoseList->CountItems() - 1;
3865
3866	while (l <= r) {
3867		index = (l + r) >> 1;
3868		int32 result
3869			= (int32)(y - fVSPoseList->ItemAt(index)->Location(this).y);
3870
3871		if (result < 0)
3872			r = index - 1;
3873		else if (result > 0)
3874			l = index + 1;
3875		else {
3876			// compare turned out equal, find first pose
3877			while (index > 0
3878				&& y == fVSPoseList->ItemAt(index - 1)->Location(this).y)
3879				index--;
3880			return index;
3881		}
3882	}
3883
3884	// didn't find pose AT location y - bump index to proper insert point
3885	while (index < fVSPoseList->CountItems()
3886		&& fVSPoseList->ItemAt(index)->Location(this).y <= y) {
3887		index++;
3888	}
3889
3890	// if flag is true then constrain index to legal value since this
3891	// method returns the proper insertion point which could be outside
3892	// the current bounds of the list
3893	if (constrainIndex && index >= fVSPoseList->CountItems())
3894		index = fVSPoseList->CountItems() - 1;
3895
3896	return index;
3897}
3898
3899
3900void
3901BPoseView::AddToVSList(BPose* pose)
3902{
3903	int32 index = FirstIndexAtOrBelow((int32)pose->Location(this).y, false);
3904	fVSPoseList->AddItem(pose, index);
3905}
3906
3907
3908int32
3909BPoseView::RemoveFromVSList(const BPose* pose)
3910{
3911	//int32 index = FirstIndexAtOrBelow((int32)pose->Location(this).y);
3912		// This optimisation is buggy and the index returned can be greater
3913		// than the actual index of the pose we search, thus missing it
3914		// and failing to remove it. This having severe implications
3915		// everywhere in the code as it is asserted that it must be always
3916		// in sync with fPoseList. See ticket #4322.
3917	int32 index = 0;
3918
3919	int32 count = fVSPoseList->CountItems();
3920	for (; index < count; index++) {
3921		BPose* matchingPose = fVSPoseList->ItemAt(index);
3922		ASSERT(matchingPose);
3923		if (!matchingPose)
3924			return -1;
3925
3926		if (pose == matchingPose) {
3927			fVSPoseList->RemoveItemAt(index);
3928			return index;
3929		}
3930	}
3931
3932	return -1;
3933}
3934
3935
3936BPoint
3937BPoseView::PinToGrid(BPoint point, BPoint grid, BPoint offset) const
3938{
3939	if (grid.x == 0 || grid.y == 0)
3940		return point;
3941
3942	point -= offset;
3943	BPoint	gridLoc(point);
3944
3945	if (point.x >= 0)
3946		gridLoc.x = floorf((point.x / grid.x) + 0.5f) * grid.x;
3947	else
3948		gridLoc.x = ceilf((point.x / grid.x) - 0.5f) * grid.x;
3949
3950	if (point.y >= 0)
3951		gridLoc.y = floorf((point.y / grid.y) + 0.5f) * grid.y;
3952	else
3953		gridLoc.y = ceilf((point.y / grid.y) - 0.5f) * grid.y;
3954
3955	gridLoc += offset;
3956
3957	return gridLoc;
3958}
3959
3960
3961void
3962BPoseView::ResetPosePlacementHint()
3963{
3964	fHintLocation = PinToGrid(BPoint(LeftTop().x + fOffset.x,
3965		LeftTop().y + fOffset.y), fGrid, fOffset);
3966}
3967
3968
3969void
3970BPoseView::SelectPoses(int32 start, int32 end)
3971{
3972	// clear selection list
3973	fSelectionList->MakeEmpty();
3974	fMimeTypesInSelectionCache.MakeEmpty();
3975	fSelectionPivotPose = NULL;
3976	fRealPivotPose = NULL;
3977
3978	bool iconMode = ViewMode() != kListMode;
3979	BPoint loc(0, start * fListElemHeight);
3980	BRect bounds(Bounds());
3981
3982	PoseList* poseList = CurrentPoseList();
3983	int32 count = poseList->CountItems();
3984	for (int32 index = start; index < end && index < count; index++) {
3985		BPose* pose = poseList->ItemAt(index);
3986		fSelectionList->AddItem(pose);
3987		if (index == start)
3988			fSelectionPivotPose = pose;
3989		if (!pose->IsSelected()) {
3990			pose->Select(true);
3991			BRect poseRect;
3992			if (iconMode)
3993				poseRect = pose->CalcRect(this);
3994			else
3995				poseRect = pose->CalcRect(loc, this, false);
3996
3997			if (bounds.Intersects(poseRect)) {
3998				Invalidate(poseRect);
3999			}
4000		}
4001
4002		loc.y += fListElemHeight;
4003	}
4004}
4005
4006
4007void
4008BPoseView::MoveOrChangePoseSelection(int32 to)
4009{
4010	PoseList* poseList = CurrentPoseList();
4011	BPose* first = fSelectionList->FirstItem();
4012
4013	if (first != NULL && fMultipleSelection
4014		&& (modifiers() & B_SHIFT_KEY) != 0) {
4015		// Extend selection
4016		BPose* target = poseList->ItemAt(to);
4017		BPose* last = fSelectionList->LastItem();
4018		int32 firstIndex = poseList->IndexOf(first);
4019		int32 lastIndex = poseList->IndexOf(last);
4020
4021		int32 from = to < firstIndex ? firstIndex : lastIndex;
4022		int32 step = from < to ? 1 : -1;
4023
4024		// TODO: shrink selection depending on anchor
4025		bool select = true;
4026
4027		for (int32 index = from; step > 0 ? index <= to : index >= to;
4028				index += step) {
4029			BPose* pose = poseList->ItemAt(index);
4030			if (pose != NULL && pose->IsSelected() != select)
4031				AddRemovePoseFromSelection(pose, index, select);
4032		}
4033		if (target != NULL)
4034			ScrollIntoView(target, to);
4035	} else {
4036		SelectPose(poseList->ItemAt(to), to);
4037	}
4038}
4039
4040
4041void
4042BPoseView::ScrollIntoView(BPose* pose, int32 index)
4043{
4044	ScrollIntoView(CalcPoseRect(pose, index, true));
4045}
4046
4047
4048void
4049BPoseView::ScrollIntoView(BRect poseRect)
4050{
4051	if (IsDesktopWindow())
4052		return;
4053
4054	if (ViewMode() == kListMode) {
4055		// if we're in list view then we only care that the entire
4056		// pose is visible vertically, not horizontally
4057		poseRect.left = 0;
4058		poseRect.right = poseRect.left + 1;
4059	}
4060
4061	if (!Bounds().Contains(poseRect))
4062		SetScrollBarsTo(poseRect.LeftTop());
4063}
4064
4065
4066void
4067BPoseView::SelectPose(BPose* pose, int32 index, bool scrollIntoView)
4068{
4069	if (pose == NULL || fSelectionList->CountItems() > 1 || !pose->IsSelected())
4070		ClearSelection();
4071
4072	AddPoseToSelection(pose, index, scrollIntoView);
4073
4074	if (pose != NULL)
4075		fSelectionPivotPose = pose;
4076}
4077
4078
4079void
4080BPoseView::AddPoseToSelection(BPose* pose, int32 index, bool scrollIntoView)
4081{
4082	// TODO: need to check if pose is member of selection list
4083	if (pose != NULL && !pose->IsSelected()) {
4084		pose->Select(true);
4085		fSelectionList->AddItem(pose);
4086
4087		BRect poseRect = CalcPoseRect(pose, index);
4088		Invalidate(poseRect);
4089
4090		if (scrollIntoView)
4091			ScrollIntoView(poseRect);
4092
4093		if (fSelectionChangedHook)
4094			ContainerWindow()->SelectionChanged();
4095	}
4096}
4097
4098
4099void
4100BPoseView::RemovePoseFromSelection(BPose* pose)
4101{
4102	if (fSelectionPivotPose == pose)
4103		fSelectionPivotPose = NULL;
4104
4105	if (fRealPivotPose == pose)
4106		fRealPivotPose = NULL;
4107
4108	if (!fSelectionList->RemoveItem(pose)) {
4109		// wasn't selected to begin with
4110		return;
4111	}
4112
4113	pose->Select(false);
4114	if (ViewMode() == kListMode) {
4115		// TODO: need a simple call to CalcRect that works both in listView and
4116		// icon view modes without the need for an index/pos
4117		PoseList* poseList = CurrentPoseList();
4118		int32 count = poseList->CountItems();
4119		BPoint loc(0, 0);
4120		for (int32 index = 0; index < count; index++) {
4121			if (pose == poseList->ItemAt(index)) {
4122				Invalidate(pose->CalcRect(loc, this));
4123				break;
4124			}
4125			loc.y += fListElemHeight;
4126		}
4127	} else
4128		Invalidate(pose->CalcRect(this));
4129
4130	if (fSelectionChangedHook)
4131		ContainerWindow()->SelectionChanged();
4132}
4133
4134
4135bool
4136BPoseView::EachItemInDraggedSelection(const BMessage* message,
4137	bool (*func)(BPose*, BPoseView*, void*), BPoseView* poseView,
4138	void* passThru)
4139{
4140	BContainerWindow* srcWindow;
4141	if (message->FindPointer("src_window", (void**)&srcWindow) != B_OK)
4142		return false;
4143
4144	AutoLock<BWindow> lock(srcWindow);
4145	if (!lock)
4146		return false;
4147
4148	PoseList* selectionList = srcWindow->PoseView()->SelectionList();
4149	int32 count = selectionList->CountItems();
4150
4151	for (int32 index = 0; index < count; index++) {
4152		BPose* pose = selectionList->ItemAt(index);
4153		if (func(pose, poseView, passThru))
4154			// early iteration termination
4155			return true;
4156	}
4157
4158	return false;
4159}
4160
4161
4162static bool
4163ContainsOne(BString* string, const char* matchString)
4164{
4165	return strcmp(string->String(), matchString) == 0;
4166}
4167
4168
4169bool
4170BPoseView::FindDragNDropAction(const BMessage* dragMessage, bool &canCopy,
4171	bool &canMove, bool &canLink, bool &canErase)
4172{
4173	canCopy = false;
4174	canMove = false;
4175	canErase = false;
4176	canLink = false;
4177	if (!dragMessage->HasInt32("be:actions"))
4178		return false;
4179
4180	int32 action;
4181	for (int32 index = 0;
4182			dragMessage->FindInt32("be:actions", index, &action) == B_OK;
4183			index++) {
4184		switch (action) {
4185			case B_MOVE_TARGET:
4186				canMove = true;
4187				break;
4188
4189			case B_COPY_TARGET:
4190				canCopy = true;
4191				break;
4192
4193			case B_TRASH_TARGET:
4194				canErase = true;
4195				break;
4196
4197			case B_LINK_TARGET:
4198				canLink = true;
4199				break;
4200		}
4201	}
4202
4203	return canCopy || canMove || canErase || canLink;
4204}
4205
4206
4207bool
4208BPoseView::CanTrashForeignDrag(const Model* targetModel)
4209{
4210	return targetModel->IsTrash();
4211}
4212
4213
4214bool
4215BPoseView::CanCopyOrMoveForeignDrag(const Model* targetModel,
4216	const BMessage* dragMessage)
4217{
4218	if (!targetModel->IsDirectory())
4219		return false;
4220
4221	// in order to handle a clipping file, the drag initiator must be able
4222	// do deal with B_FILE_MIME_TYPE
4223	for (int32 index = 0; ; index++) {
4224		const char* type;
4225		if (dragMessage->FindString("be:types", index, &type) != B_OK)
4226			break;
4227
4228		if (strcasecmp(type, B_FILE_MIME_TYPE) == 0)
4229			return true;
4230	}
4231
4232	return false;
4233}
4234
4235
4236bool
4237BPoseView::CanHandleDragSelection(const Model* target,
4238	const BMessage* dragMessage, bool ignoreTypes)
4239{
4240	if (ignoreTypes)
4241		return target->IsDropTarget();
4242
4243	ASSERT(dragMessage != NULL);
4244
4245	BContainerWindow* srcWindow;
4246	status_t result = dragMessage->FindPointer("src_window", (void**)&srcWindow);
4247	if (result != B_OK || srcWindow == NULL) {
4248		// handle a foreign drag
4249		bool canCopy;
4250		bool canMove;
4251		bool canErase;
4252		bool canLink;
4253		FindDragNDropAction(dragMessage, canCopy, canMove, canLink, canErase);
4254		if (canErase && CanTrashForeignDrag(target))
4255			return true;
4256
4257		if (canCopy || canMove) {
4258			if (CanCopyOrMoveForeignDrag(target, dragMessage))
4259				return true;
4260
4261			// TODO: collect all mime types here and pass into
4262			// target->IsDropTargetForList(mimeTypeList);
4263		}
4264
4265		// handle an old style entry_refs only darg message
4266		if (dragMessage->HasRef("refs") && target->IsDirectory())
4267			return true;
4268
4269		// handle simple text clipping drag&drop message
4270		if (dragMessage->HasData(kPlainTextMimeType, B_MIME_TYPE)
4271			&& target->IsDirectory()) {
4272			return true;
4273		}
4274
4275		// handle simple bitmap clipping drag&drop message
4276		if (target->IsDirectory()
4277			&& (dragMessage->HasData(kBitmapMimeType, B_MESSAGE_TYPE)
4278				|| dragMessage->HasData(kLargeIconType, B_MESSAGE_TYPE)
4279				|| dragMessage->HasData(kMiniIconType, B_MESSAGE_TYPE))) {
4280			return true;
4281		}
4282
4283		// TODO: check for a drag message full of refs, feed a list of their
4284		// types to target->IsDropTargetForList(mimeTypeList);
4285		return false;
4286	}
4287
4288	ASSERT(srcWindow != NULL);
4289
4290	AutoLock<BWindow> lock(srcWindow);
4291	if (!lock)
4292		return false;
4293
4294	BObjectList<BString>* mimeTypeList
4295		= srcWindow->PoseView()->MimeTypesInSelection();
4296	if (mimeTypeList->IsEmpty()) {
4297		PoseList* selectionList = srcWindow->PoseView()->SelectionList();
4298		if (!selectionList->IsEmpty()) {
4299			// no cached data yet, build the cache
4300			int32 count = selectionList->CountItems();
4301
4302			for (int32 index = 0; index < count; index++) {
4303				// get the mime type of the model, following a possible symlink
4304				BEntry entry(selectionList->ItemAt(
4305					index)->TargetModel()->EntryRef(), true);
4306				if (entry.InitCheck() != B_OK)
4307					continue;
4308
4309 				BFile file(&entry, O_RDONLY);
4310				BNodeInfo mime(&file);
4311
4312				if (mime.InitCheck() != B_OK)
4313					continue;
4314
4315				char mimeType[B_MIME_TYPE_LENGTH];
4316				mime.GetType(mimeType);
4317
4318				// add unique type string
4319				if (!WhileEachListItem(mimeTypeList, ContainsOne,
4320						(const char*)mimeType)) {
4321					BString* newMimeString = new BString(mimeType);
4322					mimeTypeList->AddItem(newMimeString);
4323				}
4324			}
4325		}
4326	}
4327
4328	return target->IsDropTargetForList(mimeTypeList);
4329}
4330
4331
4332void
4333BPoseView::TrySettingPoseLocation(BNode* node, BPoint point)
4334{
4335	if (ViewMode() == kListMode)
4336		return;
4337
4338	if ((modifiers() & B_COMMAND_KEY) != 0) {
4339		// align to grid if needed
4340		point = PinToGrid(point, fGrid, fOffset);
4341	}
4342
4343	Model* targetModel = TargetModel();
4344	ASSERT(targetModel != NULL);
4345
4346	if (targetModel != NULL && targetModel->NodeRef() != NULL
4347		&& FSSetPoseLocation(targetModel->NodeRef()->node, node, point)
4348			== B_OK) {
4349		// get rid of opposite endianness attribute
4350		node->RemoveAttr(kAttrPoseInfoForeign);
4351	}
4352}
4353
4354
4355status_t
4356BPoseView::CreateClippingFile(BPoseView* poseView, BFile &result,
4357	char* resultingName, BDirectory* directory, BMessage* message,
4358	const char* fallbackName, bool setLocation, BPoint dropPoint)
4359{
4360	// build a file name
4361	// try picking it up from the message
4362	const char* suggestedName;
4363	if (message && message->FindString("be:clip_name", &suggestedName) == B_OK)
4364		strncpy(resultingName, suggestedName, B_FILE_NAME_LENGTH - 1);
4365	else
4366		strcpy(resultingName, fallbackName);
4367
4368	FSMakeOriginalName(resultingName, directory, "");
4369
4370	// create a clipping file
4371	status_t error = directory->CreateFile(resultingName, &result, true);
4372	if (error != B_OK)
4373		return error;
4374
4375	if (setLocation && poseView != NULL)
4376		poseView->TrySettingPoseLocation(&result, dropPoint);
4377
4378	return B_OK;
4379}
4380
4381
4382static int32
4383RunMimeTypeDestinationMenu(const char* actionText,
4384	const BObjectList<BString>* types,
4385	const BObjectList<BString>* specificItems, BPoint where)
4386{
4387	int32 count;
4388	if (types != NULL)
4389		count = types->CountItems();
4390	else
4391		count = specificItems->CountItems();
4392
4393	if (count == 0)
4394		return 0;
4395
4396	BPopUpMenu* menu = new BPopUpMenu("create clipping");
4397	menu->SetFont(be_plain_font);
4398
4399	for (int32 index = 0; index < count; index++) {
4400		const char* embedTypeAs = NULL;
4401		char buffer[256];
4402		if (types) {
4403			types->ItemAt(index)->String();
4404			BMimeType mimeType(embedTypeAs);
4405
4406			if (mimeType.GetShortDescription(buffer) == B_OK)
4407				embedTypeAs = buffer;
4408		}
4409
4410		BString description;
4411		if (specificItems->ItemAt(index)->Length()) {
4412			description << (const BString &)(*specificItems->ItemAt(index));
4413
4414			if (embedTypeAs)
4415				description << " (" << embedTypeAs << ")";
4416
4417		} else if (types)
4418			description = embedTypeAs;
4419
4420		BString labelText;
4421		if (actionText) {
4422			int32 length = 1024 - 1 - (int32)strlen(actionText);
4423			if (length > 0) {
4424				description.Truncate(length);
4425				labelText.SetTo(actionText);
4426				labelText.ReplaceFirst("%s", description.String());
4427			} else
4428				labelText.SetTo(B_TRANSLATE("label too long"));
4429		} else
4430			labelText = description;
4431
4432		menu->AddItem(new BMenuItem(labelText.String(), 0));
4433	}
4434
4435	menu->AddSeparatorItem();
4436	menu->AddItem(new BMenuItem(B_TRANSLATE("Cancel"), 0));
4437
4438	int32 result = -1;
4439	BMenuItem* resultingItem = menu->Go(where, false, true);
4440	if (resultingItem) {
4441		int32 index = menu->IndexOf(resultingItem);
4442		if (index < count)
4443			result = index;
4444	}
4445
4446	delete menu;
4447
4448	return result;
4449}
4450
4451
4452bool
4453BPoseView::HandleMessageDropped(BMessage* message)
4454{
4455	ASSERT(message->WasDropped());
4456
4457	// reset system cursor in case it was altered by drag and drop
4458	SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
4459	fCursorCheck = false;
4460
4461	if (!fDropEnabled)
4462		return false;
4463
4464	BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window());
4465	if (window != NULL && message->HasData("RGBColor", 'RGBC')) {
4466 		// do not handle roColor-style drops here, pass them on to the desktop
4467		BMessenger((BHandler*)window).SendMessage(message);
4468
4469		return true;
4470 	}
4471
4472	if (fDropTarget && !DragSelectionContains(fDropTarget, message))
4473		HiliteDropTarget(false);
4474
4475	fDropTarget = NULL;
4476
4477	ASSERT(TargetModel() != NULL);
4478	BPoint offset;
4479	BPoint dropPoint(message->DropPoint(&offset));
4480	ConvertFromScreen(&dropPoint);
4481
4482	// tenatively figure out the pose we dropped the file onto
4483	int32 index;
4484	BPose* targetPose = FindPose(dropPoint, &index);
4485	Model tmpTarget;
4486	Model* targetModel = NULL;
4487	if (targetPose != NULL) {
4488		targetModel = targetPose->TargetModel();
4489		if (targetModel->IsSymLink()
4490			&& tmpTarget.SetTo(targetPose->TargetModel()->EntryRef(),
4491				true, true) == B_OK) {
4492			targetModel = &tmpTarget;
4493		}
4494	}
4495
4496	return HandleDropCommon(message, targetModel, targetPose, this, dropPoint);
4497}
4498
4499
4500bool
4501BPoseView::HandleDropCommon(BMessage* message, Model* targetModel,
4502	BPose* targetPose, BView* view, BPoint dropPoint)
4503{
4504	uint32 buttons = (uint32)message->FindInt32("buttons");
4505
4506	BContainerWindow* containerWindow = NULL;
4507	BPoseView* poseView = dynamic_cast<BPoseView*>(view);
4508	if (poseView != NULL)
4509		containerWindow = poseView->ContainerWindow();
4510
4511	// look for srcWindow to determine whether drag was initiated in tracker
4512	BContainerWindow* srcWindow = NULL;
4513	status_t result = message->FindPointer("src_window", (void**)&srcWindow);
4514	if (result != B_OK || srcWindow == NULL) {
4515		// drag was from another app
4516
4517		if (targetModel == NULL && poseView != NULL)
4518			targetModel = poseView->TargetModel();
4519
4520		// figure out if we dropped a file onto a directory and set
4521		// the targetDirectory to it, else set it to this pose view
4522		BDirectory targetDirectory;
4523		if (targetModel != NULL && targetModel->IsDirectory())
4524			targetDirectory.SetTo(targetModel->EntryRef());
4525
4526		if (targetModel != NULL && targetModel->IsRoot()) {
4527			// don't drop anything into the root disk
4528			return false;
4529		}
4530
4531		bool canCopy;
4532		bool canMove;
4533		bool canErase;
4534		bool canLink;
4535		if (FindDragNDropAction(message, canCopy, canMove, canLink, canErase)) {
4536			// new D&D protocol
4537			// what action can the drag initiator do?
4538			if (canErase && CanTrashForeignDrag(targetModel)) {
4539				BMessage reply(B_TRASH_TARGET);
4540				message->SendReply(&reply);
4541				return true;
4542			}
4543
4544			if ((canCopy || canMove)
4545				&& CanCopyOrMoveForeignDrag(targetModel, message)) {
4546				// handle the promise style drag&drop
4547
4548				// fish for specification of specialized menu items
4549				BObjectList<BString> actionSpecifiers(10, true);
4550				for (int32 index = 0; ; index++) {
4551					const char* string;
4552					if (message->FindString("be:actionspecifier", index,
4553							&string) != B_OK) {
4554						break;
4555					}
4556
4557					ASSERT(string != NULL);
4558					actionSpecifiers.AddItem(new BString(string));
4559				}
4560
4561				// build the list of types the drag originator offers
4562				BObjectList<BString> types(10, true);
4563				BObjectList<BString> typeNames(10, true);
4564				for (int32 index = 0; ; index++) {
4565					const char* string;
4566					if (message->FindString("be:filetypes", index, &string)
4567							!= B_OK) {
4568						break;
4569					}
4570
4571					ASSERT(string != NULL);
4572					types.AddItem(new BString(string));
4573
4574					const char* typeName = "";
4575					message->FindString("be:type_descriptions", index,
4576						&typeName);
4577					typeNames.AddItem(new BString(typeName));
4578				}
4579
4580				int32 specificTypeIndex = -1;
4581				int32 specificActionIndex = -1;
4582
4583				// if control down, run a popup menu
4584				if (canCopy
4585					&& SecondaryMouseButtonDown(modifiers(), buttons)) {
4586					if (actionSpecifiers.CountItems() > 0) {
4587						specificActionIndex = RunMimeTypeDestinationMenu(NULL,
4588							NULL, &actionSpecifiers,
4589							view->ConvertToScreen(dropPoint));
4590
4591						if (specificActionIndex == -1)
4592							return false;
4593					} else if (types.CountItems() > 0) {
4594						specificTypeIndex = RunMimeTypeDestinationMenu(
4595							B_TRANSLATE("Create %s clipping"),
4596							&types, &typeNames,
4597							view->ConvertToScreen(dropPoint));
4598
4599						if (specificTypeIndex == -1)
4600							return false;
4601					}
4602				}
4603
4604				char name[B_FILE_NAME_LENGTH];
4605				BFile file;
4606				if (CreateClippingFile(poseView, file, name, &targetDirectory,
4607						message, B_TRANSLATE("Untitled clipping"),
4608						targetPose == NULL, dropPoint) != B_OK) {
4609					return false;
4610				}
4611
4612				// here is a file for the drag initiator, it is up to it now
4613				// to stuff it with the goods
4614
4615				// build the reply message
4616				BMessage reply(canCopy ? B_COPY_TARGET : B_MOVE_TARGET);
4617				reply.AddString("be:types", B_FILE_MIME_TYPE);
4618				if (specificTypeIndex != -1) {
4619					// we had the user pick a specific type from a menu, use it
4620					reply.AddString("be:filetypes",
4621						types.ItemAt(specificTypeIndex)->String());
4622
4623					if (typeNames.ItemAt(specificTypeIndex)->Length()) {
4624						reply.AddString("be:type_descriptions",
4625							typeNames.ItemAt(specificTypeIndex)->String());
4626					}
4627				}
4628
4629				if (specificActionIndex != -1) {
4630					// we had the user pick a specific type from a menu, use it
4631					reply.AddString("be:actionspecifier",
4632						actionSpecifiers.ItemAt(specificActionIndex)->String());
4633				}
4634
4635				reply.AddRef("directory", targetModel->EntryRef());
4636				reply.AddString("name", name);
4637
4638				// Attach any data the originator may have tagged on
4639				BMessage data;
4640				if (message->FindMessage("be:originator-data", &data) == B_OK)
4641					reply.AddMessage("be:originator-data", &data);
4642
4643				// copy over all the file types the drag initiator claimed to
4644				// support
4645				for (int32 index = 0; ; index++) {
4646					const char* type;
4647					if (message->FindString("be:filetypes", index, &type)
4648							!= B_OK) {
4649						break;
4650					}
4651					reply.AddString("be:filetypes", type);
4652				}
4653
4654				message->SendReply(&reply);
4655				return true;
4656			}
4657		}
4658
4659		if (message->HasRef("refs")) {
4660			// TODO: decide here on copy, move or create symlink
4661			// look for specific command or bring up popup
4662			// Unify this with local drag&drop
4663
4664			if (!targetModel->IsDirectory()) {
4665				// bail if we are not a directory
4666				return false;
4667			}
4668
4669			bool canRelativeLink = false;
4670			if (!canCopy && !canMove && !canLink && containerWindow) {
4671				if (SecondaryMouseButtonDown(modifiers(), buttons)) {
4672					switch (containerWindow->ShowDropContextMenu(dropPoint)) {
4673						case kCreateRelativeLink:
4674							canRelativeLink = true;
4675							break;
4676
4677						case kCreateLink:
4678							canLink = true;
4679							break;
4680
4681						case kMoveSelectionTo:
4682							canMove = true;
4683							break;
4684
4685						case kCopySelectionTo:
4686							canCopy = true;
4687							break;
4688
4689						case kCancelButton:
4690						default:
4691							// user canceled context menu
4692							return true;
4693					}
4694				} else
4695					canCopy = true;
4696			}
4697
4698			uint32 moveMode;
4699			if (canCopy)
4700				moveMode = kCopySelectionTo;
4701			else if (canMove)
4702				moveMode = kMoveSelectionTo;
4703			else if (canLink)
4704				moveMode = kCreateLink;
4705			else if (canRelativeLink)
4706				moveMode = kCreateRelativeLink;
4707			else {
4708				TRESPASS();
4709				return true;
4710			}
4711
4712			// handle refs by performing a copy
4713			BObjectList<entry_ref>* entryList
4714				= new BObjectList<entry_ref>(10, true);
4715
4716			for (int32 index = 0; ; index++) {
4717				// copy all enclosed refs into a list
4718				entry_ref ref;
4719				if (message->FindRef("refs", index, &ref) != B_OK)
4720					break;
4721				entryList->AddItem(new entry_ref(ref));
4722			}
4723
4724			int32 count = entryList->CountItems();
4725			if (count != 0) {
4726				BList* pointList = 0;
4727				if (poseView != NULL && targetPose != NULL) {
4728					// calculate a pointList to make the icons land
4729					// were we dropped them
4730					pointList = new BList(count);
4731					// force the the icons to lay out in 5 columns
4732					for (int32 index = 0; count; index++) {
4733						for (int32 j = 0; count && j < 4; j++, count--) {
4734							BPoint point(
4735								dropPoint + BPoint(j * poseView->fGrid.x,
4736								index * poseView->fGrid.y));
4737							pointList->AddItem(
4738								new BPoint(poseView->PinToGrid(point,
4739								poseView->fGrid, poseView->fOffset)));
4740						}
4741					}
4742				}
4743
4744				// perform asynchronous copy
4745				FSMoveToFolder(entryList, new BEntry(targetModel->EntryRef()),
4746					moveMode, pointList);
4747
4748				return true;
4749			}
4750
4751			// nothing to copy, list doesn't get consumed
4752			delete entryList;
4753			return true;
4754		}
4755		if (message->HasData(kPlainTextMimeType, B_MIME_TYPE)) {
4756			// text dropped, make into a clipping file
4757			if (!targetModel->IsDirectory()) {
4758				// bail if we are not a directory
4759				return false;
4760			}
4761
4762			// find the text
4763			ssize_t textLength;
4764			const char* text;
4765			if (message->FindData(kPlainTextMimeType, B_MIME_TYPE,
4766				(const void**)&text, &textLength) != B_OK) {
4767				return false;
4768			}
4769
4770			char name[B_FILE_NAME_LENGTH];
4771			BFile file;
4772			if (CreateClippingFile(poseView, file, name, &targetDirectory,
4773					message, B_TRANSLATE("Untitled clipping"), !targetPose,
4774					dropPoint) != B_OK) {
4775				return false;
4776			}
4777
4778			// write out the file
4779			if (file.Seek(0, SEEK_SET) == B_ERROR
4780				|| file.Write(text, (size_t)textLength) < 0
4781				|| file.SetSize(textLength) != B_OK) {
4782				// failed to write file, remove file and bail
4783				file.Unset();
4784				BEntry entry(&targetDirectory, name);
4785				entry.Remove();
4786				PRINT(("error writing text into file %s\n", name));
4787			}
4788
4789			// pick up TextView styles if available and save them with the file
4790			const text_run_array* textRuns = NULL;
4791			ssize_t dataSize = 0;
4792			if (message->FindData("application/x-vnd.Be-text_run_array",
4793					B_MIME_TYPE, (const void**)&textRuns, &dataSize) == B_OK
4794					&& textRuns && dataSize) {
4795				// save styles the same way StyledEdit does
4796				int32 tmpSize = dataSize;
4797				void* data = BTextView::FlattenRunArray(textRuns, &tmpSize);
4798				file.WriteAttr("styles", B_RAW_TYPE, 0, data, (size_t)tmpSize);
4799				free(data);
4800			}
4801
4802			// mark as a clipping file
4803			int32 tmp;
4804			file.WriteAttr(kAttrClippingFile, B_RAW_TYPE, 0, &tmp,
4805				sizeof(int32));
4806
4807			// set the file type
4808			BNodeInfo info(&file);
4809			info.SetType(kPlainTextMimeType);
4810
4811			return true;
4812		}
4813
4814		if (message->HasData(kBitmapMimeType, B_MESSAGE_TYPE)
4815			|| message->HasData(kLargeIconType, B_MESSAGE_TYPE)
4816			|| message->HasData(kMiniIconType, B_MESSAGE_TYPE)) {
4817			// bitmap, make into a clipping file
4818			if (!targetModel->IsDirectory()) {
4819				// bail if we are not a directory
4820				return false;
4821			}
4822
4823			BMessage embeddedBitmap;
4824			if (message->FindMessage(kBitmapMimeType, &embeddedBitmap)
4825					!= B_OK
4826				&& message->FindMessage(kLargeIconType, &embeddedBitmap)
4827					!= B_OK
4828				&& message->FindMessage(kMiniIconType, &embeddedBitmap)
4829					!= B_OK) {
4830				return false;
4831			}
4832
4833			char name[B_FILE_NAME_LENGTH];
4834
4835			BFile file;
4836			if (CreateClippingFile(poseView, file, name, &targetDirectory,
4837					message, B_TRANSLATE("Untitled bitmap"), targetPose == NULL,
4838					dropPoint) != B_OK) {
4839				return false;
4840			}
4841
4842			int32 size = embeddedBitmap.FlattenedSize();
4843			if (size > 1024 * 1024) {
4844				// bail if too large
4845				return false;
4846			}
4847
4848			char* buffer = new char [size];
4849			embeddedBitmap.Flatten(buffer, size);
4850
4851			// write out the file
4852			if (file.Seek(0, SEEK_SET) == B_ERROR
4853				|| file.Write(buffer, (size_t)size) < 0
4854				|| file.SetSize(size) != B_OK) {
4855				// failed to write file, remove file and bail
4856				file.Unset();
4857				BEntry entry(&targetDirectory, name);
4858				entry.Remove();
4859				PRINT(("error writing bitmap into file %s\n", name));
4860			}
4861
4862			// mark as a clipping file
4863			int32 tmp;
4864			file.WriteAttr(kAttrClippingFile, B_RAW_TYPE, 0, &tmp,
4865				sizeof(int32));
4866
4867			// set the file type
4868			BNodeInfo info(&file);
4869			info.SetType(kBitmapMimeType);
4870
4871			return true;
4872		}
4873
4874		return false;
4875	}
4876
4877	ASSERT(srcWindow != NULL);
4878
4879	if (srcWindow == containerWindow) {
4880		// drag started in this window
4881		containerWindow->Activate();
4882		containerWindow->UpdateIfNeeded();
4883		poseView->ResetPosePlacementHint();
4884
4885		if (DragSelectionContains(targetPose, message)) {
4886			// drop on self
4887			targetModel = NULL;
4888		}
4889	}
4890
4891	bool wasHandled = false;
4892	bool ignoreTypes = (modifiers() & B_CONTROL_KEY) != 0;
4893
4894	if (targetModel != NULL && containerWindow != NULL) {
4895		// TODO: pick files to drop/launch on a case by case basis
4896		if (targetModel->IsDirectory()) {
4897			MoveSelectionInto(targetModel, srcWindow, containerWindow,
4898				buttons, dropPoint, false);
4899			wasHandled = true;
4900		} else if (CanHandleDragSelection(targetModel, message, ignoreTypes)) {
4901			LaunchAppWithSelection(targetModel, message, !ignoreTypes);
4902			wasHandled = true;
4903		}
4904	}
4905
4906	if (poseView != NULL && !wasHandled) {
4907		BPoint clickPoint = message->FindPoint("click_pt");
4908		// TODO: removed check for root here need to do that, possibly at a
4909		// different level
4910		poseView->MoveSelectionTo(dropPoint, clickPoint, srcWindow);
4911	}
4912
4913	if (poseView != NULL && poseView->fEnsurePosesVisible)
4914		poseView->CheckPoseVisibility();
4915
4916	return true;
4917}
4918
4919
4920struct LaunchParams {
4921	Model* app;
4922	bool checkTypes;
4923	BMessage* refsMessage;
4924};
4925
4926
4927static bool
4928AddOneToLaunchMessage(BPose* pose, BPoseView*, void* castToParams)
4929{
4930	LaunchParams* params = (LaunchParams*)castToParams;
4931	ThrowOnAssert(params != NULL);
4932	ThrowOnAssert(pose != NULL);
4933	ThrowOnAssert(pose->TargetModel() != NULL);
4934
4935	if (params->app->IsDropTarget(params->checkTypes
4936			? pose->TargetModel() : NULL, true)) {
4937		params->refsMessage->AddRef("refs", pose->TargetModel()->EntryRef());
4938	}
4939
4940	return false;
4941}
4942
4943
4944void
4945BPoseView::LaunchAppWithSelection(Model* appModel, const BMessage* dragMessage,
4946	bool checkTypes)
4947{
4948	// launch items from the current selection with <appModel>; only pass
4949	// the same files that we previously decided can be handled by <appModel>
4950	BMessage refs(B_REFS_RECEIVED);
4951	LaunchParams params;
4952	params.app = appModel;
4953	params.checkTypes = checkTypes;
4954	params.refsMessage = &refs;
4955
4956	// add Tracker token so that refs received recipients can script us
4957	BContainerWindow* srcWindow;
4958	if (dragMessage->FindPointer("src_window", (void**)&srcWindow) == B_OK
4959		&& srcWindow != NULL) {
4960		params.refsMessage->AddMessenger("TrackerViewToken",
4961			BMessenger(srcWindow->PoseView()));
4962	}
4963
4964	EachItemInDraggedSelection(dragMessage, AddOneToLaunchMessage, 0, &params);
4965	if (params.refsMessage->HasRef("refs"))
4966		TrackerLaunch(appModel->EntryRef(), params.refsMessage, true);
4967}
4968
4969
4970bool
4971BPoseView::DragSelectionContains(const BPose* target,
4972	const BMessage* dragMessage)
4973{
4974	return EachItemInDraggedSelection(dragMessage, OneMatches, 0,
4975		(void*)target);
4976}
4977
4978
4979void
4980BPoseView::MoveSelectionInto(Model* destFolder, BContainerWindow* srcWindow,
4981	bool forceCopy, bool forceMove, bool createLink, bool relativeLink)
4982{
4983	uint32 buttons;
4984	BPoint loc;
4985	GetMouse(&loc, &buttons);
4986	MoveSelectionInto(destFolder, srcWindow,
4987		dynamic_cast<BContainerWindow*>(Window()), buttons, loc, forceCopy,
4988		forceMove, createLink, relativeLink);
4989}
4990
4991
4992void
4993BPoseView::MoveSelectionInto(Model* destFolder, BContainerWindow* srcWindow,
4994	BContainerWindow* destWindow, uint32 buttons, BPoint loc, bool forceCopy,
4995	bool forceMove, bool createLink, bool relativeLink, BPoint clickPoint,
4996	bool dropOnGrid)
4997{
4998	AutoLock<BWindow> lock(srcWindow);
4999	if (!lock)
5000		return;
5001
5002	ASSERT(srcWindow->PoseView()->TargetModel() != NULL);
5003
5004	if (srcWindow->PoseView()->SelectionList()->CountItems() == 0)
5005		return;
5006
5007	bool createRelativeLink = relativeLink;
5008	if (SecondaryMouseButtonDown(modifiers(), buttons)
5009		&& destWindow != NULL) {
5010		switch (destWindow->ShowDropContextMenu(loc)) {
5011			case kCreateRelativeLink:
5012				createRelativeLink = true;
5013				break;
5014
5015			case kCreateLink:
5016				createLink = true;
5017				break;
5018
5019			case kMoveSelectionTo:
5020				forceMove = true;
5021				break;
5022
5023			case kCopySelectionTo:
5024				forceCopy = true;
5025				break;
5026
5027			case kCancelButton:
5028			default:
5029				// user canceled context menu
5030				return;
5031		}
5032	}
5033
5034	// make sure source and destination folders are different
5035	if (!createLink && !createRelativeLink
5036		&& (*srcWindow->PoseView()->TargetModel()->NodeRef()
5037			== *destFolder->NodeRef())) {
5038		BPoseView* targetView = srcWindow->PoseView();
5039		if (forceCopy) {
5040			targetView->DuplicateSelection(&clickPoint, &loc);
5041			return;
5042		}
5043
5044		if (targetView->ViewMode() == kListMode) {
5045			// can't move in list view
5046			return;
5047		}
5048
5049		BPoint delta = loc - clickPoint;
5050		int32 count = targetView->fSelectionList->CountItems();
5051		for (int32 index = 0; index < count; index++) {
5052			BPose* pose = targetView->fSelectionList->ItemAt(index);
5053
5054			// remove pose from VSlist before changing location
5055			// so that we "find" the correct pose to remove
5056			// need to do this because bsearch uses top of pose
5057			// to locate pose to remove
5058			targetView->RemoveFromVSList(pose);
5059			BPoint location (pose->Location(targetView) + delta);
5060			BRect oldBounds(pose->CalcRect(targetView));
5061			if (dropOnGrid) {
5062				location = targetView->PinToGrid(location, targetView->fGrid,
5063					targetView->fOffset);
5064			}
5065
5066			// TODO: don't drop poses under desktop elements
5067			//		 ie: replicants, deskbar
5068			pose->MoveTo(location, targetView);
5069
5070			targetView->RemoveFromExtent(oldBounds);
5071			targetView->AddToExtent(pose->CalcRect(targetView));
5072
5073			// remove and reinsert pose to keep VSlist sorted
5074			targetView->AddToVSList(pose);
5075		}
5076
5077		return;
5078	}
5079
5080	BEntry* destEntry = new BEntry(destFolder->EntryRef());
5081	bool destIsTrash = destFolder->IsTrash();
5082
5083	// perform asynchronous copy/move
5084	forceCopy = forceCopy || (modifiers() & B_OPTION_KEY) != 0;
5085
5086	bool okToMove = true;
5087
5088	if (destFolder->IsRoot()) {
5089		BAlert* alert = new BAlert("",
5090			B_TRANSLATE("You must drop items on one of the disk icons "
5091			"in the \"Disks\" window."), B_TRANSLATE("Cancel"), NULL, NULL,
5092			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
5093		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
5094		alert->Go();
5095		okToMove = false;
5096	}
5097
5098	// can't copy items into the trash
5099	if (forceCopy && destIsTrash) {
5100		BAlert* alert = new BAlert("",
5101			B_TRANSLATE("Sorry, you can't copy items to the Trash."),
5102			B_TRANSLATE("Cancel"), NULL, NULL, B_WIDTH_AS_USUAL,
5103			B_WARNING_ALERT);
5104		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
5105		alert->Go();
5106		okToMove = false;
5107	}
5108
5109	// can't create symlinks into the trash
5110	if (createLink && destIsTrash) {
5111		BAlert* alert = new BAlert("",
5112			B_TRANSLATE("Sorry, you can't create links in the Trash."),
5113			B_TRANSLATE("Cancel"), NULL, NULL, B_WIDTH_AS_USUAL,
5114			B_WARNING_ALERT);
5115		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
5116		alert->Go();
5117		okToMove = false;
5118	}
5119
5120	// prompt user if drag was from a query
5121	if (srcWindow->TargetModel()->IsQuery()
5122		&& !forceCopy && !destIsTrash && !createLink) {
5123		srcWindow->UpdateIfNeeded();
5124		BAlert* alert = new BAlert("",
5125			B_TRANSLATE("Are you sure you want to move or copy the selected "
5126			"item(s) to this folder?"), B_TRANSLATE("Cancel"),
5127			B_TRANSLATE("Move"), NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
5128		alert->SetShortcut(0, B_ESCAPE);
5129		okToMove = alert->Go() == 1;
5130	}
5131
5132	if (okToMove) {
5133		PoseList* selectionList = srcWindow->PoseView()->SelectionList();
5134		BList* pointList = destWindow->PoseView()->GetDropPointList(clickPoint,
5135			loc, selectionList, srcWindow->PoseView()->ViewMode() == kListMode,
5136			dropOnGrid);
5137		int32 selectionSize = selectionList->CountItems();
5138		BObjectList<entry_ref>* srcList
5139			= new BObjectList<entry_ref>(selectionSize, true);
5140
5141		if (srcWindow->TargetModel()->IsVirtualDirectory()) {
5142			// resolve symlink and add the resulting entry_ref to the list
5143			for (int32 i = 0; i < selectionSize; i++) {
5144				Model* model = selectionList->ItemAt(i)->ResolvedModel();
5145				if (model != NULL)
5146					srcList->AddItem(new entry_ref(*(model->EntryRef())));
5147			}
5148		} else
5149			CopySelectionListToEntryRefList(selectionList, srcList);
5150
5151		uint32 moveMode;
5152		if (forceCopy)
5153			moveMode = kCopySelectionTo;
5154		else if (forceMove)
5155			moveMode = kMoveSelectionTo;
5156		else if (createRelativeLink)
5157			moveMode = kCreateRelativeLink;
5158		else if (createLink)
5159			moveMode = kCreateLink;
5160		else if (!CheckDevicesEqual(srcList->ItemAt(0), destFolder))
5161			moveMode = kCopySelectionTo;
5162		else
5163			moveMode = kMoveSelectionTo;
5164
5165		FSMoveToFolder(srcList, destEntry, moveMode, pointList);
5166		return;
5167	}
5168
5169	delete destEntry;
5170}
5171
5172
5173void
5174BPoseView::MoveSelectionTo(BPoint dropPoint, BPoint clickPoint,
5175	BContainerWindow* srcWindow)
5176{
5177	// Moves selection from srcWindow into this window, copying if necessary.
5178
5179	BContainerWindow* window = ContainerWindow();
5180	if (window == NULL)
5181		return;
5182
5183	ASSERT(window->PoseView() != NULL);
5184	ASSERT(TargetModel() != NULL);
5185
5186	// make sure this window is a legal drop target
5187	if (srcWindow != window && !TargetModel()->IsDropTarget())
5188		return;
5189
5190	uint32 buttons = (uint32)window->CurrentMessage()->FindInt32("buttons");
5191	bool pinToGrid = (modifiers() & B_COMMAND_KEY) != 0;
5192	MoveSelectionInto(TargetModel(), srcWindow, window, buttons, dropPoint,
5193		false, false, false, false, clickPoint, pinToGrid);
5194}
5195
5196
5197inline void
5198UpdateWasBrokenSymlinkBinder(BPose* pose, Model* model, int32 index,
5199	BPoseView* poseView, BObjectList<Model>* fBrokenLinks)
5200{
5201	if (!model->IsSymLink())
5202		return;
5203
5204	BPoint loc(0, index * poseView->ListElemHeight());
5205	pose->UpdateWasBrokenSymlink(loc, poseView);
5206	if (model->LinkTo() != NULL)
5207		fBrokenLinks->RemoveItem(model);
5208}
5209
5210
5211void
5212BPoseView::TryUpdatingBrokenLinks()
5213{
5214	AutoLock<BWindow> lock(Window());
5215	if (!lock)
5216		return;
5217
5218	BObjectList<Model>* brokenLinksCopy = new BObjectList<Model>(*fBrokenLinks);
5219
5220	// try fixing broken symlinks, and detecting broken ones.
5221	EachPoseAndModel(fPoseList, &UpdateWasBrokenSymlinkBinder, this,
5222		fBrokenLinks);
5223
5224	for (int i = brokenLinksCopy->CountItems() - 1; i >= 0; i--) {
5225		if (!fBrokenLinks->HasItem(brokenLinksCopy->ItemAt(i)))
5226			StopWatchingParentsOf(brokenLinksCopy->ItemAt(i)->EntryRef());
5227	}
5228
5229	delete brokenLinksCopy;
5230}
5231
5232
5233void
5234BPoseView::PoseHandleDeviceUnmounted(BPose* pose, Model* model, int32 index,
5235	BPoseView* poseView, dev_t device)
5236{
5237	if (model->NodeRef()->device == device)
5238		poseView->DeletePose(model->NodeRef());
5239	else if (model->IsSymLink() && model->LinkTo() != NULL
5240		&& model->LinkTo()->NodeRef()->device == device) {
5241		poseView->DeleteSymLinkPoseTarget(model->LinkTo()->NodeRef(),
5242			pose, index);
5243	}
5244}
5245
5246
5247static void
5248OneMetaMimeChanged(BPose* pose, Model* model, int32 index,
5249	BPoseView* poseView, const char* type)
5250{
5251	ASSERT(model != NULL);
5252
5253	if (model->IconFrom() != kNode
5254		&& model->IconFrom() != kUnknownSource
5255		&& model->IconFrom() != kUnknownNotFromNode
5256		// TODO: add supertype compare
5257		&& strcasecmp(model->MimeType(), type) == 0) {
5258		// metamime change very likely affected the documents icon
5259		BPoint poseLoc(0, index * poseView->ListElemHeight());
5260		pose->UpdateIcon(poseLoc, poseView);
5261	}
5262}
5263
5264
5265void
5266BPoseView::MetaMimeChanged(const char* type, const char* preferredApp)
5267{
5268	IconCache::sIconCache->IconChanged(type, preferredApp);
5269	// wait for other windows to do the same before we start
5270	// updating poses which causes icon recaching
5271	// TODO: this is a design problem that should be solved differently
5272	snooze(10000);
5273	Window()->UpdateIfNeeded();
5274
5275	EachPoseAndResolvedModel(fPoseList, &OneMetaMimeChanged, this, type);
5276}
5277
5278
5279class MetaMimeChangedAccumulator : public AccumulatingFunctionObject {
5280// pools up matching metamime change notices, executing them as a single
5281// update
5282public:
5283	MetaMimeChangedAccumulator(void (BPoseView::*func)(const char* type,
5284		const char* preferredApp),
5285		BContainerWindow* window, const char* type, const char* preferredApp)
5286		:
5287		fCallOnThis(window),
5288		fFunc(func),
5289		fType(type),
5290		fPreferredApp(preferredApp)
5291	{
5292	}
5293
5294	virtual bool CanAccumulate(const AccumulatingFunctionObject* functor) const
5295	{
5296		const MetaMimeChangedAccumulator* accumulator
5297			= dynamic_cast<const MetaMimeChangedAccumulator*>(functor);
5298		if (accumulator == NULL)
5299			return false;
5300
5301		return accumulator && accumulator->fType == fType
5302			&& accumulator->fPreferredApp == fPreferredApp;
5303	}
5304
5305	virtual void Accumulate(AccumulatingFunctionObject* DEBUG_ONLY(functor))
5306	{
5307		ASSERT(CanAccumulate(functor));
5308		// do nothing, no further accumulating needed
5309	}
5310
5311protected:
5312	virtual void operator()()
5313	{
5314		AutoLock<BWindow> lock(fCallOnThis);
5315		if (!lock)
5316			return;
5317
5318		(fCallOnThis->PoseView()->*fFunc)(fType.String(),
5319			fPreferredApp.String());
5320	}
5321
5322	virtual ulong Size() const
5323	{
5324		return sizeof (*this);
5325	}
5326
5327private:
5328	BContainerWindow* fCallOnThis;
5329	void (BPoseView::*fFunc)(const char* type, const char* preferredApp);
5330	BString fType;
5331	BString fPreferredApp;
5332};
5333
5334
5335bool
5336BPoseView::NoticeMetaMimeChanged(const BMessage* message)
5337{
5338	int32 change;
5339	if (message->FindInt32("be:which", &change) != B_OK)
5340		return true;
5341
5342	bool iconChanged = (change & B_ICON_CHANGED) != 0;
5343	bool iconForTypeChanged = (change & B_ICON_FOR_TYPE_CHANGED) != 0;
5344	bool preferredAppChanged = (change & B_APP_HINT_CHANGED)
5345		|| (change & B_PREFERRED_APP_CHANGED);
5346
5347	const char* type = NULL;
5348	const char* preferredApp = NULL;
5349
5350	if (iconChanged || preferredAppChanged)
5351		message->FindString("be:type", &type);
5352
5353	if (iconForTypeChanged) {
5354		message->FindString("be:extra_type", &type);
5355		message->FindString("be:type", &preferredApp);
5356	}
5357
5358	if (iconChanged || preferredAppChanged || iconForTypeChanged) {
5359		TaskLoop* taskLoop = ContainerWindow()->DelayedTaskLoop();
5360		ASSERT(taskLoop != NULL);
5361		taskLoop->AccumulatedRunLater(new MetaMimeChangedAccumulator(
5362			&BPoseView::MetaMimeChanged, ContainerWindow(), type, preferredApp),
5363			200000, 5000000);
5364	}
5365
5366	return true;
5367}
5368
5369
5370bool
5371BPoseView::FSNotification(const BMessage* message)
5372{
5373	node_ref itemNode;
5374	dev_t device;
5375	Model* targetModel = TargetModel();
5376
5377	switch (message->FindInt32("opcode")) {
5378		case B_ENTRY_CREATED:
5379		{
5380			ASSERT(targetModel != NULL);
5381
5382			message->FindInt32("device", &itemNode.device);
5383			node_ref dirNode;
5384			dirNode.device = itemNode.device;
5385			message->FindInt64("directory", (int64*)&dirNode.node);
5386			message->FindInt64("node", (int64*)&itemNode.node);
5387
5388			int32 count = fBrokenLinks->CountItems();
5389			bool createPose = true;
5390			// Query windows can get notices on different dirNodes
5391			// The Disks window can too
5392			// So can the Desktop, as long as the integrate flag is on
5393			TrackerSettings settings;
5394			if (targetModel != NULL && dirNode != *targetModel->NodeRef()
5395				&& !targetModel->IsQuery()
5396				&& !targetModel->IsVirtualDirectory()
5397				&& !targetModel->IsRoot()
5398				&& (!settings.ShowDisksIcon() || !IsDesktopView())) {
5399				if (count == 0)
5400					break;
5401				createPose = false;
5402			}
5403
5404			const char* name;
5405			if (message->FindString("name", &name) != B_OK) {
5406#if DEBUG
5407				SERIAL_PRINT(("no name in entry creation message\n"));
5408#endif
5409				break;
5410			}
5411			if (count != 0) {
5412				// basically, let's say we have a broken link :
5413				// ./a_link -> ./some_folder/another_folder/a_target
5414				// and that both some_folder and another_folder didn't
5415				// exist yet. We are looking if the just created folder
5416				// is 'some_folder' and watch it, expecting the creation of
5417				// 'another_folder' later and then report the link as fixed.
5418				Model* model = new Model(&dirNode, &itemNode, name);
5419				if (model->IsDirectory()) {
5420					BString createdPath(BPath(model->EntryRef()).Path());
5421					BDirectory currentDir(targetModel->EntryRef());
5422					BPath createdDir(model->EntryRef());
5423					for (int32 i = 0; i < count; i++) {
5424						BSymLink link(fBrokenLinks->ItemAt(i)->EntryRef());
5425						BPath path;
5426						link.MakeLinkedPath(&currentDir, &path);
5427						BString pathStr(path.Path());
5428						pathStr.Append("/");
5429						if (pathStr.Compare(createdPath,
5430							createdPath.Length()) == 0) {
5431							if (pathStr[createdPath.Length()] != '/')
5432								break;
5433							StopWatchingParentsOf(fBrokenLinks->ItemAt(i)
5434								->EntryRef());
5435							watch_node(&itemNode, B_WATCH_DIRECTORY, this);
5436							break;
5437						}
5438					}
5439				}
5440				delete model;
5441			}
5442			if (createPose)
5443				EntryCreated(&dirNode, &itemNode, name);
5444
5445			TryUpdatingBrokenLinks();
5446			break;
5447		}
5448
5449		case B_ENTRY_MOVED:
5450			return EntryMoved(message);
5451			break;
5452
5453		case B_ENTRY_REMOVED:
5454			message->FindInt32("device", &itemNode.device);
5455			message->FindInt64("node", (int64*)&itemNode.node);
5456
5457			// our window itself may be deleted
5458			// we must check to see if this comes as a query
5459			// notification or a node monitor notification because
5460			// if it's a query notification then we're just being told we
5461			// no longer match the query, so we don't want to close the window
5462			// but it's a node monitor notification then that means our query
5463			// file has been deleted so we close the window
5464
5465			if (message->what == B_NODE_MONITOR && targetModel != NULL
5466				&& *(targetModel->NodeRef()) == itemNode) {
5467				if (!targetModel->IsRoot()) {
5468					// it is impossible to watch for ENTRY_REMOVED in
5469					// "/" because the notification is ambiguous - the vnode
5470					// is that of the volume but the device is of the parent
5471					// not the same as the device of the volume that way we
5472					// may get aliasing for volumes with vnodes of 1
5473					// (currently the case for iso9660)
5474					DisableSaveLocation();
5475					Window()->Close();
5476				}
5477			} else {
5478				int32 index;
5479				BPose* pose = fPoseList->FindPose(&itemNode, &index);
5480				if (pose == NULL) {
5481					// couldn't find pose, first check if the node might be
5482					// target of a symlink pose;
5483					//
5484					// What happens when a node and a symlink to it are in the
5485					// same window?
5486					// They get monitored twice, we get two notifications; the
5487					// first one will get caught by the first FindPose, the
5488					// second one by the DeepFindPose
5489					//
5490					pose = fPoseList->DeepFindPose(&itemNode, &index);
5491					if (pose != NULL) {
5492						DeleteSymLinkPoseTarget(&itemNode, pose, index);
5493						break;
5494					}
5495				}
5496
5497			 	DeletePose(&itemNode);
5498				TryUpdatingBrokenLinks();
5499			}
5500			break;
5501
5502		case B_DEVICE_MOUNTED:
5503		{
5504			if (message->FindInt32("new device", &device) != B_OK)
5505				break;
5506
5507			if (targetModel != NULL && targetModel->IsRoot()) {
5508				BVolume volume(device);
5509				if (volume.InitCheck() == B_OK)
5510					CreateVolumePose(&volume, false);
5511			} else if (ContainerWindow()->IsTrash()) {
5512				// add trash items from newly mounted volume
5513
5514				BDirectory trashDir;
5515				BEntry entry;
5516				BVolume volume(device);
5517				if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK
5518					&& trashDir.GetEntry(&entry) == B_OK) {
5519					Model model(&entry);
5520					if (model.InitCheck() == B_OK)
5521						AddPoses(&model);
5522				}
5523			}
5524			TaskLoop* taskLoop = ContainerWindow()->DelayedTaskLoop();
5525			ASSERT(taskLoop);
5526			taskLoop->RunLater(NewMemberFunctionObject(
5527				&BPoseView::TryUpdatingBrokenLinks, this), 500000);
5528				// delay of 500000: wait for volumes to properly finish mounting
5529				// without this in the Model::FinishSettingUpType a symlink
5530				// to a volume would get initialized as a symlink to a directory
5531				// because IsRootDirectory looks like returns false. Either
5532				// there is a race condition or I was doing something wrong.
5533			break;
5534		}
5535
5536		case B_DEVICE_UNMOUNTED:
5537			if (message->FindInt32("device", &device) == B_OK) {
5538				if (targetModel != NULL
5539					&& targetModel->NodeRef()->device == device) {
5540					// close the window from a volume that is gone
5541					DisableSaveLocation();
5542					Window()->Close();
5543				} else if (targetModel != NULL) {
5544					EachPoseAndModel(fPoseList, &PoseHandleDeviceUnmounted,
5545						this, device);
5546				}
5547			}
5548			break;
5549
5550		case B_STAT_CHANGED:
5551		case B_ATTR_CHANGED:
5552			return AttributeChanged(message);
5553	}
5554
5555	return true;
5556}
5557
5558
5559bool
5560BPoseView::CreateSymlinkPoseTarget(Model* symlink)
5561{
5562	Model* newResolvedModel = NULL;
5563	Model* result = symlink->LinkTo();
5564
5565	if (result == NULL) {
5566		BEntry entry(symlink->EntryRef(), true);
5567		if (entry.InitCheck() == B_OK) {
5568			node_ref nref;
5569			entry_ref eref;
5570			entry.GetNodeRef(&nref);
5571			entry.GetRef(&eref);
5572			if (eref.directory != TargetModel()->NodeRef()->node)
5573				WatchNewNode(&nref, B_WATCH_STAT | B_WATCH_ATTR | B_WATCH_NAME
5574					| B_WATCH_INTERIM_STAT, this);
5575			newResolvedModel = new Model(&entry, true);
5576		} else {
5577			fBrokenLinks->AddItem(symlink);
5578			WatchParentOf(symlink->EntryRef());
5579			return true;
5580		}
5581		result = newResolvedModel;
5582	}
5583	symlink->SetLinkTo(result);
5584
5585	return true;
5586}
5587
5588
5589BPose*
5590BPoseView::EntryCreated(const node_ref* dirNode, const node_ref* itemNode,
5591	const char* name, int32* indexPtr)
5592{
5593	// reject notification if pose already exists
5594	if (fPoseList->FindPose(itemNode) || FindZombie(itemNode))
5595		return NULL;
5596
5597	BPoseView::WatchNewNode(itemNode);
5598		// have to node monitor ahead of time because Model will
5599		// cache up the file type and preferred app
5600	Model* model = new Model(dirNode, itemNode, name, true);
5601
5602	if (model->InitCheck() != B_OK) {
5603		// if we have trouble setting up model then we stuff it into
5604		// a zombie list in a half-alive state until we can properly awaken it
5605		PRINT(("2 adding model %s to zombie list, error %s\n", model->Name(),
5606			strerror(model->InitCheck())));
5607		fZombieList->AddItem(model);
5608		return NULL;
5609	}
5610
5611	PoseInfo poseInfo;
5612	ReadPoseInfo(model, &poseInfo);
5613
5614	if (!PoseVisible(model, &poseInfo)) {
5615		watch_node(model->NodeRef(), B_STOP_WATCHING, this);
5616		delete model;
5617		return NULL;
5618	}
5619
5620	// model is a symlink, cache up the symlink target or scrap
5621	// everything if target is invisible
5622	if (model->IsSymLink() && !CreateSymlinkPoseTarget(model)) {
5623		watch_node(model->NodeRef(), B_STOP_WATCHING, this);
5624		delete model;
5625		return NULL;
5626	}
5627
5628	return CreatePose(model, &poseInfo, true, indexPtr);
5629}
5630
5631
5632bool
5633BPoseView::EntryMoved(const BMessage* message)
5634{
5635	ino_t oldDir;
5636	node_ref dirNode;
5637	node_ref itemNode;
5638
5639	message->FindInt32("device", &dirNode.device);
5640	itemNode.device = dirNode.device;
5641	message->FindInt64("to directory", (int64*)&dirNode.node);
5642	message->FindInt64("node", (int64*)&itemNode.node);
5643	message->FindInt64("from directory", (int64*)&oldDir);
5644
5645	const char* name;
5646	if (message->FindString("name", &name) != B_OK)
5647		return true;
5648
5649	// handle special case of notifying a name change for a volume
5650	// - the notification is not enough, because the volume's device
5651	// is different than that of the root directory; we have to do a
5652	// lookup using the new volume name and get the volume device from there
5653	StatStruct st;
5654	// get the inode of the root and check if we got a notification on it
5655	if (stat("/", &st) >= 0
5656		&& st.st_dev == dirNode.device
5657		&& st.st_ino == dirNode.node) {
5658		BString buffer;
5659		buffer << "/" << name;
5660		if (stat(buffer.String(), &st) >= 0) {
5661			// point the dirNode to the actual volume
5662			itemNode.node = st.st_ino;
5663			itemNode.device = st.st_dev;
5664		}
5665	}
5666
5667	Model* targetModel = TargetModel();
5668	ThrowOnAssert(targetModel != NULL);
5669
5670	node_ref thisDirNode;
5671	if (ContainerWindow()->IsTrash()) {
5672		BDirectory trashDir;
5673		if (FSGetTrashDir(&trashDir, itemNode.device) != B_OK)
5674			return true;
5675
5676		trashDir.GetNodeRef(&thisDirNode);
5677	} else
5678		thisDirNode = *targetModel->NodeRef();
5679
5680	// see if we need to update window title (and folder itself)
5681	if (thisDirNode == itemNode) {
5682		targetModel->UpdateEntryRef(&dirNode, name);
5683		assert_cast<BContainerWindow*>(Window())->UpdateTitle();
5684	}
5685
5686	if (oldDir == dirNode.node || targetModel->IsQuery()
5687		|| targetModel->IsVirtualDirectory()) {
5688		// rename or move of entry in this directory (or query)
5689
5690		int32 index;
5691		BPose* pose = fPoseList->FindPose(&itemNode, &index);
5692		int32 poseListIndex = index;
5693		bool visible = true;
5694		if (fFiltering)
5695			visible = fFilteredPoseList->FindPose(&itemNode, &index) != NULL;
5696
5697		if (pose != NULL) {
5698			Model* poseModel = pose->TargetModel();
5699			ASSERT(poseModel != NULL);
5700			poseModel->UpdateEntryRef(&dirNode, name);
5701			// for queries we check for move to trash and remove item if so
5702			if (targetModel->IsQuery()) {
5703				PoseInfo poseInfo;
5704				ReadPoseInfo(poseModel, &poseInfo);
5705				if (!ShouldShowPose(poseModel, &poseInfo))
5706					return DeletePose(&itemNode, pose, index);
5707				return true;
5708			}
5709
5710			BPoint loc(0, index * fListElemHeight);
5711			// if we get a rename then we need to assume that we might
5712			// have missed some other attr changed notifications so we
5713			// recheck all widgets
5714			if (poseModel->OpenNode() == B_OK) {
5715				pose->UpdateAllWidgets(index, loc, this);
5716				poseModel->CloseNode();
5717				_CheckPoseSortOrder(fPoseList, pose, poseListIndex);
5718				if (fFiltering) {
5719					if (!visible && FilterPose(pose)) {
5720						BRect bounds = Bounds();
5721						float scrollBy = 0;
5722						AddPoseToList(fFilteredPoseList, true, true, pose,
5723							bounds, scrollBy, true);
5724					} else if (visible && !FilterPose(pose))
5725						RemoveFilteredPose(pose, index);
5726					else if (visible)
5727						_CheckPoseSortOrder(fFilteredPoseList, pose, index);
5728				}
5729			}
5730		} else {
5731			// also must watch for renames on zombies
5732			Model* zombie = FindZombie(&itemNode, &index);
5733			if (zombie) {
5734				PRINT(("converting model %s from a zombie\n", zombie->Name()));
5735				zombie->UpdateEntryRef(&dirNode, name);
5736				pose = ConvertZombieToPose(zombie, index);
5737			} else
5738				return false;
5739		}
5740		if (pose != NULL)
5741			pendingNodeMonitorCache.PoseCreatedOrMoved(this, pose);
5742	} else if (oldDir == thisDirNode.node)
5743		DeletePose(&itemNode);
5744	else if (dirNode.node == thisDirNode.node)
5745		EntryCreated(&dirNode, &itemNode, name);
5746
5747	TryUpdatingBrokenLinks();
5748
5749	return true;
5750}
5751
5752
5753void
5754BPoseView::WatchParentOf(const entry_ref* ref)
5755{
5756	BPath currentDir(ref);
5757	currentDir.GetParent(&currentDir);
5758	BSymLink symlink(ref);
5759	BPath path;
5760
5761	symlink.MakeLinkedPath(currentDir.Path(), &path);
5762	status_t status = path.GetParent(&path);
5763
5764	while (status == B_BAD_VALUE)
5765		status = path.GetParent(&path);
5766
5767	if (status == B_ENTRY_NOT_FOUND)
5768		return;
5769
5770	node_ref nref;
5771	BNode(path.Path()).GetNodeRef(&nref);
5772
5773	if (nref != *TargetModel()->NodeRef())
5774		watch_node(&nref, B_WATCH_DIRECTORY, this);
5775}
5776
5777
5778void
5779BPoseView::StopWatchingParentsOf(const entry_ref* ref)
5780{
5781	BPath path;
5782	BSymLink symlink(ref);
5783	BPath currentDir(ref);
5784	currentDir.GetParent(&currentDir);
5785	symlink.MakeLinkedPath(currentDir.Path(), &path);
5786
5787	if (path.InitCheck() != B_OK)
5788		return;
5789
5790	BObjectList<Model>* brokenLinksCopy = new BObjectList<Model>(*fBrokenLinks);
5791	int32 count = brokenLinksCopy->CountItems();
5792
5793	while (path.GetParent(&path) == B_OK) {
5794		if (strcmp(path.Path(), "/") == 0)
5795			break;
5796
5797		BNode dir(path.Path());
5798		node_ref dirNode;
5799		dir.GetNodeRef(&dirNode);
5800
5801		// don't stop watching yourself.
5802		if (dirNode == *TargetModel()->NodeRef())
5803			continue;
5804
5805		// make sure we don't have another broken links that still requires
5806		// to watch this directory
5807		bool keep = false;
5808		for (int32 i = count - 1; i >= 0; i--) {
5809			BSymLink link(brokenLinksCopy->ItemAt(i)->EntryRef());
5810			BPath absolutePath;
5811			link.MakeLinkedPath(currentDir.Path(), &absolutePath);
5812			if (BString(absolutePath.Path()).Compare(path.Path(),
5813					strlen(path.Path())) == 0) {
5814				// a broken link still needs to watch this folder, but
5815				// don't let that same link also watch a deeper parent.
5816				brokenLinksCopy->RemoveItemAt(i);
5817				count--;
5818				keep = true;
5819			}
5820		}
5821		if (!keep)
5822			watch_node(&dirNode, B_STOP_WATCHING, this);
5823	}
5824	delete brokenLinksCopy;
5825}
5826
5827
5828bool
5829BPoseView::AttributeChanged(const BMessage* message)
5830{
5831	node_ref itemNode;
5832	message->FindInt32("device", &itemNode.device);
5833	message->FindInt64("node", (int64*)&itemNode.node);
5834
5835	const char* attrName;
5836	if (message->FindString("attr", &attrName) != B_OK)
5837		attrName = NULL;
5838
5839	Model* targetModel = TargetModel();
5840	if (targetModel != NULL && *targetModel->NodeRef() == itemNode
5841		&& targetModel->IsNodeOpen()
5842		&& targetModel->AttrChanged(attrName)) {
5843		// the icon of our target has changed, update drag icon
5844		// TODO: make this simpler (i.e. store the icon with the window)
5845		BView* view = Window()->FindView("MenuBar");
5846		if (view != NULL) {
5847			view = view->FindView("ThisContainer");
5848			if (view != NULL) {
5849				IconCache::sIconCache->IconChanged(targetModel);
5850				view->Invalidate();
5851			}
5852		}
5853	}
5854
5855	int32 index;
5856	attr_info info;
5857	PoseList* posesFound = fPoseList->FindAllPoses(&itemNode);
5858	int32 posesCount = posesFound->CountItems();
5859	for (int i = 0; i < posesCount; i++) {
5860		BPose* pose = posesFound->ItemAt(i);
5861		Model* poseModel = pose->TargetModel();
5862		if (poseModel->IsSymLink() && *(poseModel->NodeRef()) != itemNode) {
5863			// change happened on symlink's target
5864			poseModel = poseModel->ResolveIfLink();
5865		}
5866		ASSERT(poseModel != NULL);
5867
5868		status_t result = B_OK;
5869		for (int32 count = 0; count < 100; count++) {
5870			// if node is busy, wait a little, it may be in the
5871			// middle of mimeset and we wan't to pick up the changes
5872			result = poseModel->OpenNode();
5873			if (result == B_OK || result != B_BUSY)
5874				break;
5875
5876			PRINT(("poseModel %s busy, retrying in a bit\n",
5877				poseModel->Name()));
5878			snooze(10000);
5879		}
5880		if (result != B_OK) {
5881			PRINT(("Cache Error %s\n", strerror(result)));
5882			continue;
5883		}
5884
5885		bool visible = fPoseList->FindPose(poseModel->NodeRef(),
5886			&index) != NULL;
5887		int32 poseListIndex = index;
5888
5889		if (fFiltering) {
5890			visible = fFilteredPoseList->FindPose(
5891				poseModel->NodeRef(), &index) != NULL;
5892		}
5893
5894		BPoint loc(0, index * fListElemHeight);
5895		if (attrName != NULL && poseModel->Node() != NULL) {
5896			memset(&info, 0, sizeof(attr_info));
5897			// the call below might fail if the attribute has been removed
5898			poseModel->Node()->GetAttrInfo(attrName, &info);
5899			pose->UpdateWidgetAndModel(poseModel, attrName, info.type, index,
5900				loc, this, visible);
5901			if (strcmp(attrName, kAttrMIMEType) == 0)
5902				RefreshMimeTypeList();
5903		} else {
5904			pose->UpdateWidgetAndModel(poseModel, 0, 0, index, loc, this,
5905				visible);
5906		}
5907		poseModel->CloseNode();
5908		if (fFiltering) {
5909			if (!visible && FilterPose(pose)) {
5910				visible = true;
5911				float scrollBy = 0;
5912				BRect bounds = Bounds();
5913				AddPoseToList(fFilteredPoseList, true, true, pose, bounds,
5914					scrollBy, true);
5915				continue;
5916			} else if (visible && !FilterPose(pose)) {
5917				RemoveFilteredPose(pose, index);
5918				continue;
5919			}
5920		}
5921
5922		if (attrName != NULL) {
5923			// note: the following code is wrong, because this sort of hashing
5924			// may overlap and we get aliasing
5925			uint32 attrHash = AttrHashString(attrName, info.type);
5926			if (attrHash == PrimarySort() || attrHash == SecondarySort()) {
5927				_CheckPoseSortOrder(fPoseList, pose, poseListIndex);
5928				if (fFiltering && visible)
5929					_CheckPoseSortOrder(fFilteredPoseList, pose, index);
5930			}
5931		} else {
5932			int32 fields;
5933			if (message->FindInt32("fields", &fields) != B_OK)
5934				continue;
5935
5936			for (int i = sizeof(sAttrColumnMap) / sizeof(attr_column_relation);
5937					i--;) {
5938				if (sAttrColumnMap[i].attrHash == PrimarySort()
5939					|| sAttrColumnMap[i].attrHash == SecondarySort()) {
5940					if ((fields & sAttrColumnMap[i].fieldMask) != 0) {
5941						_CheckPoseSortOrder(fPoseList, pose, poseListIndex);
5942						if (fFiltering && visible)
5943							_CheckPoseSortOrder(fFilteredPoseList, pose, index);
5944						break;
5945					}
5946				}
5947			}
5948		}
5949	}
5950	delete posesFound;
5951	if (posesCount == 0) {
5952		// we received an attr changed notification for a zombie model, it means
5953		// that although we couldn't open the node the first time, it seems
5954		// to be fine now since we're receiving notifications about it, it might
5955		// be a good time to convert it to a non-zombie state. cf. test in #4130
5956		Model* zombie = FindZombie(&itemNode, &index);
5957		if (zombie != NULL) {
5958			PRINT(("converting model %s from a zombie\n", zombie->Name()));
5959			return ConvertZombieToPose(zombie, index) != NULL;
5960		} else {
5961			PRINT(("model has no pose but is not a zombie either!\n"));
5962			return false;
5963		}
5964	}
5965
5966	return true;
5967}
5968
5969
5970void
5971BPoseView::UpdateIcon(BPose* pose)
5972{
5973	BPoint location;
5974	if (ViewMode() == kListMode) {
5975		// need to find the index of the pose in the pose list
5976		bool found = false;
5977		PoseList* poseList = CurrentPoseList();
5978		int32 count = poseList->CountItems();
5979		for (int32 index = 0; index < count; index++) {
5980			if (poseList->ItemAt(index) == pose) {
5981				location.Set(0, index * fListElemHeight);
5982				found = true;
5983				break;
5984			}
5985		}
5986
5987		if (!found)
5988			return;
5989	}
5990
5991	pose->UpdateIcon(location, this);
5992}
5993
5994
5995BPose*
5996BPoseView::ConvertZombieToPose(Model* zombie, int32 index)
5997{
5998	if (zombie->UpdateStatAndOpenNode() != B_OK)
5999		return NULL;
6000
6001	fZombieList->RemoveItemAt(index);
6002
6003	PoseInfo poseInfo;
6004	ReadPoseInfo(zombie, &poseInfo);
6005
6006	if (ShouldShowPose(zombie, &poseInfo)) {
6007		// TODO: handle symlinks here
6008		return CreatePose(zombie, &poseInfo);
6009	}
6010
6011	delete zombie;
6012
6013	return NULL;
6014}
6015
6016
6017BList*
6018BPoseView::GetDropPointList(BPoint dropStart, BPoint dropEnd, const PoseList* poses,
6019	bool sourceInListMode, bool dropOnGrid) const
6020{
6021	if (ViewMode() == kListMode)
6022		return NULL;
6023
6024	int32 count = poses->CountItems();
6025	BList* pointList = new BList(count);
6026	for (int32 index = 0; index < count; index++) {
6027		BPose* pose = poses->ItemAt(index);
6028		BPoint poseLoc;
6029		if (sourceInListMode)
6030			poseLoc = dropEnd + BPoint(0, index * (IconPoseHeight() + 3));
6031		else
6032			poseLoc = dropEnd + (pose->Location(this) - dropStart);
6033
6034		if (dropOnGrid)
6035			poseLoc = PinToGrid(poseLoc, fGrid, fOffset);
6036
6037		pointList->AddItem(new BPoint(poseLoc));
6038	}
6039
6040	return pointList;
6041}
6042
6043
6044void
6045BPoseView::DuplicateSelection(BPoint* dropStart, BPoint* dropEnd)
6046{
6047	// If there is a volume or trash folder, remove them from the list
6048	// because they cannot get copied
6049	int32 selectionSize = fSelectionList->CountItems();
6050	for (int32 index = 0; index < selectionSize; index++) {
6051		BPose* pose = (BPose*)fSelectionList->ItemAt(index);
6052		Model* poseModel = pose->TargetModel();
6053
6054		// can't duplicate a volume or the trash
6055		if (poseModel->IsTrash() || poseModel->IsVolume()) {
6056			fSelectionList->RemoveItemAt(index);
6057			index--;
6058			selectionSize--;
6059			if (fSelectionPivotPose == pose)
6060				fSelectionPivotPose = NULL;
6061
6062			if (fRealPivotPose == pose)
6063				fRealPivotPose = NULL;
6064
6065			continue;
6066		}
6067	}
6068
6069	// create entry_ref list from selection
6070	if (!fSelectionList->IsEmpty()) {
6071		BObjectList<entry_ref>* srcList = new BObjectList<entry_ref>(
6072			fSelectionList->CountItems(), true);
6073		CopySelectionListToEntryRefList(fSelectionList, srcList);
6074
6075		BList* dropPoints;
6076		if (dropStart) {
6077			dropPoints = GetDropPointList(*dropStart, *dropEnd, fSelectionList,
6078				ViewMode() == kListMode, (modifiers() & B_COMMAND_KEY) != 0);
6079		} else
6080			dropPoints = NULL;
6081
6082		// perform asynchronous duplicate
6083		FSDuplicate(srcList, dropPoints);
6084	}
6085}
6086
6087
6088void
6089BPoseView::SelectPoseAtLocation(BPoint point)
6090{
6091	int32 index;
6092	BPose* pose = FindPose(point, &index);
6093	if (pose != NULL)
6094		SelectPose(pose, index);
6095}
6096
6097
6098void
6099BPoseView::MoveListToTrash(BObjectList<entry_ref>* list, bool selectNext,
6100	bool deleteDirectly)
6101{
6102	if (!list->CountItems())
6103		return;
6104
6105	BObjectList<FunctionObject>* taskList =
6106		new BObjectList<FunctionObject>(2, true);
6107		// new owning list of tasks
6108
6109	// first move selection to trash,
6110	if (deleteDirectly) {
6111		taskList->AddItem(NewFunctionObject(FSDeleteRefList, list,
6112			false, true));
6113	} else {
6114		taskList->AddItem(NewFunctionObject(FSMoveToTrash, list,
6115			(BList*)NULL, false));
6116	}
6117
6118	if (selectNext && ViewMode() == kListMode) {
6119		// next, if in list view mode try selecting the next item after
6120		BPose* pose = fSelectionList->ItemAt(0);
6121
6122		// find a point in the pose
6123		BPoint pointInPose(kListOffset + 5, 5);
6124		int32 index = IndexOfPose(pose);
6125		pointInPose.y += fListElemHeight * index;
6126
6127		TTracker* tracker = dynamic_cast<TTracker*>(be_app);
6128		if (tracker != NULL) {
6129			ThrowOnAssert(TargetModel() != NULL);
6130
6131			// add a function object to the list of tasks to run
6132			// that will select the next item after the one we just
6133			// deleted
6134			taskList->AddItem(NewMemberFunctionObject(
6135				&TTracker::SelectPoseAtLocationSoon, tracker,
6136				*TargetModel()->NodeRef(), pointInPose));
6137		}
6138	}
6139	// execute the two tasks in order
6140	ThreadSequence::Launch(taskList, true);
6141}
6142
6143
6144inline void
6145CopyOneTrashedRefAsEntry(const entry_ref* ref, BObjectList<entry_ref>* trashList,
6146	BObjectList<entry_ref>* noTrashList, std::map<int32, bool>* deviceHasTrash)
6147{
6148	std::map<int32, bool> &deviceHasTrashTmp = *deviceHasTrash;
6149		// work around stupid binding problems with EachListItem
6150
6151	BDirectory entryDir(ref);
6152	bool isVolume = entryDir.IsRootDirectory();
6153		// volumes will get unmounted
6154
6155	// see if pose's device has a trash
6156	int32 device = ref->device;
6157	BDirectory trashDir;
6158
6159	// cache up the result in a map so that we don't have to keep calling
6160	// FSGetTrashDir over and over
6161	if (!isVolume
6162		&& deviceHasTrashTmp.find(device) == deviceHasTrashTmp.end()) {
6163		deviceHasTrashTmp[device] = FSGetTrashDir(&trashDir, device) == B_OK;
6164	}
6165
6166	if (isVolume || deviceHasTrashTmp[device])
6167		trashList->AddItem(new entry_ref(*ref));
6168	else
6169		noTrashList->AddItem(new entry_ref(*ref));
6170}
6171
6172
6173static void
6174CopyPoseOneAsEntry(BPose* pose, BObjectList<entry_ref>* trashList,
6175	BObjectList<entry_ref>* noTrashList, std::map<int32, bool>* deviceHasTrash)
6176{
6177	CopyOneTrashedRefAsEntry(pose->TargetModel()->EntryRef(), trashList,
6178		noTrashList, deviceHasTrash);
6179}
6180
6181
6182static bool
6183CheckVolumeReadOnly(const entry_ref* ref)
6184{
6185	BVolume volume (ref->device);
6186	if (volume.IsReadOnly()) {
6187		BAlert* alert = new BAlert ("",
6188			B_TRANSLATE("Files cannot be moved or deleted from a read-only "
6189			"volume."), B_TRANSLATE("Cancel"), NULL, NULL, B_WIDTH_AS_USUAL,
6190			B_STOP_ALERT);
6191		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
6192		alert->Go();
6193		return false;
6194	}
6195
6196	return true;
6197}
6198
6199
6200void
6201BPoseView::MoveSelectionOrEntryToTrash(const entry_ref* ref, bool selectNext)
6202{
6203	BObjectList<entry_ref>* entriesToTrash = new
6204		BObjectList<entry_ref>(fSelectionList->CountItems());
6205	BObjectList<entry_ref>* entriesToDeleteOnTheSpot = new
6206		BObjectList<entry_ref>(20, true);
6207	std::map<int32, bool> deviceHasTrash;
6208
6209	if (ref != NULL) {
6210		if (!CheckVolumeReadOnly(ref)) {
6211			delete entriesToTrash;
6212			delete entriesToDeleteOnTheSpot;
6213			return;
6214		}
6215		CopyOneTrashedRefAsEntry(ref, entriesToTrash, entriesToDeleteOnTheSpot,
6216			&deviceHasTrash);
6217	} else {
6218		if (!CheckVolumeReadOnly(
6219				fSelectionList->ItemAt(0)->TargetModel()->EntryRef())) {
6220			delete entriesToTrash;
6221			delete entriesToDeleteOnTheSpot;
6222			return;
6223		}
6224		EachListItem(fSelectionList, CopyPoseOneAsEntry, entriesToTrash,
6225			entriesToDeleteOnTheSpot, &deviceHasTrash);
6226	}
6227
6228	if (entriesToDeleteOnTheSpot->CountItems()) {
6229		BString alertText;
6230		if (ref != NULL) {
6231			alertText.SetTo(B_TRANSLATE("The selected item cannot be moved to "
6232				"the Trash. Would you like to delete it instead? "
6233				"(This operation cannot be reverted.)"));
6234		} else {
6235			alertText.SetTo(B_TRANSLATE("Some of the selected items cannot be "
6236				"moved to the Trash. Would you like to delete them instead? "
6237				"(This operation cannot be reverted.)"));
6238		}
6239
6240		BAlert* alert = new BAlert("", alertText.String(),
6241			B_TRANSLATE("Cancel"), B_TRANSLATE("Delete"));
6242		alert->SetShortcut(0, B_ESCAPE);
6243		if (alert->Go() == 0)
6244			return;
6245	}
6246
6247	MoveListToTrash(entriesToTrash, selectNext, false);
6248	MoveListToTrash(entriesToDeleteOnTheSpot, selectNext, true);
6249}
6250
6251
6252void
6253BPoseView::MoveSelectionToTrash(bool selectNext)
6254{
6255	if (fSelectionList->IsEmpty())
6256		return;
6257
6258	// create entry_ref list from selection
6259	// separate items that can be trashed from ones that cannot
6260
6261	MoveSelectionOrEntryToTrash(0, selectNext);
6262}
6263
6264
6265void
6266BPoseView::MoveEntryToTrash(const entry_ref* ref, bool selectNext)
6267{
6268	MoveSelectionOrEntryToTrash(ref, selectNext);
6269}
6270
6271
6272void
6273BPoseView::DeleteSelection(bool selectNext, bool askUser)
6274{
6275	int32 count = fSelectionList->CountItems();
6276	if (count <= 0)
6277		return;
6278
6279	if (!CheckVolumeReadOnly(
6280			fSelectionList->ItemAt(0)->TargetModel()->EntryRef())) {
6281		return;
6282	}
6283
6284	BObjectList<entry_ref>* entriesToDelete
6285		= new BObjectList<entry_ref>(count, true);
6286
6287	for (int32 index = 0; index < count; index++) {
6288		entriesToDelete->AddItem(new entry_ref(
6289			*fSelectionList->ItemAt(index)->TargetModel()->EntryRef()));
6290	}
6291
6292	Delete(entriesToDelete, selectNext, askUser);
6293}
6294
6295
6296void
6297BPoseView::RestoreSelectionFromTrash(bool selectNext)
6298{
6299	int32 count = fSelectionList -> CountItems();
6300	if (count <= 0)
6301		return;
6302
6303	BObjectList<entry_ref>* entriesToRestore
6304		= new BObjectList<entry_ref>(count, true);
6305
6306	for (int32 index = 0; index < count; index++) {
6307		entriesToRestore->AddItem(new entry_ref(
6308			*fSelectionList->ItemAt(index)->TargetModel()->EntryRef()));
6309	}
6310
6311	RestoreItemsFromTrash(entriesToRestore, selectNext);
6312}
6313
6314
6315void
6316BPoseView::Delete(const entry_ref &ref, bool selectNext, bool askUser)
6317{
6318	BObjectList<entry_ref>* entriesToDelete
6319		= new BObjectList<entry_ref>(1, true);
6320	entriesToDelete->AddItem(new entry_ref(ref));
6321
6322	Delete(entriesToDelete, selectNext, askUser);
6323}
6324
6325
6326void
6327BPoseView::Delete(BObjectList<entry_ref>* list, bool selectNext, bool askUser)
6328{
6329	if (list->CountItems() == 0) {
6330		delete list;
6331		return;
6332	}
6333
6334	BObjectList<FunctionObject>* taskList =
6335		new BObjectList<FunctionObject>(2, true);
6336
6337	// first move selection to trash,
6338	taskList->AddItem(NewFunctionObject(FSDeleteRefList, list, false, askUser));
6339
6340	if (selectNext && ViewMode() == kListMode) {
6341		// next, if in list view mode try selecting the next item after
6342		BPose* pose = fSelectionList->ItemAt(0);
6343
6344		// find a point in the pose
6345		BPoint pointInPose(kListOffset + 5, 5);
6346		int32 index = IndexOfPose(pose);
6347		pointInPose.y += fListElemHeight * index;
6348
6349		TTracker* tracker = dynamic_cast<TTracker*>(be_app);
6350		if (tracker != NULL) {
6351			ThrowOnAssert(TargetModel() != NULL);
6352
6353			// add a function object to the list of tasks to run
6354			// that will select the next item after the one we just
6355			// deleted
6356			Model* targetModel = TargetModel();
6357			ASSERT(targetModel != NULL);
6358			taskList->AddItem(NewMemberFunctionObject(
6359				&TTracker::SelectPoseAtLocationSoon, tracker,
6360				*targetModel->NodeRef(), pointInPose));
6361		}
6362	}
6363
6364	// execute the two tasks in order
6365	ThreadSequence::Launch(taskList, true);
6366}
6367
6368
6369void
6370BPoseView::RestoreItemsFromTrash(BObjectList<entry_ref>* list, bool selectNext)
6371{
6372	if (list->CountItems() == 0) {
6373		delete list;
6374		return;
6375	}
6376
6377	BObjectList<FunctionObject>* taskList =
6378		new BObjectList<FunctionObject>(2, true);
6379
6380	// first restoree selection
6381	taskList->AddItem(NewFunctionObject(FSRestoreRefList, list, false));
6382
6383	if (selectNext && ViewMode() == kListMode) {
6384		// next, if in list view mode try selecting the next item after
6385		BPose* pose = fSelectionList->ItemAt(0);
6386
6387		// find a point in the pose
6388		BPoint pointInPose(kListOffset + 5, 5);
6389		int32 index = IndexOfPose(pose);
6390		pointInPose.y += fListElemHeight * index;
6391
6392		TTracker* tracker = dynamic_cast<TTracker*>(be_app);
6393		if (tracker != NULL) {
6394			ThrowOnAssert(TargetModel() != NULL);
6395
6396			// add a function object to the list of tasks to run
6397			// that will select the next item after the one we just
6398			// restored
6399			Model* targetModel = TargetModel();
6400			ASSERT(targetModel != NULL);
6401			taskList->AddItem(NewMemberFunctionObject(
6402				&TTracker::SelectPoseAtLocationSoon, tracker,
6403				*targetModel->NodeRef(), pointInPose));
6404		}
6405	}
6406
6407	// execute the two tasks in order
6408	ThreadSequence::Launch(taskList, true);
6409}
6410
6411
6412void
6413BPoseView::SelectAll()
6414{
6415	BRect bounds(Bounds());
6416
6417	// clear selection list
6418	fSelectionList->MakeEmpty();
6419	fMimeTypesInSelectionCache.MakeEmpty();
6420	fSelectionPivotPose = NULL;
6421	fRealPivotPose = NULL;
6422
6423	int32 startIndex = 0;
6424	BPoint loc(0, fListElemHeight * startIndex);
6425
6426	bool iconMode = ViewMode() != kListMode;
6427
6428	PoseList* poseList = CurrentPoseList();
6429	int32 count = poseList->CountItems();
6430	for (int32 index = startIndex; index < count; index++) {
6431		BPose* pose = poseList->ItemAt(index);
6432		fSelectionList->AddItem(pose);
6433		if (index == startIndex)
6434			fSelectionPivotPose = pose;
6435
6436		if (!pose->IsSelected()) {
6437			pose->Select(true);
6438
6439			BRect poseRect;
6440			if (iconMode)
6441				poseRect = pose->CalcRect(this);
6442			else
6443				poseRect = pose->CalcRect(loc, this);
6444
6445			if (bounds.Intersects(poseRect)) {
6446				pose->Draw(poseRect, bounds, this, false);
6447				Flush();
6448			}
6449		}
6450
6451		loc.y += fListElemHeight;
6452	}
6453
6454	if (fSelectionChangedHook)
6455		ContainerWindow()->SelectionChanged();
6456}
6457
6458
6459void
6460BPoseView::InvertSelection()
6461{
6462	// Since this function shares most code with
6463	// SelectAll(), we could make SelectAll() empty the selection,
6464	// then call InvertSelection()
6465
6466	BRect bounds(Bounds());
6467
6468	int32 startIndex = 0;
6469	BPoint loc(0, fListElemHeight *