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