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