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