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