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