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