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