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