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