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