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