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