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