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