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