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