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