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