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