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