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