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