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