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