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