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