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