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