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