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