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