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