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