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