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