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