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