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