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