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