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