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