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