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