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