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