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