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