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