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