xref: /haiku/src/kits/tracker/PoseView.cpp (revision 2141d2fe3a5df2f55f3590f67660573b50d1d1d3)
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 	delete params;
1389 
1390 	AutoLockingMessenger lock(target);
1391 
1392 	if (!lock.IsLocked())
1393 		return B_ERROR;
1394 
1395 	thread_id threadID = find_thread(NULL);
1396 
1397 	BPoseView* view = dynamic_cast<BPoseView*>(lock.Handler());
1398 	ThrowOnAssert(view != NULL);
1399 
1400 	BWindow* window = dynamic_cast<BWindow*>(lock.Looper());
1401 	ThrowOnAssert(window != NULL);
1402 
1403 	// allocate the iterator we will use for adding poses; this
1404 	// can be a directory or any other collection of entry_refs, such
1405 	// as results of a query; subclasses override this to provide
1406 	// other than standard directory iterations
1407 	EntryListBase* container = view->InitDirentIterator(&ref);
1408 	if (container == NULL) {
1409 		view->HideBarberPole();
1410 		return B_ERROR;
1411 	}
1412 
1413 	AddPosesResult* posesResult = new AddPosesResult;
1414 	posesResult->fCount = 0;
1415 	int32 modelChunkIndex = -1;
1416 	bigtime_t nextChunkTime = 0;
1417 	uint32 watchMask = view->WatchNewNodeMask();
1418 
1419 	bool hideDotFiles = TrackerSettings().HideDotFiles();
1420 #if DEBUG
1421 	for (int32 index = 0; index < kMaxAddPosesChunk; index++)
1422 		posesResult->fModels[index] = (Model*)0xdeadbeef;
1423 #endif
1424 
1425 	try {
1426 		for (;;) {
1427 			lock.Unlock();
1428 
1429 			status_t result = B_OK;
1430 			char entBuf[1024];
1431 			dirent* eptr = (dirent*)entBuf;
1432 			Model* model = 0;
1433 			node_ref dirNode;
1434 			node_ref itemNode;
1435 
1436 			int32 count = container->GetNextDirents(eptr, 1024, 1);
1437 			if (count <= 0 && modelChunkIndex == -1)
1438 				break;
1439 
1440 			if (count > 0) {
1441 				ASSERT(count == 1);
1442 
1443 				if ((!hideDotFiles && (!strcmp(eptr->d_name, ".")
1444 					|| !strcmp(eptr->d_name, "..")))
1445 					|| (hideDotFiles && eptr->d_name[0] == '.')) {
1446 					continue;
1447 				}
1448 
1449 				dirNode.device = eptr->d_pdev;
1450 				dirNode.node = eptr->d_pino;
1451 				itemNode.device = eptr->d_dev;
1452 				itemNode.node = eptr->d_ino;
1453 
1454 				BPoseView::WatchNewNode(&itemNode, watchMask, lock.Target());
1455 					// have to node monitor ahead of time because Model will
1456 					// cache up the file type and preferred app
1457 					// OK to call when poseView is not locked
1458 
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* window = ContainerWindow();
1669 	if (window != NULL)
1670 		window->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 			DoDelete();
2407 			break;
2408 
2409 		case kMoveToTrash:
2410 			DoMoveToTrash();
2411 			break;
2412 
2413 		case kCleanupAll:
2414 			Cleanup(true);
2415 			break;
2416 
2417 		case kCleanup:
2418 			Cleanup();
2419 			break;
2420 
2421 		case kEditQuery:
2422 			EditQueries();
2423 			break;
2424 
2425 		case kRunAutomounterSettings:
2426 			be_app->PostMessage(message);
2427 			break;
2428 
2429 		case kNewEntryFromTemplate:
2430 			if (message->HasRef("refs_template"))
2431 				NewFileFromTemplate(message);
2432 			break;
2433 
2434 		case kNewFolder:
2435 			NewFolder(message);
2436 			break;
2437 
2438 		case kUnmountVolume:
2439 			UnmountSelectedVolumes();
2440 			break;
2441 
2442 		case kEmptyTrash:
2443 			FSEmptyTrash();
2444 			break;
2445 
2446 		case kGetInfo:
2447 			OpenInfoWindows();
2448 			break;
2449 
2450 		case kIdentifyEntry:
2451 		{
2452 			bool force;
2453 			if (message->FindBool("force", &force) != B_OK)
2454 				force = false;
2455 
2456 			IdentifySelection(force);
2457 			break;
2458 		}
2459 
2460 		case kEditItem:
2461 		{
2462 			if (ActivePose())
2463 				break;
2464 
2465 			BPose* pose = fSelectionList->FirstItem();
2466 			if (pose != NULL) {
2467 				BPoint where(0,
2468 					CurrentPoseList()->IndexOf(pose) * fListElemHeight);
2469 				pose->EditFirstWidget(where, this);
2470 			}
2471 			break;
2472 		}
2473 
2474 		case kOpenParentDir:
2475 			OpenParent();
2476 			break;
2477 
2478 		case kCopyAttributes:
2479 			if (be_clipboard->Lock()) {
2480 				be_clipboard->Clear();
2481 				BMessage* data = be_clipboard->Data();
2482 				if (data != NULL) {
2483 					// copy attributes to the clipboard
2484 					BMessage state;
2485 					SaveState(state);
2486 
2487 					BMallocIO stream;
2488 					ssize_t size;
2489 					if (state.Flatten(&stream, &size) == B_OK) {
2490 						data->AddData("application/tracker-columns",
2491 							B_MIME_TYPE, stream.Buffer(), size);
2492 						be_clipboard->Commit();
2493 					}
2494 				}
2495 				be_clipboard->Unlock();
2496 			}
2497 			break;
2498 
2499 		case kPasteAttributes:
2500 			if (be_clipboard->Lock()) {
2501 				BMessage* data = be_clipboard->Data();
2502 				if (data != NULL) {
2503 					// find the attributes in the clipboard
2504 					const void* buffer;
2505 					ssize_t size;
2506 					if (data->FindData("application/tracker-columns",
2507 							B_MIME_TYPE, &buffer, &size) == B_OK) {
2508 						BMessage state;
2509 						if (state.Unflatten((const char*)buffer) == B_OK) {
2510 							// remove all current columns (one always stays)
2511 							BColumn* old;
2512 							while ((old = ColumnAt(0)) != NULL) {
2513 								if (!RemoveColumn(old, false))
2514 									break;
2515 							}
2516 
2517 							// add new columns
2518 							for (int32 index = 0; ; index++) {
2519 								BColumn* column
2520 									= BColumn::InstantiateFromMessage(state,
2521 										index);
2522 								if (column == NULL)
2523 									break;
2524 
2525 								AddColumn(column);
2526 							}
2527 
2528 							// remove the last old one
2529 							RemoveColumn(old, false);
2530 
2531 							// set sorting mode
2532 							BViewState* viewState
2533 								= BViewState::InstantiateFromMessage(state);
2534 							if (viewState != NULL) {
2535 								SetPrimarySort(viewState->PrimarySort());
2536 								SetSecondarySort(viewState->SecondarySort());
2537 								SetReverseSort(viewState->ReverseSort());
2538 
2539 								SortPoses();
2540 								Invalidate();
2541 							}
2542 						}
2543 					}
2544 				}
2545 				be_clipboard->Unlock();
2546 			}
2547 			break;
2548 
2549 		case kArrangeBy:
2550 		{
2551 			uint32 attrHash;
2552 			if (message->FindInt32("attr_hash", (int32*)&attrHash) == B_OK) {
2553 				if (ColumnFor(attrHash) == NULL)
2554 					HandleAttrMenuItemSelected(message);
2555 
2556 				if (PrimarySort() == attrHash)
2557 					attrHash = 0;
2558 
2559 				SetPrimarySort(attrHash);
2560 				SetSecondarySort(0);
2561 				Cleanup(true);
2562 			}
2563 			break;
2564 		}
2565 
2566 		case kArrangeReverseOrder:
2567 			SetReverseSort(!fViewState->ReverseSort());
2568 			Cleanup(true);
2569 			break;
2570 
2571 		case kAttributeItem:
2572 			HandleAttrMenuItemSelected(message);
2573 			break;
2574 
2575 		case kAddPrinter:
2576 			be_app->PostMessage(message);
2577 			break;
2578 
2579 		case kMakeActivePrinter:
2580 			SetDefaultPrinter();
2581 			break;
2582 
2583 #if DEBUG
2584 		case kTestIconCache:
2585 			RunIconCacheTests();
2586 			break;
2587 
2588 		case 'dbug':
2589 		{
2590 			int32 selectCount = CountSelected();
2591 			for (int32 index = 0; index < selectCount; index++)
2592 				fSelectionList->ItemAt(index)->PrintToStream();
2593 
2594 			break;
2595 		}
2596 #ifdef CHECK_OPEN_MODEL_LEAKS
2597 		case 'dpfl':
2598 			DumpOpenModels(false);
2599 			break;
2600 
2601 		case 'dpfL':
2602 			DumpOpenModels(true);
2603 			break;
2604 #endif
2605 #endif
2606 
2607 		case kCheckTypeahead:
2608 		{
2609 			bigtime_t doubleClickSpeed;
2610 			get_click_speed(&doubleClickSpeed);
2611 			if (system_time() - fLastKeyTime > (doubleClickSpeed * 2)) {
2612 				fCountView->SetTypeAhead("");
2613 				delete fKeyRunner;
2614 				fKeyRunner = NULL;
2615 			}
2616 			break;
2617 		}
2618 
2619 		case B_OBSERVER_NOTICE_CHANGE:
2620 		{
2621 			int32 observerWhat;
2622 			if (message->FindInt32("be:observe_change_what", &observerWhat)
2623 					== B_OK) {
2624 				switch (observerWhat) {
2625 					case kDateFormatChanged:
2626 						UpdateDateColumns(message);
2627 						break;
2628 
2629 					case kVolumesOnDesktopChanged:
2630 						AdaptToVolumeChange(message);
2631 						break;
2632 
2633 					case kDesktopIntegrationChanged:
2634 						AdaptToDesktopIntegrationChange(message);
2635 						break;
2636 
2637 					case kShowSelectionWhenInactiveChanged:
2638 					{
2639 						// Updating the settings here will propagate
2640 						// setting changed from Tracker to all open
2641 						// file panels as well
2642 						bool showSelection;
2643 						if (message->FindBool("ShowSelectionWhenInactive",
2644 								&showSelection) == B_OK) {
2645 							fShowSelectionWhenInactive = showSelection;
2646 							TrackerSettings().SetShowSelectionWhenInactive(
2647 								fShowSelectionWhenInactive);
2648 						}
2649 						Invalidate();
2650 						break;
2651 					}
2652 
2653 					case kTransparentSelectionChanged:
2654 					{
2655 						bool transparentSelection;
2656 						if (message->FindBool("TransparentSelection",
2657 								&transparentSelection) == B_OK) {
2658 							fTransparentSelection = transparentSelection;
2659 							TrackerSettings().SetTransparentSelection(
2660 								fTransparentSelection);
2661 						}
2662 						break;
2663 					}
2664 
2665 					case kSortFolderNamesFirstChanged:
2666 						if (ViewMode() == kListMode) {
2667 							TrackerSettings settings;
2668 							bool sortFolderNamesFirst;
2669 							if (message->FindBool("SortFolderNamesFirst",
2670 								&sortFolderNamesFirst) == B_OK) {
2671 								settings.SetSortFolderNamesFirst(
2672 									sortFolderNamesFirst);
2673 							}
2674 							NameAttributeText::SetSortFolderNamesFirst(
2675 								settings.SortFolderNamesFirst());
2676 							RealNameAttributeText::SetSortFolderNamesFirst(
2677 								settings.SortFolderNamesFirst());
2678 							SortPoses();
2679 							Invalidate();
2680 						}
2681 						break;
2682 
2683 					case kHideDotFilesChanged:
2684 					{
2685 						TrackerSettings settings;
2686 						bool hideDotFiles;
2687 						if (message->FindBool("HideDotFiles",
2688 								&hideDotFiles) == B_OK) {
2689 							settings.SetHideDotFiles(hideDotFiles);
2690 						}
2691 
2692 						Refresh();
2693 						break;
2694 					}
2695 
2696 					case kTypeAheadFilteringChanged:
2697 					{
2698 						TrackerSettings settings;
2699 						bool typeAheadFiltering;
2700 						if (message->FindBool("TypeAheadFiltering",
2701 								&typeAheadFiltering) == B_OK) {
2702 							settings.SetTypeAheadFiltering(typeAheadFiltering);
2703 						}
2704 
2705 						if (fFiltering && !typeAheadFiltering)
2706 							StopFiltering();
2707 						break;
2708 					}
2709 				}
2710 			}
2711 			break;
2712 		}
2713 
2714 		default:
2715 			_inherited::MessageReceived(message);
2716 			break;
2717 	}
2718 }
2719 
2720 
2721 bool
2722 BPoseView::RemoveColumn(BColumn* columnToRemove, bool runAlert)
2723 {
2724 	// make sure last column is not removed
2725 	if (CountColumns() == 1) {
2726 		if (runAlert) {
2727 			BAlert* alert = new BAlert("",
2728 				B_TRANSLATE("You must have at least one attribute showing."),
2729 				B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
2730 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
2731 			alert->Go();
2732 		}
2733 
2734 		return false;
2735 	}
2736 
2737 	// column exists so remove it from list
2738 	int32 columnIndex = IndexOfColumn(columnToRemove);
2739 	float offset = columnToRemove->Offset();
2740 
2741 	int32 poseCount = fPoseList->CountItems();
2742 	for (int32 index = 0; index < poseCount; index++)
2743 		fPoseList->ItemAt(index)->RemoveWidget(this, columnToRemove);
2744 
2745 	fColumnList->RemoveItem(columnToRemove, false);
2746 	fTitleView->RemoveTitle(columnToRemove);
2747 
2748 	float attrWidth = columnToRemove->Width();
2749 	delete columnToRemove;
2750 
2751 	int32 count = CountColumns();
2752 	for (int32 index = columnIndex; index < count; index++) {
2753 		BColumn* column = ColumnAt(index);
2754 		column->SetOffset(column->Offset()
2755 			- (attrWidth + kTitleColumnExtraMargin));
2756 	}
2757 
2758 	BRect rect(Bounds());
2759 	rect.left = offset;
2760 	Invalidate(rect);
2761 
2762 	ContainerWindow()->MarkAttributesMenu();
2763 
2764 	if (IsWatchingDateFormatChange()) {
2765 		int32 columnCount = CountColumns();
2766 		bool anyDateAttributesLeft = false;
2767 
2768 		for (int32 i = 0; i < columnCount; i++) {
2769 			BColumn* column = ColumnAt(i);
2770 			if (column->AttrType() == B_TIME_TYPE)
2771 				anyDateAttributesLeft = true;
2772 
2773 			if (anyDateAttributesLeft)
2774 				break;
2775 		}
2776 
2777 		if (!anyDateAttributesLeft)
2778 			StopWatchDateFormatChange();
2779 	}
2780 
2781 	fStateNeedsSaving = true;
2782 
2783 	if (fFiltering) {
2784 		// the column we removed might just be the one that was used to filter
2785 		int32 poseCount = fFilteredPoseList->CountItems();
2786 		for (int32 i = poseCount - 1; i >= 0; i--) {
2787 			BPose* pose = fFilteredPoseList->ItemAt(i);
2788 			if (!FilterPose(pose))
2789 				RemoveFilteredPose(pose, i);
2790 		}
2791 	}
2792 
2793 	return true;
2794 }
2795 
2796 
2797 bool
2798 BPoseView::AddColumn(BColumn* newColumn, const BColumn* after)
2799 {
2800 	if (after == NULL && CountColumns() > 0)
2801 		after = LastColumn();
2802 
2803 	// add new column after last column
2804 	float offset;
2805 	int32 afterColumnIndex;
2806 	if (after != NULL) {
2807 		offset = after->Offset() + after->Width() + kTitleColumnExtraMargin;
2808 		afterColumnIndex = IndexOfColumn(after);
2809 	} else {
2810 		offset = StartOffset();
2811 		afterColumnIndex = CountColumns() - 1;
2812 	}
2813 
2814 	// add the new column
2815 	fColumnList->AddItem(newColumn, afterColumnIndex + 1);
2816 	if (fTitleView != NULL)
2817 		fTitleView->AddTitle(newColumn);
2818 
2819 	BRect rect(Bounds());
2820 
2821 	// add widget for all visible poses
2822 	PoseList* poseList = CurrentPoseList();
2823 	int32 poseCount = poseList->CountItems();
2824 	int32 startIndex = (int32)(rect.top / fListElemHeight);
2825 	BPoint loc(0, startIndex * fListElemHeight);
2826 
2827 	for (int32 index = startIndex; index < poseCount; index++) {
2828 		BPose* pose = poseList->ItemAt(index);
2829 		if (pose->WidgetFor(newColumn->AttrHash()) == NULL)
2830 			pose->AddWidget(this, newColumn);
2831 
2832 		loc.y += fListElemHeight;
2833 		if (loc.y > rect.bottom)
2834 			break;
2835 	}
2836 
2837 	// rearrange column titles to fit new column
2838 	newColumn->SetOffset(offset);
2839 	float attrWidth = newColumn->Width();
2840 
2841 	int32 count = CountColumns();
2842 	for (int32 index = afterColumnIndex + 2; index < count; index++) {
2843 		BColumn* column = ColumnAt(index);
2844 		ASSERT(newColumn != column);
2845 		column->SetOffset(column->Offset() + (attrWidth
2846 			+ kTitleColumnExtraMargin));
2847 	}
2848 
2849 	rect.left = offset;
2850 	Invalidate(rect);
2851 	ContainerWindow()->MarkAttributesMenu();
2852 
2853 	// Check if this is a time attribute and if so,
2854 	// start watching for changed in time/date format:
2855 	if (!IsWatchingDateFormatChange() && newColumn->AttrType() == B_TIME_TYPE)
2856 		StartWatchDateFormatChange();
2857 
2858 	fStateNeedsSaving =  true;
2859 
2860 	if (fFiltering) {
2861 		// the column we added might just add new poses to be showed
2862 		fFilteredPoseList->MakeEmpty();
2863 		fFiltering = false;
2864 		StartFiltering();
2865 	}
2866 
2867 	return true;
2868 }
2869 
2870 
2871 void
2872 BPoseView::HandleAttrMenuItemSelected(BMessage* message)
2873 {
2874 	// see if source was a menu item
2875 	BMenuItem* item;
2876 	if (message->FindPointer("source", (void**)&item) != B_OK)
2877 		item = NULL;
2878 
2879 	// find out which column was selected
2880 	uint32 attrHash;
2881 	if (message->FindInt32("attr_hash", (int32*)&attrHash) != B_OK)
2882 		return;
2883 
2884 	BColumn* column = ColumnFor(attrHash);
2885 	if (column != NULL) {
2886 		RemoveColumn(column, true);
2887 		return;
2888 	} else {
2889 		// collect info about selected attribute
2890 		const char* attrName;
2891 		if (message->FindString("attr_name", &attrName) != B_OK)
2892 			return;
2893 
2894 		uint32 attrType;
2895 		if (message->FindInt32("attr_type", (int32*)&attrType) != B_OK)
2896 			return;
2897 
2898 		float attrWidth;
2899 		if (message->FindFloat("attr_width", &attrWidth) != B_OK)
2900 			return;
2901 
2902 		alignment attrAlign;
2903 		if (message->FindInt32("attr_align", (int32*)&attrAlign) != B_OK)
2904 			return;
2905 
2906 		bool isEditable;
2907 		if (message->FindBool("attr_editable", &isEditable) != B_OK)
2908 			return;
2909 
2910 		bool isStatfield;
2911 		if (message->FindBool("attr_statfield", &isStatfield) != B_OK)
2912 			return;
2913 
2914 		const char* displayAs;
2915 		message->FindString("attr_display_as", &displayAs);
2916 
2917 		column = new BColumn(item->Label(), attrWidth, attrAlign,
2918 			attrName, attrType, displayAs, isStatfield, isEditable);
2919 		AddColumn(column);
2920 		if (item->Menu()->Supermenu() == NULL)
2921 			delete item->Menu();
2922 	}
2923 }
2924 
2925 
2926 const int32 kSanePoseLocation = 50000;
2927 
2928 
2929 void
2930 BPoseView::ReadPoseInfo(Model* model, PoseInfo* poseInfo)
2931 {
2932 	BModelOpener opener(model);
2933 	if (model->Node() == NULL)
2934 		return;
2935 
2936 	ReadAttrResult result = kReadAttrFailed;
2937 	BEntry entry;
2938 	model->GetEntry(&entry);
2939 	bool isTrash = model->IsTrash() && IsDesktopView();
2940 
2941 	// special case the "root" disks icon
2942 	// as well as the trash on desktop
2943 	if (model->IsRoot() || isTrash) {
2944 		BDirectory dir;
2945 		if (FSGetDeskDir(&dir) == B_OK) {
2946 			const char* poseInfoAttr = isTrash
2947 				? kAttrTrashPoseInfo
2948 				: kAttrDisksPoseInfo;
2949 			const char* poseInfoAttrForeign = isTrash
2950 				? kAttrTrashPoseInfoForeign
2951 				: kAttrDisksPoseInfoForeign;
2952 			result = ReadAttr(&dir, poseInfoAttr, poseInfoAttrForeign,
2953 				B_RAW_TYPE, 0, poseInfo, sizeof(*poseInfo),
2954 				&PoseInfo::EndianSwap);
2955 		}
2956 	} else {
2957 		ASSERT(model->IsNodeOpen());
2958 		time_t now = time(NULL);
2959 
2960 		for (int32 count = 10; count >= 0; count--) {
2961 			if (model->Node() == NULL)
2962 				break;
2963 
2964 			result = ReadAttr(model->Node(), kAttrPoseInfo,
2965 				kAttrPoseInfoForeign, B_RAW_TYPE, 0, poseInfo,
2966 				sizeof(*poseInfo), &PoseInfo::EndianSwap);
2967 
2968 			if (result != kReadAttrFailed) {
2969 				// got it, bail
2970 				break;
2971 			}
2972 
2973 			// if we're in one of the icon modes and it's a newly created item
2974 			// then we're going to retry a few times to see if we can get some
2975 			// pose info to properly place the icon
2976 			if (ViewMode() == kListMode)
2977 				break;
2978 
2979 			const StatStruct* stat = model->StatBuf();
2980 			if (stat->st_crtime < now - 5 || stat->st_crtime > now)
2981 				break;
2982 
2983 			//PRINT(("retrying to read pose info for %s, %d\n",
2984 			//	model->Name(), count));
2985 
2986 			snooze(10000);
2987 		}
2988 	}
2989 
2990 	if (result == kReadAttrFailed) {
2991 		poseInfo->fInitedDirectory = -1LL;
2992 		poseInfo->fInvisible = false;
2993 	} else if (TargetModel() == NULL
2994 		|| (poseInfo->fInitedDirectory != model->EntryRef()->directory
2995 			&& (poseInfo->fInitedDirectory
2996 				!= TargetModel()->NodeRef()->node))) {
2997 		// info was read properly but it's not for this directory
2998 		poseInfo->fInitedDirectory = -1LL;
2999 	} else if (poseInfo->fLocation.x < -kSanePoseLocation
3000 		|| poseInfo->fLocation.x > kSanePoseLocation
3001 		|| poseInfo->fLocation.y < -kSanePoseLocation
3002 		|| poseInfo->fLocation.y > kSanePoseLocation) {
3003 		// location values not realistic, probably screwed up, force reset
3004 		poseInfo->fInitedDirectory = -1LL;
3005 	}
3006 }
3007 
3008 
3009 ExtendedPoseInfo*
3010 BPoseView::ReadExtendedPoseInfo(Model* model)
3011 {
3012 	BModelOpener opener(model);
3013 	if (model->Node() == NULL)
3014 		return NULL;
3015 
3016 	ReadAttrResult result = kReadAttrFailed;
3017 
3018 	const char* extendedPoseInfoAttrName;
3019 	const char* extendedPoseInfoAttrForeignName;
3020 
3021 	// special case the "root" disks icon
3022 	if (model->IsRoot()) {
3023 		BDirectory dir;
3024 		if (FSGetDeskDir(&dir) == B_OK) {
3025 			extendedPoseInfoAttrName = kAttrExtendedDisksPoseInfo;
3026 			extendedPoseInfoAttrForeignName = kAttrExtendedDisksPoseInfoForegin;
3027 		} else
3028 			return NULL;
3029 	} else {
3030 		extendedPoseInfoAttrName = kAttrExtendedPoseInfo;
3031 		extendedPoseInfoAttrForeignName = kAttrExtendedPoseInfoForegin;
3032 	}
3033 
3034 	type_code type;
3035 	size_t size;
3036 	result = GetAttrInfo(model->Node(), extendedPoseInfoAttrName,
3037 		extendedPoseInfoAttrForeignName, &type, &size);
3038 
3039 	if (result == kReadAttrFailed)
3040 		return NULL;
3041 
3042 	char* buffer = new char[ExtendedPoseInfo::SizeWithHeadroom(size)];
3043 	ExtendedPoseInfo* poseInfo = reinterpret_cast<ExtendedPoseInfo*>(buffer);
3044 	result = ReadAttr(model->Node(), extendedPoseInfoAttrName,
3045 		extendedPoseInfoAttrForeignName,
3046 		B_RAW_TYPE, 0, buffer, size, &ExtendedPoseInfo::EndianSwap);
3047 
3048 	// check that read worked, and data is sane
3049 	if (result == kReadAttrFailed
3050 		|| size > poseInfo->SizeWithHeadroom()
3051 		|| size < poseInfo->Size()) {
3052 		delete[] buffer;
3053 		return NULL;
3054 	}
3055 
3056 	return poseInfo;
3057 }
3058 
3059 
3060 void
3061 BPoseView::SetViewMode(uint32 newMode)
3062 {
3063 	uint32 oldMode = ViewMode();
3064 	uint32 lastIconSize = fViewState->LastIconSize();
3065 
3066 	if (newMode == oldMode
3067 		&& (newMode != kIconMode || lastIconSize == fViewState->IconSize())) {
3068 		return;
3069 	}
3070 
3071 	ASSERT(!IsFilePanel());
3072 
3073 	uint32 lastIconMode = fViewState->LastIconMode();
3074 	if (newMode != kListMode) {
3075 		fViewState->SetLastIconMode(newMode);
3076 		if (oldMode == kIconMode)
3077 			fViewState->SetLastIconSize(fViewState->IconSize());
3078 	}
3079 
3080 	fViewState->SetViewMode(newMode);
3081 
3082 	// Try to lock the center of the pose view when scaling icons, but not
3083 	// if we are the desktop.
3084 	BPoint scaleOffset(0, 0);
3085 	bool iconSizeChanged = newMode == kIconMode && oldMode == kIconMode;
3086 	if (!IsDesktopWindow() && iconSizeChanged) {
3087 		// definitely changing the icon size, so we will need to scroll
3088 		BRect bounds(Bounds());
3089 		BPoint center(bounds.LeftTop());
3090 		center.x += bounds.Width() / 2.0;
3091 		center.y += bounds.Height() / 2.0;
3092 		// convert the center into "unscaled icon placement" space
3093 		float oldScale = lastIconSize / 32.0;
3094 		BPoint unscaledCenter(center.x / oldScale, center.y / oldScale);
3095 		// get the new center in "scaled icon placement" place
3096 		float newScale = fViewState->IconSize() / 32.0f;
3097 		BPoint newCenter(unscaledCenter.x * newScale,
3098 			unscaledCenter.y * newScale);
3099 		scaleOffset = newCenter - center;
3100 	}
3101 
3102 	// toggle view layout between listmode and non-listmode, if necessary
3103 	BContainerWindow* window = ContainerWindow();
3104 	if (oldMode == kListMode) {
3105 		if (fFiltering)
3106 			ClearFilter();
3107 
3108 		if (window != NULL)
3109 			window->HideAttributesMenu();
3110 
3111 		fTitleView->Hide();
3112 	} else if (newMode == kListMode) {
3113 		if (window != NULL)
3114 			window->ShowAttributesMenu();
3115 
3116 		fTitleView->Show();
3117 	}
3118 
3119 	CommitActivePose();
3120 	SetIconPoseHeight();
3121 	GetLayoutInfo(newMode, &fGrid, &fOffset);
3122 
3123 	// see if we need to map icons into new mode
3124 	bool mapIcons = false;
3125 	if (fOkToMapIcons) {
3126 		mapIcons = (newMode != kListMode) && (newMode != lastIconMode
3127 			|| fViewState->IconSize() != lastIconSize);
3128 	}
3129 
3130 	// check if we need to re-place poses when they are out of view
3131 	bool checkLocations = IsDesktopWindow() && iconSizeChanged;
3132 
3133 	BPoint oldOffset;
3134 	BPoint oldGrid;
3135 	if (mapIcons)
3136 		GetLayoutInfo(lastIconMode, &oldGrid, &oldOffset);
3137 
3138 	BRect bounds(Bounds());
3139 	PoseList newPoseList(30);
3140 
3141 	if (newMode != kListMode) {
3142 		int32 poseCount = fPoseList->CountItems();
3143 		for (int32 index = 0; index < poseCount; index++) {
3144 			BPose* pose = fPoseList->ItemAt(index);
3145 			if (pose->HasLocation() == false) {
3146 				newPoseList.AddItem(pose);
3147 			} else if (checkLocations && !IsValidLocation(pose)) {
3148 				// this icon has a location, but needs to be remapped, because
3149 				// it is going out of view for example
3150 				RemoveFromVSList(pose);
3151 				newPoseList.AddItem(pose);
3152 			} else if (iconSizeChanged) {
3153 				// The pose location is still changed in view coordinates,
3154 				// so it needs to be changed anyways!
3155 				pose->SetSaveLocation();
3156 			} else if (mapIcons) {
3157 				MapToNewIconMode(pose, oldGrid, oldOffset);
3158 			}
3159 		}
3160 	}
3161 
3162 	// invalidate before anything else to avoid flickering, especially when
3163 	// scrolling is also performed (invalidating before scrolling will cause
3164 	// app_server to scroll silently, ie not visibly)
3165 	Invalidate();
3166 
3167 	// update origin in case of a list <-> icon mode transition
3168 	BPoint newOrigin;
3169 	if (newMode == kListMode)
3170 		newOrigin = fViewState->ListOrigin();
3171 	else
3172 		newOrigin = fViewState->IconOrigin() + scaleOffset;
3173 
3174 	PinPointToValidRange(newOrigin);
3175 
3176 	DisableScrollBars();
3177 	ScrollTo(newOrigin);
3178 
3179 	// reset hint and arrange poses which DO NOT have a location yet
3180 	ResetPosePlacementHint();
3181 	int32 poseCount = newPoseList.CountItems();
3182 	for (int32 index = 0; index < poseCount; index++) {
3183 		BPose* pose = newPoseList.ItemAt(index);
3184 		PlacePose(pose, bounds);
3185 		AddToVSList(pose);
3186 	}
3187 
3188 	SortPoses();
3189 	if (newMode != kListMode)
3190 		RecalcExtent();
3191 
3192 	UpdateScrollRange();
3193 	SetScrollBarsTo(newOrigin);
3194 	EnableScrollBars();
3195 	ContainerWindow()->ViewModeChanged(oldMode, newMode);
3196 }
3197 
3198 
3199 void
3200 BPoseView::MapToNewIconMode(BPose* pose, BPoint oldGrid, BPoint oldOffset)
3201 {
3202 	BPoint delta;
3203 	BPoint poseLoc;
3204 
3205 	poseLoc = PinToGrid(pose->Location(this), oldGrid, oldOffset);
3206 	delta = pose->Location(this) - poseLoc;
3207 	poseLoc -= oldOffset;
3208 
3209 	if (poseLoc.x >= 0)
3210 		poseLoc.x = floorf(poseLoc.x / oldGrid.x) * fGrid.x;
3211 	else
3212 		poseLoc.x = ceilf(poseLoc.x / oldGrid.x) * fGrid.x;
3213 
3214 	if (poseLoc.y >= 0)
3215 		poseLoc.y = floorf(poseLoc.y / oldGrid.y) * fGrid.y;
3216 	else
3217 		poseLoc.y = ceilf(poseLoc.y / oldGrid.y) * fGrid.y;
3218 
3219 	if ((delta.x != 0) || (delta.y != 0)) {
3220 		if (delta.x >= 0)
3221 			delta.x = fGrid.x * floorf(delta.x / oldGrid.x);
3222 		else
3223 			delta.x = fGrid.x * ceilf(delta.x / oldGrid.x);
3224 
3225 		if (delta.y >= 0)
3226 			delta.y = fGrid.y * floorf(delta.y / oldGrid.y);
3227 		else
3228 			delta.y = fGrid.y * ceilf(delta.y / oldGrid.y);
3229 
3230 		poseLoc += delta;
3231 	}
3232 
3233 	poseLoc += fOffset;
3234 	pose->SetLocation(poseLoc, this);
3235 	pose->SetSaveLocation();
3236 }
3237 
3238 
3239 void
3240 BPoseView::SetPosesClipboardMode(uint32 clipboardMode)
3241 {
3242 	if (ViewMode() == kListMode) {
3243 		PoseList* poseList = CurrentPoseList();
3244 		int32 poseCount = poseList->CountItems();
3245 
3246 		BPoint loc(0,0);
3247 		for (int32 index = 0; index < poseCount; index++) {
3248 			BPose* pose = poseList->ItemAt(index);
3249 			if (pose->ClipboardMode() != clipboardMode) {
3250 				pose->SetClipboardMode(clipboardMode);
3251 				Invalidate(pose->CalcRect(loc, this, false));
3252 			}
3253 			loc.y += fListElemHeight;
3254 		}
3255 	} else {
3256 		int32 poseCount = fPoseList->CountItems();
3257 		for (int32 index = 0; index < poseCount; index++) {
3258 			BPose* pose = fPoseList->ItemAt(index);
3259 			if (pose->ClipboardMode() != clipboardMode) {
3260 				pose->SetClipboardMode(clipboardMode);
3261 				BRect poseRect(pose->CalcRect(this));
3262 				Invalidate(poseRect);
3263 			}
3264 		}
3265 	}
3266 }
3267 
3268 
3269 void
3270 BPoseView::UpdatePosesClipboardModeFromClipboard(BMessage* clipboardReport)
3271 {
3272 	CommitActivePose();
3273 	fSelectionPivotPose = NULL;
3274 	fRealPivotPose = NULL;
3275 	bool fullInvalidateNeeded = false;
3276 
3277 	node_ref node;
3278 	clipboardReport->FindInt32("device", &node.device);
3279 	clipboardReport->FindInt64("directory", &node.node);
3280 
3281 	bool clearClipboard = false;
3282 	clipboardReport->FindBool("clearClipboard", &clearClipboard);
3283 
3284 	if (clearClipboard && fHasPosesInClipboard) {
3285 		// clear all poses
3286 		int32 poseCount = fPoseList->CountItems();
3287 		for (int32 index = 0; index < poseCount; index++) {
3288 			BPose* pose = fPoseList->ItemAt(index);
3289 			pose->Select(false);
3290 			pose->SetClipboardMode(0);
3291 		}
3292 		SetHasPosesInClipboard(false);
3293 		fullInvalidateNeeded = true;
3294 		fHasPosesInClipboard = false;
3295 	}
3296 
3297 	BRect bounds(Bounds());
3298 	BPoint loc(0, 0);
3299 	bool hasPosesInClipboard = false;
3300 	int32 foundNodeIndex = 0;
3301 
3302 	TClipboardNodeRef* clipNode = NULL;
3303 	ssize_t size;
3304 	for (int32 index = 0; clipboardReport->FindData("tcnode", T_CLIPBOARD_NODE,
3305 			index, (const void**)&clipNode, &size) == B_OK; index++) {
3306 		BPose* pose = fPoseList->FindPose(&clipNode->node, &foundNodeIndex);
3307 		if (pose == NULL)
3308 			continue;
3309 
3310 		if (clipNode->moveMode != pose->ClipboardMode() || pose->IsSelected()) {
3311 			pose->SetClipboardMode(clipNode->moveMode);
3312 			pose->Select(false);
3313 
3314 			if (!fullInvalidateNeeded) {
3315 				if (ViewMode() == kListMode) {
3316 					if (fFiltering) {
3317 						pose = fFilteredPoseList->FindPose(&clipNode->node,
3318 							&foundNodeIndex);
3319 					}
3320 
3321 					if (pose != NULL) {
3322 						loc.y = foundNodeIndex * fListElemHeight;
3323 						if (loc.y <= bounds.bottom && loc.y >= bounds.top)
3324 							Invalidate(pose->CalcRect(loc, this, false));
3325 					}
3326 				} else {
3327 					BRect poseRect(pose->CalcRect(this));
3328 					if (bounds.Contains(poseRect.LeftTop())
3329 						|| bounds.Contains(poseRect.LeftBottom())
3330 						|| bounds.Contains(poseRect.RightBottom())
3331 						|| bounds.Contains(poseRect.RightTop())) {
3332 						Invalidate(poseRect);
3333 					}
3334 				}
3335 			}
3336 			if (clipNode->moveMode)
3337 				hasPosesInClipboard = true;
3338 		}
3339 	}
3340 
3341 	fSelectionList->MakeEmpty();
3342 	fMimeTypesInSelectionCache.MakeEmpty();
3343 
3344 	SetHasPosesInClipboard(hasPosesInClipboard || fHasPosesInClipboard);
3345 
3346 	if (fullInvalidateNeeded)
3347 		Invalidate();
3348 }
3349 
3350 
3351 void
3352 BPoseView::PlaceFolder(const entry_ref* ref, const BMessage* message)
3353 {
3354 	BNode node(ref);
3355 	BPoint location;
3356 	bool setPosition = false;
3357 
3358 	if (message->FindPoint("be:invoke_origin", &location) == B_OK) {
3359 		// new folder created from popup, place on click point
3360 		setPosition = true;
3361 		location = ConvertFromScreen(location);
3362 	} else if (ViewMode() != kListMode) {
3363 		// new folder created by keyboard shortcut
3364 		uint32 buttons;
3365 		GetMouse(&location, &buttons);
3366 		BPoint globalLocation(location);
3367 		ConvertToScreen(&globalLocation);
3368 		// check if mouse over window
3369 		if (Window()->Frame().Contains(globalLocation))
3370 			// create folder under mouse
3371 			setPosition = true;
3372 	}
3373 
3374 	if (setPosition) {
3375 		Model* targetModel = TargetModel();
3376 		if (targetModel != NULL && targetModel->NodeRef() != NULL) {
3377 			FSSetPoseLocation(targetModel->NodeRef()->node, &node,
3378 				location);
3379 		}
3380 	}
3381 }
3382 
3383 
3384 void
3385 BPoseView::NewFileFromTemplate(const BMessage* message)
3386 {
3387 	Model* targetModel = TargetModel();
3388 	ThrowOnAssert(targetModel != NULL);
3389 
3390 	entry_ref destEntryRef;
3391 	node_ref destNodeRef;
3392 
3393 	BDirectory destDir(targetModel->NodeRef());
3394 	if (destDir.InitCheck() != B_OK)
3395 		return;
3396 
3397 	// TODO: Localise this
3398 	char fileName[B_FILE_NAME_LENGTH] = "New ";
3399 	strlcat(fileName, message->FindString("name"), sizeof(fileName));
3400 	FSMakeOriginalName(fileName, &destDir, " copy");
3401 
3402 	entry_ref srcRef;
3403 	message->FindRef("refs_template", &srcRef);
3404 
3405 	BDirectory dir(&srcRef);
3406 
3407 	if (dir.InitCheck() == B_OK) {
3408 		// special handling of directories
3409 		if (FSCreateNewFolderIn(targetModel->NodeRef(), &destEntryRef,
3410 				&destNodeRef) == B_OK) {
3411 			BEntry destEntry(&destEntryRef);
3412 			destEntry.Rename(fileName);
3413 		}
3414 	} else {
3415 		BFile srcFile(&srcRef, B_READ_ONLY);
3416 		BFile destFile(&destDir, fileName, B_READ_WRITE | B_CREATE_FILE);
3417 
3418 		// copy the data from the template file
3419 		char* buffer = new char[1024];
3420 		ssize_t result;
3421 		do {
3422 			result = srcFile.Read(buffer, 1024);
3423 
3424 			if (result > 0) {
3425 				ssize_t written = destFile.Write(buffer, (size_t)result);
3426 				if (written != result)
3427 					result = written < B_OK ? written : B_ERROR;
3428 			}
3429 		} while (result > 0);
3430 		delete[] buffer;
3431 	}
3432 
3433 	// todo: create an UndoItem
3434 
3435 	// copy the attributes from the template file
3436 	BNode srcNode(&srcRef);
3437 	BNode destNode(&destDir, fileName);
3438 	FSCopyAttributesAndStats(&srcNode, &destNode, false);
3439 
3440 	BEntry entry(&destDir, fileName);
3441 	entry.GetRef(&destEntryRef);
3442 
3443 	// try to place new item at click point or under mouse if possible
3444 	PlaceFolder(&destEntryRef, message);
3445 
3446 	// start renaming the entry
3447 	int32 index;
3448 	BPose* pose = EntryCreated(targetModel->NodeRef(), &destNodeRef,
3449 		destEntryRef.name, &index);
3450 
3451 	if (pose != NULL) {
3452 		WatchNewNode(pose->TargetModel()->NodeRef());
3453 		UpdateScrollRange();
3454 		CommitActivePose();
3455 		SelectPose(pose, index);
3456 		pose->EditFirstWidget(BPoint(0, index * fListElemHeight), this);
3457 	}
3458 }
3459 
3460 
3461 void
3462 BPoseView::NewFolder(const BMessage* message)
3463 {
3464 	Model* targetModel = TargetModel();
3465 	ThrowOnAssert(targetModel != NULL);
3466 
3467 	entry_ref ref;
3468 	node_ref nodeRef;
3469 
3470 	if (FSCreateNewFolderIn(targetModel->NodeRef(), &ref, &nodeRef) == B_OK) {
3471 		// try to place new folder at click point or under mouse if possible
3472 
3473 		PlaceFolder(&ref, message);
3474 
3475 		int32 index;
3476 		BPose* pose = EntryCreated(targetModel->NodeRef(), &nodeRef, ref.name,
3477 			&index);
3478 
3479 		if (fFiltering) {
3480 			if (fFilteredPoseList->FindPose(&nodeRef, &index) == NULL) {
3481 				float scrollBy = 0;
3482 				BRect bounds = Bounds();
3483 				AddPoseToList(fFilteredPoseList, true, true, pose, bounds,
3484 					scrollBy, true, &index);
3485 			}
3486 		}
3487 
3488 		if (pose != NULL) {
3489 			UpdateScrollRange();
3490 			CommitActivePose();
3491 			SelectPose(pose, index);
3492 			pose->EditFirstWidget(BPoint(0, index * fListElemHeight), this);
3493 		}
3494 	}
3495 }
3496 
3497 
3498 void
3499 BPoseView::Cleanup(bool doAll)
3500 {
3501 	if (ViewMode() == kListMode)
3502 		return;
3503 
3504 	BContainerWindow* window = ContainerWindow();
3505 	if (window == NULL)
3506 		return;
3507 
3508 	// replace all icons from the top
3509 	if (doAll) {
3510 		// sort by sort field
3511 		SortPoses();
3512 
3513 		DisableScrollBars();
3514 		ClearExtent();
3515 		ClearSelection();
3516 		ScrollTo(B_ORIGIN);
3517 		UpdateScrollRange();
3518 		SetScrollBarsTo(B_ORIGIN);
3519 		ResetPosePlacementHint();
3520 
3521 		BRect viewBounds(Bounds());
3522 
3523 		// relocate all poses in list (reset vs list)
3524 		fVSPoseList->MakeEmpty();
3525 		int32 poseCount = fPoseList->CountItems();
3526 		for (int32 index = 0; index < poseCount; index++) {
3527 			BPose* pose = fPoseList->ItemAt(index);
3528 			PlacePose(pose, viewBounds);
3529 			AddToVSList(pose);
3530 		}
3531 
3532 		RecalcExtent();
3533 
3534 		// scroll icons into view so that leftmost icon is "fOffset" from left
3535 		UpdateScrollRange();
3536 		EnableScrollBars();
3537 
3538 		if (HScrollBar()) {
3539 			float min;
3540 			float max;
3541 			HScrollBar()->GetRange(&min, &max);
3542 			HScrollBar()->SetValue(min);
3543 		}
3544 
3545 		UpdateScrollRange();
3546 		Invalidate(viewBounds);
3547 	} else {
3548 		// clean up items to nearest locations
3549 		BRect viewBounds(Bounds());
3550 		int32 poseCount = fPoseList->CountItems();
3551 		for (int32 index = 0; index < poseCount; index++) {
3552 			BPose* pose = fPoseList->ItemAt(index);
3553 			BPoint location(pose->Location(this));
3554 			BPoint newLocation(PinToGrid(location, fGrid, fOffset));
3555 
3556 			bool intersectsDesktopElements = !IsValidLocation(pose);
3557 
3558 			// do we need to move pose to a grid location?
3559 			if (newLocation != location || intersectsDesktopElements) {
3560 				// remove pose from VSlist so it doesn't "bump" into itself
3561 				RemoveFromVSList(pose);
3562 
3563 				// try new grid location
3564 				BRect oldBounds(pose->CalcRect(this));
3565 				BRect poseBounds(oldBounds);
3566 				pose->MoveTo(newLocation, this);
3567 				if (SlotOccupied(oldBounds, viewBounds)
3568 					|| intersectsDesktopElements) {
3569 					ResetPosePlacementHint();
3570 					PlacePose(pose, viewBounds);
3571 					poseBounds = pose->CalcRect(this);
3572 				}
3573 
3574 				AddToVSList(pose);
3575 				AddToExtent(poseBounds);
3576 
3577  				if (viewBounds.Intersects(poseBounds))
3578 					Invalidate(poseBounds);
3579  				if (viewBounds.Intersects(oldBounds))
3580 					Invalidate(oldBounds);
3581 			}
3582 		}
3583 	}
3584 }
3585 
3586 
3587 void
3588 BPoseView::PlacePose(BPose* pose, BRect &viewBounds)
3589 {
3590 	// move pose to probable location
3591 	pose->SetLocation(fHintLocation, this);
3592 	BRect rect(pose->CalcRect(this));
3593 	BPoint deltaFromBounds(fHintLocation - rect.LeftTop());
3594 
3595 	// make pose rect a little bigger to ensure space between poses
3596 	rect.InsetBy(-3, 0);
3597 
3598 	bool checkValidLocation = IsDesktopWindow();
3599 
3600 	// find an empty slot to put pose into
3601 	while (SlotOccupied(rect, viewBounds)
3602 		// check good location on the desktop
3603 		|| (checkValidLocation && !IsValidLocation(rect))) {
3604 		NextSlot(pose, rect, viewBounds);
3605 		// we've scanned the entire desktop without finding an available
3606 		// position, give up and simply place it towards the top left.
3607 		if (checkValidLocation && !rect.Intersects(viewBounds)) {
3608 			fHintLocation = PinToGrid(BPoint(0.0, 0.0), fGrid, fOffset);
3609 			pose->SetLocation(fHintLocation, this);
3610 			rect = pose->CalcRect(this);
3611 			break;
3612 		}
3613 	}
3614 
3615 	rect.InsetBy(3, 0);
3616 
3617 	fHintLocation = pose->Location(this) + BPoint(fGrid.x, 0);
3618 
3619 	pose->SetLocation(rect.LeftTop() + deltaFromBounds, this);
3620 	pose->SetSaveLocation();
3621 }
3622 
3623 
3624 bool
3625 BPoseView::IsValidLocation(const BPose* pose)
3626 {
3627 	if (!IsDesktopWindow())
3628 		return true;
3629 
3630 	BRect rect(pose->CalcRect(this));
3631 	rect.InsetBy(-3, 0);
3632 	return IsValidLocation(rect);
3633 }
3634 
3635 
3636 bool
3637 BPoseView::IsValidLocation(const BRect& rect)
3638 {
3639 	if (!IsDesktopWindow())
3640 		return true;
3641 
3642 	// on the desktop, don't allow icons outside of the view bounds
3643 	if (!Bounds().Contains(rect))
3644 		return false;
3645 
3646 	// also check the deskbar frame
3647 	BRect deskbarFrame;
3648 	if (GetDeskbarFrame(&deskbarFrame) == B_OK) {
3649 		deskbarFrame.InsetBy(-10, -10);
3650 		if (deskbarFrame.Intersects(rect))
3651 			return false;
3652 	}
3653 
3654 	// check replicants
3655 	for (int32 i = 0; BView* child = ChildAt(i); i++) {
3656 		BRect childFrame = child->Frame();
3657 		childFrame.InsetBy(-5, -5);
3658 		if (childFrame.Intersects(rect))
3659 			return false;
3660 	}
3661 
3662 	// location is ok
3663 	return true;
3664 }
3665 
3666 
3667 status_t
3668 BPoseView::GetDeskbarFrame(BRect* frame)
3669 {
3670 	// only really check the Deskbar frame every half a second,
3671 	// use a cached value otherwise
3672 	status_t result = B_OK;
3673 	bigtime_t now = system_time();
3674 	if (fLastDeskbarFrameCheckTime + 500000 < now) {
3675 		// it's time to check the Deskbar frame again
3676 		result = get_deskbar_frame(&fDeskbarFrame);
3677 		fLastDeskbarFrameCheckTime = now;
3678 	}
3679 	*frame = fDeskbarFrame;
3680 
3681 	return result;
3682 }
3683 
3684 
3685 void
3686 BPoseView::CheckAutoPlacedPoses()
3687 {
3688 	if (ViewMode() == kListMode)
3689 		return;
3690 
3691 	BRect viewBounds(Bounds());
3692 
3693 	int32 poseCount = fPoseList->CountItems();
3694 	for (int32 index = 0; index < poseCount; index++) {
3695 		BPose* pose = fPoseList->ItemAt(index);
3696 		if (pose->WasAutoPlaced()) {
3697 			RemoveFromVSList(pose);
3698 			fHintLocation = pose->Location(this);
3699 			BRect oldBounds(pose->CalcRect(this));
3700 			PlacePose(pose, viewBounds);
3701 
3702 			BRect newBounds(pose->CalcRect(this));
3703 			AddToVSList(pose);
3704 			pose->SetAutoPlaced(false);
3705 			AddToExtent(newBounds);
3706 
3707 			Invalidate(oldBounds);
3708 			Invalidate(newBounds);
3709 		}
3710 	}
3711 }
3712 
3713 
3714 void
3715 BPoseView::CheckPoseVisibility(BRect* newFrame)
3716 {
3717 	bool desktop = IsDesktopWindow() && newFrame != 0;
3718 
3719 	BRect deskFrame;
3720 	if (desktop) {
3721 		ASSERT(newFrame);
3722 		deskFrame = *newFrame;
3723 	}
3724 
3725 	ASSERT(ViewMode() != kListMode);
3726 
3727 	BRect bounds(Bounds());
3728 	bounds.InsetBy(20, 20);
3729 
3730 	int32 poseCount = fPoseList->CountItems();
3731 	for (int32 index = 0; index < poseCount; index++) {
3732 		BPose* pose = fPoseList->ItemAt(index);
3733 		BPoint newLocation(pose->Location(this));
3734 		bool locationNeedsUpdating = false;
3735 
3736 		if (desktop) {
3737 			// we just switched screen resolution, pick up the right
3738 			// icon locations for the new resolution
3739 			Model* model = pose->TargetModel();
3740 			ExtendedPoseInfo* info = ReadExtendedPoseInfo(model);
3741 			if (info && info->HasLocationForFrame(deskFrame)) {
3742 				BPoint locationForFrame = info->LocationForFrame(deskFrame);
3743 				if (locationForFrame != newLocation) {
3744 					// found one and it is different from the current
3745 					newLocation = locationForFrame;
3746 					locationNeedsUpdating = true;
3747 					Invalidate(pose->CalcRect(this));
3748 						// make sure the old icon gets erased
3749 					RemoveFromVSList(pose);
3750 					pose->SetLocation(newLocation, this);
3751 						// set the new location
3752 				}
3753 			}
3754 			delete[] (char*)info;
3755 				// TODO: fix up this mess
3756 		}
3757 
3758 		BRect rect(pose->CalcRect(this));
3759 		if (!rect.Intersects(bounds)) {
3760 			// pose doesn't fit on screen
3761 			if (!locationNeedsUpdating) {
3762 				// didn't already invalidate and remove in the desktop case
3763 				Invalidate(rect);
3764 				RemoveFromVSList(pose);
3765 			}
3766 			BPoint loc(pose->Location(this));
3767 			loc.ConstrainTo(bounds);
3768 				// place it onscreen
3769 
3770 			pose->SetLocation(loc, this);
3771 				// set the new location
3772 			locationNeedsUpdating = true;
3773 		}
3774 
3775 		if (locationNeedsUpdating) {
3776 			// pose got reposition by one or both of the above
3777 			pose->SetSaveLocation();
3778 			AddToVSList(pose);
3779 				// add it at the new location
3780 			Invalidate(pose->CalcRect(this));
3781 				// make sure the new pose location updates properly
3782 		}
3783 	}
3784 }
3785 
3786 
3787 bool
3788 BPoseView::SlotOccupied(BRect poseRect, BRect viewBounds) const
3789 {
3790 	if (fVSPoseList->IsEmpty())
3791 		return false;
3792 
3793 	// ## be sure to keep this code in sync with calls to NextSlot
3794 	// ## in terms of the comparison of fHintLocation and PinToGrid
3795 	if (poseRect.right >= viewBounds.right) {
3796 		BPoint point(viewBounds.left + fOffset.x, 0);
3797 		point = PinToGrid(point, fGrid, fOffset);
3798 		if (fHintLocation.x != point.x)
3799 			return true;
3800 	}
3801 
3802 	// search only nearby poses (vertically)
3803 	int32 index = FirstIndexAtOrBelow((int32)(poseRect.top - IconPoseHeight()));
3804 	int32 numPoses = fVSPoseList->CountItems();
3805 
3806 	while (index < numPoses && fVSPoseList->ItemAt(index)->Location(this).y
3807 			< poseRect.bottom) {
3808 		BRect rect(fVSPoseList->ItemAt(index)->CalcRect(this));
3809 		if (poseRect.Intersects(rect))
3810 			return true;
3811 
3812 		index++;
3813 	}
3814 
3815 	return false;
3816 }
3817 
3818 
3819 void
3820 BPoseView::NextSlot(BPose* pose, BRect &poseRect, BRect viewBounds)
3821 {
3822 	// move to next slot
3823 	poseRect.OffsetBy(fGrid.x, 0);
3824 
3825 	// if we reached the end of row go down to next row
3826 	if (poseRect.right > viewBounds.right) {
3827 		fHintLocation.y += fGrid.y;
3828 		fHintLocation.x = viewBounds.left + fOffset.x;
3829 		fHintLocation = PinToGrid(fHintLocation, fGrid, fOffset);
3830 		pose->SetLocation(fHintLocation, this);
3831 		poseRect = pose->CalcRect(this);
3832 		poseRect.InsetBy(-3, 0);
3833 	}
3834 }
3835 
3836 
3837 int32
3838 BPoseView::FirstIndexAtOrBelow(int32 y, bool constrainIndex) const
3839 {
3840 // This method performs a binary search on the vertically sorted pose list
3841 // and returns either the index of the first pose at a given y location or
3842 // the proper index to insert a new pose into the list.
3843 
3844 	int32 index = 0;
3845 	int32 l = 0;
3846 	int32 r = fVSPoseList->CountItems() - 1;
3847 
3848 	while (l <= r) {
3849 		index = (l + r) >> 1;
3850 		int32 result
3851 			= (int32)(y - fVSPoseList->ItemAt(index)->Location(this).y);
3852 
3853 		if (result < 0)
3854 			r = index - 1;
3855 		else if (result > 0)
3856 			l = index + 1;
3857 		else {
3858 			// compare turned out equal, find first pose
3859 			while (index > 0
3860 				&& y == fVSPoseList->ItemAt(index - 1)->Location(this).y)
3861 				index--;
3862 			return index;
3863 		}
3864 	}
3865 
3866 	// didn't find pose AT location y - bump index to proper insert point
3867 	while (index < fVSPoseList->CountItems()
3868 		&& fVSPoseList->ItemAt(index)->Location(this).y <= y) {
3869 		index++;
3870 	}
3871 
3872 	// if flag is true then constrain index to legal value since this
3873 	// method returns the proper insertion point which could be outside
3874 	// the current bounds of the list
3875 	if (constrainIndex && index >= fVSPoseList->CountItems())
3876 		index = fVSPoseList->CountItems() - 1;
3877 
3878 	return index;
3879 }
3880 
3881 
3882 void
3883 BPoseView::AddToVSList(BPose* pose)
3884 {
3885 	int32 index = FirstIndexAtOrBelow((int32)pose->Location(this).y, false);
3886 	fVSPoseList->AddItem(pose, index);
3887 }
3888 
3889 
3890 int32
3891 BPoseView::RemoveFromVSList(const BPose* pose)
3892 {
3893 	//int32 index = FirstIndexAtOrBelow((int32)pose->Location(this).y);
3894 		// This optimisation is buggy and the index returned can be greater
3895 		// than the actual index of the pose we search, thus missing it
3896 		// and failing to remove it. This having severe implications
3897 		// everywhere in the code as it is asserted that it must be always
3898 		// in sync with fPoseList. See ticket #4322.
3899 	int32 index = 0;
3900 
3901 	int32 poseCount = fVSPoseList->CountItems();
3902 	for (; index < poseCount; index++) {
3903 		BPose* matchingPose = fVSPoseList->ItemAt(index);
3904 		ASSERT(matchingPose);
3905 		if (!matchingPose)
3906 			return -1;
3907 
3908 		if (pose == matchingPose) {
3909 			fVSPoseList->RemoveItemAt(index);
3910 			return index;
3911 		}
3912 	}
3913 
3914 	return -1;
3915 }
3916 
3917 
3918 BPoint
3919 BPoseView::PinToGrid(BPoint point, BPoint grid, BPoint offset) const
3920 {
3921 	if (grid.x == 0 || grid.y == 0)
3922 		return point;
3923 
3924 	point -= offset;
3925 	BPoint	gridLoc(point);
3926 
3927 	if (point.x >= 0)
3928 		gridLoc.x = floorf((point.x / grid.x) + 0.5f) * grid.x;
3929 	else
3930 		gridLoc.x = ceilf((point.x / grid.x) - 0.5f) * grid.x;
3931 
3932 	if (point.y >= 0)
3933 		gridLoc.y = floorf((point.y / grid.y) + 0.5f) * grid.y;
3934 	else
3935 		gridLoc.y = ceilf((point.y / grid.y) - 0.5f) * grid.y;
3936 
3937 	gridLoc += offset;
3938 
3939 	return gridLoc;
3940 }
3941 
3942 
3943 void
3944 BPoseView::ResetPosePlacementHint()
3945 {
3946 	fHintLocation = PinToGrid(BPoint(LeftTop().x + fOffset.x,
3947 		LeftTop().y + fOffset.y), fGrid, fOffset);
3948 }
3949 
3950 
3951 void
3952 BPoseView::SelectPoses(int32 start, int32 end)
3953 {
3954 	// clear selection list
3955 	fSelectionList->MakeEmpty();
3956 	fMimeTypesInSelectionCache.MakeEmpty();
3957 	fSelectionPivotPose = NULL;
3958 	fRealPivotPose = NULL;
3959 
3960 	bool iconMode = ViewMode() != kListMode;
3961 	BPoint loc(0, start * fListElemHeight);
3962 	BRect bounds(Bounds());
3963 
3964 	PoseList* poseList = CurrentPoseList();
3965 	int32 poseCount = poseList->CountItems();
3966 	for (int32 index = start; index < end && index < poseCount; index++) {
3967 		BPose* pose = poseList->ItemAt(index);
3968 		fSelectionList->AddItem(pose);
3969 		if (index == start)
3970 			fSelectionPivotPose = pose;
3971 		if (!pose->IsSelected()) {
3972 			pose->Select(true);
3973 			BRect poseRect;
3974 			if (iconMode)
3975 				poseRect = pose->CalcRect(this);
3976 			else
3977 				poseRect = pose->CalcRect(loc, this, false);
3978 
3979 			if (bounds.Intersects(poseRect)) {
3980 				Invalidate(poseRect);
3981 			}
3982 		}
3983 
3984 		loc.y += fListElemHeight;
3985 	}
3986 }
3987 
3988 
3989 void
3990 BPoseView::MoveOrChangePoseSelection(int32 to)
3991 {
3992 	PoseList* poseList = CurrentPoseList();
3993 	BPose* first = fSelectionList->FirstItem();
3994 
3995 	if (first != NULL && fMultipleSelection
3996 		&& (modifiers() & B_SHIFT_KEY) != 0) {
3997 		// Extend selection
3998 		BPose* target = poseList->ItemAt(to);
3999 		BPose* last = fSelectionList->LastItem();
4000 		int32 firstIndex = poseList->IndexOf(first);
4001 		int32 lastIndex = poseList->IndexOf(last);
4002 
4003 		int32 from = to < firstIndex ? firstIndex : lastIndex;
4004 		int32 step = from < to ? 1 : -1;
4005 
4006 		// TODO: shrink selection depending on anchor
4007 		bool select = true;
4008 
4009 		for (int32 index = from; step > 0 ? index <= to : index >= to;
4010 				index += step) {
4011 			BPose* pose = poseList->ItemAt(index);
4012 			if (pose != NULL && pose->IsSelected() != select)
4013 				AddRemovePoseFromSelection(pose, index, select);
4014 		}
4015 		if (target != NULL)
4016 			ScrollIntoView(target, to);
4017 	} else {
4018 		SelectPose(poseList->ItemAt(to), to);
4019 	}
4020 }
4021 
4022 
4023 void
4024 BPoseView::ScrollIntoView(BPose* pose, int32 index)
4025 {
4026 	ScrollIntoView(CalcPoseRect(pose, index, true));
4027 }
4028 
4029 
4030 void
4031 BPoseView::ScrollIntoView(BRect poseRect)
4032 {
4033 	if (IsDesktopWindow())
4034 		return;
4035 
4036 	BPoint oldPos = Bounds().LeftTop(), newPos = oldPos;
4037 
4038 	// In list mode, only scroll vertically
4039 	if (ViewMode() != kListMode) {
4040 		if (poseRect.left < Bounds().left
4041 			|| poseRect.Width() > Bounds().Width())
4042 			newPos.x += poseRect.left - Bounds().left;
4043 		else if (poseRect.right > Bounds().right)
4044 			newPos.x += poseRect.right - Bounds().right;
4045 	}
4046 
4047 	if (poseRect.top < Bounds().top
4048 		|| poseRect.Height() > Bounds().Height())
4049 		newPos.y += poseRect.top - Bounds().top;
4050 	else if (poseRect.bottom > Bounds().bottom)
4051 		newPos.y += poseRect.bottom - Bounds().bottom;
4052 
4053 	if (newPos != oldPos)
4054 		SetScrollBarsTo(newPos);
4055 }
4056 
4057 
4058 void
4059 BPoseView::SelectPose(BPose* pose, int32 index, bool scrollIntoView)
4060 {
4061 	if (pose == NULL || CountSelected() > 1 || !pose->IsSelected())
4062 		ClearSelection();
4063 
4064 	AddPoseToSelection(pose, index, scrollIntoView);
4065 
4066 	if (pose != NULL)
4067 		fSelectionPivotPose = pose;
4068 }
4069 
4070 
4071 void
4072 BPoseView::AddPoseToSelection(BPose* pose, int32 index, bool scrollIntoView)
4073 {
4074 	// TODO: need to check if pose is member of selection list
4075 	if (pose != NULL && !pose->IsSelected()) {
4076 		pose->Select(true);
4077 		fSelectionList->AddItem(pose);
4078 
4079 		BRect poseRect = CalcPoseRect(pose, index);
4080 		Invalidate(poseRect);
4081 
4082 		if (scrollIntoView)
4083 			ScrollIntoView(poseRect);
4084 
4085 		if (fSelectionChangedHook)
4086 			ContainerWindow()->SelectionChanged();
4087 	}
4088 }
4089 
4090 
4091 void
4092 BPoseView::RemovePoseFromSelection(BPose* pose)
4093 {
4094 	if (fSelectionPivotPose == pose)
4095 		fSelectionPivotPose = NULL;
4096 
4097 	if (fRealPivotPose == pose)
4098 		fRealPivotPose = NULL;
4099 
4100 	if (!fSelectionList->RemoveItem(pose)) {
4101 		// wasn't selected to begin with
4102 		return;
4103 	}
4104 
4105 	pose->Select(false);
4106 	if (ViewMode() == kListMode) {
4107 		// TODO: need a simple call to CalcRect that works both in listView and
4108 		// icon view modes without the need for an index/pos
4109 		PoseList* poseList = CurrentPoseList();
4110 		int32 poseCount = poseList->CountItems();
4111 		BPoint loc(0, 0);
4112 		for (int32 index = 0; index < poseCount; index++) {
4113 			if (pose == poseList->ItemAt(index)) {
4114 				Invalidate(pose->CalcRect(loc, this));
4115 				break;
4116 			}
4117 			loc.y += fListElemHeight;
4118 		}
4119 	} else
4120 		Invalidate(pose->CalcRect(this));
4121 
4122 	if (fSelectionChangedHook)
4123 		ContainerWindow()->SelectionChanged();
4124 }
4125 
4126 
4127 bool
4128 BPoseView::EachItemInDraggedSelection(const BMessage* message,
4129 	bool (*func)(BPose*, BPoseView*, void*), BPoseView* poseView,
4130 	void* passThru)
4131 {
4132 	BContainerWindow* srcWindow;
4133 	if (message->FindPointer("src_window", (void**)&srcWindow) != B_OK)
4134 		return false;
4135 
4136 	AutoLock<BWindow> lock(srcWindow);
4137 	if (!lock)
4138 		return false;
4139 
4140 	PoseList* selectionList = srcWindow->PoseView()->SelectionList();
4141 	int32 selectCount = selectionList->CountItems();
4142 
4143 	for (int32 index = 0; index < selectCount; index++) {
4144 		BPose* pose = selectionList->ItemAt(index);
4145 		if (func(pose, poseView, passThru))
4146 			// early iteration termination
4147 			return true;
4148 	}
4149 
4150 	return false;
4151 }
4152 
4153 
4154 static bool
4155 ContainsOne(BString* string, const char* matchString)
4156 {
4157 	return strcmp(string->String(), matchString) == 0;
4158 }
4159 
4160 
4161 bool
4162 BPoseView::FindDragNDropAction(const BMessage* dragMessage, bool &canCopy,
4163 	bool &canMove, bool &canLink, bool &canErase)
4164 {
4165 	canCopy = false;
4166 	canMove = false;
4167 	canErase = false;
4168 	canLink = false;
4169 	if (!dragMessage->HasInt32("be:actions"))
4170 		return false;
4171 
4172 	int32 action;
4173 	for (int32 index = 0;
4174 			dragMessage->FindInt32("be:actions", index, &action) == B_OK;
4175 			index++) {
4176 		switch (action) {
4177 			case B_MOVE_TARGET:
4178 				canMove = true;
4179 				break;
4180 
4181 			case B_COPY_TARGET:
4182 				canCopy = true;
4183 				break;
4184 
4185 			case B_TRASH_TARGET:
4186 				canErase = true;
4187 				break;
4188 
4189 			case B_LINK_TARGET:
4190 				canLink = true;
4191 				break;
4192 		}
4193 	}
4194 
4195 	return canCopy || canMove || canErase || canLink;
4196 }
4197 
4198 
4199 bool
4200 BPoseView::CanTrashForeignDrag(const Model* targetModel)
4201 {
4202 	return targetModel->IsTrash();
4203 }
4204 
4205 
4206 bool
4207 BPoseView::CanCopyOrMoveForeignDrag(const Model* targetModel,
4208 	const BMessage* dragMessage)
4209 {
4210 	if (!targetModel->IsDirectory() && !targetModel->IsVirtualDirectory())
4211 		return false;
4212 
4213 	// in order to handle a clipping file, the drag initiator must be able
4214 	// do deal with B_FILE_MIME_TYPE
4215 	for (int32 index = 0; ; index++) {
4216 		const char* type;
4217 		if (dragMessage->FindString("be:types", index, &type) != B_OK)
4218 			break;
4219 
4220 		if (strcasecmp(type, B_FILE_MIME_TYPE) == 0)
4221 			return true;
4222 	}
4223 
4224 	return false;
4225 }
4226 
4227 
4228 bool
4229 BPoseView::CanHandleDragSelection(const Model* target,
4230 	const BMessage* dragMessage, bool ignoreTypes)
4231 {
4232 	if (ignoreTypes)
4233 		return target->IsDropTarget();
4234 
4235 	ASSERT(dragMessage != NULL);
4236 
4237 	BContainerWindow* srcWindow;
4238 	status_t result = dragMessage->FindPointer("src_window", (void**)&srcWindow);
4239 	if (result != B_OK || srcWindow == NULL) {
4240 		// handle a foreign drag
4241 		bool canCopy;
4242 		bool canMove;
4243 		bool canErase;
4244 		bool canLink;
4245 		FindDragNDropAction(dragMessage, canCopy, canMove, canLink, canErase);
4246 		if (canErase && CanTrashForeignDrag(target))
4247 			return true;
4248 
4249 		if (canCopy || canMove) {
4250 			if (CanCopyOrMoveForeignDrag(target, dragMessage))
4251 				return true;
4252 
4253 			// TODO: collect all mime types here and pass into
4254 			// target->IsDropTargetForList(mimeTypeList);
4255 		}
4256 
4257 		// handle an old style entry_refs only drag message
4258 		if (dragMessage->HasRef("refs")
4259 			&& (target->IsDirectory() || target->IsVirtualDirectory())) {
4260 			return true;
4261 		}
4262 
4263 		// handle simple text clipping drag&drop message
4264 		if (dragMessage->HasData(kPlainTextMimeType, B_MIME_TYPE)
4265 			&& target->IsDirectory()) {
4266 			return true;
4267 		}
4268 
4269 		// handle simple bitmap clipping drag&drop message
4270 		if (target->IsDirectory()
4271 			&& (dragMessage->HasData(kBitmapMimeType, B_MESSAGE_TYPE)
4272 				|| dragMessage->HasData(kLargeIconType, B_MESSAGE_TYPE)
4273 				|| dragMessage->HasData(kMiniIconType, B_MESSAGE_TYPE))) {
4274 			return true;
4275 		}
4276 
4277 		// TODO: check for a drag message full of refs, feed a list of their
4278 		// types to target->IsDropTargetForList(mimeTypeList);
4279 		return false;
4280 	}
4281 
4282 	ASSERT(srcWindow != NULL);
4283 
4284 	AutoLock<BWindow> lock(srcWindow);
4285 	if (!lock)
4286 		return false;
4287 
4288 	BObjectList<BString>* mimeTypeList
4289 		= srcWindow->PoseView()->MimeTypesInSelection();
4290 	if (mimeTypeList->IsEmpty()) {
4291 		PoseList* selectionList = srcWindow->PoseView()->SelectionList();
4292 		if (!selectionList->IsEmpty()) {
4293 			// no cached data yet, build the cache
4294 			int32 selectCount = selectionList->CountItems();
4295 
4296 			for (int32 index = 0; index < selectCount; index++) {
4297 				// get the mime type of the model, following a possible symlink
4298 				BEntry entry(selectionList->ItemAt(
4299 					index)->TargetModel()->EntryRef(), true);
4300 				if (entry.InitCheck() != B_OK)
4301 					continue;
4302 
4303  				BFile file(&entry, O_RDONLY);
4304 				BNodeInfo mime(&file);
4305 
4306 				if (mime.InitCheck() != B_OK)
4307 					continue;
4308 
4309 				char mimeType[B_MIME_TYPE_LENGTH];
4310 				mime.GetType(mimeType);
4311 
4312 				// add unique type string
4313 				if (!WhileEachListItem(mimeTypeList, ContainsOne,
4314 						(const char*)mimeType)) {
4315 					BString* newMimeString = new BString(mimeType);
4316 					mimeTypeList->AddItem(newMimeString);
4317 				}
4318 			}
4319 		}
4320 	}
4321 
4322 	return target->IsDropTargetForList(mimeTypeList);
4323 }
4324 
4325 
4326 void
4327 BPoseView::TrySettingPoseLocation(BNode* node, BPoint point)
4328 {
4329 	if (ViewMode() == kListMode)
4330 		return;
4331 
4332 	if ((modifiers() & B_COMMAND_KEY) != 0) {
4333 		// align to grid if needed
4334 		point = PinToGrid(point, fGrid, fOffset);
4335 	}
4336 
4337 	Model* targetModel = TargetModel();
4338 	ASSERT(targetModel != NULL);
4339 
4340 	if (targetModel != NULL && targetModel->NodeRef() != NULL
4341 		&& FSSetPoseLocation(targetModel->NodeRef()->node, node, point)
4342 			== B_OK) {
4343 		// get rid of opposite endianness attribute
4344 		node->RemoveAttr(kAttrPoseInfoForeign);
4345 	}
4346 }
4347 
4348 
4349 status_t
4350 BPoseView::CreateClippingFile(BPoseView* poseView, BFile &result,
4351 	char* resultingName, BDirectory* directory, BMessage* message,
4352 	const char* fallbackName, bool setLocation, BPoint dropPoint)
4353 {
4354 	// build a file name
4355 	// try picking it up from the message
4356 	const char* suggestedName;
4357 	if (message && message->FindString("be:clip_name", &suggestedName) == B_OK)
4358 		strncpy(resultingName, suggestedName, B_FILE_NAME_LENGTH - 1);
4359 	else
4360 		strcpy(resultingName, fallbackName);
4361 
4362 	FSMakeOriginalName(resultingName, directory, "");
4363 
4364 	// create a clipping file
4365 	status_t error = directory->CreateFile(resultingName, &result, true);
4366 	if (error != B_OK)
4367 		return error;
4368 
4369 	if (setLocation && poseView != NULL)
4370 		poseView->TrySettingPoseLocation(&result, dropPoint);
4371 
4372 	return B_OK;
4373 }
4374 
4375 
4376 static int32
4377 RunMimeTypeDestinationMenu(const char* actionText,
4378 	const BObjectList<BString>* types,
4379 	const BObjectList<BString>* specificItems, BPoint where)
4380 {
4381 	int32 count;
4382 	if (types != NULL)
4383 		count = types->CountItems();
4384 	else
4385 		count = specificItems->CountItems();
4386 
4387 	if (count == 0)
4388 		return 0;
4389 
4390 	BPopUpMenu* menu = new BPopUpMenu("create clipping");
4391 	menu->SetFont(be_plain_font);
4392 
4393 	for (int32 index = 0; index < count; index++) {
4394 		const char* embedTypeAs = NULL;
4395 		char buffer[256];
4396 		if (types) {
4397 			types->ItemAt(index)->String();
4398 			BMimeType mimeType(embedTypeAs);
4399 
4400 			if (mimeType.GetShortDescription(buffer) == B_OK)
4401 				embedTypeAs = buffer;
4402 		}
4403 
4404 		BString description;
4405 		if (specificItems->ItemAt(index)->Length()) {
4406 			description << (const BString &)(*specificItems->ItemAt(index));
4407 
4408 			if (embedTypeAs)
4409 				description << " (" << embedTypeAs << ")";
4410 
4411 		} else if (types)
4412 			description = embedTypeAs;
4413 
4414 		BString labelText;
4415 		if (actionText) {
4416 			int32 length = 1024 - 1 - (int32)strlen(actionText);
4417 			if (length > 0) {
4418 				description.Truncate(length);
4419 				labelText.SetTo(actionText);
4420 				labelText.ReplaceFirst("%s", description.String());
4421 			} else
4422 				labelText.SetTo(B_TRANSLATE("label too long"));
4423 		} else
4424 			labelText = description;
4425 
4426 		menu->AddItem(new BMenuItem(labelText.String(), 0));
4427 	}
4428 
4429 	menu->AddSeparatorItem();
4430 	menu->AddItem(new BMenuItem(B_TRANSLATE("Cancel"), 0));
4431 
4432 	int32 result = -1;
4433 	BMenuItem* resultingItem = menu->Go(where, false, true);
4434 	if (resultingItem) {
4435 		int32 index = menu->IndexOf(resultingItem);
4436 		if (index < count)
4437 			result = index;
4438 	}
4439 
4440 	delete menu;
4441 
4442 	return result;
4443 }
4444 
4445 
4446 bool
4447 BPoseView::HandleMessageDropped(BMessage* message)
4448 {
4449 	ASSERT(message->WasDropped());
4450 
4451 	// reset system cursor in case it was altered by drag and drop
4452 	SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
4453 	fCursorCheck = false;
4454 
4455 	if (!fDropEnabled)
4456 		return false;
4457 
4458 	BContainerWindow* window = dynamic_cast<BContainerWindow*>(Window());
4459 	if (window != NULL && message->HasData("RGBColor", 'RGBC')) {
4460 		// do not handle roColor-style drops here, pass them on to the desktop
4461 		BMessenger((BHandler*)window).SendMessage(message);
4462 
4463 		return true;
4464 	}
4465 
4466 	if (fDropTarget && !DragSelectionContains(fDropTarget, message))
4467 		HiliteDropTarget(false);
4468 
4469 	fDropTarget = NULL;
4470 
4471 	ASSERT(TargetModel() != NULL);
4472 	BPoint offset;
4473 	BPoint dropPoint(message->DropPoint(&offset));
4474 	ConvertFromScreen(&dropPoint);
4475 
4476 	// tenatively figure out the pose we dropped the file onto
4477 	int32 index;
4478 	BPose* targetPose = FindPose(dropPoint, &index);
4479 	Model tmpTarget;
4480 	Model* targetModel = NULL;
4481 	if (targetPose != NULL) {
4482 		targetModel = targetPose->TargetModel();
4483 		if (targetModel->IsSymLink()
4484 			&& tmpTarget.SetTo(targetPose->TargetModel()->EntryRef(),
4485 				true, true) == B_OK) {
4486 			targetModel = &tmpTarget;
4487 		}
4488 	}
4489 
4490 	return HandleDropCommon(message, targetModel, targetPose, this, dropPoint);
4491 }
4492 
4493 
4494 bool
4495 BPoseView::HandleDropCommon(BMessage* message, Model* targetModel,
4496 	BPose* targetPose, BView* view, BPoint dropPoint)
4497 {
4498 	uint32 buttons = (uint32)message->FindInt32("buttons");
4499 
4500 	BContainerWindow* window = NULL;
4501 	BPoseView* poseView = dynamic_cast<BPoseView*>(view);
4502 	if (poseView != NULL)
4503 		window = poseView->ContainerWindow();
4504 
4505 	// look for srcWindow to determine whether drag was initiated in tracker
4506 	BContainerWindow* srcWindow = NULL;
4507 	status_t result = message->FindPointer("src_window", (void**)&srcWindow);
4508 	if (result != B_OK || srcWindow == NULL) {
4509 		// drag was from another app
4510 
4511 		if (targetModel == NULL && poseView != NULL)
4512 			targetModel = poseView->TargetModel();
4513 
4514 		// figure out if we dropped a file onto a directory and set
4515 		// the targetDirectory to it, else set it to this pose view
4516 		BDirectory targetDirectory;
4517 		if (targetModel != NULL && targetModel->IsDirectory())
4518 			targetDirectory.SetTo(targetModel->EntryRef());
4519 
4520 		if (targetModel != NULL && targetModel->IsRoot()) {
4521 			// don't drop anything into the root disk
4522 			return false;
4523 		}
4524 
4525 		bool canCopy;
4526 		bool canMove;
4527 		bool canErase;
4528 		bool canLink;
4529 		if (FindDragNDropAction(message, canCopy, canMove, canLink, canErase)) {
4530 			// new D&D protocol
4531 			// what action can the drag initiator do?
4532 			if (canErase && CanTrashForeignDrag(targetModel)) {
4533 				BMessage reply(B_TRASH_TARGET);
4534 				message->SendReply(&reply);
4535 				return true;
4536 			}
4537 
4538 			if ((canCopy || canMove)
4539 				&& CanCopyOrMoveForeignDrag(targetModel, message)) {
4540 				// handle the promise style drag&drop
4541 
4542 				// fish for specification of specialized menu items
4543 				BObjectList<BString> actionSpecifiers(10, true);
4544 				for (int32 index = 0; ; index++) {
4545 					const char* string;
4546 					if (message->FindString("be:actionspecifier", index,
4547 							&string) != B_OK) {
4548 						break;
4549 					}
4550 
4551 					ASSERT(string != NULL);
4552 					actionSpecifiers.AddItem(new BString(string));
4553 				}
4554 
4555 				// build the list of types the drag originator offers
4556 				BObjectList<BString> types(10, true);
4557 				BObjectList<BString> typeNames(10, true);
4558 				for (int32 index = 0; ; index++) {
4559 					const char* string;
4560 					if (message->FindString("be:filetypes", index, &string)
4561 							!= B_OK) {
4562 						break;
4563 					}
4564 
4565 					ASSERT(string != NULL);
4566 					types.AddItem(new BString(string));
4567 
4568 					const char* typeName = "";
4569 					message->FindString("be:type_descriptions", index,
4570 						&typeName);
4571 					typeNames.AddItem(new BString(typeName));
4572 				}
4573 
4574 				int32 specificTypeIndex = -1;
4575 				int32 specificActionIndex = -1;
4576 
4577 				// if control down, run a popup menu
4578 				if (canCopy
4579 					&& SecondaryMouseButtonDown(modifiers(), buttons)) {
4580 					if (actionSpecifiers.CountItems() > 0) {
4581 						specificActionIndex = RunMimeTypeDestinationMenu(NULL,
4582 							NULL, &actionSpecifiers,
4583 							view->ConvertToScreen(dropPoint));
4584 
4585 						if (specificActionIndex == -1)
4586 							return false;
4587 					} else if (types.CountItems() > 0) {
4588 						specificTypeIndex = RunMimeTypeDestinationMenu(
4589 							B_TRANSLATE("Create %s clipping"),
4590 							&types, &typeNames,
4591 							view->ConvertToScreen(dropPoint));
4592 
4593 						if (specificTypeIndex == -1)
4594 							return false;
4595 					}
4596 				}
4597 
4598 				char name[B_FILE_NAME_LENGTH];
4599 				BFile file;
4600 				if (CreateClippingFile(poseView, file, name, &targetDirectory,
4601 						message, B_TRANSLATE("Untitled clipping"),
4602 						targetPose == NULL, dropPoint) != B_OK) {
4603 					return false;
4604 				}
4605 
4606 				// here is a file for the drag initiator, it is up to it now
4607 				// to stuff it with the goods
4608 
4609 				// build the reply message
4610 				BMessage reply(canCopy ? B_COPY_TARGET : B_MOVE_TARGET);
4611 				reply.AddString("be:types", B_FILE_MIME_TYPE);
4612 				if (specificTypeIndex != -1) {
4613 					// we had the user pick a specific type from a menu, use it
4614 					reply.AddString("be:filetypes",
4615 						types.ItemAt(specificTypeIndex)->String());
4616 
4617 					if (typeNames.ItemAt(specificTypeIndex)->Length()) {
4618 						reply.AddString("be:type_descriptions",
4619 							typeNames.ItemAt(specificTypeIndex)->String());
4620 					}
4621 				}
4622 
4623 				if (specificActionIndex != -1) {
4624 					// we had the user pick a specific type from a menu, use it
4625 					reply.AddString("be:actionspecifier",
4626 						actionSpecifiers.ItemAt(specificActionIndex)->String());
4627 				}
4628 
4629 				reply.AddRef("directory", targetModel->EntryRef());
4630 				reply.AddString("name", name);
4631 
4632 				// Attach any data the originator may have tagged on
4633 				BMessage data;
4634 				if (message->FindMessage("be:originator-data", &data) == B_OK)
4635 					reply.AddMessage("be:originator-data", &data);
4636 
4637 				// copy over all the file types the drag initiator claimed to
4638 				// support
4639 				for (int32 index = 0; ; index++) {
4640 					const char* type;
4641 					if (message->FindString("be:filetypes", index, &type)
4642 							!= B_OK) {
4643 						break;
4644 					}
4645 					reply.AddString("be:filetypes", type);
4646 				}
4647 
4648 				message->SendReply(&reply);
4649 				return true;
4650 			}
4651 		}
4652 
4653 		if (message->HasRef("refs")) {
4654 			// TODO: decide here on copy, move or create symlink
4655 			// look for specific command or bring up popup
4656 			// Unify this with local drag&drop
4657 
4658 			if (!targetModel->IsDirectory()
4659 				&& !targetModel->IsVirtualDirectory()) {
4660 				// bail if we are not a directory
4661 				return false;
4662 			}
4663 
4664 			bool canRelativeLink = false;
4665 			if (!canCopy && !canMove && !canLink && window) {
4666 				if (SecondaryMouseButtonDown(modifiers(), buttons)) {
4667 					switch (window->ShowDropContextMenu(dropPoint,
4668 							srcWindow != NULL ? srcWindow->PoseView() : NULL)) {
4669 						case kCreateRelativeLink:
4670 							canRelativeLink = true;
4671 							break;
4672 
4673 						case kCreateLink:
4674 							canLink = true;
4675 							break;
4676 
4677 						case kMoveSelectionTo:
4678 							canMove = true;
4679 							break;
4680 
4681 						case kCopySelectionTo:
4682 							canCopy = true;
4683 							break;
4684 
4685 						case kCancelButton:
4686 						default:
4687 							// user canceled context menu
4688 							return true;
4689 					}
4690 				} else
4691 					canCopy = true;
4692 			}
4693 
4694 			uint32 moveMode;
4695 			if (canCopy)
4696 				moveMode = kCopySelectionTo;
4697 			else if (canMove)
4698 				moveMode = kMoveSelectionTo;
4699 			else if (canLink)
4700 				moveMode = kCreateLink;
4701 			else if (canRelativeLink)
4702 				moveMode = kCreateRelativeLink;
4703 			else {
4704 				TRESPASS();
4705 				return true;
4706 			}
4707 
4708 			// handle refs by performing a copy
4709 			BObjectList<entry_ref>* entryList
4710 				= new BObjectList<entry_ref>(10, true);
4711 
4712 			for (int32 index = 0; ; index++) {
4713 				// copy all enclosed refs into a list
4714 				entry_ref ref;
4715 				if (message->FindRef("refs", index, &ref) != B_OK)
4716 					break;
4717 				entryList->AddItem(new entry_ref(ref));
4718 			}
4719 
4720 			int32 count = entryList->CountItems();
4721 			if (count != 0) {
4722 				BList* pointList = 0;
4723 				if (poseView != NULL && targetPose != NULL) {
4724 					// calculate a pointList to make the icons land
4725 					// were we dropped them
4726 					pointList = new BList(count);
4727 					// force the the icons to lay out in 5 columns
4728 					for (int32 index = 0; count; index++) {
4729 						for (int32 j = 0; count && j < 4; j++, count--) {
4730 							BPoint point(
4731 								dropPoint + BPoint(j * poseView->fGrid.x,
4732 								index * poseView->fGrid.y));
4733 							pointList->AddItem(
4734 								new BPoint(poseView->PinToGrid(point,
4735 								poseView->fGrid, poseView->fOffset)));
4736 						}
4737 					}
4738 				}
4739 
4740 				// perform asynchronous copy
4741 				FSMoveToFolder(entryList, new BEntry(targetModel->EntryRef()),
4742 					moveMode, pointList);
4743 
4744 				return true;
4745 			}
4746 
4747 			// nothing to copy, list doesn't get consumed
4748 			delete entryList;
4749 			return true;
4750 		}
4751 		if (message->HasData(kPlainTextMimeType, B_MIME_TYPE)) {
4752 			// text dropped, make into a clipping file
4753 			if (!targetModel->IsDirectory()) {
4754 				// bail if we are not a directory
4755 				return false;
4756 			}
4757 
4758 			// find the text
4759 			ssize_t textLength;
4760 			const char* text;
4761 			if (message->FindData(kPlainTextMimeType, B_MIME_TYPE,
4762 				(const void**)&text, &textLength) != B_OK) {
4763 				return false;
4764 			}
4765 
4766 			char name[B_FILE_NAME_LENGTH];
4767 			BFile file;
4768 			if (CreateClippingFile(poseView, file, name, &targetDirectory,
4769 					message, B_TRANSLATE("Untitled clipping"), !targetPose,
4770 					dropPoint) != B_OK) {
4771 				return false;
4772 			}
4773 
4774 			// write out the file
4775 			if (file.Seek(0, SEEK_SET) == B_ERROR
4776 				|| file.Write(text, (size_t)textLength) < 0
4777 				|| file.SetSize(textLength) != B_OK) {
4778 				// failed to write file, remove file and bail
4779 				file.Unset();
4780 				BEntry entry(&targetDirectory, name);
4781 				entry.Remove();
4782 				PRINT(("error writing text into file %s\n", name));
4783 			}
4784 
4785 			// pick up TextView styles if available and save them with the file
4786 			const text_run_array* textRuns = NULL;
4787 			ssize_t dataSize = 0;
4788 			if (message->FindData("application/x-vnd.Be-text_run_array",
4789 					B_MIME_TYPE, (const void**)&textRuns, &dataSize) == B_OK
4790 					&& textRuns && dataSize) {
4791 				// save styles the same way StyledEdit does
4792 				int32 tmpSize = dataSize;
4793 				void* data = BTextView::FlattenRunArray(textRuns, &tmpSize);
4794 				file.WriteAttr("styles", B_RAW_TYPE, 0, data, (size_t)tmpSize);
4795 				free(data);
4796 			}
4797 
4798 			// mark as a clipping file
4799 			int32 tmp;
4800 			file.WriteAttr(kAttrClippingFile, B_RAW_TYPE, 0, &tmp,
4801 				sizeof(int32));
4802 
4803 			// set the file type
4804 			BNodeInfo info(&file);
4805 			info.SetType(kPlainTextMimeType);
4806 
4807 			return true;
4808 		}
4809 
4810 		if (message->HasData(kBitmapMimeType, B_MESSAGE_TYPE)
4811 			|| message->HasData(kLargeIconType, B_MESSAGE_TYPE)
4812 			|| message->HasData(kMiniIconType, B_MESSAGE_TYPE)) {
4813 			// bitmap, make into a clipping file
4814 			if (!targetModel->IsDirectory()) {
4815 				// bail if we are not a directory
4816 				return false;
4817 			}
4818 
4819 			BMessage embeddedBitmap;
4820 			if (message->FindMessage(kBitmapMimeType, &embeddedBitmap)
4821 					!= B_OK
4822 				&& message->FindMessage(kLargeIconType, &embeddedBitmap)
4823 					!= B_OK
4824 				&& message->FindMessage(kMiniIconType, &embeddedBitmap)
4825 					!= B_OK) {
4826 				return false;
4827 			}
4828 
4829 			char name[B_FILE_NAME_LENGTH];
4830 
4831 			BFile file;
4832 			if (CreateClippingFile(poseView, file, name, &targetDirectory,
4833 					message, B_TRANSLATE("Untitled bitmap"), targetPose == NULL,
4834 					dropPoint) != B_OK) {
4835 				return false;
4836 			}
4837 
4838 			int32 size = embeddedBitmap.FlattenedSize();
4839 			if (size > 1024 * 1024) {
4840 				// bail if too large
4841 				return false;
4842 			}
4843 
4844 			char* buffer = new char [size];
4845 			embeddedBitmap.Flatten(buffer, size);
4846 
4847 			// write out the file
4848 			if (file.Seek(0, SEEK_SET) == B_ERROR
4849 				|| file.Write(buffer, (size_t)size) < 0
4850 				|| file.SetSize(size) != B_OK) {
4851 				// failed to write file, remove file and bail
4852 				file.Unset();
4853 				BEntry entry(&targetDirectory, name);
4854 				entry.Remove();
4855 				PRINT(("error writing bitmap into file %s\n", name));
4856 			}
4857 
4858 			// mark as a clipping file
4859 			int32 tmp;
4860 			file.WriteAttr(kAttrClippingFile, B_RAW_TYPE, 0, &tmp,
4861 				sizeof(int32));
4862 
4863 			// set the file type
4864 			BNodeInfo info(&file);
4865 			info.SetType(kBitmapMimeType);
4866 
4867 			return true;
4868 		}
4869 
4870 		return false;
4871 	}
4872 
4873 	ASSERT(srcWindow != NULL);
4874 
4875 	if (srcWindow == window) {
4876 		// drag started in this window
4877 		window->Activate();
4878 		window->UpdateIfNeeded();
4879 		poseView->ResetPosePlacementHint();
4880 
4881 		if (DragSelectionContains(targetPose, message)) {
4882 			// drop on self
4883 			targetModel = NULL;
4884 		}
4885 	}
4886 
4887 	bool wasHandled = false;
4888 	bool ignoreTypes = (modifiers() & B_CONTROL_KEY) != 0;
4889 
4890 	if (targetModel != NULL && window != NULL) {
4891 		// TODO: pick files to drop/launch on a case by case basis
4892 		if (targetModel->IsDirectory() || targetModel->IsVirtualDirectory()) {
4893 			MoveSelectionInto(targetModel, srcWindow, window, buttons,
4894 				dropPoint, false);
4895 			wasHandled = true;
4896 		} else if (CanHandleDragSelection(targetModel, message, ignoreTypes)) {
4897 			LaunchAppWithSelection(targetModel, message, !ignoreTypes);
4898 			wasHandled = true;
4899 		}
4900 	}
4901 
4902 	if (poseView != NULL && !wasHandled) {
4903 		BPoint clickPoint = message->FindPoint("click_pt");
4904 		// TODO: removed check for root here need to do that, possibly at a
4905 		// different level
4906 		poseView->MoveSelectionTo(dropPoint, clickPoint, srcWindow);
4907 	}
4908 
4909 	if (poseView != NULL && poseView->fEnsurePosesVisible)
4910 		poseView->CheckPoseVisibility();
4911 
4912 	return true;
4913 }
4914 
4915 
4916 struct LaunchParams {
4917 	Model* app;
4918 	bool checkTypes;
4919 	BMessage* refsMessage;
4920 };
4921 
4922 
4923 static bool
4924 AddOneToLaunchMessage(BPose* pose, BPoseView*, void* castToParams)
4925 {
4926 	LaunchParams* params = (LaunchParams*)castToParams;
4927 	ThrowOnAssert(params != NULL);
4928 	ThrowOnAssert(pose != NULL);
4929 	ThrowOnAssert(pose->TargetModel() != NULL);
4930 
4931 	if (params->app->IsDropTarget(params->checkTypes
4932 			? pose->TargetModel() : NULL, true)) {
4933 		params->refsMessage->AddRef("refs", pose->TargetModel()->EntryRef());
4934 	}
4935 
4936 	return false;
4937 }
4938 
4939 
4940 void
4941 BPoseView::LaunchAppWithSelection(Model* appModel, const BMessage* dragMessage,
4942 	bool checkTypes)
4943 {
4944 	// launch items from the current selection with <appModel>; only pass
4945 	// the same files that we previously decided can be handled by <appModel>
4946 	BMessage refs(B_REFS_RECEIVED);
4947 	LaunchParams params;
4948 	params.app = appModel;
4949 	params.checkTypes = checkTypes;
4950 	params.refsMessage = &refs;
4951 
4952 	// add Tracker token so that refs received recipients can script us
4953 	BContainerWindow* srcWindow;
4954 	if (dragMessage->FindPointer("src_window", (void**)&srcWindow) == B_OK
4955 		&& srcWindow != NULL) {
4956 		params.refsMessage->AddMessenger("TrackerViewToken",
4957 			BMessenger(srcWindow->PoseView()));
4958 	}
4959 
4960 	EachItemInDraggedSelection(dragMessage, AddOneToLaunchMessage, 0, &params);
4961 	if (params.refsMessage->HasRef("refs"))
4962 		TrackerLaunch(appModel->EntryRef(), params.refsMessage, true);
4963 }
4964 
4965 
4966 bool
4967 BPoseView::DragSelectionContains(const BPose* target,
4968 	const BMessage* dragMessage)
4969 {
4970 	return EachItemInDraggedSelection(dragMessage, OneMatches, 0,
4971 		(void*)target);
4972 }
4973 
4974 
4975 void
4976 BPoseView::MoveSelectionInto(Model* destFolder, BContainerWindow* srcWindow,
4977 	bool forceCopy, bool forceMove, bool createLink, bool relativeLink)
4978 {
4979 	uint32 buttons;
4980 	BPoint loc;
4981 	GetMouse(&loc, &buttons);
4982 	MoveSelectionInto(destFolder, srcWindow,
4983 		dynamic_cast<BContainerWindow*>(Window()), buttons, loc, forceCopy,
4984 		forceMove, createLink, relativeLink);
4985 }
4986 
4987 
4988 void
4989 BPoseView::MoveSelectionInto(Model* destFolder, BContainerWindow* srcWindow,
4990 	BContainerWindow* destWindow, uint32 buttons, BPoint loc, bool forceCopy,
4991 	bool forceMove, bool createLink, bool relativeLink, BPoint clickPoint,
4992 	bool dropOnGrid)
4993 {
4994 	AutoLock<BWindow> lock(srcWindow);
4995 	if (!lock)
4996 		return;
4997 
4998 	ASSERT(srcWindow->PoseView()->TargetModel() != NULL);
4999 
5000 	if (srcWindow->PoseView()->CountSelected() == 0)
5001 		return;
5002 
5003 	bool createRelativeLink = relativeLink;
5004 	if (SecondaryMouseButtonDown(modifiers(), buttons)
5005 		&& destWindow != NULL) {
5006 		switch (destWindow->ShowDropContextMenu(loc,
5007 				srcWindow != NULL ? srcWindow->PoseView() : NULL)) {
5008 			case kCreateRelativeLink:
5009 				createRelativeLink = true;
5010 				break;
5011 
5012 			case kCreateLink:
5013 				createLink = true;
5014 				break;
5015 
5016 			case kMoveSelectionTo:
5017 				forceMove = true;
5018 				break;
5019 
5020 			case kCopySelectionTo:
5021 				forceCopy = true;
5022 				break;
5023 
5024 			case kCancelButton:
5025 			default:
5026 				// user canceled context menu
5027 				return;
5028 		}
5029 	}
5030 
5031 	// make sure source and destination folders are different
5032 	if (!createLink && !createRelativeLink
5033 		&& (*srcWindow->PoseView()->TargetModel()->NodeRef()
5034 			== *destFolder->NodeRef())) {
5035 		BPoseView* targetView = srcWindow->PoseView();
5036 		if (forceCopy) {
5037 			targetView->DuplicateSelection(&clickPoint, &loc);
5038 			return;
5039 		}
5040 
5041 		if (targetView->ViewMode() == kListMode) {
5042 			// can't move in list view
5043 			return;
5044 		}
5045 
5046 		BPoint delta = loc - clickPoint;
5047 		int32 selectCount = targetView->CountSelected();
5048 		for (int32 index = 0; index < selectCount; index++) {
5049 			BPose* pose = targetView->SelectionList()->ItemAt(index);
5050 
5051 			// remove pose from VSlist before changing location
5052 			// so that we "find" the correct pose to remove
5053 			// need to do this because bsearch uses top of pose
5054 			// to locate pose to remove
5055 			targetView->RemoveFromVSList(pose);
5056 			BPoint location(pose->Location(targetView) + delta);
5057 			BRect oldBounds(pose->CalcRect(targetView));
5058 			if (dropOnGrid) {
5059 				location = targetView->PinToGrid(location, targetView->fGrid,
5060 					targetView->fOffset);
5061 			}
5062 
5063 			// TODO: don't drop poses under desktop elements
5064 			//		 ie: replicants, deskbar
5065 			pose->MoveTo(location, targetView);
5066 
5067 			targetView->RemoveFromExtent(oldBounds);
5068 			targetView->AddToExtent(pose->CalcRect(targetView));
5069 
5070 			// remove and reinsert pose to keep VSlist sorted
5071 			targetView->AddToVSList(pose);
5072 		}
5073 
5074 		return;
5075 	}
5076 
5077 	BEntry* destEntry = new BEntry(destFolder->EntryRef());
5078 	bool destIsTrash = destFolder->IsTrash();
5079 
5080 	// perform asynchronous copy/move
5081 	forceCopy = forceCopy || (modifiers() & B_OPTION_KEY) != 0;
5082 
5083 	bool okToMove = true;
5084 
5085 	if (destFolder->IsRoot()) {
5086 		BAlert* alert = new BAlert("",
5087 			B_TRANSLATE("You must drop items on one of the disk icons "
5088 			"in the \"Disks\" window."), B_TRANSLATE("Cancel"), NULL, NULL,
5089 			B_WIDTH_AS_USUAL, B_WARNING_ALERT);
5090 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
5091 		alert->Go();
5092 		okToMove = false;
5093 	}
5094 
5095 	// can't copy to read-only volume
5096 	BVolume destVolume(destFolder->NodeRef()->device);
5097 	if (destVolume.InitCheck() == B_OK && destVolume.IsReadOnly()) {
5098 		BAlert* alert = new BAlert("",
5099 			B_TRANSLATE("You can't move or copy items to read-only volumes."),
5100 			B_TRANSLATE("Cancel"), 0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
5101 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
5102 		alert->Go();
5103 		okToMove = false;
5104 	}
5105 
5106 	// can't copy items into the trash
5107 	if (forceCopy && destIsTrash) {
5108 		BAlert* alert = new BAlert("",
5109 			B_TRANSLATE("Sorry, you can't copy items to the Trash."),
5110 			B_TRANSLATE("Cancel"), NULL, NULL, B_WIDTH_AS_USUAL,
5111 			B_WARNING_ALERT);
5112 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
5113 		alert->Go();
5114 		okToMove = false;
5115 	}
5116 
5117 	// can't create symlinks into the trash
5118 	if (createLink && destIsTrash) {
5119 		BAlert* alert = new BAlert("",
5120 			B_TRANSLATE("Sorry, you can't create links in the Trash."),
5121 			B_TRANSLATE("Cancel"), NULL, NULL, B_WIDTH_AS_USUAL,
5122 			B_WARNING_ALERT);
5123 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
5124 		alert->Go();
5125 		okToMove = false;
5126 	}
5127 
5128 	// prompt user if drag was from a query
5129 	if (srcWindow->TargetModel()->IsQuery()
5130 		&& !forceCopy && !destIsTrash && !createLink) {
5131 		srcWindow->UpdateIfNeeded();
5132 		BAlert* alert = new BAlert("",
5133 			B_TRANSLATE("Are you sure you want to move or copy the selected "
5134 			"item(s) to this folder?"), B_TRANSLATE("Cancel"),
5135 			B_TRANSLATE("Move"), NULL, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
5136 		alert->SetShortcut(0, B_ESCAPE);
5137 		okToMove = alert->Go() == 1;
5138 	}
5139 
5140 	if (okToMove) {
5141 		PoseList* selectionList = srcWindow->PoseView()->SelectionList();
5142 		BList* pointList = destWindow->PoseView()->GetDropPointList(clickPoint,
5143 			loc, selectionList, srcWindow->PoseView()->ViewMode() == kListMode,
5144 			dropOnGrid);
5145 		int32 selectionSize = selectionList->CountItems();
5146 		BObjectList<entry_ref>* srcList
5147 			= new BObjectList<entry_ref>(selectionSize, true);
5148 
5149 		if (srcWindow->TargetModel()->IsVirtualDirectory()) {
5150 			// resolve symlink and add the resulting entry_ref to the list
5151 			for (int32 i = 0; i < selectionSize; i++) {
5152 				Model* model = selectionList->ItemAt(i)->ResolvedModel();
5153 				if (model != NULL)
5154 					srcList->AddItem(new entry_ref(*(model->EntryRef())));
5155 			}
5156 		} else
5157 			CopySelectionListToEntryRefList(selectionList, srcList);
5158 
5159 		uint32 moveMode;
5160 		if (forceCopy)
5161 			moveMode = kCopySelectionTo;
5162 		else if (forceMove)
5163 			moveMode = kMoveSelectionTo;
5164 		else if (createRelativeLink)
5165 			moveMode = kCreateRelativeLink;
5166 		else if (createLink)
5167 			moveMode = kCreateLink;
5168 		else if (!CheckDevicesEqual(srcList->ItemAt(0), destFolder))
5169 			moveMode = kCopySelectionTo;
5170 		else
5171 			moveMode = kMoveSelectionTo;
5172 
5173 		FSMoveToFolder(srcList, destEntry, moveMode, pointList);
5174 		return;
5175 	}
5176 
5177 	delete destEntry;
5178 }
5179 
5180 
5181 void
5182 BPoseView::MoveSelectionTo(BPoint dropPoint, BPoint clickPoint,
5183 	BContainerWindow* srcWindow)
5184 {
5185 	// Moves selection from srcWindow into this window, copying if necessary.
5186 
5187 	BContainerWindow* window = ContainerWindow();
5188 	if (window == NULL)
5189 		return;
5190 
5191 	ASSERT(window->PoseView() != NULL);
5192 	ASSERT(TargetModel() != NULL);
5193 
5194 	// make sure this window is a legal drop target
5195 	if (srcWindow != window && !TargetModel()->IsDropTarget())
5196 		return;
5197 
5198 	uint32 buttons = (uint32)window->CurrentMessage()->FindInt32("buttons");
5199 	bool pinToGrid = (modifiers() & B_COMMAND_KEY) != 0;
5200 	MoveSelectionInto(TargetModel(), srcWindow, window, buttons, dropPoint,
5201 		false, false, false, false, clickPoint, pinToGrid);
5202 }
5203 
5204 
5205 inline void
5206 UpdateWasBrokenSymlinkBinder(BPose* pose, Model* model, int32 index,
5207 	BPoseView* poseView, BObjectList<Model>* fBrokenLinks)
5208 {
5209 	if (!model->IsSymLink())
5210 		return;
5211 
5212 	BPoint loc(0, index * poseView->ListElemHeight());
5213 	pose->UpdateWasBrokenSymlink(loc, poseView);
5214 	if (model->LinkTo() != NULL)
5215 		fBrokenLinks->RemoveItem(model);
5216 }
5217 
5218 
5219 void
5220 BPoseView::TryUpdatingBrokenLinks()
5221 {
5222 	AutoLock<BWindow> lock(Window());
5223 	if (!lock)
5224 		return;
5225 
5226 	BObjectList<Model>* brokenLinksCopy = new BObjectList<Model>(*fBrokenLinks);
5227 
5228 	// try fixing broken symlinks, and detecting broken ones.
5229 	EachPoseAndModel(fPoseList, &UpdateWasBrokenSymlinkBinder, this,
5230 		fBrokenLinks);
5231 
5232 	for (int i = brokenLinksCopy->CountItems() - 1; i >= 0; i--) {
5233 		if (!fBrokenLinks->HasItem(brokenLinksCopy->ItemAt(i)))
5234 			StopWatchingParentsOf(brokenLinksCopy->ItemAt(i)->EntryRef());
5235 	}
5236 
5237 	delete brokenLinksCopy;
5238 }
5239 
5240 
5241 void
5242 BPoseView::PoseHandleDeviceUnmounted(BPose* pose, Model* model, int32 index,
5243 	BPoseView* poseView, dev_t device)
5244 {
5245 	if (model->NodeRef()->device == device)
5246 		poseView->DeletePose(model->NodeRef());
5247 	else if (model->IsSymLink() && model->LinkTo() != NULL
5248 		&& model->LinkTo()->NodeRef()->device == device) {
5249 		poseView->DeleteSymLinkPoseTarget(model->LinkTo()->NodeRef(),
5250 			pose, index);
5251 	}
5252 }
5253 
5254 
5255 static void
5256 OneMetaMimeChanged(BPose* pose, Model* model, int32 index,
5257 	BPoseView* poseView, const char* type)
5258 {
5259 	ASSERT(model != NULL);
5260 
5261 	if (model->IconFrom() != kNode
5262 		&& model->IconFrom() != kUnknownSource
5263 		&& model->IconFrom() != kUnknownNotFromNode
5264 		// TODO: add supertype compare
5265 		&& strcasecmp(model->MimeType(), type) == 0) {
5266 		// metamime change very likely affected the documents icon
5267 		BPoint poseLoc(0, index * poseView->ListElemHeight());
5268 		pose->UpdateIcon(poseLoc, poseView);
5269 	}
5270 }
5271 
5272 
5273 void
5274 BPoseView::MetaMimeChanged(const char* type, const char* preferredApp)
5275 {
5276 	IconCache::sIconCache->IconChanged(type, preferredApp);
5277 	// wait for other windows to do the same before we start
5278 	// updating poses which causes icon recaching
5279 	// TODO: this is a design problem that should be solved differently
5280 	snooze(10000);
5281 	Window()->UpdateIfNeeded();
5282 
5283 	EachPoseAndResolvedModel(fPoseList, &OneMetaMimeChanged, this, type);
5284 }
5285 
5286 
5287 class MetaMimeChangedAccumulator : public AccumulatingFunctionObject {
5288 // pools up matching metamime change notices, executing them as a single
5289 // update
5290 public:
5291 	MetaMimeChangedAccumulator(void (BPoseView::*func)(const char* type,
5292 		const char* preferredApp),
5293 		BContainerWindow* window, const char* type, const char* preferredApp)
5294 		:
5295 		fCallOnThis(window),
5296 		fFunc(func),
5297 		fType(type),
5298 		fPreferredApp(preferredApp)
5299 	{
5300 	}
5301 
5302 	virtual bool CanAccumulate(const AccumulatingFunctionObject* functor) const
5303 	{
5304 		const MetaMimeChangedAccumulator* accumulator
5305 			= dynamic_cast<const MetaMimeChangedAccumulator*>(functor);
5306 		if (accumulator == NULL)
5307 			return false;
5308 
5309 		return accumulator && accumulator->fType == fType
5310 			&& accumulator->fPreferredApp == fPreferredApp;
5311 	}
5312 
5313 	virtual void Accumulate(AccumulatingFunctionObject* DEBUG_ONLY(functor))
5314 	{
5315 		ASSERT(CanAccumulate(functor));
5316 		// do nothing, no further accumulating needed
5317 	}
5318 
5319 protected:
5320 	virtual void operator()()
5321 	{
5322 		AutoLock<BWindow> lock(fCallOnThis);
5323 		if (!lock)
5324 			return;
5325 
5326 		(fCallOnThis->PoseView()->*fFunc)(fType.String(),
5327 			fPreferredApp.String());
5328 	}
5329 
5330 	virtual ulong Size() const
5331 	{
5332 		return sizeof (*this);
5333 	}
5334 
5335 private:
5336 	BContainerWindow* fCallOnThis;
5337 	void (BPoseView::*fFunc)(const char* type, const char* preferredApp);
5338 	BString fType;
5339 	BString fPreferredApp;
5340 };
5341 
5342 
5343 bool
5344 BPoseView::NoticeMetaMimeChanged(const BMessage* message)
5345 {
5346 	int32 change;
5347 	if (message->FindInt32("be:which", &change) != B_OK)
5348 		return true;
5349 
5350 	bool iconChanged = (change & B_ICON_CHANGED) != 0;
5351 	bool iconForTypeChanged = (change & B_ICON_FOR_TYPE_CHANGED) != 0;
5352 	bool preferredAppChanged = (change & B_APP_HINT_CHANGED)
5353 		|| (change & B_PREFERRED_APP_CHANGED);
5354 
5355 	const char* type = NULL;
5356 	const char* preferredApp = NULL;
5357 
5358 	if (iconChanged || preferredAppChanged)
5359 		message->FindString("be:type", &type);
5360 
5361 	if (iconForTypeChanged) {
5362 		message->FindString("be:extra_type", &type);
5363 		message->FindString("be:type", &preferredApp);
5364 	}
5365 
5366 	if (iconChanged || preferredAppChanged || iconForTypeChanged) {
5367 		TaskLoop* taskLoop = ContainerWindow()->DelayedTaskLoop();
5368 		ASSERT(taskLoop != NULL);
5369 		taskLoop->AccumulatedRunLater(new MetaMimeChangedAccumulator(
5370 			&BPoseView::MetaMimeChanged, ContainerWindow(), type, preferredApp),
5371 			200000, 5000000);
5372 	}
5373 
5374 	return true;
5375 }
5376 
5377 
5378 bool
5379 BPoseView::FSNotification(const BMessage* message)
5380 {
5381 	node_ref itemNode;
5382 	dev_t device;
5383 	Model* targetModel = TargetModel();
5384 
5385 	switch (message->FindInt32("opcode")) {
5386 		case B_ENTRY_CREATED:
5387 		{
5388 			ASSERT(targetModel != NULL);
5389 
5390 			message->FindInt32("device", &itemNode.device);
5391 			node_ref dirNode;
5392 			dirNode.device = itemNode.device;
5393 			message->FindInt64("directory", (int64*)&dirNode.node);
5394 			message->FindInt64("node", (int64*)&itemNode.node);
5395 
5396 			int32 count = fBrokenLinks->CountItems();
5397 			bool createPose = true;
5398 			// Query windows can get notices on different dirNodes
5399 			// The Disks window can too
5400 			// So can the Desktop, as long as the integrate flag is on
5401 			TrackerSettings settings;
5402 			if (targetModel != NULL && dirNode != *targetModel->NodeRef()
5403 				&& !targetModel->IsQuery()
5404 				&& !targetModel->IsVirtualDirectory()
5405 				&& !targetModel->IsRoot()
5406 				&& (!settings.ShowDisksIcon() || !IsDesktopView())) {
5407 				if (count == 0)
5408 					break;
5409 				createPose = false;
5410 			}
5411 
5412 			const char* name;
5413 			if (message->FindString("name", &name) != B_OK) {
5414 #if DEBUG
5415 				SERIAL_PRINT(("no name in entry creation message\n"));
5416 #endif
5417 				break;
5418 			}
5419 			if (count != 0) {
5420 				// basically, let's say we have a broken link :
5421 				// ./a_link -> ./some_folder/another_folder/a_target
5422 				// and that both some_folder and another_folder didn't
5423 				// exist yet. We are looking if the just created folder
5424 				// is 'some_folder' and watch it, expecting the creation of
5425 				// 'another_folder' later and then report the link as fixed.
5426 				Model* model = new Model(&dirNode, &itemNode, name);
5427 				if (model->IsDirectory()) {
5428 					BString createdPath(BPath(model->EntryRef()).Path());
5429 					BDirectory currentDir(targetModel->EntryRef());
5430 					BPath createdDir(model->EntryRef());
5431 					for (int32 i = 0; i < count; i++) {
5432 						BSymLink link(fBrokenLinks->ItemAt(i)->EntryRef());
5433 						BPath path;
5434 						link.MakeLinkedPath(&currentDir, &path);
5435 						BString pathStr(path.Path());
5436 						pathStr.Append("/");
5437 						if (pathStr.Compare(createdPath,
5438 							createdPath.Length()) == 0) {
5439 							if (pathStr[createdPath.Length()] != '/')
5440 								break;
5441 							StopWatchingParentsOf(fBrokenLinks->ItemAt(i)
5442 								->EntryRef());
5443 							watch_node(&itemNode, B_WATCH_DIRECTORY, this);
5444 							break;
5445 						}
5446 					}
5447 				}
5448 				delete model;
5449 			}
5450 			if (createPose)
5451 				EntryCreated(&dirNode, &itemNode, name);
5452 
5453 			TryUpdatingBrokenLinks();
5454 			break;
5455 		}
5456 
5457 		case B_ENTRY_MOVED:
5458 			return EntryMoved(message);
5459 			break;
5460 
5461 		case B_ENTRY_REMOVED:
5462 			message->FindInt32("device", &itemNode.device);
5463 			message->FindInt64("node", (int64*)&itemNode.node);
5464 
5465 			// our window itself may be deleted
5466 			// we must check to see if this comes as a query
5467 			// notification or a node monitor notification because
5468 			// if it's a query notification then we're just being told we
5469 			// no longer match the query, so we don't want to close the window
5470 			// but it's a node monitor notification then that means our query
5471 			// file has been deleted so we close the window
5472 
5473 			if (message->what == B_NODE_MONITOR && targetModel != NULL
5474 				&& *(targetModel->NodeRef()) == itemNode) {
5475 				if (!targetModel->IsRoot()) {
5476 					// it is impossible to watch for ENTRY_REMOVED in
5477 					// "/" because the notification is ambiguous - the vnode
5478 					// is that of the volume but the device is of the parent
5479 					// not the same as the device of the volume that way we
5480 					// may get aliasing for volumes with vnodes of 1
5481 					// (currently the case for iso9660)
5482 					DisableSaveLocation();
5483 					Window()->Close();
5484 				}
5485 			} else {
5486 				int32 index;
5487 				BPose* pose = fPoseList->FindPose(&itemNode, &index);
5488 				if (pose == NULL) {
5489 					// couldn't find pose, first check if the node might be
5490 					// target of a symlink pose;
5491 					//
5492 					// What happens when a node and a symlink to it are in the
5493 					// same window?
5494 					// They get monitored twice, we get two notifications; the
5495 					// first one will get caught by the first FindPose, the
5496 					// second one by the DeepFindPose
5497 					//
5498 					pose = fPoseList->DeepFindPose(&itemNode, &index);
5499 					if (pose != NULL) {
5500 						DeleteSymLinkPoseTarget(&itemNode, pose, index);
5501 						break;
5502 					}
5503 				}
5504 
5505 			 	DeletePose(&itemNode);
5506 				TryUpdatingBrokenLinks();
5507 			}
5508 			break;
5509 
5510 		case B_DEVICE_MOUNTED:
5511 		{
5512 			if (message->FindInt32("new device", &device) != B_OK)
5513 				break;
5514 
5515 			if (targetModel != NULL && targetModel->IsRoot()) {
5516 				BVolume volume(device);
5517 				if (volume.InitCheck() == B_OK)
5518 					CreateVolumePose(&volume, false);
5519 			} else if (ContainerWindow()->IsTrash()) {
5520 				// add trash items from newly mounted volume
5521 
5522 				BDirectory trashDir;
5523 				BEntry entry;
5524 				BVolume volume(device);
5525 				if (FSGetTrashDir(&trashDir, volume.Device()) == B_OK
5526 					&& trashDir.GetEntry(&entry) == B_OK) {
5527 					Model model(&entry);
5528 					if (model.InitCheck() == B_OK)
5529 						AddPoses(&model);
5530 				}
5531 			}
5532 			TaskLoop* taskLoop = ContainerWindow()->DelayedTaskLoop();
5533 			ASSERT(taskLoop);
5534 			taskLoop->RunLater(NewMemberFunctionObject(
5535 				&BPoseView::TryUpdatingBrokenLinks, this), 500000);
5536 				// delay of 500000: wait for volumes to properly finish mounting
5537 				// without this in the Model::FinishSettingUpType a symlink
5538 				// to a volume would get initialized as a symlink to a directory
5539 				// because IsRootDirectory looks like returns false. Either
5540 				// there is a race condition or I was doing something wrong.
5541 			break;
5542 		}
5543 
5544 		case B_DEVICE_UNMOUNTED:
5545 			if (message->FindInt32("device", &device) == B_OK) {
5546 				if (targetModel != NULL
5547 					&& targetModel->NodeRef()->device == device) {
5548 					// close the window from a volume that is gone
5549 					DisableSaveLocation();
5550 					Window()->Close();
5551 				} else if (targetModel != NULL) {
5552 					EachPoseAndModel(fPoseList, &PoseHandleDeviceUnmounted,
5553 						this, device);
5554 				}
5555 			}
5556 			break;
5557 
5558 		case B_STAT_CHANGED:
5559 		case B_ATTR_CHANGED:
5560 			return AttributeChanged(message);
5561 	}
5562 
5563 	return true;
5564 }
5565 
5566 
5567 bool
5568 BPoseView::CreateSymlinkPoseTarget(Model* symlink)
5569 {
5570 	Model* newResolvedModel = NULL;
5571 	Model* result = symlink->LinkTo();
5572 
5573 	if (result == NULL) {
5574 		BEntry entry(symlink->EntryRef(), true);
5575 		if (entry.InitCheck() == B_OK) {
5576 			node_ref nref;
5577 			entry_ref eref;
5578 			entry.GetNodeRef(&nref);
5579 			entry.GetRef(&eref);
5580 			if (eref.directory != TargetModel()->NodeRef()->node)
5581 				WatchNewNode(&nref, B_WATCH_STAT | B_WATCH_ATTR | B_WATCH_NAME
5582 					| B_WATCH_INTERIM_STAT, this);
5583 			newResolvedModel = new Model(&entry, true);
5584 		} else {
5585 			fBrokenLinks->AddItem(symlink);
5586 			WatchParentOf(symlink->EntryRef());
5587 			return true;
5588 		}
5589 		result = newResolvedModel;
5590 	}
5591 	symlink->SetLinkTo(result);
5592 
5593 	return true;
5594 }
5595 
5596 
5597 BPose*
5598 BPoseView::EntryCreated(const node_ref* dirNode, const node_ref* itemNode,
5599 	const char* name, int32* indexPtr)
5600 {
5601 	// reject notification if pose already exists
5602 	if (fPoseList->FindPose(itemNode) || FindZombie(itemNode))
5603 		return NULL;
5604 
5605 	BPoseView::WatchNewNode(itemNode);
5606 		// have to node monitor ahead of time because Model will
5607 		// cache up the file type and preferred app
5608 	Model* model = new Model(dirNode, itemNode, name, true);
5609 
5610 	if (model->InitCheck() != B_OK) {
5611 		// if we have trouble setting up model then we stuff it into
5612 		// a zombie list in a half-alive state until we can properly awaken it
5613 		PRINT(("2 adding model %s to zombie list, error %s\n", model->Name(),
5614 			strerror(model->InitCheck())));
5615 		fZombieList->AddItem(model);
5616 		return NULL;
5617 	}
5618 
5619 	PoseInfo poseInfo;
5620 	ReadPoseInfo(model, &poseInfo);
5621 
5622 	if (!PoseVisible(model, &poseInfo)) {
5623 		watch_node(model->NodeRef(), B_STOP_WATCHING, this);
5624 		delete model;
5625 		return NULL;
5626 	}
5627 
5628 	// model is a symlink, cache up the symlink target or scrap
5629 	// everything if target is invisible
5630 	if (model->IsSymLink() && !CreateSymlinkPoseTarget(model)) {
5631 		watch_node(model->NodeRef(), B_STOP_WATCHING, this);
5632 		delete model;
5633 		return NULL;
5634 	}
5635 
5636 	return CreatePose(model, &poseInfo, true, indexPtr);
5637 }
5638 
5639 
5640 bool
5641 BPoseView::EntryMoved(const BMessage* message)
5642 {
5643 	ino_t oldDir;
5644 	node_ref dirNode;
5645 	node_ref itemNode;
5646 
5647 	message->FindInt32("device", &dirNode.device);
5648 	itemNode.device = dirNode.device;
5649 	message->FindInt64("to directory", (int64*)&dirNode.node);
5650 	message->FindInt64("node", (int64*)&itemNode.node);
5651 	message->FindInt64("from directory", (int64*)&oldDir);
5652 
5653 	const char* name;
5654 	if (message->FindString("name", &name) != B_OK)
5655 		return true;
5656 
5657 	// handle special case of notifying a name change for a volume
5658 	// - the notification is not enough, because the volume's device
5659 	// is different than that of the root directory; we have to do a
5660 	// lookup using the new volume name and get the volume device from there
5661 	StatStruct st;
5662 	// get the inode of the root and check if we got a notification on it
5663 	if (stat("/", &st) >= 0
5664 		&& st.st_dev == dirNode.device
5665 		&& st.st_ino == dirNode.node) {
5666 		BString buffer;
5667 		buffer << "/" << name;
5668 		if (stat(buffer.String(), &st) >= 0) {
5669 			// point the dirNode to the actual volume
5670 			itemNode.node = st.st_ino;
5671 			itemNode.device = st.st_dev;
5672 		}
5673 	}
5674 
5675 	Model* targetModel = TargetModel();
5676 	ThrowOnAssert(targetModel != NULL);
5677 
5678 	node_ref thisDirNode;
5679 	if (ContainerWindow()->IsTrash()) {
5680 		BDirectory trashDir;
5681 		if (FSGetTrashDir(&trashDir, itemNode.device) != B_OK)
5682 			return true;
5683 
5684 		trashDir.GetNodeRef(&thisDirNode);
5685 	} else
5686 		thisDirNode = *targetModel->NodeRef();
5687 
5688 	// see if we need to update window title (and folder itself)
5689 	if (thisDirNode == itemNode) {
5690 		targetModel->UpdateEntryRef(&dirNode, name);
5691 		assert_cast<BContainerWindow*>(Window())->UpdateTitle();
5692 	}
5693 
5694 	if (oldDir == dirNode.node || targetModel->IsQuery()
5695 		|| targetModel->IsVirtualDirectory()) {
5696 		// rename or move of entry in this directory (or query)
5697 
5698 		int32 index;
5699 		BPose* pose = fPoseList->FindPose(&itemNode, &index);
5700 		int32 poseListIndex = index;
5701 		bool visible = true;
5702 		if (fFiltering)
5703 			visible = fFilteredPoseList->FindPose(&itemNode, &index) != NULL;
5704 
5705 		if (pose != NULL) {
5706 			Model* poseModel = pose->TargetModel();
5707 			ASSERT(poseModel != NULL);
5708 			poseModel->UpdateEntryRef(&dirNode, name);
5709 			// for queries we check for move to trash and remove item if so
5710 			if (targetModel->IsQuery()) {
5711 				PoseInfo poseInfo;
5712 				ReadPoseInfo(poseModel, &poseInfo);
5713 				if (!ShouldShowPose(poseModel, &poseInfo))
5714 					return DeletePose(&itemNode, pose, index);
5715 				return true;
5716 			}
5717 
5718 			BPoint loc(0, index * fListElemHeight);
5719 			// if we get a rename then we need to assume that we might
5720 			// have missed some other attr changed notifications so we
5721 			// recheck all widgets
5722 			if (poseModel->OpenNode() == B_OK) {
5723 				pose->UpdateAllWidgets(index, loc, this);
5724 				poseModel->CloseNode();
5725 				_CheckPoseSortOrder(fPoseList, pose, poseListIndex);
5726 				if (fFiltering) {
5727 					if (!visible && FilterPose(pose)) {
5728 						BRect bounds = Bounds();
5729 						float scrollBy = 0;
5730 						AddPoseToList(fFilteredPoseList, true, true, pose,
5731 							bounds, scrollBy, true);
5732 					} else if (visible && !FilterPose(pose))
5733 						RemoveFilteredPose(pose, index);
5734 					else if (visible)
5735 						_CheckPoseSortOrder(fFilteredPoseList, pose, index);
5736 				}
5737 			}
5738 		} else {
5739 			// also must watch for renames on zombies
5740 			Model* zombie = FindZombie(&itemNode, &index);
5741 			if (zombie) {
5742 				PRINT(("converting model %s from a zombie\n", zombie->Name()));
5743 				zombie->UpdateEntryRef(&dirNode, name);
5744 				pose = ConvertZombieToPose(zombie, index);
5745 			} else
5746 				return false;
5747 		}
5748 		if (pose != NULL)
5749 			pendingNodeMonitorCache.PoseCreatedOrMoved(this, pose);
5750 	} else if (oldDir == thisDirNode.node)
5751 		DeletePose(&itemNode);
5752 	else if (dirNode.node == thisDirNode.node)
5753 		EntryCreated(&dirNode, &itemNode, name);
5754 
5755 	TryUpdatingBrokenLinks();
5756 
5757 	return true;
5758 }
5759 
5760 
5761 void
5762 BPoseView::WatchParentOf(const entry_ref* ref)
5763 {
5764 	BPath currentDir(ref);
5765 	currentDir.GetParent(&currentDir);
5766 	BSymLink symlink(ref);
5767 	BPath path;
5768 
5769 	symlink.MakeLinkedPath(currentDir.Path(), &path);
5770 	status_t status = path.GetParent(&path);
5771 
5772 	while (status == B_BAD_VALUE)
5773 		status = path.GetParent(&path);
5774 
5775 	if (status == B_ENTRY_NOT_FOUND)
5776 		return;
5777 
5778 	node_ref nref;
5779 	BNode(path.Path()).GetNodeRef(&nref);
5780 
5781 	if (nref != *TargetModel()->NodeRef())
5782 		watch_node(&nref, B_WATCH_DIRECTORY, this);
5783 }
5784 
5785 
5786 void
5787 BPoseView::StopWatchingParentsOf(const entry_ref* ref)
5788 {
5789 	BPath path;
5790 	BSymLink symlink(ref);
5791 	BPath currentDir(ref);
5792 	currentDir.GetParent(&currentDir);
5793 	symlink.MakeLinkedPath(currentDir.Path(), &path);
5794 
5795 	if (path.InitCheck() != B_OK)
5796 		return;
5797 
5798 	BObjectList<Model>* brokenLinksCopy = new BObjectList<Model>(*fBrokenLinks);
5799 	int32 count = brokenLinksCopy->CountItems();
5800 
5801 	while (path.GetParent(&path) == B_OK) {
5802 		if (strcmp(path.Path(), "/") == 0)
5803 			break;
5804 
5805 		BNode dir(path.Path());
5806 		node_ref dirNode;
5807 		dir.GetNodeRef(&dirNode);
5808 
5809 		// don't stop watching yourself.
5810 		if (dirNode == *TargetModel()->NodeRef())
5811 			continue;
5812 
5813 		// make sure we don't have another broken links that still requires
5814 		// to watch this directory
5815 		bool keep = false;
5816 		for (int32 i = count - 1; i >= 0; i--) {
5817 			BSymLink link(brokenLinksCopy->ItemAt(i)->EntryRef());
5818 			BPath absolutePath;
5819 			link.MakeLinkedPath(currentDir.Path(), &absolutePath);
5820 			if (BString(absolutePath.Path()).Compare(path.Path(),
5821 					strlen(path.Path())) == 0) {
5822 				// a broken link still needs to watch this folder, but
5823 				// don't let that same link also watch a deeper parent.
5824 				brokenLinksCopy->RemoveItemAt(i);
5825 				count--;
5826 				keep = true;
5827 			}
5828 		}
5829 		if (!keep)
5830 			watch_node(&dirNode, B_STOP_WATCHING, this);
5831 	}
5832 	delete brokenLinksCopy;
5833 }
5834 
5835 
5836 bool
5837 BPoseView::AttributeChanged(const BMessage* message)
5838 {
5839 	node_ref itemNode;
5840 	message->FindInt32("device", &itemNode.device);
5841 	message->FindInt64("node", (int64*)&itemNode.node);
5842 
5843 	const char* attrName;
5844 	if (message->FindString("attr", &attrName) != B_OK)
5845 		attrName = NULL;
5846 
5847 	Model* targetModel = TargetModel();
5848 	if (targetModel != NULL && *targetModel->NodeRef() == itemNode
5849 		&& targetModel->IsNodeOpen()
5850 		&& targetModel->AttrChanged(attrName)) {
5851 		// the icon of our target has changed, update drag icon
5852 		// TODO: make this simpler (i.e. store the icon with the window)
5853 		BView* view = Window()->FindView("MenuBar");
5854 		if (view != NULL) {
5855 			view = view->FindView("ThisContainer");
5856 			if (view != NULL) {
5857 				IconCache::sIconCache->IconChanged(targetModel);
5858 				view->Invalidate();
5859 			}
5860 		}
5861 	}
5862 
5863 	int32 index;
5864 	attr_info info;
5865 	PoseList* posesFound = fPoseList->FindAllPoses(&itemNode);
5866 	int32 posesCount = posesFound->CountItems();
5867 	for (int i = 0; i < posesCount; i++) {
5868 		BPose* pose = posesFound->ItemAt(i);
5869 		Model* poseModel = pose->TargetModel();
5870 		if (poseModel->IsSymLink() && *(poseModel->NodeRef()) != itemNode) {
5871 			// change happened on symlink's target
5872 			poseModel = poseModel->ResolveIfLink();
5873 		}
5874 		ASSERT(poseModel != NULL);
5875 
5876 		status_t result = B_OK;
5877 		for (int32 count = 0; count < 100; count++) {
5878 			// if node is busy, wait a little, it may be in the
5879 			// middle of mimeset and we wan't to pick up the changes
5880 			result = poseModel->OpenNode();
5881 			if (result == B_OK || result != B_BUSY)
5882 				break;
5883 
5884 			PRINT(("poseModel %s busy, retrying in a bit\n",
5885 				poseModel->Name()));
5886 			snooze(10000);
5887 		}
5888 		if (result != B_OK) {
5889 			PRINT(("Cache Error %s\n", strerror(result)));
5890 			continue;
5891 		}
5892 
5893 		bool visible = fPoseList->FindPose(poseModel->NodeRef(),
5894 			&index) != NULL;
5895 		int32 poseListIndex = index;
5896 
5897 		if (fFiltering) {
5898 			visible = fFilteredPoseList->FindPose(
5899 				poseModel->NodeRef(), &index) != NULL;
5900 		}
5901 
5902 		BPoint loc(0, index * fListElemHeight);
5903 		if (attrName != NULL && poseModel->Node() != NULL) {
5904 			memset(&info, 0, sizeof(attr_info));
5905 			// the call below might fail if the attribute has been removed
5906 			poseModel->Node()->GetAttrInfo(attrName, &info);
5907 			pose->UpdateWidgetAndModel(poseModel, attrName, info.type, index,
5908 				loc, this, visible);
5909 			if (strcmp(attrName, kAttrMIMEType) == 0)
5910 				RefreshMimeTypeList();
5911 		} else {
5912 			pose->UpdateWidgetAndModel(poseModel, 0, 0, index, loc, this,
5913 				visible);
5914 		}
5915 		poseModel->CloseNode();
5916 		if (fFiltering) {
5917 			if (!visible && FilterPose(pose)) {
5918 				visible = true;
5919 				float scrollBy = 0;
5920 				BRect bounds = Bounds();
5921 				AddPoseToList(fFilteredPoseList, true, true, pose, bounds,
5922 					scrollBy, true);
5923 				continue;
5924 			} else if (visible && !FilterPose(pose)) {
5925 				RemoveFilteredPose(pose, index);
5926 				continue;
5927 			}
5928 		}
5929 
5930 		if (attrName != NULL) {
5931 			// note: the following code is wrong, because this sort of hashing
5932 			// may overlap and we get aliasing
5933 			uint32 attrHash = AttrHashString(attrName, info.type);
5934 			if (attrHash == PrimarySort() || attrHash == SecondarySort()) {
5935 				_CheckPoseSortOrder(fPoseList, pose, poseListIndex);
5936 				if (fFiltering && visible)
5937 					_CheckPoseSortOrder(fFilteredPoseList, pose, index);
5938 			}
5939 		} else {
5940 			int32 fields;
5941 			if (message->FindInt32("fields", &fields) != B_OK)
5942 				continue;
5943 
5944 			for (int i = sizeof(sAttrColumnMap) / sizeof(attr_column_relation);
5945 					i--;) {
5946 				if (sAttrColumnMap[i].attrHash == PrimarySort()
5947 					|| sAttrColumnMap[i].attrHash == SecondarySort()) {
5948 					if ((fields & sAttrColumnMap[i].fieldMask) != 0) {
5949 						_CheckPoseSortOrder(fPoseList, pose, poseListIndex);
5950 						if (fFiltering && visible)
5951 							_CheckPoseSortOrder(fFilteredPoseList, pose, index);
5952 						break;
5953 					}
5954 				}
5955 			}
5956 		}
5957 	}
5958 	delete posesFound;
5959 	if (posesCount == 0) {
5960 		// we received an attr changed notification for a zombie model, it means
5961 		// that although we couldn't open the node the first time, it seems
5962 		// to be fine now since we're receiving notifications about it, it might
5963 		// be a good time to convert it to a non-zombie state. cf. test in #4130
5964 		Model* zombie = FindZombie(&itemNode, &index);
5965 		if (zombie != NULL) {
5966 			PRINT(("converting model %s from a zombie\n", zombie->Name()));
5967 			return ConvertZombieToPose(zombie, index) != NULL;
5968 		} else {
5969 			PRINT(("model has no pose but is not a zombie either!\n"));
5970 			return false;
5971 		}
5972 	}
5973 
5974 	return true;
5975 }
5976 
5977 
5978 void
5979 BPoseView::UpdateIcon(BPose* pose)
5980 {
5981 	BPoint location;
5982 	if (ViewMode() == kListMode) {
5983 		// need to find the index of the pose in the pose list
5984 		bool found = false;
5985 		PoseList* poseList = CurrentPoseList();
5986 		int32 poseCount = poseList->CountItems();
5987 		for (int32 index = 0; index < poseCount; index++) {
5988 			if (poseList->ItemAt(index) == pose) {
5989 				location.Set(0, index * fListElemHeight);
5990 				found = true;
5991 				break;
5992 			}
5993 		}
5994 
5995 		if (!found)
5996 			return;
5997 	}
5998 
5999 	pose->UpdateIcon(location, this);
6000 }
6001 
6002 
6003 BPose*
6004 BPoseView::ConvertZombieToPose(Model* zombie, int32 index)
6005 {
6006 	if (zombie->UpdateStatAndOpenNode() != B_OK)
6007 		return NULL;
6008 
6009 	fZombieList->RemoveItemAt(index);
6010 
6011 	PoseInfo poseInfo;
6012 	ReadPoseInfo(zombie, &poseInfo);
6013 
6014 	if (ShouldShowPose(zombie, &poseInfo)) {
6015 		// TODO: handle symlinks here
6016 		return CreatePose(zombie, &poseInfo);
6017 	}
6018 
6019 	delete zombie;
6020 
6021 	return NULL;
6022 }
6023 
6024 
6025 BList*
6026 BPoseView::GetDropPointList(BPoint dropStart, BPoint dropEnd, const PoseList* poses,
6027 	bool sourceInListMode, bool dropOnGrid) const
6028 {
6029 	if (ViewMode() == kListMode)
6030 		return NULL;
6031 
6032 	int32 poseCount = poses->CountItems();
6033 	BList* pointList = new BList(poseCount);
6034 	for (int32 index = 0; index < poseCount; index++) {
6035 		BPose* pose = poses->ItemAt(index);
6036 		BPoint poseLoc;
6037 		if (sourceInListMode)
6038 			poseLoc = dropEnd + BPoint(0, index * (IconPoseHeight() + 3));
6039 		else
6040 			poseLoc = dropEnd + (pose->Location(this) - dropStart);
6041 
6042 		if (dropOnGrid)
6043 			poseLoc = PinToGrid(poseLoc, fGrid, fOffset);
6044 
6045 		pointList->AddItem(new BPoint(poseLoc));
6046 	}
6047 
6048 	return pointList;
6049 }
6050 
6051 
6052 void
6053 BPoseView::DuplicateSelection(BPoint* dropStart, BPoint* dropEnd)
6054 {
6055 	// If there is a volume or trash folder, remove them from the list
6056 	// because they cannot get copied
6057 	int32 selectCount = CountSelected();
6058 	for (int32 index = 0; index < selectCount; index++) {
6059 		BPose* pose = (BPose*)fSelectionList->ItemAt(index);
6060 		Model* poseModel = pose->TargetModel();
6061 
6062 		// can't duplicate a volume or the trash
6063 		if (poseModel->IsTrash() || poseModel->IsVolume()) {
6064 			fSelectionList->RemoveItemAt(index);
6065 			index--;
6066 			selectCount--;
6067 			if (fSelectionPivotPose == pose)
6068 				fSelectionPivotPose = NULL;
6069 
6070 			if (fRealPivotPose == pose)
6071 				fRealPivotPose = NULL;
6072 
6073 			continue;
6074 		}
6075 	}
6076 
6077 	// create entry_ref list from selection
6078 	if (!fSelectionList->IsEmpty()) {
6079 		BObjectList<entry_ref>* srcList
6080 			= new BObjectList<entry_ref>(CountSelected(), true);
6081 		CopySelectionListToEntryRefList(fSelectionList, srcList);
6082 
6083 		BList* dropPoints;
6084 		if (dropStart) {
6085 			dropPoints = GetDropPointList(*dropStart, *dropEnd, fSelectionList,
6086 				ViewMode() == kListMode, (modifiers() & B_COMMAND_KEY) != 0);
6087 		} else
6088 			dropPoints = NULL;
6089 
6090 		// perform asynchronous duplicate
6091 		FSDuplicate(srcList, dropPoints);
6092 	}
6093 }
6094 
6095 
6096 void
6097 BPoseView::SelectPoseAtLocation(BPoint point)
6098 {
6099 	int32 index;
6100 	BPose* pose = FindPose(point, &index);
6101 	if (pose != NULL)
6102 		SelectPose(pose, index);
6103 }
6104 
6105 
6106 void
6107 BPoseView::MoveListToTrash(BObjectList<entry_ref>* list, bool selectNext,
6108 	bool deleteDirectly)
6109 {
6110 	if (!list->CountItems())
6111 		return;
6112 
6113 	BObjectList<FunctionObject>* taskList =
6114 		new BObjectList<FunctionObject>(2, true);
6115 		// new owning list of tasks
6116 
6117 	// first move selection to trash,
6118 	if (deleteDirectly) {
6119 		taskList->AddItem(NewFunctionObject(FSDeleteRefList, list,
6120 			false, true));
6121 	} else {
6122 		taskList->AddItem(NewFunctionObject(FSMoveToTrash, list,
6123 			(BList*)NULL, false));
6124 	}
6125 
6126 	if (selectNext && ViewMode() == kListMode) {
6127 		// next, if in list view mode try selecting the next item after
6128 		BPose* pose = fSelectionList->ItemAt(0);
6129 
6130 		// find a point in the pose
6131 		BPoint pointInPose(fListOffset + 5, 5);
6132 		int32 index = IndexOfPose(pose);
6133 		pointInPose.y += fListElemHeight * index;
6134 
6135 		TTracker* tracker = dynamic_cast<TTracker*>(be_app);
6136 		if (tracker != NULL) {
6137 			ThrowOnAssert(TargetModel() != NULL);
6138 
6139 			// add a function object to the list of tasks to run
6140 			// that will select the next item after the one we just
6141 			// deleted
6142 			taskList->AddItem(NewMemberFunctionObject(
6143 				&TTracker::SelectPoseAtLocationSoon, tracker,
6144 				*TargetModel()->NodeRef(), pointInPose));
6145 		}
6146 	}
6147 	// execute the two tasks in order
6148 	ThreadSequence::Launch(taskList, true);
6149 }
6150 
6151 
6152 inline void
6153 CopyOneTrashedRefAsEntry(const entry_ref* ref, BObjectList<entry_ref>* trashList,
6154 	BObjectList<entry_ref>* noTrashList, std::map<int32, bool>* deviceHasTrash)
6155 {
6156 	std::map<int32, bool> &deviceHasTrashTmp = *deviceHasTrash;
6157 		// work around stupid binding problems with EachListItem
6158 
6159 	BDirectory entryDir(ref);
6160 	bool isVolume = entryDir.IsRootDirectory();
6161 		// volumes will get unmounted
6162 
6163 	// see if pose's device has a trash
6164 	int32 device = ref->device;
6165 	BDirectory trashDir;
6166 
6167 	// cache up the result in a map so that we don't have to keep calling
6168 	// FSGetTrashDir over and over
6169 	if (!isVolume
6170 		&& deviceHasTrashTmp.find(device) == deviceHasTrashTmp.end()) {
6171 		deviceHasTrashTmp[device] = FSGetTrashDir(&trashDir, device) == B_OK;
6172 	}
6173 
6174 	if (isVolume || deviceHasTrashTmp[device])
6175 		trashList->AddItem(new entry_ref(*ref));
6176 	else
6177 		noTrashList->AddItem(new entry_ref(*ref));
6178 }
6179 
6180 
6181 static void
6182 CopyPoseOneAsEntry(BPose* pose, BObjectList<entry_ref>* trashList,
6183 	BObjectList<entry_ref>* noTrashList, std::map<int32, bool>* deviceHasTrash)
6184 {
6185 	CopyOneTrashedRefAsEntry(pose->TargetModel()->EntryRef(), trashList,
6186 		noTrashList, deviceHasTrash);
6187 }
6188 
6189 
6190 static bool
6191 CheckVolumeReadOnly(const entry_ref* ref)
6192 {
6193 	BVolume volume (ref->device);
6194 	if (volume.IsReadOnly()) {
6195 		BAlert* alert = new BAlert("",
6196 			B_TRANSLATE("Files cannot be moved or deleted from a read-only "
6197 			"volume."), B_TRANSLATE("Cancel"), NULL, NULL, B_WIDTH_AS_USUAL,
6198 			B_STOP_ALERT);
6199 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
6200 		alert->Go();
6201 		return false;
6202 	}
6203 
6204 	return true;
6205 }
6206 
6207 
6208 void
6209 BPoseView::MoveSelectionOrEntryToTrash(const entry_ref* ref, bool selectNext)
6210 {
6211 	BObjectList<entry_ref>* entriesToTrash = new
6212 		BObjectList<entry_ref>(CountSelected());
6213 	BObjectList<entry_ref>* entriesToDeleteOnTheSpot = new
6214 		BObjectList<entry_ref>(20, true);
6215 	std::map<int32, bool> deviceHasTrash;
6216 
6217 	if (ref != NULL) {
6218 		if (!CheckVolumeReadOnly(ref)) {
6219 			delete entriesToTrash;
6220 			delete entriesToDeleteOnTheSpot;
6221 			return;
6222 		}
6223 		CopyOneTrashedRefAsEntry(ref, entriesToTrash, entriesToDeleteOnTheSpot,
6224 			&deviceHasTrash);
6225 	} else {
6226 		if (!CheckVolumeReadOnly(
6227 				fSelectionList->ItemAt(0)->TargetModel()->EntryRef())) {
6228 			delete entriesToTrash;
6229 			delete entriesToDeleteOnTheSpot;
6230 			return;
6231 		}
6232 		EachListItem(fSelectionList, CopyPoseOneAsEntry, entriesToTrash,
6233 			entriesToDeleteOnTheSpot, &deviceHasTrash);
6234 	}
6235 
6236 	if (entriesToDeleteOnTheSpot->CountItems()) {
6237 		BString alertText;
6238 		if (ref != NULL) {
6239 			alertText.SetTo(B_TRANSLATE("The selected item cannot be moved to "
6240 				"the Trash. Would you like to delete it instead? "
6241 				"(This operation cannot be reverted.)"));
6242 		} else {
6243 			alertText.SetTo(B_TRANSLATE("Some of the selected items cannot be "
6244 				"moved to the Trash. Would you like to delete them instead? "
6245 				"(This operation cannot be reverted.)"));
6246 		}
6247 
6248 		BAlert* alert = new BAlert("", alertText.String(),
6249 			B_TRANSLATE("Cancel"), B_TRANSLATE("Delete"));
6250 		alert->SetShortcut(0, B_ESCAPE);
6251 		if (alert->Go() == 0)
6252 			return;
6253 	}
6254 
6255 	MoveListToTrash(entriesToTrash, selectNext, false);
6256 	MoveListToTrash(entriesToDeleteOnTheSpot, selectNext, true);
6257 }
6258 
6259 
6260 void
6261 BPoseView::MoveSelectionToTrash(bool selectNext)
6262 {
6263 	if (fSelectionList->IsEmpty())
6264 		return;
6265 
6266 	// create entry_ref list from selection
6267 	// separate items that can be trashed from ones that cannot
6268 
6269 	MoveSelectionOrEntryToTrash(0, selectNext);
6270 }
6271 
6272 
6273 void
6274 BPoseView::MoveEntryToTrash(const entry_ref* ref, bool selectNext)
6275 {
6276 	MoveSelectionOrEntryToTrash(ref, selectNext);
6277 }
6278 
6279 
6280 void
6281 BPoseView::DeleteSelection(bool selectNext, bool confirm)
6282 {
6283 	int32 selectCount = CountSelected();
6284 	if (selectCount <= 0)
6285 		return;
6286 
6287 	if (!CheckVolumeReadOnly(
6288 			fSelectionList->ItemAt(0)->TargetModel()->EntryRef())) {
6289 		return;
6290 	}
6291 
6292 	BObjectList<entry_ref>* entriesToDelete
6293 		= new BObjectList<entry_ref>(selectCount, true);
6294 
6295 	for (int32 index = 0; index < selectCount; index++) {
6296 		entriesToDelete->AddItem(new entry_ref(
6297 			*fSelectionList->ItemAt(index)->TargetModel()->EntryRef()));
6298 	}
6299 
6300 	Delete(entriesToDelete, selectNext, confirm);
6301 }
6302 
6303 
6304 void
6305 BPoseView::RestoreSelectionFromTrash(bool selectNext)
6306 {
6307 	int32 selectCount = CountSelected();
6308 	if (selectCount <= 0)
6309 		return;
6310 
6311 	BObjectList<entry_ref>* entriesToRestore
6312 		= new BObjectList<entry_ref>(selectCount, true);
6313 
6314 	for (int32 index = 0; index < selectCount; index++) {
6315 		entriesToRestore->AddItem(new entry_ref(
6316 			*fSelectionList->ItemAt(index)->TargetModel()->EntryRef()));
6317 	}
6318 
6319 	RestoreItemsFromTrash(entriesToRestore, selectNext);
6320 }
6321 
6322 
6323 void
6324 BPoseView::Delete(const entry_ref &ref, bool selectNext, bool confirm)
6325 {
6326 	BObjectList<entry_ref>* entriesToDelete
6327 		= new BObjectList<entry_ref>(1, true);
6328 	entriesToDelete->AddItem(new entry_ref(ref));
6329 
6330 	Delete(entriesToDelete, selectNext, confirm);
6331 }
6332 
6333 
6334 void
6335 BPoseView::Delete(BObjectList<entry_ref>* list, bool selectNext, bool confirm)
6336 {
6337 	if (list->CountItems() == 0) {
6338 		delete list;
6339 		return;
6340 	}
6341 
6342 	BObjectList<FunctionObject>* taskList =
6343 		new BObjectList<FunctionObject>(2, true);
6344 
6345 	// first move selection to trash,
6346 	taskList->AddItem(NewFunctionObject(FSDeleteRefList, list, false, confirm));
6347 
6348 	if (selectNext && ViewMode() == kListMode) {
6349 		// next, if in list view mode try selecting the next item after
6350 		BPose* pose = fSelectionList->ItemAt(0);
6351 
6352 		// find a point in the pose
6353 		BPoint pointInPose(fListOffset + 5, 5);
6354 		int32 index = IndexOfPose(pose);
6355 		pointInPose.y += fListElemHeight * index;
6356 
6357 		TTracker* tracker = dynamic_cast<TTracker*>(be_app);
6358 		if (tracker != NULL) {
6359 			ThrowOnAssert(TargetModel() != NULL);
6360 
6361 			// add a function object to the list of tasks to run
6362 			// that will select the next item after the one we just
6363 			// deleted
6364 			Model* targetModel = TargetModel();
6365 			ASSERT(targetModel != NULL);
6366 			taskList->AddItem(NewMemberFunctionObject(
6367 				&TTracker::SelectPoseAtLocationSoon, tracker,
6368 				*targetModel->NodeRef(), pointInPose));
6369 		}
6370 	}
6371 
6372 	// execute the two tasks in order
6373 	ThreadSequence::Launch(taskList, true);
6374 }
6375 
6376 
6377 void
6378 BPoseView::RestoreItemsFromTrash(BObjectList<entry_ref>* list, bool selectNext)
6379 {
6380 	if (list->CountItems() == 0) {
6381 		delete list;
6382 		return;
6383 	}
6384 
6385 	BObjectList<FunctionObject>* taskList =
6386 		new BObjectList<FunctionObject>(2, true);
6387 
6388 	// first restoree selection
6389 	taskList->AddItem(NewFunctionObject(FSRestoreRefList, list, false));
6390 
6391 	if (selectNext && ViewMode() == kListMode) {
6392 		// next, if in list view mode try selecting the next item after
6393 		BPose* pose = fSelectionList->ItemAt(0);
6394 
6395 		// find a point in the pose
6396 		BPoint pointInPose(fListOffset + 5, 5);
6397 		int32 index = IndexOfPose(pose);
6398 		pointInPose.y += fListElemHeight * index;
6399 
6400 		TTracker* tracker = dynamic_cast<TTracker*>(be_app);
6401 		if (tracker != NULL) {
6402 			ThrowOnAssert(TargetModel() != NULL);
6403 
6404 			// add a function object to the list of tasks to run
6405 			// that will select the next item after the one we just
6406 			// restored
6407 			Model* targetModel = TargetModel();
6408 			ASSERT(targetModel != NULL);
6409 			taskList->AddItem(NewMemberFunctionObject(
6410 				&TTracker::SelectPoseAtLocationSoon, tracker,
6411 				*targetModel->NodeRef(), pointInPose));
6412 		}
6413 	}
6414 
6415 	// execute the two tasks in order
6416 	ThreadSequence::Launch(taskList, true);
6417 }
6418 
6419 
6420 void
6421 BPoseView::DoDelete()
6422 {
6423 	ExcludeTrashFromSelection();
6424 
6425 	// Trash deletes instantly without checking for confirmation
6426 	if (TargetModel()->IsTrash())
6427 		return DeleteSelection(true, false);
6428 
6429 	DeleteSelection();
6430 }
6431 
6432 
6433 void
6434 BPoseView::DoMoveToTrash()
6435 {
6436 	ExcludeTrashFromSelection();
6437 
6438 	// happens when called from within Open with... for example
6439 	if (TargetModel() == NULL)
6440 		return;
6441 
6442 	// Trash deletes instantly without checking for confirmation
6443 	if (TargetModel()->IsTrash())
6444 		return DeleteSelection(true, false);
6445 
6446 	bool shiftDown = (Window()->CurrentMessage()->FindInt32("modifiers")
6447 		& B_SHIFT_KEY) != 0;
6448 	if (shiftDown)
6449 		DeleteSelection();
6450 	else
6451 		MoveSelectionToTrash();
6452 }
6453 
6454 
6455 void
6456 BPoseView::SelectAll()
6457 {
6458 	BRect bounds(Bounds());
6459 
6460 	// clear selection list
6461 	fSelectionList->MakeEmpty();
6462 	fMimeTypesInSelectionCache.MakeEmpty();
6463 	fSelectionPivotPose = NULL;
6464 	fRealPivotPose = NULL;
6465 
6466 	int32 startIndex = 0;
6467 	BPoint loc(0, fListElemHeight * startIndex);
6468 
6469 	bool iconMode = ViewMode() != kListMode;
6470 
6471 	PoseList* poseList = CurrentPoseList();
6472 	int32 poseCount = poseList->CountItems();
6473 	for (int32 index = startIndex; index < poseCount; index++) {
6474 		BPose* pose = poseList->ItemAt(index);
6475 		fSelectionList->AddItem(pose);
6476 		if (index == startIndex)
6477 			fSelectionPivotPose = pose;
6478 
6479 		if (!pose->IsSelected()) {
6480 			pose->Select(true);
6481 
6482 			BRect poseRect;
6483 			if (iconMode)
6484 				poseRect = pose->CalcRect(this);
6485 			else
6486 				poseRect = pose->CalcRect(loc, this);
6487 
6488 			if (bounds.Intersects(poseRect)) {
6489 				pose->Draw(poseRect, bounds, this, false);
6490 				Flush();
6491 			}
6492 		}
6493 
6494 		loc.y += fListElemHeight;
6495 	}
6496 
6497 	if (fSelectionChangedHook)
6498 		ContainerWindow()->SelectionChanged();
6499 }
6500 
6501 
6502 void
6503 BPoseView::InvertSelection()
6504 {
6505 	// Since this function shares most code with
6506 	// SelectAll(), we could make SelectAll() empty the selection,
6507 	// then call InvertSelection()
6508 
6509 	BRect bounds(Bounds());
6510 
6511 	int32 startIndex = 0;
6512 	BPoint loc(0, fListElemHeight * startIndex);
6513 
6514 	fMimeTypesInSelectionCache.MakeEmpty();
6515 	fSelectionPivotPose = NULL;
6516 	fRealPivotPose = NULL;
6517 
6518 	bool iconMode = ViewMode() != kListMode;
6519 
6520 	PoseList* poseList = CurrentPoseList();
6521 	int32 poseCount = poseList->CountItems();
6522 	for (int32 index = startIndex; index < poseCount; index++) {
6523 		BPose* pose = poseList->ItemAt(index);
6524 
6525 		if (pose->IsSelected()) {
6526 			fSelectionList->RemoveItem(pose);
6527 			pose->Select(false);
6528 		} else {
6529 			if (index == startIndex)
6530 				fSelectionPivotPose = pose;
6531 
6532 			fSelectionList->AddItem(pose);
6533 			pose->Select(true);
6534 		}
6535 
6536 		BRect poseRect;
6537 		if (iconMode)
6538 			poseRect = pose->CalcRect(this);
6539 		else
6540 			poseRect = pose->CalcRect(loc, this);
6541 
6542 		if (bounds.Intersects(poseRect))
6543 			Invalidate();
6544 
6545 		loc.y += fListElemHeight;
6546 	}
6547 
6548 	if (fSelectionChangedHook)
6549 		ContainerWindow()->SelectionChanged();
6550 }
6551 
6552 
6553 int32
6554 BPoseView::SelectMatchingEntries(const BMessage* message)
6555 {
6556 	int32 matchCount = 0;
6557 	SetMultipleSelection(true);
6558 
6559 	ClearSelection();
6560 
6561 	TrackerStringExpressionType expressionType;
6562 	BString expression;
6563 	const char* expressionPointer;
6564 	bool invertSelection;
6565 	bool ignoreCase;
6566 
6567 	message->FindInt32("ExpressionType", (int32*)&expressionType);
6568 	message->FindString("Expression", &expressionPointer);
6569 	message->FindBool("InvertSelection", &invertSelection);
6570 	message->FindBool("IgnoreCase", &ignoreCase);
6571 
6572 	expression = expressionPointer;
6573 
6574 	PoseList* poseList = CurrentPoseList();
6575 	int32 poseCount = poseList->CountItems();
6576 	TrackerString name;
6577 
6578 	RegExp regExpression;
6579 
6580 	// Make sure we don't have any errors in the expression
6581 	// before we match the names:
6582 	if (expressionType == kRegexpMatch) {
6583 		regExpression.SetTo(expression);
6584 
6585 		if (regExpression.InitCheck() != B_OK) {
6586 			BString message(
6587 				B_TRANSLATE("Error in regular expression:\n\n'%errstring'"));
6588 			message.ReplaceFirst("%errstring", regExpression.ErrorString());
6589 			BAlert* alert = new BAlert("", message.String(), B_TRANSLATE("OK"),
6590 				NULL, NULL,	B_WIDTH_AS_USUAL, B_STOP_ALERT);
6591 			alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
6592 			alert->Go();
6593 			return 0;
6594 		}
6595 	}
6596 
6597 	// There is room for optimizations here: If regexp-type match, the Matches()
6598 	// function compiles the expression for every entry. One could use
6599 	// TrackerString::CompileRegExp and reuse the expression. However, then we
6600 	// have to take care of the case sensitivity ourselves.
6601 	for (int32 index = 0; index < poseCount; index++) {
6602 		BPose* pose = poseList->ItemAt(index);
6603 		name = pose->TargetModel()->Name();
6604 		if (name.Matches(expression.String(), !ignoreCase, expressionType)
6605 				^ invertSelection) {
6606 			matchCount++;
6607 			AddPoseToSelection(pose, index);
6608 		}
6609 	}
6610 
6611 	Window()->Activate();
6612 		// Make sure the window is activated for
6613 		// subsequent manipulations. Esp. needed
6614 		// for the Desktop window.
6615 
6616 	return matchCount;
6617 }
6618 
6619 
6620 void
6621 BPoseView::ShowSelectionWindow()
6622 {
6623 	Window()->PostMessage(kShowSelectionWindow);
6624 }
6625 
6626 
6627 void
6628 BPoseView::KeyDown(const char* bytes, int32 count)
6629 {
6630 	char key = bytes[0];
6631 
6632 	switch (key) {
6633 		case B_LEFT_ARROW:
6634 		case B_RIGHT_ARROW:
6635 		case B_UP_ARROW:
6636 		case B_DOWN_ARROW:
6637 		{
6638 			int32 index;
6639 			BPose* pose = FindNearbyPose(key, &index);
6640 			if (pose == NULL)
6641 				break;
6642 
6643 			if (fMultipleSelection && (modifiers() & B_SHIFT_KEY) != 0) {
6644 				if (pose->IsSelected()) {
6645 					RemovePoseFromSelection(fSelectionList->LastItem());
6646 					fSelectionPivotPose = pose;
6647 					ScrollIntoView(pose, index);
6648 				} else
6649 					AddPoseToSelection(pose, index, true);
6650 			} else
6651 				SelectPose(pose, index);
6652 
6653 			break;
6654 		}
6655 
6656 		case B_RETURN:
6657 			if (fFiltering && CountSelected() == 0)
6658 				SelectPose(fFilteredPoseList->FirstItem(), 0);
6659 
6660 			OpenSelection();
6661 
6662 			if (fFiltering && (modifiers() & B_SHIFT_KEY) != 0)
6663 				StopFiltering();
6664 
6665 			break;
6666 
6667 		case B_HOME:
6668 			// select the first entry (if in listview mode), and
6669 			// scroll to the top of the view
6670 			if (ViewMode() == kListMode)
6671 				MoveOrChangePoseSelection(0);
6672 			else
6673 				ScrollView(B_HOME);
6674 			break;
6675 
6676 		case B_END:
6677 			// select the last entry (if in listview mode), and
6678 			// scroll to the bottom of the view
6679 			if (ViewMode() == kListMode)
6680 				MoveOrChangePoseSelection(CurrentPoseList()->CountItems() - 1);
6681 			else
6682 				ScrollView(B_END);
6683 			break;
6684 
6685 		case B_PAGE_UP:
6686 			if (ViewMode() == kListMode) {
6687 				// Select first visible pose
6688 				int32 firstIndex = CurrentPoseList()->IndexOf(
6689 					fSelectionList->FirstItem());
6690 				int32 index;
6691 				BPose* first = FirstVisiblePose(&index);
6692 				if (first != NULL) {
6693 					if (index == firstIndex) {
6694 						ScrollView(B_PAGE_UP);
6695 						first = FirstVisiblePose(&index);
6696 					}
6697 					MoveOrChangePoseSelection(index);
6698 				}
6699 			} else
6700 				ScrollView(B_PAGE_UP);
6701 			break;
6702 
6703 		case B_PAGE_DOWN:
6704 			if (ViewMode() == kListMode) {
6705 				// Select last visible pose
6706 				int32 lastIndex = CurrentPoseList()->IndexOf(
6707 					fSelectionList->LastItem());
6708 				int32 index;
6709 				BPose* last = LastVisiblePose(&index);
6710 				if (last != NULL) {
6711 					if (index == lastIndex) {
6712 						ScrollView(B_PAGE_DOWN);
6713 						last = LastVisiblePose(&index);
6714 					}
6715 					MoveOrChangePoseSelection(index);
6716 				}
6717 			} else
6718 				ScrollView(B_PAGE_DOWN);
6719 			break;
6720 
6721 		case B_TAB:
6722 			if (IsFilePanel())
6723 				_inherited::KeyDown(bytes, count);
6724 			else {
6725 				if (ViewMode() == kListMode
6726 					&& TrackerSettings().TypeAheadFiltering()) {
6727 					break;
6728 				}
6729 
6730 				if (fSelectionList->IsEmpty())
6731 					sMatchString.Truncate(0);
6732 				else {
6733 					BPose* pose = fSelectionList->FirstItem();
6734 					sMatchString.SetTo(pose->TargetModel()->Name());
6735 				}
6736 
6737 				bool reverse
6738 					= (Window()->CurrentMessage()->FindInt32("modifiers")
6739 						& B_SHIFT_KEY) != 0;
6740 				int32 index;
6741 				BPose* pose = FindNextMatch(&index, reverse);
6742 				if (pose == NULL) {
6743 					// wrap around
6744 					if (reverse)
6745 						sMatchString.SetTo(0x7f, 1);
6746 					else
6747 						sMatchString.Truncate(0);
6748 
6749 					pose = FindNextMatch(&index, reverse);
6750 				}
6751 
6752 				SelectPose(pose, index);
6753 			}
6754 			break;
6755 
6756 		case B_DELETE:
6757 		{
6758 			DoMoveToTrash();
6759 			break;
6760 		}
6761 
6762 		case B_BACKSPACE:
6763 		{
6764 			if (fFiltering) {
6765 				BString* lastString = fFilterStrings.LastItem();
6766 				if (lastString->Length() == 0) {
6767 					int32 stringCount = fFilterStrings.CountItems();
6768 					if (stringCount > 1)
6769 						delete fFilterStrings.RemoveItemAt(stringCount - 1);
6770 					else
6771 						break;
6772 				} else
6773 					lastString->TruncateChars(lastString->CountChars() - 1);
6774 
6775 				fCountView->RemoveFilterCharacter();
6776 				FilterChanged();
6777 				break;
6778 			}
6779 
6780 			if (sMatchString.Length() == 0)
6781 				break;
6782 
6783 			// remove last char from the typeahead buffer
6784 			sMatchString.TruncateChars(sMatchString.CountChars() - 1);
6785 
6786 			fLastKeyTime = system_time();
6787 
6788 			fCountView->SetTypeAhead(sMatchString.String());
6789 
6790 			// select our new string
6791 			int32 index;
6792 			BPose* pose = FindBestMatch(&index);
6793 			if (pose == NULL)
6794 				break;
6795 
6796 			SelectPose(pose, index);
6797 			break;
6798 		}
6799 
6800 		case B_FUNCTION_KEY:
6801 		{
6802 			BMessage* message = Window()->CurrentMessage();
6803 			if (message != NULL) {
6804 				int32 key;
6805 				if (message->FindInt32("key", &key) == B_OK && key == B_F2_KEY)
6806 					Window()->PostMessage(kEditItem, this);
6807 			}
6808 			break;
6809 		}
6810 
6811 		case B_INSERT:
6812 			break;
6813 
6814 		default:
6815 		{
6816 			// handle typeahead selection / filtering
6817 
6818 			if (ViewMode() == kListMode
6819 				&& TrackerSettings().TypeAheadFiltering()) {
6820 				if (key == ' ' && modifiers() & B_SHIFT_KEY) {
6821 					if (fFilterStrings.LastItem()->Length() == 0)
6822 						break;
6823 
6824 					fFilterStrings.AddItem(new BString());
6825 					fCountView->AddFilterCharacter("|");
6826 					break;
6827 				}
6828 
6829 				fFilterStrings.LastItem()->AppendChars(bytes, 1);
6830 				fCountView->AddFilterCharacter(bytes);
6831 				FilterChanged();
6832 				break;
6833 			}
6834 
6835 			bigtime_t doubleClickSpeed;
6836 			get_click_speed(&doubleClickSpeed);
6837 
6838 			// start watching
6839 			if (fKeyRunner == NULL) {
6840 				fKeyRunner = new BMessageRunner(this,
6841 					new BMessage(kCheckTypeahead), doubleClickSpeed);
6842 				if (fKeyRunner->InitCheck() != B_OK)
6843 					return;
6844 			}
6845 
6846 			// figure out the time at which the keypress happened
6847 			bigtime_t eventTime;
6848 			BMessage* message = Window()->CurrentMessage();
6849 			if (message == NULL
6850 				|| message->FindInt64("when", &eventTime) < B_OK) {
6851 				eventTime = system_time();
6852 			}
6853 
6854 			// add char to existing matchString or start new match string
6855 			if (eventTime - fLastKeyTime < (doubleClickSpeed * 2))
6856 				sMatchString.AppendChars(bytes, 1);
6857 			else
6858 				sMatchString.SetToChars(bytes, 1);
6859 
6860 			fLastKeyTime = eventTime;
6861 
6862 			fCountView->SetTypeAhead(sMatchString.String());
6863 
6864 			int32 index;
6865 			BPose* pose = FindBestMatch(&index);
6866 			if (pose == NULL)
6867 				break;
6868 
6869 			SelectPose(pose, index);
6870 			break;
6871 		}
6872 	}
6873 }
6874 
6875 
6876 BPose*
6877 BPoseView::FindNextMatch(int32* matchingIndex, bool reverse)
6878 {
6879 	char bestSoFar[B_FILE_NAME_LENGTH] = { 0 };
6880 	BPose* poseToSelect = NULL;
6881 
6882 	// loop through all poses to find match
6883 	int32 poseCount = fPoseList->CountItems();
6884 	for (int32 index = 0; index < poseCount; index++) {
6885 		BPose* pose = fPoseList->ItemAt(index);
6886 
6887 		if (reverse) {
6888 			if (sMatchString.ICompare(pose->TargetModel()->Name()) > 0) {
6889 				if (strcasecmp(pose->TargetModel()->Name(), bestSoFar) >= 0
6890 					|| !bestSoFar[0]) {
6891 					strlcpy(bestSoFar, pose->TargetModel()->Name(),
6892 						sizeof(bestSoFar));
6893 					poseToSelect = pose;
6894 					*matchingIndex = index;
6895 				}
6896 			}
6897 		} else if (sMatchString.ICompare(pose->TargetModel()->Name()) < 0) {
6898 			if (strcasecmp(pose->TargetModel()->Name(), bestSoFar) <= 0
6899 				|| !bestSoFar[0]) {
6900 				strlcpy(bestSoFar, pose->TargetModel()->Name(),
6901 					sizeof(bestSoFar));
6902 				poseToSelect = pose;
6903 				*matchingIndex = index;
6904 			}
6905 		}
6906 	}
6907 
6908 	return poseToSelect;
6909 }
6910 
6911 
6912 BPose*
6913 BPoseView::FindBestMatch(int32* index)
6914 {
6915 	BPose* poseToSelect = NULL;
6916 	float bestScore = -1;
6917 	int32 poseCount = fPoseList->CountItems();
6918 
6919 	// loop through all poses to find match
6920 	for (int32 j = 0; j < CountColumns(); j++) {
6921 		BColumn* column = ColumnAt(j);
6922 
6923 		for (int32 i = 0; i < poseCount; i++) {
6924 			BPose* pose = fPoseList->ItemAt(i);
6925 			float score = -1;
6926 
6927 			if (ViewMode() == kListMode) {
6928 				ModelNodeLazyOpener modelOpener(pose->TargetModel());
6929 				BTextWidget* widget = pose->WidgetFor(column, this,
6930 					modelOpener);
6931 				const char* text = NULL;
6932 				if (widget != NULL)
6933 					text = widget->Text(this);
6934 
6935 				if (text != NULL)
6936 					score = ComputeTypeAheadScore(text, sMatchString.String());
6937 			} else {
6938 				score = ComputeTypeAheadScore(pose->TargetModel()->Name(),
6939 					sMatchString.String());
6940 			}
6941 
6942 			if (score > bestScore) {
6943 				poseToSelect = pose;
6944 				bestScore = score;
6945 				*index = i;
6946 			}
6947 			if (score == kExactMatchScore)
6948 				break;
6949 		}
6950 
6951 		// TODO: we might want to change this to make it always work
6952 		// over all columns, but this would require some more changes
6953 		// to how Tracker represents data (for example we could filter
6954 		// the results out).
6955 		if (bestScore > 0 || ViewMode() != kListMode)
6956 			break;
6957 	}
6958 
6959 	return poseToSelect;
6960 }
6961 
6962 
6963 static bool
6964 LinesIntersect(float s1, float e1, float s2, float e2)
6965 {
6966 	return std::max(s1, s2) < std::min(e1, e2);
6967 }
6968 
6969 
6970 BPose*
6971 BPoseView::FindNearbyPose(char arrowKey, int32* poseIndex)
6972 {
6973 	int32 resultingIndex = -1;
6974 	BPose* poseToSelect = NULL;
6975 	BPose* selectedPose = fSelectionList->LastItem();
6976 
6977 	if (ViewMode() == kListMode) {
6978 		PoseList* poseList = CurrentPoseList();
6979 
6980 		switch (arrowKey) {
6981 			case B_UP_ARROW:
6982 			case B_LEFT_ARROW:
6983 				if (selectedPose) {
6984 					resultingIndex = poseList->IndexOf(selectedPose) - 1;
6985 					poseToSelect = poseList->ItemAt(resultingIndex);
6986 					if (poseToSelect == NULL && arrowKey == B_LEFT_ARROW) {
6987 						resultingIndex = poseList->CountItems() - 1;
6988 						poseToSelect = poseList->LastItem();
6989 					}
6990 				} else {
6991 					resultingIndex = poseList->CountItems() - 1;
6992 					poseToSelect = poseList->LastItem();
6993 				}
6994 				break;
6995 
6996 			case B_DOWN_ARROW:
6997 			case B_RIGHT_ARROW:
6998 				if (selectedPose) {
6999 					resultingIndex = poseList->IndexOf(selectedPose) + 1;
7000 					poseToSelect = poseList->ItemAt(resultingIndex);
7001 					if (poseToSelect == NULL && arrowKey == B_RIGHT_ARROW) {
7002 						resultingIndex = 0;
7003 						poseToSelect = poseList->FirstItem();
7004 					}
7005 				} else {
7006 					resultingIndex = 0;
7007 					poseToSelect = poseList->FirstItem();
7008 				}
7009 				break;
7010 		}
7011 		*poseIndex = resultingIndex;
7012 
7013 		return poseToSelect;
7014 	}
7015 
7016 	// must be in one of the icon modes
7017 
7018 	// handle case where there is no current selection
7019 	if (fSelectionList->IsEmpty()) {
7020 		// find the upper-left pose (I know it's ugly!)
7021 		poseToSelect = fVSPoseList->FirstItem();
7022 		for (int32 index = 0; ;index++) {
7023 			BPose* pose = fVSPoseList->ItemAt(++index);
7024 			if (pose == NULL)
7025 				break;
7026 
7027 			if (poseToSelect != NULL) {
7028 				BRect selectedBounds;
7029 				selectedBounds = poseToSelect->CalcRect(this);
7030 				BRect poseRect(pose->CalcRect(this));
7031 
7032 				if (poseRect.top > selectedBounds.top)
7033 					break;
7034 
7035 				if (poseRect.left < selectedBounds.left)
7036 					poseToSelect = pose;
7037 			}
7038 		}
7039 
7040 		return poseToSelect;
7041 	}
7042 
7043 	BRect selectionRect;
7044 	if (selectedPose != NULL)
7045 		selectionRect = selectedPose->CalcRect(this);
7046 
7047 	BRect bestRect;
7048 
7049 	// we're not in list mode so scan visually for pose to select
7050 	int32 poseCount = fPoseList->CountItems();
7051 	for (int32 index = 0; index < poseCount; index++) {
7052 		BPose* pose = fPoseList->ItemAt(index);
7053 		BRect poseRect(pose->CalcRect(this));
7054 
7055 		switch (arrowKey) {
7056 			case B_LEFT_ARROW:
7057 				if (LinesIntersect(poseRect.top, poseRect.bottom,
7058 						selectionRect.top, selectionRect.bottom)
7059 					&& poseRect.left < selectionRect.left
7060 					&& (poseRect.left > bestRect.left
7061 						|| !bestRect.IsValid())) {
7062 					bestRect = poseRect;
7063 					poseToSelect = pose;
7064 				}
7065 				break;
7066 
7067 			case B_RIGHT_ARROW:
7068 				if (LinesIntersect(poseRect.top, poseRect.bottom,
7069 						selectionRect.top, selectionRect.bottom)
7070 					&& poseRect.right > selectionRect.right
7071 					&& (poseRect.right < bestRect.right
7072 							|| !bestRect.IsValid())) {
7073 					bestRect = poseRect;
7074 					poseToSelect = pose;
7075 				}
7076 				break;
7077 
7078 			case B_UP_ARROW:
7079 				if (LinesIntersect(poseRect.left, poseRect.right,
7080 						selectionRect.left, selectionRect.right)
7081 					&& poseRect.top < selectionRect.top
7082 					&& (poseRect.top > bestRect.top
7083 						|| !bestRect.IsValid())) {
7084 					bestRect = poseRect;
7085 					poseToSelect = pose;
7086 				}
7087 				break;
7088 
7089 			case B_DOWN_ARROW:
7090 				if (LinesIntersect(poseRect.left, poseRect.right,
7091 						selectionRect.left, selectionRect.right)
7092 					&& poseRect.bottom > selectionRect.bottom
7093 					&& (poseRect.bottom < bestRect.bottom
7094 						|| !bestRect.IsValid())) {
7095 					bestRect = poseRect;
7096 					poseToSelect = pose;
7097 				}
7098 				break;
7099 		}
7100 	}
7101 
7102 	if (poseToSelect != NULL)
7103 		return poseToSelect;
7104 
7105 	return selectedPose;
7106 }
7107 
7108 
7109 void
7110 BPoseView::ShowContextMenu(BPoint where)
7111 {
7112 	BContainerWindow* window = ContainerWindow();
7113 	if (window == NULL)
7114 		return;
7115 
7116 	// handle pose selection
7117 	int32 index;
7118 	BPose* pose = FindPose(where, &index);
7119 	if (pose != NULL) {
7120 		if (!pose->IsSelected()) {
7121 			ClearSelection();
7122 			pose->Select(true);
7123 			fSelectionList->AddItem(pose);
7124 			DrawPose(pose, index, false);
7125 		}
7126 	} else
7127 		ClearSelection();
7128 
7129 	window->Activate();
7130 	window->UpdateIfNeeded();
7131 	window->ShowContextMenu(where, pose == NULL ? NULL
7132 		: pose->TargetModel()->EntryRef());
7133 
7134 	if (fSelectionChangedHook)
7135 		window->SelectionChanged();
7136 }
7137 
7138 
7139 void
7140 BPoseView::_BeginSelectionRect(const BPoint& point, bool shouldExtend)
7141 {
7142 	// set initial empty selection rectangle
7143 	fSelectionRectInfo.rect = BRect(point, point - BPoint(1, 1));
7144 
7145 	if (!fTransparentSelection) {
7146 		SetDrawingMode(B_OP_INVERT);
7147 		StrokeRect(fSelectionRectInfo.rect, B_MIXED_COLORS);
7148 		SetDrawingMode(B_OP_OVER);
7149 	}
7150 
7151 	fSelectionRectInfo.lastRect = fSelectionRectInfo.rect;
7152 	fSelectionRectInfo.selection = new BList;
7153 	fSelectionRectInfo.startPoint = point;
7154 	fSelectionRectInfo.lastPoint = point;
7155 	fSelectionRectInfo.isDragging = true;
7156 
7157 	if (fAutoScrollState == kAutoScrollOff) {
7158 		fAutoScrollState = kAutoScrollOn;
7159 		Window()->SetPulseRate(20000);
7160 	}
7161 }
7162 
7163 
7164 static void
7165 AddIfPoseSelected(BPose* pose, PoseList* list)
7166 {
7167 	if (pose->IsSelected())
7168 		list->AddItem(pose);
7169 }
7170 
7171 
7172 void
7173 BPoseView::_UpdateSelectionRect(const BPoint& point)
7174 {
7175 	if (point != fSelectionRectInfo.lastPoint) {
7176 		fSelectionRectInfo.lastPoint = point;
7177 
7178 		// erase last rect
7179 		if (!fTransparentSelection) {
7180 			SetDrawingMode(B_OP_INVERT);
7181 			StrokeRect(fSelectionRectInfo.rect, B_MIXED_COLORS);
7182 			SetDrawingMode(B_OP_OVER);
7183 		}
7184 
7185 		fSelectionRectInfo.rect.top = std::min(point.y,
7186 			fSelectionRectInfo.startPoint.y);
7187 		fSelectionRectInfo.rect.left = std::min(point.x,
7188 			fSelectionRectInfo.startPoint.x);
7189 		fSelectionRectInfo.rect.bottom = std::max(point.y,
7190 			fSelectionRectInfo.startPoint.y);
7191 		fSelectionRectInfo.rect.right = std::max(point.x,
7192 			fSelectionRectInfo.startPoint.x);
7193 
7194 		fIsDrawingSelectionRect = true;
7195 
7196 		// use current selection rectangle to scan poses
7197 		SelectPoses(fSelectionRectInfo.rect,
7198 			&fSelectionRectInfo.selection);
7199 
7200 		Window()->UpdateIfNeeded();
7201 
7202 		// draw new rect
7203 		if (!fTransparentSelection) {
7204 			SetDrawingMode(B_OP_INVERT);
7205 			StrokeRect(fSelectionRectInfo.rect, B_MIXED_COLORS);
7206 			SetDrawingMode(B_OP_OVER);
7207 		} else {
7208 			BRegion updateRegion1;
7209 			BRegion updateRegion2;
7210 
7211 			bool sameWidth = fSelectionRectInfo.rect.Width()
7212 				== fSelectionRectInfo.lastRect.Width();
7213 			bool sameHeight = fSelectionRectInfo.rect.Height()
7214 				== fSelectionRectInfo.lastRect.Height();
7215 
7216 			updateRegion1.Include(fSelectionRectInfo.rect);
7217 			updateRegion1.Exclude(fSelectionRectInfo.lastRect.InsetByCopy(
7218 				sameWidth ? 0 : 1, sameHeight ? 0 : 1));
7219 			updateRegion2.Include(fSelectionRectInfo.lastRect);
7220 			updateRegion2.Exclude(fSelectionRectInfo.rect.InsetByCopy(
7221 				sameWidth ? 0 : 1, sameHeight ? 0 : 1));
7222 			updateRegion1.Include(&updateRegion2);
7223 			BRect unionRect = fSelectionRectInfo.rect
7224 				& fSelectionRectInfo.lastRect;
7225 			updateRegion1.Exclude(unionRect
7226 				& BRect(-2000, fSelectionRectInfo.startPoint.y, 2000,
7227 				fSelectionRectInfo.startPoint.y));
7228 			updateRegion1.Exclude(unionRect
7229 				& BRect(fSelectionRectInfo.startPoint.x, -2000,
7230 				fSelectionRectInfo.startPoint.x, 2000));
7231 
7232 			fSelectionRectInfo.lastRect = fSelectionRectInfo.rect;
7233 
7234 			Invalidate(&updateRegion1);
7235 			Window()->UpdateIfNeeded();
7236 		}
7237 		Flush();
7238 	}
7239 }
7240 
7241 
7242 void
7243 BPoseView::_EndSelectionRect()
7244 {
7245 	delete fSelectionRectInfo.selection;
7246 	fSelectionRectInfo.selection = NULL;
7247 
7248 	fSelectionRectInfo.isDragging = false;
7249 	fIsDrawingSelectionRect = false;
7250 		// TODO: remove BPose dependency?
7251 
7252 	// do final erase of selection rect
7253 	if (!fTransparentSelection) {
7254 		SetDrawingMode(B_OP_INVERT);
7255 		StrokeRect(fSelectionRectInfo.rect, B_MIXED_COLORS);
7256 		SetDrawingMode(B_OP_COPY);
7257 		fSelectionRectInfo.rect.Set(0, 0, -1, -1);
7258 	} else {
7259 		Invalidate(fSelectionRectInfo.rect);
7260 		fSelectionRectInfo.rect.Set(0, 0, -1, -1);
7261 		Window()->UpdateIfNeeded();
7262 	}
7263 
7264 	// we now need to update the pose view's selection list by clearing it
7265 	// and then polling each pose for selection state and rebuilding list
7266 	fSelectionList->MakeEmpty();
7267 	fMimeTypesInSelectionCache.MakeEmpty();
7268 
7269 	EachListItem(fPoseList, AddIfPoseSelected, fSelectionList);
7270 
7271 	// and now make sure that the pivot point is in sync
7272 	if (fSelectionPivotPose && !fSelectionList->HasItem(fSelectionPivotPose))
7273 		fSelectionPivotPose = NULL;
7274 	if (fRealPivotPose && !fSelectionList->HasItem(fRealPivotPose))
7275 		fRealPivotPose = NULL;
7276 }
7277 
7278 
7279 void
7280 BPoseView::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage)
7281 {
7282 	if (fSelectionRectInfo.isDragging)
7283 		_UpdateSelectionRect(where);
7284 
7285 	if (!fDropEnabled || dragMessage == NULL)
7286 		return;
7287 
7288 	BContainerWindow* window = ContainerWindow();
7289 	if (window == NULL)
7290 		return;
7291 
7292 	if (!window->Dragging())
7293 		window->DragStart(dragMessage);
7294 
7295 	switch (transit) {
7296 		case B_INSIDE_VIEW:
7297 		case B_ENTERED_VIEW:
7298 			UpdateDropTarget(where, dragMessage, window->ContextMenu());
7299 			if (fAutoScrollState == kAutoScrollOff) {
7300 				// turn on auto scrolling if it's not yet on
7301 				fAutoScrollState = kWaitForTransition;
7302 				window->SetPulseRate(100000);
7303 			}
7304 			break;
7305 
7306 		case B_EXITED_VIEW:
7307 			DragStop();
7308 			// reset cursor in case we set it to the copy cursor
7309 			// in UpdateDropTarget
7310 			SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
7311 			fCursorCheck = false;
7312 			// TODO: autoscroll here
7313 			if (!window->ContextMenu()) {
7314 				HiliteDropTarget(false);
7315 				fDropTarget = NULL;
7316 			}
7317 			break;
7318 	}
7319 }
7320 
7321 
7322 void
7323 BPoseView::MouseDragged(const BMessage* message)
7324 {
7325 	if (fTextWidgetToCheck != NULL)
7326 		fTextWidgetToCheck->CancelWait();
7327 
7328 	fTrackRightMouseUp = false;
7329 	fTrackMouseUp = false;
7330 
7331 	BPoint where;
7332 	uint32 buttons = 0;
7333 	if (message->FindPoint("be:view_where", &where) != B_OK
7334 		|| message->FindInt32("buttons", (int32*)&buttons) != B_OK) {
7335 		return;
7336 	}
7337 
7338 	bool extendSelection = (modifiers() & B_COMMAND_KEY) != 0
7339 		&& fMultipleSelection;
7340 
7341 	int32 index;
7342 	BPose* pose = FindPose(where, &index);
7343 	if (pose != NULL)
7344 		DragSelectedPoses(pose, where);
7345 	else if (buttons == B_PRIMARY_MOUSE_BUTTON)
7346 		_BeginSelectionRect(where, extendSelection);
7347 }
7348 
7349 
7350 void
7351 BPoseView::MouseLongDown(const BMessage* message)
7352 {
7353 	fTrackRightMouseUp = false;
7354 	fTrackMouseUp = false;
7355 
7356 	BPoint where;
7357 	if (message->FindPoint("where", &where) != B_OK)
7358 		return;
7359 
7360 	ShowContextMenu(where);
7361 }
7362 
7363 
7364 void
7365 BPoseView::MouseIdle(const BMessage* message)
7366 {
7367 	BPoint where;
7368 	uint32 buttons = 0;
7369 	GetMouse(&where, &buttons);
7370 		// We could retrieve 'where' from the incoming
7371 		// message but we need the buttons state anyway
7372 		// and B_MOUSE_IDLE message doesn't pass it
7373 	BContainerWindow* window = ContainerWindow();
7374 
7375 	if (buttons == 0 || window == NULL || !window->Dragging())
7376 		return;
7377 
7378 	if (fDropTarget != NULL) {
7379 		FrameForPose(fDropTarget, true, &fStartFrame);
7380 		ShowContextMenu(where);
7381 	} else
7382 		window->Activate();
7383 }
7384 
7385 
7386 void
7387 BPoseView::MouseDown(BPoint where)
7388 {
7389 	// handle disposing of drag data lazily
7390 	DragStop();
7391 	BContainerWindow* window = ContainerWindow();
7392 	if (window == NULL)
7393 		return;
7394 
7395 	if (IsDesktopWindow()) {
7396 		BScreen screen(Window());
7397 		rgb_color color = screen.DesktopColor();
7398 		SetLowColor(color);
7399 		SetViewColor(color);
7400 	}
7401 
7402 	MakeFocus();
7403 
7404 	uint32 buttons = (uint32)window->CurrentMessage()->FindInt32("buttons");
7405 	uint32 modifierKeys = modifiers();
7406 	bool secondaryMouseButtonDown
7407 		= SecondaryMouseButtonDown(modifierKeys, buttons);
7408 	fTrackRightMouseUp = secondaryMouseButtonDown;
7409 	fTrackMouseUp = !secondaryMouseButtonDown;
7410 	bool extendSelection = (modifierKeys & B_COMMAND_KEY) != 0
7411 		&& fMultipleSelection;
7412 
7413 	CommitActivePose();
7414 
7415 	int32 index;
7416 	BPose* pose = FindPose(where, &index);
7417 	if (pose != NULL) {
7418 		if (!pose->IsSelected() || !secondaryMouseButtonDown)
7419 			AddRemoveSelectionRange(where, extendSelection, pose);
7420 
7421 		if (fTextWidgetToCheck != NULL
7422 			&& (pose != fLastClickedPose || secondaryMouseButtonDown)) {
7423 			fTextWidgetToCheck->CancelWait();
7424 		}
7425 
7426 		if (!extendSelection && WasDoubleClick(pose, where, buttons)
7427 			&& buttons == B_PRIMARY_MOUSE_BUTTON
7428 			&& fLastClickButtons == B_PRIMARY_MOUSE_BUTTON
7429 			&& (modifierKeys & B_CONTROL_KEY) == 0) {
7430 			fTrackRightMouseUp = false;
7431 			fTrackMouseUp = false;
7432 			// special handling for path field double-clicks
7433 			if (!WasClickInPath(pose, index, where))
7434 				OpenSelection(pose, &index);
7435 		}
7436 	} else {
7437 		// click was not in any pose
7438 		fLastClickedPose = NULL;
7439 		if (fTextWidgetToCheck != NULL)
7440 			fTextWidgetToCheck->CancelWait();
7441 
7442 		window->Activate();
7443 		window->UpdateIfNeeded();
7444 
7445 		// only clear selection if we are not extending it
7446 		if (!extendSelection || !fSelectionRectEnabled || !fMultipleSelection)
7447 			ClearSelection();
7448 
7449 		// show desktop context menu
7450 		if (SecondaryMouseButtonDown(modifierKeys, buttons))
7451 			ShowContextMenu(where);
7452 	}
7453 
7454 	if (fSelectionChangedHook)
7455 		window->SelectionChanged();
7456 }
7457 
7458 
7459 void
7460 BPoseView::SetTextWidgetToCheck(BTextWidget* widget, BTextWidget* old)
7461 {
7462 	if (old == NULL || fTextWidgetToCheck == old)
7463 		fTextWidgetToCheck = widget;
7464 }
7465 
7466 
7467 void
7468 BPoseView::MouseUp(BPoint where)
7469 {
7470 	if (fSelectionRectInfo.isDragging)
7471 		_EndSelectionRect();
7472 
7473 	int32 index;
7474 	BPose* pose = FindPose(where, &index);
7475 	uint32 lastButtons = Window()->CurrentMessage()->FindInt32("last_buttons");
7476 	if (pose != NULL && fLastClickedPose != NULL && fAllowPoseEditing
7477 		&& !fTrackRightMouseUp) {
7478 		// This handy field has been added by the tracking filter.
7479 		// we need lastButtons for right button mouse-up tracking,
7480 		// because there's currently no way to know wich buttons were
7481 		// released in BView::MouseUp (unlike BView::KeyUp)
7482 		pose->MouseUp(BPoint(0, index * fListElemHeight), this, where, index);
7483 	}
7484 
7485 	// Showing the pose context menu is done on mouse up (or long click)
7486 	// to make right button dragging possible
7487 	if (pose != NULL && fTrackRightMouseUp
7488 		&& (SecondaryMouseButtonDown(modifiers(), lastButtons))) {
7489 		if (!pose->IsSelected()) {
7490 			ClearSelection();
7491 			pose->Select(true);
7492 			fSelectionList->AddItem(pose);
7493 			DrawPose(pose, index, false);
7494 		}
7495 		ShowContextMenu(where);
7496 	}
7497 
7498 	if (fTrackMouseUp)
7499 		Window()->Activate();
7500 
7501 	fTrackRightMouseUp = false;
7502 	fTrackMouseUp = false;
7503 }
7504 
7505 
7506 bool
7507 BPoseView::WasClickInPath(const BPose* pose, int32 index,
7508 	BPoint mouseLocation) const
7509 {
7510 	if (pose == NULL || (ViewMode() != kListMode))
7511 		return false;
7512 
7513 	BPoint loc(0, index * fListElemHeight);
7514 	BTextWidget* widget;
7515 	if (!pose->PointInPose(loc, this, mouseLocation, &widget) || !widget)
7516 		return false;
7517 
7518 	// note: the following code is wrong, because this sort of hashing
7519 	// may overlap and we get aliasing
7520 	if (widget->AttrHash() != AttrHashString(kAttrPath, B_STRING_TYPE))
7521 		return false;
7522 
7523 	BEntry entry(widget->Text(this));
7524 	if (entry.InitCheck() != B_OK)
7525 		return false;
7526 
7527 	entry_ref ref;
7528 	if (entry.GetRef(&ref) == B_OK) {
7529 		BMessage message(B_REFS_RECEIVED);
7530 		message.AddRef("refs", &ref);
7531 		be_app->PostMessage(&message);
7532 		return true;
7533 	}
7534 
7535 	return false;
7536 }
7537 
7538 
7539 bool
7540 BPoseView::WasDoubleClick(const BPose* pose, BPoint point, int32 buttons)
7541 {
7542 	// check proximity
7543 	BPoint delta = point - fLastClickPoint;
7544 	int32 clicks = Window()->CurrentMessage()->FindInt32("clicks");
7545 
7546 	if (clicks == 2
7547 		&& fabs(delta.x) < kDoubleClickTresh
7548 		&& fabs(delta.y) < kDoubleClickTresh
7549 		&& pose == fLastClickedPose) {
7550 		fLastClickPoint.Set(INT32_MAX, INT32_MAX);
7551 		fLastClickedPose = NULL;
7552 		if (fTextWidgetToCheck != NULL)
7553 			fTextWidgetToCheck->CancelWait();
7554 
7555 		return buttons == fLastClickButtons;
7556 	}
7557 
7558 	fLastClickPoint = point;
7559 	fLastClickedPose = pose;
7560 	fLastClickButtons = buttons;
7561 
7562 	return false;
7563 }
7564 
7565 
7566 static void
7567 AddPoseRefToMessage(Model* model, BMessage* message)
7568 {
7569 	// Make sure that every file added to the message has its
7570 	// MIME type set.
7571 	BNode node(model->EntryRef());
7572 	if (node.InitCheck() == B_OK) {
7573 		BNodeInfo info(&node);
7574 		char type[B_MIME_TYPE_LENGTH];
7575 		type[0] = '\0';
7576 		if (info.GetType(type) != B_OK) {
7577 			BPath path(model->EntryRef());
7578 			if (path.InitCheck() == B_OK)
7579 				update_mime_info(path.Path(), false, false, false);
7580 		}
7581 	}
7582 	message->AddRef("refs", model->EntryRef());
7583 }
7584 
7585 
7586 void
7587 BPoseView::DragSelectedPoses(const BPose* pose, BPoint clickPoint)
7588 {
7589 	if (!fDragEnabled)
7590 		return;
7591 
7592 	ASSERT(pose);
7593 
7594 	// make sure pose is selected, it could have been deselected as part of
7595 	// a click during selection extention
7596 	if (!pose->IsSelected())
7597 		return;
7598 
7599 	// setup tracking rect by unioning all selected pose rects
7600 	BMessage message(B_SIMPLE_DATA);
7601 	message.AddPointer("src_window", Window());
7602 	message.AddPoint("click_pt", clickPoint);
7603 
7604 	// add Tracker token so that refs received recipients can script us
7605 	message.AddMessenger("TrackerViewToken", BMessenger(this));
7606 
7607 	// cannot use EachPoseAndModel here, because that iterates the selected
7608 	// poses in reverse order
7609 	int32 selectCount = CountSelected();
7610 	for (int32 index = 0; index < selectCount; index++) {
7611 		AddPoseRefToMessage(fSelectionList->ItemAt(index)->TargetModel(),
7612 			&message);
7613 	}
7614 
7615 	// make sure button is still down
7616 	uint32 buttons;
7617 	BPoint tempLoc;
7618 	GetMouse(&tempLoc, &buttons);
7619 	if (buttons != 0) {
7620 		int32 index = CurrentPoseList()->IndexOf(pose);
7621 		message.AddInt32("buttons", (int32)buttons);
7622 		BRect dragRect(GetDragRect(index));
7623 		BBitmap* dragBitmap = NULL;
7624 		BPoint offset;
7625 #ifdef DRAG_FRAME
7626 		if (dragRect.Width() < kTransparentDragThreshold.x
7627 			&& dragRect.Height() < kTransparentDragThreshold.y) {
7628 			dragBitmap = MakeDragBitmap(dragRect, clickPoint, index, offset);
7629 		}
7630 #else
7631 		// The bitmap is now always created (if DRAG_FRAME is not defined)
7632 		dragBitmap = MakeDragBitmap(dragRect, clickPoint, index, offset);
7633 #endif
7634 		if (dragBitmap != NULL) {
7635 			DragMessage(&message, dragBitmap, B_OP_ALPHA, offset);
7636 				// this DragMessage supports alpha blending
7637 		} else
7638 			DragMessage(&message, dragRect);
7639 
7640 		// turn on auto scrolling
7641 		fAutoScrollState = kWaitForTransition;
7642 		Window()->SetPulseRate(100000);
7643 	}
7644 }
7645 
7646 
7647 BBitmap*
7648 BPoseView::MakeDragBitmap(BRect dragRect, BPoint clickedPoint,
7649 	int32 clickedPoseIndex, BPoint &offset)
7650 {
7651 	BRect inner(clickedPoint.x - kTransparentDragThreshold.x / 2,
7652 		clickedPoint.y - kTransparentDragThreshold.y / 2,
7653 		clickedPoint.x + kTransparentDragThreshold.x / 2,
7654 		clickedPoint.y + kTransparentDragThreshold.y / 2);
7655 
7656 	// (BRect & BRect) doesn't work correctly if the rectangles don't intersect
7657 	// this catches a bug that is produced somewhere before this function is
7658 	// called
7659 	if (!inner.Intersects(dragRect))
7660 		return NULL;
7661 
7662 	inner = inner & dragRect;
7663 
7664 	// If the selection is bigger than the specified limit, the
7665 	// contents will fade out when they come near the borders
7666 	bool fadeTop = false;
7667 	bool fadeBottom = false;
7668 	bool fadeLeft = false;
7669 	bool fadeRight = false;
7670 	bool fade = false;
7671 	if (inner.left > dragRect.left) {
7672 		inner.left = std::max(inner.left - 32, dragRect.left);
7673 		fade = fadeLeft = true;
7674 	}
7675 	if (inner.right < dragRect.right) {
7676 		inner.right = std::min(inner.right + 32, dragRect.right);
7677 		fade = fadeRight = true;
7678 	}
7679 	if (inner.top > dragRect.top) {
7680 		inner.top = std::max(inner.top - 32, dragRect.top);
7681 		fade = fadeTop = true;
7682 	}
7683 	if (inner.bottom < dragRect.bottom) {
7684 		inner.bottom = std::min(inner.bottom + 32, dragRect.bottom);
7685 		fade = fadeBottom = true;
7686 	}
7687 
7688 	// set the offset for the dragged bitmap (for the BView::DragMessage() call)
7689 	offset = clickedPoint - BPoint(2, 1) - inner.LeftTop();
7690 
7691 	BRect rect(inner);
7692 	rect.OffsetTo(B_ORIGIN);
7693 
7694 	BBitmap* bitmap = new BBitmap(rect, B_RGBA32, true);
7695 	bitmap->Lock();
7696 	BView* view = new BView(bitmap->Bounds(), "", B_FOLLOW_NONE, 0);
7697 	bitmap->AddChild(view);
7698 
7699 	view->SetOrigin(0, 0);
7700 
7701 	BRect clipRect(view->Bounds());
7702 	BRegion newClip;
7703 	newClip.Set(clipRect);
7704 	view->ConstrainClippingRegion(&newClip);
7705 
7706 	memset(bitmap->Bits(), 0, bitmap->BitsLength());
7707 
7708 	view->SetDrawingMode(B_OP_ALPHA);
7709 	view->SetHighColor(0, 0, 0, uint8(fade ? 164 : 128));
7710 		// set the level of transparency by value
7711 	view->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE);
7712 
7713 	BRect bounds(Bounds());
7714 
7715 	PoseList* poseList = CurrentPoseList();
7716 	if (ViewMode() == kListMode) {
7717 		int32 poseCount = poseList->CountItems();
7718 		int32 startIndex = (int32)(bounds.top / fListElemHeight);
7719 		BPoint loc(0, startIndex * fListElemHeight);
7720 
7721 		for (int32 index = startIndex; index < poseCount; index++) {
7722 			BPose* pose = poseList->ItemAt(index);
7723 			if (pose->IsSelected()) {
7724 				BRect poseRect(pose->CalcRect(loc, this, true));
7725 				if (poseRect.Intersects(inner)) {
7726 					BPoint offsetBy(-inner.LeftTop().x, -inner.LeftTop().y);
7727 					pose->Draw(poseRect, poseRect, this, view, true, offsetBy,
7728 						false);
7729 				}
7730 			}
7731 			loc.y += fListElemHeight;
7732 			if (loc.y > bounds.bottom)
7733 				break;
7734 		}
7735 	} else {
7736 		// add rects for visible poses only (uses VSList!!)
7737 		int32 startIndex
7738 			= FirstIndexAtOrBelow((int32)(bounds.top - IconPoseHeight()));
7739 		int32 poseCount = fVSPoseList->CountItems();
7740 
7741 		for (int32 index = startIndex; index < poseCount; index++) {
7742 			BPose* pose = fVSPoseList->ItemAt(index);
7743 			if (pose != NULL && pose->IsSelected()) {
7744 				BRect poseRect(pose->CalcRect(this));
7745 				if (!poseRect.Intersects(inner))
7746 					continue;
7747 
7748 				BPoint offsetBy(-inner.LeftTop().x, -inner.LeftTop().y);
7749 				pose->Draw(poseRect, poseRect, this, view, true, offsetBy,
7750 					false);
7751 			}
7752 		}
7753 	}
7754 
7755 	view->Sync();
7756 
7757 	// fade out the contents if necessary
7758 	if (fade) {
7759 		uint32* bits = (uint32*)bitmap->Bits();
7760 		int32 width = bitmap->BytesPerRow() / 4;
7761 
7762 		if (fadeLeft)
7763 			FadeRGBA32Horizontal(bits, width, int32(rect.bottom), 0, 64);
7764 
7765 		if (fadeRight) {
7766 			FadeRGBA32Horizontal(bits, width, int32(rect.bottom),
7767 				int32(rect.right), int32(rect.right) - 64);
7768 		}
7769 
7770 		if (fadeTop)
7771 			FadeRGBA32Vertical(bits, width, int32(rect.bottom), 0, 64);
7772 
7773 		if (fadeBottom) {
7774 			FadeRGBA32Vertical(bits, width, int32(rect.bottom),
7775 				int32(rect.bottom), int32(rect.bottom) - 64);
7776 		}
7777 	}
7778 
7779 	bitmap->Unlock();
7780 	return bitmap;
7781 }
7782 
7783 
7784 BRect
7785 BPoseView::GetDragRect(int32 clickedPoseIndex)
7786 {
7787 	BRect result;
7788 	BRect bounds(Bounds());
7789 
7790 	PoseList* poseList = CurrentPoseList();
7791 	BPose* pose = poseList->ItemAt(clickedPoseIndex);
7792 	if (ViewMode() == kListMode) {
7793 		// get starting rect of clicked pose
7794 		result = CalcPoseRectList(pose, clickedPoseIndex, true);
7795 
7796 		// add rects for visible poses only
7797 		int32 poseCount = poseList->CountItems();
7798 		int32 startIndex = (int32)(bounds.top / fListElemHeight);
7799 		BPoint loc(0, startIndex * fListElemHeight);
7800 
7801 		for (int32 index = startIndex; index < poseCount; index++) {
7802 			pose = poseList->ItemAt(index);
7803 			if (pose->IsSelected())
7804 				result = result | pose->CalcRect(loc, this, true);
7805 
7806 			loc.y += fListElemHeight;
7807 			if (loc.y > bounds.bottom)
7808 				break;
7809 		}
7810 	} else {
7811 		// get starting rect of clicked pose
7812 		result = pose->CalcRect(this);
7813 
7814 		// add rects for visible poses only (uses VSList!!)
7815 		int32 poseCount = fVSPoseList->CountItems();
7816 		for (int32 index = FirstIndexAtOrBelow(
7817 					(int32)(bounds.top - IconPoseHeight()));
7818 				index < poseCount; index++) {
7819 			BPose* pose = fVSPoseList->ItemAt(index);
7820 			if (pose != NULL) {
7821 				if (pose->IsSelected())
7822 					result = result | pose->CalcRect(this);
7823 
7824 				if (pose->Location(this).y > bounds.bottom)
7825 					break;
7826 			}
7827 		}
7828 	}
7829 
7830 	return result;
7831 }
7832 
7833 
7834 void
7835 BPoseView::SelectPoses(BRect selectionRect, BList** oldList)
7836 {
7837 	// TODO: This is a mess due to pose rect calculation and list management
7838 	// being different for list vs. icon modes. Refactoring needed.
7839 
7840 	const bool inListMode = (ViewMode() == kListMode);
7841 
7842 	// collect all the poses which are enclosed inside the selection rect
7843 	BList* newList = new BList;
7844 	BRect bounds(Bounds());
7845 
7846 	int32 startIndex;
7847 	if (inListMode) {
7848 		startIndex = (int32)(selectionRect.top / fListElemHeight);
7849 	} else {
7850 		startIndex = FirstIndexAtOrBelow(
7851 			(int32)(selectionRect.top - IconPoseHeight()), true);
7852 	}
7853 	if (startIndex < 0)
7854 		startIndex = 0;
7855 
7856 	BPoint listLoc;
7857 	if (inListMode)
7858 		listLoc.Set(0, startIndex * fListElemHeight);
7859 
7860 	PoseList* poseList = inListMode ? CurrentPoseList() : fVSPoseList;
7861 	const int32 poseCount = inListMode ? poseList->CountItems() : fPoseList->CountItems();
7862 	for (int32 index = startIndex; index < poseCount; index++) {
7863 		BPose* pose = poseList->ItemAt(index);
7864 		if (pose == NULL)
7865 			continue;
7866 
7867 		BRect poseRect;
7868 		if (inListMode)
7869 			poseRect = pose->CalcRect(listLoc, this);
7870 		else
7871 			poseRect = pose->CalcRect(this);
7872 
7873 		if (selectionRect.Intersects(poseRect)) {
7874 			bool selected = pose->IsSelected();
7875 			pose->Select(!fSelectionList->HasItem(pose));
7876 			newList->AddItem((void*)(addr_t)index);
7877 				// this sucks, need to clean up using a vector class instead
7878 				// of BList
7879 
7880 			if ((selected != pose->IsSelected())
7881 				&& poseRect.Intersects(bounds)) {
7882 				Invalidate(poseRect);
7883 			}
7884 
7885 			// first Pose selected gets to be the pivot.
7886 			if ((fSelectionPivotPose == NULL) && (selected == false))
7887 				fSelectionPivotPose = pose;
7888 		}
7889 
7890 		if (inListMode) {
7891 			listLoc.y += fListElemHeight;
7892 			if (listLoc.y > selectionRect.bottom)
7893 				break;
7894 		} else {
7895 			if (pose->Location(this).y > selectionRect.bottom)
7896 				break;
7897 		}
7898 	}
7899 
7900 	// take the old set of enclosed poses and invert selection state
7901 	// on those which are no longer enclosed
7902 	int32 count = (*oldList)->CountItems();
7903 	for (int32 index = 0; index < count; index++) {
7904 		int32 oldIndex = (addr_t)(*oldList)->ItemAt(index);
7905 
7906 		if (!newList->HasItem((void*)(addr_t)oldIndex)) {
7907 			BPose* pose = poseList->ItemAt(oldIndex);
7908 			pose->Select(!pose->IsSelected());
7909 
7910 			BRect poseRect;
7911 			if (inListMode) {
7912 				listLoc.Set(0, oldIndex * fListElemHeight);
7913 				poseRect = pose->CalcRect(listLoc, this);
7914 			} else {
7915 				poseRect = pose->CalcRect(this);
7916 			}
7917 
7918 			if (poseRect.Intersects(bounds))
7919 				Invalidate(poseRect);
7920 		}
7921 	}
7922 
7923 	delete *oldList;
7924 	*oldList = newList;
7925 }
7926 
7927 
7928 void
7929 BPoseView::AddRemoveSelectionRange(BPoint where, bool extendSelection,
7930 	BPose* pose)
7931 {
7932 	ASSERT(pose != NULL);
7933 
7934  	if (pose == fSelectionPivotPose && !extendSelection)
7935  		return;
7936 
7937 	if (fMultipleSelection && (modifiers() & B_SHIFT_KEY) != 0 && fSelectionPivotPose) {
7938 		// multi pose extend/shrink current selection
7939 		bool select = !pose->IsSelected() || !extendSelection;
7940 			// This weird bit of logic causes the selection to always
7941 			// center around the pivot point, unless you choose to hold
7942 			// down COMMAND, which will unselect between the pivot and
7943 			// the most recently selected Pose.
7944 
7945 		if (!extendSelection) {
7946 			// Remember fSelectionPivotPose because ClearSelection() NULLs it
7947 			// and we need it to be preserved.
7948 			const BPose* savedPivotPose = fSelectionPivotPose;
7949  			ClearSelection();
7950 	 		fSelectionPivotPose = savedPivotPose;
7951 		}
7952 
7953 		if (ViewMode() == kListMode) {
7954 			PoseList* poseList = CurrentPoseList();
7955 			int32 currentSelectedIndex = poseList->IndexOf(pose);
7956 			int32 lastSelectedIndex = poseList->IndexOf(fSelectionPivotPose);
7957 
7958 			int32 startRange;
7959 			int32 endRange;
7960 
7961 			if (lastSelectedIndex < currentSelectedIndex) {
7962 				startRange = lastSelectedIndex;
7963 				endRange = currentSelectedIndex;
7964 			} else {
7965 				startRange = currentSelectedIndex;
7966 				endRange = lastSelectedIndex;
7967 			}
7968 
7969 			for (int32 i = startRange; i <= endRange; i++)
7970 				AddRemovePoseFromSelection(poseList->ItemAt(i), i, select);
7971 		} else {
7972 			BRect selection(where, fSelectionPivotPose->Location(this));
7973 
7974 			// Things will get odd if we don't 'fix' the selection rect.
7975 			if (selection.left > selection.right)
7976 				std::swap(selection.left, selection.right);
7977 
7978 			if (selection.top > selection.bottom)
7979 				std::swap(selection.top, selection.bottom);
7980 
7981 			// If the selection rect is not at least 1 pixel high/wide, things
7982 			// are also not going to work out.
7983 			if (selection.IntegerWidth() < 1)
7984 				selection.right = selection.left + 1.0f;
7985 
7986 			if (selection.IntegerHeight() < 1)
7987 				selection.bottom = selection.top + 1.0f;
7988 
7989 			ASSERT(selection.IsValid());
7990 
7991 			int32 poseCount = fPoseList->CountItems();
7992 			for (int32 index = poseCount - 1; index >= 0; index--) {
7993 				BPose* currPose = fPoseList->ItemAt(index);
7994 				// TODO: works only in non-list mode?
7995 				if (selection.Intersects(currPose->CalcRect(this)))
7996 					AddRemovePoseFromSelection(currPose, index, select);
7997 			}
7998 		}
7999 	} else {
8000 		int32 index = CurrentPoseList()->IndexOf(pose);
8001 		if (!extendSelection) {
8002 			if (!pose->IsSelected()) {
8003 				// create new selection
8004 				ClearSelection();
8005 				AddRemovePoseFromSelection(pose, index, true);
8006 				fSelectionPivotPose = pose;
8007 			}
8008 		} else {
8009 			fMimeTypesInSelectionCache.MakeEmpty();
8010 			AddRemovePoseFromSelection(pose, index, !pose->IsSelected());
8011 		}
8012 	}
8013 
8014 	// If the list is empty, there cannot be a pivot pose,
8015 	// however if the list is not empty there must be a pivot
8016 	// pose.
8017 	if (fSelectionList->IsEmpty()) {
8018 		fSelectionPivotPose = NULL;
8019 		fRealPivotPose = NULL;
8020 	} else if (fSelectionPivotPose == NULL) {
8021 		fSelectionPivotPose = pose;
8022 		fRealPivotPose = pose;
8023 	}
8024 }
8025 
8026 
8027 void
8028 BPoseView::DeleteSymLinkPoseTarget(const node_ref* itemNode, BPose* pose,
8029 	int32 index)
8030 {
8031 	ASSERT(pose->TargetModel()->IsSymLink());
8032 	watch_node(itemNode, B_STOP_WATCHING, this);
8033 
8034 	// watch the parent of the symlink, so that we know when the symlink
8035 	// can be considered fixed.
8036 	WatchParentOf(pose->TargetModel()->EntryRef());
8037 
8038 	BPoint loc(0, index * fListElemHeight);
8039 	pose->TargetModel()->SetLinkTo(NULL);
8040 	pose->UpdateBrokenSymLink(loc, this);
8041 }
8042 
8043 
8044 bool
8045 BPoseView::DeletePose(const node_ref* itemNode, BPose* pose, int32 index)
8046 {
8047 	watch_node(itemNode, B_STOP_WATCHING, this);
8048 
8049 	if (pose == NULL)
8050 		pose = fPoseList->FindPose(itemNode, &index);
8051 
8052 	if (pose != NULL) {
8053 		fInsertedNodes.Remove(*itemNode);
8054 		if (pose->TargetModel()->IsSymLink()) {
8055 			fBrokenLinks->RemoveItem(pose->TargetModel());
8056 			StopWatchingParentsOf(pose->TargetModel()->EntryRef());
8057 			Model* target = pose->TargetModel()->LinkTo();
8058 			if (target)
8059 				watch_node(target->NodeRef(), B_STOP_WATCHING, this);
8060 		}
8061 
8062 		ASSERT(TargetModel());
8063 
8064 		if (pose == fDropTarget)
8065 			fDropTarget = NULL;
8066 
8067 		if (pose == ActivePose())
8068 			CommitActivePose();
8069 
8070 		Window()->UpdateIfNeeded();
8071 
8072 		// remove it from list no matter what since it might be in list
8073 		// but not "selected" since selection is hidden
8074 		fSelectionList->RemoveItem(pose);
8075 		if (fSelectionPivotPose == pose)
8076 			fSelectionPivotPose = NULL;
8077 		if (fRealPivotPose == pose)
8078 			fRealPivotPose = NULL;
8079 
8080 		if (pose->IsSelected() && fSelectionChangedHook)
8081 			ContainerWindow()->SelectionChanged();
8082 
8083 		fPoseList->RemoveItemAt(index);
8084 
8085 		bool visible = true;
8086 		if (fFiltering) {
8087 			if (fFilteredPoseList->FindPose(itemNode, &index) != NULL)
8088 				fFilteredPoseList->RemoveItemAt(index);
8089 			else
8090 				visible = false;
8091 		}
8092 
8093 		fMimeTypeListIsDirty = true;
8094 
8095 		if (pose->HasLocation())
8096 			RemoveFromVSList(pose);
8097 
8098 		if (visible) {
8099 			BRect invalidRect;
8100 			if (ViewMode() == kListMode)
8101 				invalidRect = CalcPoseRectList(pose, index);
8102 			else
8103 				invalidRect = pose->CalcRect(this);
8104 
8105 			if (ViewMode() == kListMode)
8106 				CloseGapInList(&invalidRect);
8107 			else
8108 				RemoveFromExtent(invalidRect);
8109 
8110 			Invalidate(invalidRect);
8111 			UpdateCount();
8112 			UpdateScrollRange();
8113 			ResetPosePlacementHint();
8114 
8115 			if (ViewMode() == kListMode) {
8116 				BRect bounds(Bounds());
8117 				int32 index = (int32)(bounds.bottom / fListElemHeight);
8118 				BPose* pose = CurrentPoseList()->ItemAt(index);
8119 
8120 				if (pose == NULL && bounds.top > 0) {
8121 					// scroll up a little
8122 					BView::ScrollTo(bounds.left,
8123 						std::max(bounds.top - fListElemHeight, 0.0f));
8124 				}
8125 			}
8126 		}
8127 
8128 		delete pose;
8129 	} else {
8130 		// we might be getting a delete for an item in the zombie list
8131 		Model* zombie = FindZombie(itemNode, &index);
8132 		if (zombie) {
8133 			PRINT(("deleting zombie model %s\n", zombie->Name()));
8134 			fZombieList->RemoveItemAt(index);
8135 			delete zombie;
8136 		} else
8137 			return false;
8138 	}
8139 
8140 	return true;
8141 }
8142 
8143 
8144 Model*
8145 BPoseView::FindZombie(const node_ref* itemNode, int32* resultingIndex)
8146 {
8147 	int32 count = fZombieList->CountItems();
8148 	for (int32 index = 0; index < count; index++) {
8149 		Model* zombie = fZombieList->ItemAt(index);
8150 		if (*zombie->NodeRef() == *itemNode) {
8151 			if (resultingIndex)
8152 				*resultingIndex = index;
8153 			return zombie;
8154 		}
8155 	}
8156 
8157 	return NULL;
8158 }
8159 
8160 
8161 // return pose at location h,v (search list starting from bottom so
8162 // drawing and hit detection reflect the same pose ordering)
8163 BPose*
8164 BPoseView::FindPose(BPoint point, int32* poseIndex) const
8165 {
8166 	if (ViewMode() == kListMode) {
8167 		int32 index = (int32)(point.y / fListElemHeight);
8168 		if (poseIndex != NULL)
8169 			*poseIndex = index;
8170 
8171 		BPoint loc(0, index * fListElemHeight);
8172 		BPose* pose = CurrentPoseList()->ItemAt(index);
8173 		if (pose != NULL && pose->PointInPose(loc, this, point))
8174 			return pose;
8175 	} else {
8176 		int32 poseCount = fPoseList->CountItems();
8177 		for (int32 index = poseCount - 1; index >= 0; index--) {
8178 			BPose* pose = fPoseList->ItemAt(index);
8179 			if (pose->PointInPose(this, point)) {
8180 				if (poseIndex)
8181 					*poseIndex = index;
8182 
8183 				return pose;
8184 			}
8185 		}
8186 	}
8187 
8188 	return NULL;
8189 }
8190 
8191 
8192 BPose*
8193 BPoseView::FirstVisiblePose(int32* _index) const
8194 {
8195 	ASSERT(ViewMode() == kListMode);
8196 	return FindPose(BPoint(fListOffset,
8197 		Bounds().top + fListElemHeight - 1), _index);
8198 }
8199 
8200 
8201 BPose*
8202 BPoseView::LastVisiblePose(int32* _index) const
8203 {
8204 	ASSERT(ViewMode() == kListMode);
8205 	BPose* pose = FindPose(BPoint(fListOffset, Bounds().top + Frame().Height()
8206 		- fListElemHeight + 2), _index);
8207 	if (pose == NULL) {
8208 		// Just get the last one
8209 		pose = CurrentPoseList()->LastItem();
8210 		if (_index != NULL)
8211 			*_index = CurrentPoseList()->CountItems() - 1;
8212 	}
8213 	return pose;
8214 }
8215 
8216 
8217 void
8218 BPoseView::OpenSelection(BPose* clickedPose, int32* index)
8219 {
8220 	BPose* singleWindowBrowsePose = clickedPose;
8221 	TrackerSettings settings;
8222 
8223 	// get first selected pose in selection if none was clicked
8224 	if (settings.SingleWindowBrowse()
8225 		&& !singleWindowBrowsePose
8226 		&& CountSelected() == 1
8227 		&& !IsFilePanel()) {
8228 		singleWindowBrowsePose = fSelectionList->ItemAt(0);
8229 	}
8230 
8231 	// check if we can use the single window mode
8232 	if (settings.SingleWindowBrowse()
8233 		&& !IsDesktopWindow()
8234 		&& !IsFilePanel()
8235 		&& (modifiers() & B_OPTION_KEY) == 0
8236 		&& TargetModel()->IsDirectory()
8237 		&& singleWindowBrowsePose
8238 		&& singleWindowBrowsePose->ResolvedModel()
8239 		&& singleWindowBrowsePose->ResolvedModel()->IsDirectory()) {
8240 		// Switch to new directory
8241 		BMessage msg(kSwitchDirectory);
8242 		msg.AddRef("refs", singleWindowBrowsePose->ResolvedModel()->EntryRef());
8243 		Window()->PostMessage(&msg);
8244 	} else {
8245 		// otherwise use standard method
8246 		OpenSelectionCommon(clickedPose, index, false);
8247 	}
8248 
8249 }
8250 
8251 
8252 void
8253 BPoseView::OpenSelectionUsing(BPose* clickedPose, int32* index)
8254 {
8255 	OpenSelectionCommon(clickedPose, index, true);
8256 }
8257 
8258 
8259 void
8260 BPoseView::OpenSelectionCommon(BPose* clickedPose, int32* poseIndex,
8261 	bool openWith)
8262 {
8263 	int32 selectCount = CountSelected();
8264 	if (selectCount == 0)
8265 		return;
8266 
8267 	BMessage message(B_REFS_RECEIVED);
8268 
8269 	for (int32 index = 0; index < selectCount; index++) {
8270 		BPose* pose = fSelectionList->ItemAt(index);
8271 		message.AddRef("refs", pose->TargetModel()->EntryRef());
8272 
8273 		// close parent window if option down and we're not the desktop
8274 		// and we're not in single window mode
8275 		if (dynamic_cast<TTracker*>(be_app) == NULL
8276 			|| (modifiers() & B_OPTION_KEY) == 0
8277 			|| IsFilePanel()
8278 			|| IsDesktopWindow()
8279 			|| TrackerSettings().SingleWindowBrowse()) {
8280 			continue;
8281 		}
8282 
8283 		ASSERT(TargetModel());
8284 		message.AddData("nodeRefsToClose", B_RAW_TYPE, TargetModel()->NodeRef(),
8285 			sizeof (node_ref));
8286 	}
8287 
8288 	if (openWith)
8289 		message.AddInt32("launchUsingSelector", 0);
8290 
8291 	// add a messenger to the launch message that will be used to
8292 	// dispatch scripting calls from apps to the PoseView
8293 	message.AddMessenger("TrackerViewToken", BMessenger(this));
8294 
8295 	if (fSelectionHandler)
8296 		fSelectionHandler->PostMessage(&message);
8297 
8298 	if (clickedPose) {
8299 		ASSERT(poseIndex);
8300 		if (ViewMode() == kListMode)
8301 			DrawOpenAnimation(CalcPoseRectList(clickedPose, *poseIndex, true));
8302 		else
8303 			DrawOpenAnimation(clickedPose->CalcRect(this));
8304 	}
8305 }
8306 
8307 
8308 void
8309 BPoseView::DrawOpenAnimation(BRect rect)
8310 {
8311 	SetDrawingMode(B_OP_INVERT);
8312 
8313 	BRect box1(rect);
8314 	box1.InsetBy(rect.Width() / 2 - 2, rect.Height() / 2 - 2);
8315 	BRect box2(box1);
8316 
8317 	for (int32 index = 0; index < 7; index++) {
8318 		box2 = box1;
8319 		box2.InsetBy(-2, -2);
8320 		StrokeRect(box1, B_MIXED_COLORS);
8321 		Sync();
8322 		StrokeRect(box2, B_MIXED_COLORS);
8323 		Sync();
8324 		snooze(10000);
8325 		StrokeRect(box1, B_MIXED_COLORS);
8326 		StrokeRect(box2, B_MIXED_COLORS);
8327 		Sync();
8328 		box1 = box2;
8329 	}
8330 
8331 	SetDrawingMode(B_OP_OVER);
8332 }
8333 
8334 
8335 void
8336 BPoseView::ApplyBackgroundColor()
8337 {
8338 	float bgTint = TargetVolumeIsReadOnly()
8339 		? ReadOnlyTint(ui_color(B_DOCUMENT_BACKGROUND_COLOR)) : B_NO_TINT;
8340 	SetViewUIColor(B_DOCUMENT_BACKGROUND_COLOR, bgTint);
8341 	SetLowUIColor(B_DOCUMENT_BACKGROUND_COLOR, bgTint);
8342 }
8343 
8344 
8345 void
8346 BPoseView::UnmountSelectedVolumes()
8347 {
8348 	BVolume boot;
8349 	BVolumeRoster().GetBootVolume(&boot);
8350 
8351 	int32 selectCount = CountSelected();
8352 	for (int32 index = 0; index < selectCount; index++) {
8353 		BPose* pose = fSelectionList->ItemAt(index);
8354 		if (pose == NULL)
8355 			continue;
8356 
8357 		Model* model = pose->TargetModel();
8358 		if (model->IsVolume()) {
8359 			BVolume volume(model->NodeRef()->device);
8360 			if (volume != boot) {
8361 				TTracker* tracker = dynamic_cast<TTracker*>(be_app);
8362 				if (tracker != NULL)
8363 					tracker->SaveAllPoseLocations();
8364 
8365 				BMessage message(kUnmountVolume);
8366 				message.AddInt32("device_id", volume.Device());
8367 				be_app->PostMessage(&message);
8368 			}
8369 		}
8370 	}
8371 }
8372 
8373 
8374 void
8375 BPoseView::ClearPoses()
8376 {
8377 	CommitActivePose();
8378 	SavePoseLocations();
8379 	ClearFilter();
8380 
8381 	// clear all pose lists
8382 	fPoseList->MakeEmpty();
8383 	fMimeTypeListIsDirty = true;
8384 	fVSPoseList->MakeEmpty();
8385 	fZombieList->MakeEmpty();
8386 	fSelectionList->MakeEmpty();
8387 	fSelectionPivotPose = NULL;
8388 	fRealPivotPose = NULL;
8389 	fMimeTypesInSelectionCache.MakeEmpty();
8390 	fBrokenLinks->MakeEmpty();
8391 
8392 	DisableScrollBars();
8393 	ScrollTo(B_ORIGIN);
8394 	UpdateScrollRange();
8395 	SetScrollBarsTo(B_ORIGIN);
8396 	EnableScrollBars();
8397 	ResetPosePlacementHint();
8398 	ClearExtent();
8399 
8400 	if (fSelectionChangedHook)
8401 		ContainerWindow()->SelectionChanged();
8402 }
8403 
8404 
8405 void
8406 BPoseView::SwitchDir(const entry_ref* newDirRef, AttributeStreamNode* node)
8407 {
8408 	ASSERT(TargetModel());
8409 	if (*newDirRef == *TargetModel()->EntryRef())
8410 		// no change
8411 		return;
8412 
8413 	Model* model = new Model(newDirRef, true);
8414 	if (model->InitCheck() != B_OK || !model->IsDirectory()) {
8415 		delete model;
8416 		return;
8417 	}
8418 
8419 	CommitActivePose();
8420 
8421 	// before clearing and adding new poses, we reset "blessed" async
8422 	// thread id to prevent old add_poses thread from adding any more icons
8423 	// the new add_poses thread will then set fAddPosesThread to its ID and it
8424 	// will be allowed to add icons
8425 	fAddPosesThreads.clear();
8426 	fInsertedNodes.Clear();
8427 
8428 	delete fModel;
8429 	fModel = model;
8430 
8431 	// check if model is a trash dir, if so
8432 	// update ContainerWindow's fIsTrash, etc.
8433 	// variables to indicate new state
8434 	if (ContainerWindow() != NULL)
8435 		ContainerWindow()->UpdateIfTrash(model);
8436 
8437 	StopWatching();
8438 	ClearPoses();
8439 
8440 	// Restore state, might fail if the state has never been saved for this node
8441 	uint32 oldMode = ViewMode();
8442 	bool viewStateRestored = false;
8443 	if (node != NULL) {
8444 		BViewState* previousState = fViewState;
8445 		RestoreState(node);
8446 		viewStateRestored = (fViewState != previousState);
8447 	}
8448 
8449 	if (viewStateRestored) {
8450 		if (ViewMode() == kListMode && oldMode != kListMode) {
8451 			if (ContainerWindow() != NULL)
8452 				ContainerWindow()->ShowAttributesMenu();
8453 
8454 			fTitleView->Show();
8455 		} else if (ViewMode() != kListMode && oldMode == kListMode) {
8456 			fTitleView->Hide();
8457 
8458 			if (ContainerWindow() != NULL)
8459 				ContainerWindow()->HideAttributesMenu();
8460 		} else if (ViewMode() == kListMode && oldMode == kListMode)
8461 			fTitleView->Invalidate();
8462 
8463 		BPoint origin;
8464 		if (ViewMode() == kListMode)
8465 			origin = fViewState->ListOrigin();
8466 		else
8467 			origin = fViewState->IconOrigin();
8468 
8469 		PinPointToValidRange(origin);
8470 
8471 		SetIconPoseHeight();
8472 		GetLayoutInfo(ViewMode(), &fGrid, &fOffset);
8473 		ResetPosePlacementHint();
8474 
8475 		DisableScrollBars();
8476 		ScrollTo(origin);
8477 		UpdateScrollRange();
8478 		SetScrollBarsTo(origin);
8479 		EnableScrollBars();
8480 	} else {
8481 		ResetOrigin();
8482 		ResetPosePlacementHint();
8483 	}
8484 
8485 	StartWatching();
8486 
8487 	// be sure this happens after origin is set and window is sized
8488 	// properly for proper icon caching!
8489 
8490 	if (ContainerWindow() != NULL && ContainerWindow()->IsTrash())
8491 		AddTrashPoses();
8492 	else
8493 		AddPoses(TargetModel());
8494 	TargetModel()->CloseNode();
8495 
8496 	if (!IsDesktopWindow()) {
8497 		ApplyBackgroundColor();
8498 		if (ContainerWindow() != NULL)
8499 			ContainerWindow()->UpdateBackgroundImage();
8500 	}
8501 
8502 	Invalidate();
8503 
8504 	fLastKeyTime = 0;
8505 }
8506 
8507 
8508 void
8509 BPoseView::Refresh()
8510 {
8511 	ASSERT(TargetModel());
8512 	if (TargetModel()->OpenNode() != B_OK)
8513 		return;
8514 
8515 	StopWatching();
8516 	fInsertedNodes.Clear();
8517 	ClearPoses();
8518 	StartWatching();
8519 
8520 	// be sure this happens after origin is set and window is sized
8521 	// properly for proper icon caching!
8522 	AddPoses(TargetModel());
8523 	TargetModel()->CloseNode();
8524 
8525 	if (fRefFilter != NULL) {
8526 		fFiltering = false;
8527 		StartFiltering();
8528 	}
8529 
8530 	Invalidate();
8531 	ResetOrigin();
8532 	ResetPosePlacementHint();
8533 }
8534 
8535 
8536 void
8537 BPoseView::ResetOrigin()
8538 {
8539 	DisableScrollBars();
8540 	ScrollTo(B_ORIGIN);
8541 	UpdateScrollRange();
8542 	SetScrollBarsTo(B_ORIGIN);
8543 	EnableScrollBars();
8544 }
8545 
8546 
8547 void
8548 BPoseView::EditQueries()
8549 {
8550 	// edit selected queries
8551 	SendSelectionAsRefs(kEditQuery, true);
8552 }
8553 
8554 
8555 void
8556 BPoseView::SendSelectionAsRefs(uint32 what, bool onlyQueries)
8557 {
8558 	// fix this by having a proper selection iterator
8559 
8560 	int32 selectCount = CountSelected();
8561 	if (selectCount <= 0)
8562 		return;
8563 
8564 	bool haveRef = false;
8565 	BMessage message;
8566 	message.what = what;
8567 
8568 	for (int32 index = 0; index < selectCount; index++) {
8569 		BPose* pose = fSelectionList->ItemAt(index);
8570 		if (onlyQueries) {
8571 			// to check if pose is a query, follow any symlink first
8572 			BEntry resolvedEntry(pose->TargetModel()->EntryRef(), true);
8573 			if (resolvedEntry.InitCheck() != B_OK)
8574 				continue;
8575 
8576 			Model model(&resolvedEntry);
8577 			if (!model.IsQuery() && !model.IsQueryTemplate())
8578 				continue;
8579 		}
8580 		haveRef = true;
8581 		message.AddRef("refs", pose->TargetModel()->EntryRef());
8582 	}
8583 	if (!haveRef)
8584 		return;
8585 
8586 	if (onlyQueries)
8587 		// this is used to make query templates come up in a special edit window
8588 		message.AddBool("editQueryOnPose", onlyQueries);
8589 
8590 	BMessenger(kTrackerSignature).SendMessage(&message);
8591 }
8592 
8593 
8594 void
8595 BPoseView::OpenInfoWindows()
8596 {
8597 	BMessenger tracker(kTrackerSignature);
8598 	if (!tracker.IsValid()) {
8599 		BAlert* alert = new BAlert("",
8600 			B_TRANSLATE("The Tracker must be running to see Info windows."),
8601 			B_TRANSLATE("Cancel"), NULL, NULL, B_WIDTH_AS_USUAL,
8602 			B_WARNING_ALERT);
8603 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
8604 		alert->Go();
8605 		return;
8606 	}
8607 
8608 	if (fSelectionList != NULL && fSelectionList->CountItems() > 0)
8609 		SendSelectionAsRefs(kGetInfo);
8610 	else if (TargetModel()->EntryRef() != NULL) {
8611 		BMessage message(kGetInfo);
8612 		message.AddRef("refs", TargetModel()->EntryRef());
8613 		BMessenger(kTrackerSignature).SendMessage(&message);
8614 	}
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 	BEntry entry;
8869 	BNode parent;
8870 	node_ref nref;
8871 	int32 selectCount = fSelectionList->CountItems();
8872 
8873 	if (selectCount > 1 && TargetModel()->IsQuery()) {
8874 		// multiple items selected in query, consider the whole selection
8875 		// to be read-only if any item's volume is read-only
8876 		for (int32 i = 0; i < selectCount; i++) {
8877 			BPose* pose = fSelectionList->ItemAt(i);
8878 			if (pose == NULL || pose->TargetModel() == NULL)
8879 				continue;
8880 
8881 			entry.SetTo(pose->TargetModel()->EntryRef());
8882 			if (FSGetParentVirtualDirectoryAware(entry, parent) == B_OK) {
8883 				parent.GetNodeRef(&nref);
8884 				volume.SetTo(nref.device);
8885 				if (volume.InitCheck() == B_OK && volume.IsReadOnly())
8886 					return true;
8887 			}
8888 		}
8889 	} else if (selectCount > 0) {
8890 		// only check first item's volume, assume rest are the same
8891 		entry.SetTo(fSelectionList->FirstItem()->TargetModel()->EntryRef());
8892 		if (FSGetParentVirtualDirectoryAware(entry, parent) == B_OK) {
8893 			parent.GetNodeRef(&nref);
8894 			volume.SetTo(nref.device);
8895 		}
8896 	} else {
8897 		// no items selected, check target volume instead
8898 		volume.SetTo(TargetModel()->NodeRef()->device);
8899 	}
8900 
8901 	return volume.InitCheck() == B_OK && volume.IsReadOnly();
8902 }
8903 
8904 
8905 bool
8906 BPoseView::TargetVolumeIsReadOnly() const
8907 {
8908 	Model* target = TargetModel();
8909 	BVolume volume(target->NodeRef()->device);
8910 
8911 	return target->IsQuery() || target->IsQueryTemplate()
8912 		|| target->IsVirtualDirectory()
8913 		|| (volume.InitCheck() == B_OK && volume.IsReadOnly());
8914 }
8915 
8916 
8917 bool
8918 BPoseView::CanEditName() const
8919 {
8920 	if (CountSelected() != 1)
8921 		return false;
8922 
8923 	Model* selected = fSelectionList->FirstItem()->TargetModel();
8924 	return !ActivePose() && selected != NULL && !selected->IsDesktop()
8925 		&& !selected->IsRoot() && !selected->IsTrash();
8926 }
8927 
8928 
8929 bool
8930 BPoseView::CanMoveToTrashOrDuplicate() const
8931 {
8932 	const int32 selectCount = CountSelected();
8933 	if (selectCount < 1)
8934 		return false;
8935 
8936 	if (SelectedVolumeIsReadOnly())
8937 		return false;
8938 
8939 	BPose* pose;
8940 	Model* selected;
8941 	for (int32 i = 0; i < selectCount; i++) {
8942 		pose = fSelectionList->ItemAt(i);
8943 		selected = pose->TargetModel();
8944 		if (pose == NULL || selected == NULL)
8945 			continue;
8946 
8947 		if (selected->IsDesktop() || selected->IsRoot() || selected->IsTrash())
8948 			return false;
8949 	}
8950 
8951 	return true;
8952 }
8953 
8954 
8955 void
8956 BPoseView::RemoveFromExtent(const BRect &rect)
8957 {
8958 	ASSERT(ViewMode() != kListMode);
8959 
8960 	if (rect.left <= fExtent.left || rect.right >= fExtent.right
8961 		|| rect.top <= fExtent.top || rect.bottom >= fExtent.bottom)
8962 		RecalcExtent();
8963 }
8964 
8965 
8966 void
8967 BPoseView::RecalcExtent()
8968 {
8969 	ASSERT(ViewMode() != kListMode);
8970 
8971 	ClearExtent();
8972 	int32 poseCount = fPoseList->CountItems();
8973 	for (int32 index = 0; index < poseCount; index++)
8974 		AddToExtent(fPoseList->ItemAt(index)->CalcRect(this));
8975 }
8976 
8977 
8978 BRect
8979 BPoseView::Extent() const
8980 {
8981 	BRect rect;
8982 
8983 	if (ViewMode() == kListMode) {
8984 		BColumn* column = fColumnList->LastItem();
8985 		if (column != NULL) {
8986 			rect.left = rect.top = 0;
8987 			rect.right = column->Offset() + column->Width()
8988 				+ kTitleColumnRightExtraMargin - kRoomForLine / 2.0f;
8989 			rect.bottom = fListElemHeight * CurrentPoseList()->CountItems();
8990 		} else
8991 			rect.Set(LeftTop().x, LeftTop().y, LeftTop().x, LeftTop().y);
8992 	} else {
8993 		rect = fExtent;
8994 		rect.left -= fOffset.x;
8995 		rect.top -= fOffset.y;
8996 		rect.right += fOffset.x;
8997 		rect.bottom += fOffset.y;
8998 		if (!rect.IsValid())
8999 			rect.Set(LeftTop().x, LeftTop().y, LeftTop().x, LeftTop().y);
9000 	}
9001 
9002 	return rect;
9003 }
9004 
9005 
9006 void
9007 BPoseView::SetScrollBarsTo(BPoint point)
9008 {
9009 	if (fHScrollBar && fVScrollBar) {
9010 		fHScrollBar->SetValue(point.x);
9011 		fVScrollBar->SetValue(point.y);
9012 	} else {
9013 		// TODO: I don't know what this was supposed to work around
9014 		// (ie why it wasn't calling ScrollTo(point) simply). Although
9015 		// it cannot have been tested, since it was broken before, I am
9016 		// still leaving this, since I know there can be a subtle change in
9017 		// behaviour (BView<->BScrollBar feedback effects) when scrolling
9018 		// both directions at once versus separately.
9019 		BPoint origin = LeftTop();
9020 		ScrollTo(BPoint(origin.x, point.y));
9021 		ScrollTo(point);
9022 	}
9023 }
9024 
9025 
9026 void
9027 BPoseView::PinPointToValidRange(BPoint& origin)
9028 {
9029 	// !NaN and valid range
9030 	// the following checks are not broken even they look like they are
9031 	if (!(origin.x >= 0) && !(origin.x <= 0))
9032 		origin.x = 0;
9033 	else if (origin.x < -40000.0 || origin.x > 40000.0)
9034 		origin.x = 0;
9035 
9036 	if (!(origin.y >= 0) && !(origin.y <= 0))
9037 		origin.y = 0;
9038 	else if (origin.y < -40000.0 || origin.y > 40000.0)
9039 		origin.y = 0;
9040 }
9041 
9042 
9043 void
9044 BPoseView::UpdateScrollRange()
9045 {
9046 	// TODO: some calls to UpdateScrollRange don't do the right thing because
9047 	// Extent doesn't return the right value (too early in PoseView lifetime??)
9048 	//
9049 	// This happened most with file panels, when opening a parent - added
9050 	// an extra call to UpdateScrollRange in SelectChildInParent to work
9051 	// around this
9052 
9053 	AutoLock<BWindow> lock(Window());
9054 	if (!lock)
9055 		return;
9056 
9057 	BRect bounds(Bounds());
9058 
9059 	BPoint origin(LeftTop());
9060 	BRect extent(Extent());
9061 
9062 	lock.Unlock();
9063 
9064 	BPoint minVal(std::min(extent.left, origin.x),
9065 		std::min(extent.top, origin.y));
9066 
9067 	BPoint maxVal((extent.right - bounds.right) + origin.x,
9068 		(extent.bottom - bounds.bottom) + origin.y);
9069 
9070 	maxVal.x = std::max(maxVal.x, origin.x);
9071 	maxVal.y = std::max(maxVal.y, origin.y);
9072 
9073 	if (fHScrollBar) {
9074 		float scrollMin;
9075 		float scrollMax;
9076 		fHScrollBar->GetRange(&scrollMin, &scrollMax);
9077 		if (minVal.x != scrollMin || maxVal.x != scrollMax) {
9078 			fHScrollBar->SetRange(minVal.x, maxVal.x);
9079 			fHScrollBar->SetSteps(fListElemHeight / 2.0f, bounds.Width());
9080 		}
9081 	}
9082 
9083 	if (fVScrollBar) {
9084 		float scrollMin;
9085 		float scrollMax;
9086 		fVScrollBar->GetRange(&scrollMin, &scrollMax);
9087 
9088 		if (minVal.y != scrollMin || maxVal.y != scrollMax) {
9089 			fVScrollBar->SetRange(minVal.y, maxVal.y);
9090 			fVScrollBar->SetSteps(fListElemHeight / 2.0f, bounds.Height());
9091 		}
9092 	}
9093 
9094 	// set proportions for bars
9095 	BRect totalExtent(extent | bounds);
9096 
9097 	if (fHScrollBar && totalExtent.Width() != 0.0) {
9098 		float proportion = bounds.Width() / totalExtent.Width();
9099 		if (fHScrollBar->Proportion() != proportion)
9100 			fHScrollBar->SetProportion(proportion);
9101 	}
9102 
9103 	if (fVScrollBar && totalExtent.Height() != 0.0) {
9104 		float proportion = bounds.Height() / totalExtent.Height();
9105 		if (fVScrollBar->Proportion() != proportion)
9106 			fVScrollBar->SetProportion(proportion);
9107 	}
9108 }
9109 
9110 
9111 void
9112 BPoseView::DrawPose(BPose* pose, int32 index, bool fullDraw)
9113 {
9114 	BRect rect = CalcPoseRect(pose, index, fullDraw);
9115 
9116 	if (TrackerSettings().ShowVolumeSpaceBar()
9117 		&& pose->TargetModel()->IsVolume()) {
9118 		Invalidate(rect);
9119 	} else
9120 		pose->Draw(rect, rect, this, fullDraw);
9121 }
9122 
9123 
9124 rgb_color
9125 BPoseView::TextColor(bool selected) const
9126 {
9127 	if (IsDesktopWindow())
9128 		return DeskTextColor();
9129 
9130 	if (selected)
9131 		return ui_color(B_DOCUMENT_BACKGROUND_COLOR);
9132 	else
9133 		return ui_color(B_DOCUMENT_TEXT_COLOR);
9134 }
9135 
9136 
9137 rgb_color
9138 BPoseView::BackColor(bool selected) const
9139 {
9140 	if (selected) {
9141 		if (IsDesktopWindow())
9142 			return DeskTextBackColor();
9143 
9144 		return InvertedBackColor();
9145 	} else {
9146 		if (IsDesktopWindow())
9147 			return BView::ViewColor();
9148 
9149 		rgb_color background = ui_color(B_DOCUMENT_BACKGROUND_COLOR);
9150 		return tint_color(background,
9151 			TargetVolumeIsReadOnly() ? ReadOnlyTint(background) : B_NO_TINT);
9152 	}
9153 }
9154 
9155 
9156 rgb_color
9157 BPoseView::InvertedBackColor() const
9158 {
9159 	rgb_color background = ui_color(B_DOCUMENT_BACKGROUND_COLOR);
9160 	rgb_color inverted = invert_color(background);
9161 
9162 	// The colors are different enough, we can use inverted
9163 	if (rgb_color::Contrast(background, inverted) > 127)
9164 		return inverted;
9165 
9166 	// use black or white
9167 	return background.IsLight() ? kBlack : kWhite;
9168 }
9169 
9170 
9171 void
9172 BPoseView::Draw(BRect updateRect)
9173 {
9174 	if (IsDesktopWindow()) {
9175 		BScreen screen(Window());
9176 		rgb_color color = screen.DesktopColor();
9177 		SetLowColor(color);
9178 		SetViewColor(color);
9179 	}
9180 	DrawViewCommon(updateRect);
9181 
9182 	if ((Flags() & B_DRAW_ON_CHILDREN) == 0)
9183 		DrawAfterChildren(updateRect);
9184 }
9185 
9186 
9187 void
9188 BPoseView::DrawAfterChildren(BRect updateRect)
9189 {
9190 	if (fTransparentSelection && fSelectionRectInfo.rect.IsValid()) {
9191 		SetDrawingMode(B_OP_ALPHA);
9192 		rgb_color color = ui_color(B_NAVIGATION_BASE_COLOR);
9193 		color.alpha = 128;
9194 		SetHighColor(color);
9195 		if (fSelectionRectInfo.rect.Width() == 0
9196 			|| fSelectionRectInfo.rect.Height() == 0) {
9197 			StrokeLine(fSelectionRectInfo.rect.LeftTop(),
9198 				fSelectionRectInfo.rect.RightBottom());
9199 		} else {
9200 			StrokeRect(fSelectionRectInfo.rect);
9201 			BRect interior = fSelectionRectInfo.rect;
9202 			interior.InsetBy(1, 1);
9203 			if (interior.IsValid()) {
9204 				color = ui_color(B_CONTROL_HIGHLIGHT_COLOR);
9205 				color.alpha = 90;
9206 				SetHighColor(color);
9207 				FillRect(interior);
9208 			}
9209 		}
9210 		SetDrawingMode(B_OP_OVER);
9211 	}
9212 }
9213 
9214 
9215 void
9216 BPoseView::SynchronousUpdate(BRect updateRect, bool clip)
9217 {
9218 	if (clip) {
9219 		BRegion updateRegion;
9220 		updateRegion.Set(updateRect);
9221 		ConstrainClippingRegion(&updateRegion);
9222 	}
9223 
9224 	Invalidate(updateRect);
9225 	Window()->UpdateIfNeeded();
9226 
9227 	if (clip)
9228 		ConstrainClippingRegion(NULL);
9229 }
9230 
9231 
9232 void
9233 BPoseView::DrawViewCommon(const BRect& updateRect)
9234 {
9235 	if (ViewMode() == kListMode) {
9236 		PoseList* poseList = CurrentPoseList();
9237 		int32 poseCount = poseList->CountItems();
9238 		int32 startIndex
9239 			= (int32)((updateRect.top - fListElemHeight) / fListElemHeight);
9240 
9241 		if (startIndex < 0)
9242 			startIndex = 0;
9243 
9244 		BPoint location(0, startIndex * fListElemHeight);
9245 
9246 		for (int32 index = startIndex; index < poseCount; index++) {
9247 			BPose* pose = poseList->ItemAt(index);
9248 			BRect poseRect(pose->CalcRect(location, this, true));
9249 			pose->Draw(poseRect, updateRect, this, true);
9250 			location.y += fListElemHeight;
9251 			if (location.y >= updateRect.bottom)
9252 				break;
9253 		}
9254 	} else {
9255 		int32 poseCount = fPoseList->CountItems();
9256 		for (int32 index = 0; index < poseCount; index++) {
9257 			BPose* pose = fPoseList->ItemAt(index);
9258 			BRect poseRect(pose->CalcRect(this));
9259 			if (updateRect.Intersects(poseRect))
9260 				pose->Draw(poseRect, updateRect, this, true);
9261 		}
9262 	}
9263 }
9264 
9265 
9266 void
9267 BPoseView::ColumnRedraw(BRect updateRect)
9268 {
9269 	// used for dynamic column resizing using an offscreen draw buffer
9270 	ASSERT(ViewMode() == kListMode);
9271 
9272 #if COLUMN_MODE_ON_DESKTOP
9273 	if (IsDesktopWindow()) {
9274 		BScreen screen(Window());
9275 		rgb_color d = screen.DesktopColor();
9276 		SetLowColor(d);
9277 		SetViewColor(d);
9278 	}
9279 #endif
9280 
9281 	int32 startIndex
9282 		= (int32)((updateRect.top - fListElemHeight) / fListElemHeight);
9283 	if (startIndex < 0)
9284 		startIndex = 0;
9285 
9286 	PoseList* poseList = CurrentPoseList();
9287 	int32 poseCount = poseList->CountItems();
9288 	if (poseCount <= 0)
9289 		return;
9290 
9291 	BPoint location(0, startIndex * fListElemHeight);
9292 	BRect srcRect = poseList->ItemAt(0)->CalcRect(B_ORIGIN, this, false);
9293 	srcRect.right += 1024;	// need this to erase correctly
9294 	sOffscreen->BeginUsing(srcRect);
9295 	BView* offscreenView = sOffscreen->View();
9296 
9297 	BRegion updateRegion;
9298 	updateRegion.Set(updateRect);
9299 	ConstrainClippingRegion(&updateRegion);
9300 
9301 	for (int32 index = startIndex; index < poseCount; index++) {
9302 		BPose* pose = poseList->ItemAt(index);
9303 
9304 		offscreenView->SetDrawingMode(B_OP_COPY);
9305 		offscreenView->SetLowColor(LowColor());
9306 		offscreenView->FillRect(offscreenView->Bounds(), B_SOLID_LOW);
9307 
9308 		BRect dstRect = srcRect;
9309 		dstRect.OffsetTo(location);
9310 
9311 		BPoint offsetBy(0, -(index * ListElemHeight()));
9312 		pose->Draw(dstRect, updateRect, this, offscreenView, true,
9313 			offsetBy, pose->IsSelected());
9314 
9315 		offscreenView->Sync();
9316 		SetDrawingMode(B_OP_COPY);
9317 		DrawBitmap(sOffscreen->Bitmap(), srcRect, dstRect);
9318 		location.y += fListElemHeight;
9319 		if (location.y > updateRect.bottom)
9320 			break;
9321 	}
9322 
9323 	sOffscreen->DoneUsing();
9324 	ConstrainClippingRegion(0);
9325 }
9326 
9327 
9328 void
9329 BPoseView::CloseGapInList(BRect* invalidRect)
9330 {
9331 	(*invalidRect).bottom = Extent().bottom + fListElemHeight;
9332 	BRect bounds(Bounds());
9333 
9334 	if (bounds.Intersects(*invalidRect)) {
9335 		BRect destRect(*invalidRect);
9336 		destRect = destRect & bounds;
9337 		destRect.bottom -= fListElemHeight;
9338 
9339 		BRect srcRect(destRect);
9340 		srcRect.OffsetBy(0, fListElemHeight);
9341 
9342 		if (srcRect.Intersects(bounds) || destRect.Intersects(bounds))
9343 			CopyBits(srcRect, destRect);
9344 
9345 		*invalidRect = srcRect;
9346 		(*invalidRect).top = destRect.bottom;
9347 	}
9348 }
9349 
9350 
9351 void
9352 BPoseView::CheckPoseSortOrder(BPose* pose, int32 oldIndex)
9353 {
9354 	_CheckPoseSortOrder(CurrentPoseList(), pose, oldIndex);
9355 }
9356 
9357 
9358 void
9359 BPoseView::_CheckPoseSortOrder(PoseList* poseList, BPose* pose, int32 oldIndex)
9360 {
9361 	if (ViewMode() != kListMode)
9362 		return;
9363 
9364 	Window()->UpdateIfNeeded();
9365 
9366 	// take pose out of list for BSearch
9367 	poseList->RemoveItemAt(oldIndex);
9368 	int32 afterIndex;
9369 	int32 orientation = BSearchList(poseList, pose, &afterIndex, oldIndex);
9370 
9371 	int32 newIndex;
9372 	if (orientation == kInsertAtFront)
9373 		newIndex = 0;
9374 	else
9375 		newIndex = afterIndex + 1;
9376 
9377 	if (newIndex == oldIndex) {
9378 		poseList->AddItem(pose, oldIndex);
9379 		return;
9380 	}
9381 
9382 	if (fFiltering && poseList != fFilteredPoseList) {
9383 		poseList->AddItem(pose, newIndex);
9384 		return;
9385 	}
9386 
9387 	BRect invalidRect(CalcPoseRectList(pose, oldIndex));
9388 	CloseGapInList(&invalidRect);
9389 	Invalidate(invalidRect);
9390 		// need to invalidate for the last item in the list
9391 	InsertPoseAfter(pose, &afterIndex, orientation, &invalidRect);
9392 	poseList->AddItem(pose, newIndex);
9393 	Invalidate(invalidRect);
9394 }
9395 
9396 
9397 static int
9398 PoseCompareAddWidget(const BPose* p1, const BPose* p2, BPoseView* view)
9399 {
9400 	// pose comparison and lazy text widget adding
9401 
9402 	uint32 sort = view->PrimarySort();
9403 	BColumn* column = view->ColumnFor(sort);
9404 	if (column == NULL)
9405 		return 0;
9406 
9407 	BPose* primary;
9408 	BPose* secondary;
9409 	if (!view->ReverseSort()) {
9410 		primary = const_cast<BPose*>(p1);
9411 		secondary = const_cast<BPose*>(p2);
9412 	} else {
9413 		primary = const_cast<BPose*>(p2);
9414 		secondary = const_cast<BPose*>(p1);
9415 	}
9416 
9417 	int32 result = 0;
9418 	// We perform a loop in case there is a secondary sort
9419 	for (int32 count = 0; ; count++) {
9420 		BTextWidget* widget1 = primary->WidgetFor(sort);
9421 		if (widget1 == NULL)
9422 			widget1 = primary->AddWidget(view, column);
9423 
9424 		BTextWidget* widget2 = secondary->WidgetFor(sort);
9425 		if (widget2 == NULL)
9426 			widget2 = secondary->AddWidget(view, column);
9427 
9428 		if (widget1 == NULL || widget2 == NULL)
9429 			return result;
9430 
9431 		result = widget1->Compare(*widget2, view);
9432 
9433 		// We either have a non-equal result, or are on the second iteration
9434 		// for secondary sort. Either way, return.
9435 		if (result != 0 || count != 0)
9436 			return result;
9437 
9438 		// Non-equal result, sort by secondary attribute
9439 		sort = view->SecondarySort();
9440 		if (!sort)
9441 			return result;
9442 
9443 		column = view->ColumnFor(sort);
9444 		if (column == NULL)
9445 			return result;
9446 	}
9447 
9448 	return result;
9449 }
9450 
9451 
9452 static BPose*
9453 BSearch(PoseList* table, const BPose* key, BPoseView* view,
9454 	int (*cmp)(const BPose*, const BPose*, BPoseView*), bool returnClosest)
9455 {
9456 	int32 r = table->CountItems();
9457 	BPose* result = 0;
9458 
9459 	for (int32 l = 1; l <= r;) {
9460 		int32 m = (l + r) / 2;
9461 
9462 		result = table->ItemAt(m - 1);
9463 		int32 compareResult = (cmp)(result, key, view);
9464 		if (compareResult == 0)
9465 			return result;
9466 		else if (compareResult < 0)
9467 			l = m + 1;
9468 		else
9469 			r = m - 1;
9470 	}
9471 	if (returnClosest)
9472 		return result;
9473 
9474 	return NULL;
9475 }
9476 
9477 
9478 int32
9479 BPoseView::BSearchList(PoseList* poseList, const BPose* pose,
9480 	int32* resultingIndex, int32 oldIndex)
9481 {
9482 	// check to see if insertion should be at beginning of list
9483 	const BPose* firstPose = poseList->FirstItem();
9484 	if (!firstPose)
9485 		return kInsertAtFront;
9486 
9487 	if (PoseCompareAddWidget(pose, firstPose, this) < 0) {
9488 		*resultingIndex = 0;
9489 		return kInsertAtFront;
9490 	}
9491 
9492 	int32 poseCount = poseList->CountItems();
9493 
9494 	// look if old position is still ok, by comparing to siblings
9495 	bool valid = oldIndex > 0 && oldIndex < poseCount - 1;
9496 	valid = valid && PoseCompareAddWidget(pose,
9497 		poseList->ItemAt(oldIndex - 1), this) >= 0;
9498 	// the current item is gone, so not oldIndex+1
9499 	valid = valid && PoseCompareAddWidget(pose,
9500 		poseList->ItemAt(oldIndex), this) <= 0;
9501 
9502 	if (valid) {
9503 		*resultingIndex = oldIndex - 1;
9504 		return kInsertAfter;
9505 	}
9506 
9507 	*resultingIndex = poseCount - 1;
9508 
9509 	const BPose* searchResult = BSearch(poseList, pose, this,
9510 		PoseCompareAddWidget);
9511 
9512 	if (searchResult != NULL) {
9513 		// what are we doing here??
9514 		// looks like we are skipping poses with identical search results or
9515 		// something
9516 		int32 index = poseList->IndexOf(searchResult);
9517 		for (; index < poseCount; index++) {
9518 			int32 result = PoseCompareAddWidget(pose, poseList->ItemAt(index),
9519 				this);
9520 			if (result <= 0) {
9521 				--index;
9522 				break;
9523 			}
9524 		}
9525 
9526 		if (index != poseCount)
9527 			*resultingIndex = index;
9528 	}
9529 
9530 	return kInsertAfter;
9531 }
9532 
9533 
9534 void
9535 BPoseView::SetPrimarySort(uint32 attrHash)
9536 {
9537 	BColumn* column = ColumnFor(attrHash);
9538 	if (column != NULL) {
9539 		fViewState->SetPrimarySort(attrHash);
9540 		fViewState->SetPrimarySortType(column->AttrType());
9541 	}
9542 }
9543 
9544 
9545 void
9546 BPoseView::SetSecondarySort(uint32 attrHash)
9547 {
9548 	BColumn* column = ColumnFor(attrHash);
9549 	if (column != NULL) {
9550 		fViewState->SetSecondarySort(attrHash);
9551 		fViewState->SetSecondarySortType(column->AttrType());
9552 	} else {
9553 		fViewState->SetSecondarySort(0);
9554 		fViewState->SetSecondarySortType(0);
9555 	}
9556 }
9557 
9558 
9559 void
9560 BPoseView::SetReverseSort(bool reverse)
9561 {
9562 	fViewState->SetReverseSort(reverse);
9563 }
9564 
9565 
9566 inline int
9567 PoseCompareAddWidgetBinder(const BPose* p1, const BPose* p2,
9568 	void* castToPoseView)
9569 {
9570 	return PoseCompareAddWidget(p1, p2, (BPoseView*)castToPoseView);
9571 }
9572 
9573 
9574 struct PoseComparator
9575 {
9576 	PoseComparator(BPoseView* poseView): fPoseView(poseView) { }
9577 
9578 	bool operator() (const BPose* p1, const BPose* p2)
9579 	{
9580 		return PoseCompareAddWidget(p1, p2, fPoseView) < 0;
9581 	}
9582 
9583 	BPoseView* fPoseView;
9584 };
9585 
9586 
9587 #if xDEBUG
9588 static BPose*
9589 DumpOne(BPose* pose, void*)
9590 {
9591 	pose->TargetModel()->PrintToStream(0);
9592 	return 0;
9593 }
9594 #endif
9595 
9596 
9597 void
9598 BPoseView::SortPoses()
9599 {
9600 	if (fTextWidgetToCheck != NULL)
9601 		fTextWidgetToCheck->CancelWait();
9602 
9603 	CommitActivePose();
9604 	// PRINT(("pose list count %d\n", fPoseList->CountItems()));
9605 #if xDEBUG
9606 	fPoseList->EachElement(DumpOne, 0);
9607 	PRINT(("===================\n"));
9608 #endif
9609 
9610 	BPose** poses = reinterpret_cast<BPose**>(
9611 		PoseList::Private(fPoseList).AsBList()->Items());
9612 	std::stable_sort(poses, &poses[fPoseList->CountItems()],
9613 		PoseComparator(this));
9614 	if (fFiltering) {
9615 		poses = reinterpret_cast<BPose**>(
9616 			PoseList::Private(fFilteredPoseList).AsBList()->Items());
9617 		std::stable_sort(poses, &poses[fFilteredPoseList->CountItems()],
9618 			PoseComparator(this));
9619 	}
9620 }
9621 
9622 
9623 BColumn*
9624 BPoseView::ColumnFor(uint32 attr) const
9625 {
9626 	int32 count = fColumnList->CountItems();
9627 	for (int32 index = 0; index < count; index++) {
9628 		BColumn* column = ColumnAt(index);
9629 		if (column->AttrHash() == attr)
9630 			return column;
9631 	}
9632 
9633 	return NULL;
9634 }
9635 
9636 
9637 bool
9638 BPoseView::ResizeColumnToWidest(BColumn* column)
9639 {
9640 	ASSERT(ViewMode() == kListMode);
9641 
9642 	// returns true if actually resized
9643 
9644 	float maxWidth = kMinColumnWidth;
9645 
9646 	PoseList* poseList = CurrentPoseList();
9647 	int32 poseCount = poseList->CountItems();
9648 	for (int32 i = 0; i < poseCount; ++i) {
9649 		BTextWidget* widget
9650 			= poseList->ItemAt(i)->WidgetFor(column->AttrHash());
9651 		if (widget != NULL) {
9652 			float width = widget->PreferredWidth(this);
9653 			if (width > maxWidth)
9654 				maxWidth = width;
9655 		}
9656 	}
9657 
9658 	if (maxWidth > kMinColumnWidth || maxWidth < column->Width()) {
9659 		ResizeColumn(column, maxWidth);
9660 		return true;
9661 	}
9662 
9663 	return false;
9664 }
9665 
9666 
9667 BPoint
9668 BPoseView::ResizeColumn(BColumn* column, float newSize,
9669 	float* lastLineDrawPos,
9670 	void (*drawLineFunc)(BPoseView*, BPoint, BPoint),
9671 	void (*undrawLineFunc)(BPoseView*, BPoint, BPoint))
9672 {
9673 	BRect sourceRect(Bounds());
9674 	BPoint result(sourceRect.RightBottom());
9675 
9676 	BRect destRect(sourceRect);
9677 		// we will use sourceRect and destRect for copyBits
9678 	BRect invalidateRect(sourceRect);
9679 		// this will serve to clean up after the invalidate
9680 	BRect columnDrawRect(sourceRect);
9681 		// we will use columnDrawRect to draw the actual resized column
9682 
9683 	bool shrinking = newSize < column->Width();
9684 	columnDrawRect.left = column->Offset();
9685 	columnDrawRect.right = column->Offset() + kTitleColumnRightExtraMargin
9686 		- kRoomForLine + newSize;
9687 	sourceRect.left = column->Offset() + kTitleColumnRightExtraMargin
9688 		- kRoomForLine + column->Width();
9689 	destRect.left = columnDrawRect.right;
9690 	destRect.right = destRect.left + sourceRect.Width();
9691 	invalidateRect.left = destRect.right;
9692 	invalidateRect.right = sourceRect.right;
9693 
9694 	column->SetWidth(newSize);
9695 
9696 	float offset = StartOffset();
9697 	int32 count = fColumnList->CountItems();
9698 	for (int32 index = 0; index < count; index++) {
9699 		column = fColumnList->ItemAt(index);
9700 		column->SetOffset(offset);
9701 		BColumn* last = column;
9702 		offset = last->Offset() + last->Width() + kTitleColumnExtraMargin;
9703 	}
9704 
9705 	if (shrinking) {
9706 		ColumnRedraw(columnDrawRect);
9707 		// dont have to undraw when shrinking
9708 		CopyBits(sourceRect, destRect);
9709 		if (drawLineFunc != NULL) {
9710 			ASSERT(lastLineDrawPos != NULL);
9711 			(drawLineFunc)(this, BPoint(destRect.left + kRoomForLine,
9712 					destRect.top),
9713 				BPoint(destRect.left + kRoomForLine, destRect.bottom));
9714 			*lastLineDrawPos = destRect.left + kRoomForLine;
9715 		}
9716 	} else {
9717 		CopyBits(sourceRect, destRect);
9718 		if (undrawLineFunc != NULL) {
9719 			ASSERT(lastLineDrawPos != NULL);
9720 			(undrawLineFunc)(this, BPoint(*lastLineDrawPos, sourceRect.top),
9721 				BPoint(*lastLineDrawPos, sourceRect.bottom));
9722 		}
9723 		if (drawLineFunc != NULL) {
9724 			ASSERT(lastLineDrawPos);
9725 			(drawLineFunc)(this, BPoint(destRect.left + kRoomForLine,
9726 					destRect.top),
9727 				BPoint(destRect.left + kRoomForLine, destRect.bottom));
9728 			*lastLineDrawPos = destRect.left + kRoomForLine;
9729 		}
9730 		ColumnRedraw(columnDrawRect);
9731 	}
9732 	if (invalidateRect.left < invalidateRect.right)
9733 		SynchronousUpdate(invalidateRect, true);
9734 
9735 	fStateNeedsSaving =  true;
9736 
9737 	return result;
9738 }
9739 
9740 
9741 void
9742 BPoseView::MoveColumnTo(BColumn* src, BColumn* dest)
9743 {
9744 	// find the leftmost boundary of columns we are about to reshuffle
9745 	float miny = src->Offset();
9746 	if (miny > dest->Offset())
9747 		miny = dest->Offset();
9748 
9749 	// ensure columns are in proper order in list
9750 	int32 index = fColumnList->IndexOf(dest);
9751 	fColumnList->RemoveItem(src, false);
9752 	fColumnList->AddItem(src, index);
9753 
9754 	float offset = StartOffset();
9755 	int32 count = fColumnList->CountItems();
9756 	for (int32 index = 0; index < count; index++) {
9757 		BColumn* column = fColumnList->ItemAt(index);
9758 		column->SetOffset(offset);
9759 		BColumn* last = column;
9760 		offset = last->Offset() + last->Width() + kTitleColumnExtraMargin
9761 			- kRoomForLine / 2;
9762 	}
9763 
9764 	// invalidate everything to the right of miny
9765 	BRect bounds(Bounds());
9766 	bounds.left = miny;
9767 	Invalidate(bounds);
9768 
9769 	fStateNeedsSaving =  true;
9770 }
9771 
9772 
9773 bool
9774 BPoseView::UpdateDropTarget(BPoint mouseLoc, const BMessage* dragMessage,
9775 	bool trackingContextMenu)
9776 {
9777 	ASSERT(dragMessage != NULL);
9778 
9779 	int32 index;
9780 	BPose* targetPose = FindPose(mouseLoc, &index);
9781 	if (targetPose != NULL && DragSelectionContains(targetPose, dragMessage))
9782 		targetPose = NULL;
9783 
9784 	if ((fCursorCheck && targetPose == fDropTarget)
9785 		|| (trackingContextMenu && !targetPose)) {
9786 		// no change
9787 		return false;
9788 	}
9789 
9790 	fCursorCheck = true;
9791 	if (fDropTarget && !DragSelectionContains(fDropTarget, dragMessage))
9792 		HiliteDropTarget(false);
9793 
9794 	fDropTarget = targetPose;
9795 
9796 	// dereference if symlink
9797 	Model* targetModel = NULL;
9798 	if (targetPose != NULL)
9799 		targetModel = targetPose->TargetModel();
9800 
9801 	Model tmpTarget;
9802 	if (targetModel != NULL && targetModel->IsSymLink()
9803 		&& tmpTarget.SetTo(targetPose->TargetModel()->EntryRef(), true, true)
9804 			== B_OK) {
9805 		targetModel = &tmpTarget;
9806 	}
9807 
9808 	bool ignoreTypes = (modifiers() & B_CONTROL_KEY) != 0;
9809 	if (targetPose != NULL) {
9810 		if (targetModel != NULL
9811 			&& CanHandleDragSelection(targetModel, dragMessage, ignoreTypes)) {
9812 			// new target is valid, select it
9813 			HiliteDropTarget(true);
9814 		} else {
9815 			fDropTarget = NULL;
9816 			fCursorCheck = false;
9817 		}
9818 	}
9819 	if (targetModel == NULL)
9820 		targetModel = TargetModel();
9821 
9822 	// if this is an OpenWith window, we'll have no target model
9823 	if (targetModel == NULL)
9824 		return false;
9825 
9826 	entry_ref srcRef;
9827 	if (targetModel->IsDirectory() && dragMessage->HasRef("refs")
9828 		&& dragMessage->FindRef("refs", &srcRef) == B_OK) {
9829 		Model srcModel(&srcRef);
9830 		if (!CheckDevicesEqual(&srcRef, targetModel)
9831 			&& !srcModel.IsVolume()
9832 			&& !srcModel.IsRoot()) {
9833 			BCursor copyCursor(B_CURSOR_ID_COPY);
9834 			SetViewCursor(&copyCursor);
9835 			return true;
9836 		}
9837 	}
9838 
9839 	SetViewCursor(B_CURSOR_SYSTEM_DEFAULT);
9840 
9841 	return true;
9842 }
9843 
9844 
9845 bool
9846 BPoseView::FrameForPose(BPose* targetPose, bool convert, BRect* poseRect)
9847 {
9848 	bool frameIsValid = false;
9849 	BRect bounds(Bounds());
9850 
9851 	if (ViewMode() == kListMode) {
9852 		PoseList* poseList = CurrentPoseList();
9853 		int32 poseCount = poseList->CountItems();
9854 		int32 startIndex = (int32)(bounds.top / fListElemHeight);
9855 
9856 		BPoint location(0, startIndex * fListElemHeight);
9857 		for (int32 index = startIndex; index < poseCount; index++) {
9858 			if (targetPose == poseList->ItemAt(index)) {
9859 				*poseRect = fDropTarget->CalcRect(location, this, false);
9860 				frameIsValid = true;
9861 			}
9862 
9863 			location.y += fListElemHeight;
9864 			if (location.y > bounds.bottom)
9865 				frameIsValid = false;
9866 		}
9867 	} else {
9868 		int32 startIndex = FirstIndexAtOrBelow((int32)(bounds.top
9869 			- IconPoseHeight()), true);
9870 		int32 poseCount = fVSPoseList->CountItems();
9871 
9872 		for (int32 index = startIndex; index < poseCount; index++) {
9873 			BPose* pose = fVSPoseList->ItemAt(index);
9874 			if (pose != NULL) {
9875 				if (pose == fDropTarget) {
9876 					*poseRect = pose->CalcRect(this);
9877 					frameIsValid = true;
9878 					break;
9879 				}
9880 
9881 				if (pose->Location(this).y > bounds.bottom) {
9882 					frameIsValid = false;
9883 					break;
9884 				}
9885 			}
9886 		}
9887 	}
9888 
9889 	if (convert)
9890 		ConvertToScreen(poseRect);
9891 
9892 	return frameIsValid;
9893 }
9894 
9895 
9896 bool
9897 BPoseView::MenuTrackingHook(BMenu* menu, void*)
9898 {
9899 	// return true if the menu should go away
9900 	if (!menu->LockLooper())
9901 		return false;
9902 
9903 	uint32 buttons;
9904 	BPoint location;
9905 	menu->GetMouse(&location, &buttons);
9906 
9907 	bool mouseInMenu = true;
9908 	// don't test for buttons up here and try to circumvent messaging
9909 	// lest you miss an invoke that will happen after the window goes away
9910 
9911 	BRect bounds(menu->Bounds());
9912 	bounds.InsetBy(-kMenuTrackMargin, -kMenuTrackMargin);
9913 	if (bounds.Contains(location)) {
9914 		// still in menu
9915 		mouseInMenu =  false;
9916 	}
9917 
9918 	if (mouseInMenu) {
9919 		menu->ConvertToScreen(&location);
9920 		int32 poseCount = menu->CountItems();
9921 		for (int32 index = 0 ; index < poseCount; index++) {
9922 			// iterate through all of the items in the menu
9923 			// if the submenu is showing, see if the mouse is in the submenu
9924 			BMenuItem* item = menu->ItemAt(index);
9925 			if (item && item->Submenu()) {
9926 				BWindow* window = item->Submenu()->Window();
9927 				bool inSubmenu = false;
9928 				if (window && window->Lock()) {
9929 					if (!window->IsHidden()) {
9930 						BRect frame(window->Frame());
9931 
9932 						frame.InsetBy(-kMenuTrackMargin, -kMenuTrackMargin);
9933 						inSubmenu = frame.Contains(location);
9934 					}
9935 					window->Unlock();
9936 					if (inSubmenu) {
9937 						// only one menu can have its window open bail now
9938 						mouseInMenu = false;
9939 						break;
9940 					}
9941 				}
9942 			}
9943 		}
9944 	}
9945 
9946 	menu->UnlockLooper();
9947 
9948 	return mouseInMenu;
9949 }
9950 
9951 
9952 void
9953 BPoseView::DragStop()
9954 {
9955 	fStartFrame.Set(0, 0, 0, 0);
9956 	BContainerWindow* window = ContainerWindow();
9957 	if (window != NULL)
9958 		window->DragStop();
9959 }
9960 
9961 
9962 void
9963 BPoseView::HiliteDropTarget(bool hiliteState)
9964 {
9965 	// hilites current drop target while dragging, does not modify
9966 	// selection list
9967 	if (fDropTarget == NULL)
9968 		return;
9969 
9970 	// note: fAlreadySelectedDropTarget is a trick to avoid to really search
9971 	// fSelectionList. Another solution would be to add Hilite/IsHilited just
9972 	// like Select/IsSelected in BPose and let it handle this case internally
9973 
9974 	// can happen when starting a new drag
9975 	if (fAlreadySelectedDropTarget != fDropTarget)
9976 		fAlreadySelectedDropTarget = NULL;
9977 
9978 	// don't select, this droptarget was already part of a user selection
9979 	if (fDropTarget->IsSelected() && hiliteState) {
9980 		fAlreadySelectedDropTarget = fDropTarget;
9981 		return;
9982 	}
9983 
9984 	// don't unselect the fAlreadySelectedDropTarget
9985 	if ((fAlreadySelectedDropTarget == fDropTarget) && !hiliteState) {
9986 		fAlreadySelectedDropTarget = NULL;
9987 		return;
9988 	}
9989 
9990 	fDropTarget->Select(hiliteState);
9991 
9992 	// scan all visible poses
9993 	BRect bounds(Bounds());
9994 
9995 	if (ViewMode() == kListMode) {
9996 		PoseList* poseList = CurrentPoseList();
9997 		int32 poseCount = poseList->CountItems();
9998 		int32 startIndex = (int32)(bounds.top / fListElemHeight);
9999 
10000 		BPoint location(0, startIndex * fListElemHeight);
10001 
10002 		for (int32 index = startIndex; index < poseCount; index++) {
10003 			if (fDropTarget == poseList->ItemAt(index)) {
10004 				BRect poseRect = fDropTarget->CalcRect(location, this, false);
10005 				fDropTarget->Draw(poseRect, poseRect, this, false);
10006 				break;
10007 			}
10008 
10009 			location.y += fListElemHeight;
10010 			if (location.y > bounds.bottom)
10011 				break;
10012 		}
10013 	} else {
10014 		int32 startIndex = FirstIndexAtOrBelow(
10015 			(int32)(bounds.top - IconPoseHeight()), true);
10016 		int32 poseCount = fVSPoseList->CountItems();
10017 
10018 		for (int32 index = startIndex; index < poseCount; index++) {
10019 			BPose* pose = fVSPoseList->ItemAt(index);
10020 			if (pose != NULL) {
10021 				if (pose == fDropTarget) {
10022 					BRect poseRect = pose->CalcRect(this);
10023 					// TODO: maybe leave just the else part
10024 					if (!hiliteState)
10025 						// deselecting an icon with widget drawn over background
10026 						// have to be a little tricky here - draw just the icon,
10027 						// invalidate the widget
10028 						pose->DeselectWithoutErasingBackground(poseRect, this);
10029 					else
10030 						pose->Draw(poseRect, poseRect, this, false);
10031 					break;
10032 				}
10033 
10034 				if (pose->Location(this).y > bounds.bottom)
10035 					break;
10036 			}
10037 		}
10038 	}
10039 }
10040 
10041 
10042 bool
10043 BPoseView::CheckAutoScroll(BPoint mouseLoc, bool shouldScroll)
10044 {
10045 	if (!fShouldAutoScroll)
10046 		return false;
10047 
10048 	// make sure window is in front before attempting scrolling
10049 	BContainerWindow* window = ContainerWindow();
10050 	if (window == NULL)
10051 		return false;
10052 
10053 	BRect bounds(Bounds());
10054 	BRect extent(Extent());
10055 
10056 	bool wouldScroll = false;
10057 	bool keepGoing;
10058 	float scrollIncrement;
10059 
10060 	BRect border(bounds);
10061 	border.bottom = border.top;
10062 	border.top -= kBorderHeight;
10063 	if (ViewMode() == kListMode)
10064 		border.top -= TitleView()->Bounds().Height();
10065 
10066 	bool selectionScrolling = fSelectionRectInfo.isDragging;
10067 
10068 	if (bounds.top > extent.top) {
10069 		if (selectionScrolling) {
10070 			keepGoing = mouseLoc.y < bounds.top;
10071 			if (fabs(bounds.top - mouseLoc.y) > kSlowScrollBucket)
10072 				scrollIncrement = fAutoScrollInc / 1.5f;
10073 			else
10074 				scrollIncrement = fAutoScrollInc / 4;
10075 		} else {
10076 			keepGoing = border.Contains(mouseLoc);
10077 			scrollIncrement = fAutoScrollInc;
10078 		}
10079 
10080 		if (keepGoing) {
10081 			wouldScroll = true;
10082 			if (shouldScroll) {
10083 				if (fVScrollBar != NULL) {
10084 					fVScrollBar->SetValue(
10085 						fVScrollBar->Value() - scrollIncrement);
10086 				} else
10087 					ScrollBy(0, -scrollIncrement);
10088 			}
10089 		}
10090 	}
10091 
10092 	border = bounds;
10093 	border.top = border.bottom;
10094 	border.bottom += (float)B_H_SCROLL_BAR_HEIGHT;
10095 	if (bounds.bottom < extent.bottom) {
10096 		if (selectionScrolling) {
10097 			keepGoing = mouseLoc.y > bounds.bottom;
10098 			if (fabs(bounds.bottom - mouseLoc.y) > kSlowScrollBucket)
10099 				scrollIncrement = fAutoScrollInc / 1.5f;
10100 			else
10101 				scrollIncrement = fAutoScrollInc / 4;
10102 		} else {
10103 			keepGoing = border.Contains(mouseLoc);
10104 			scrollIncrement = fAutoScrollInc;
10105 		}
10106 
10107 		if (keepGoing) {
10108 			wouldScroll = true;
10109 			if (shouldScroll) {
10110 				if (fVScrollBar != NULL) {
10111 					fVScrollBar->SetValue(
10112 						fVScrollBar->Value() + scrollIncrement);
10113 				} else
10114 					ScrollBy(0, scrollIncrement);
10115 			}
10116 		}
10117 	}
10118 
10119 	border = bounds;
10120 	border.right = border.left;
10121 	border.left -= 6;
10122 	if (bounds.left > extent.left) {
10123 		if (selectionScrolling) {
10124 			keepGoing = mouseLoc.x < bounds.left;
10125 			if (fabs(bounds.left - mouseLoc.x) > kSlowScrollBucket)
10126 				scrollIncrement = fAutoScrollInc / 1.5f;
10127 			else
10128 				scrollIncrement = fAutoScrollInc / 4;
10129 		} else {
10130 			keepGoing = border.Contains(mouseLoc);
10131 			scrollIncrement = fAutoScrollInc;
10132 		}
10133 
10134 		if (keepGoing) {
10135 			wouldScroll = true;
10136 			if (shouldScroll) {
10137 				if (fHScrollBar != NULL) {
10138 					fHScrollBar->SetValue(
10139 						fHScrollBar->Value() - scrollIncrement);
10140 				} else
10141 					ScrollBy(-scrollIncrement, 0);
10142 			}
10143 		}
10144 	}
10145 
10146 	border = bounds;
10147 	border.left = border.right;
10148 	border.right += (float)B_V_SCROLL_BAR_WIDTH;
10149 	if (bounds.right < extent.right) {
10150 		if (selectionScrolling) {
10151 			keepGoing = mouseLoc.x > bounds.right;
10152 			if (fabs(bounds.right - mouseLoc.x) > kSlowScrollBucket)
10153 				scrollIncrement = fAutoScrollInc / 1.5f;
10154 			else
10155 				scrollIncrement = fAutoScrollInc / 4;
10156 		} else {
10157 			keepGoing = border.Contains(mouseLoc);
10158 			scrollIncrement = fAutoScrollInc;
10159 		}
10160 
10161 		if (keepGoing) {
10162 			wouldScroll = true;
10163 			if (shouldScroll) {
10164 				if (fHScrollBar != NULL) {
10165 					fHScrollBar->SetValue(
10166 						fHScrollBar->Value() + scrollIncrement);
10167  				} else
10168  					ScrollBy(scrollIncrement, 0);
10169 			}
10170 		}
10171 	}
10172 
10173 	// Force selection rect update to account for the new scrolled coords
10174 	// without a mouse move
10175 	if (selectionScrolling)
10176 		_UpdateSelectionRect(mouseLoc);
10177 
10178 	return wouldScroll;
10179 }
10180 
10181 
10182 void
10183 BPoseView::HandleAutoScroll()
10184 {
10185 	if (!fShouldAutoScroll)
10186 		return;
10187 
10188 	uint32 buttons;
10189 	BPoint mouseLoc;
10190 	GetMouse(&mouseLoc, &buttons);
10191 
10192 	if (buttons == 0) {
10193 		fAutoScrollState = kAutoScrollOff;
10194 		Window()->SetPulseRate(500000);
10195 		return;
10196 	}
10197 
10198 	switch (fAutoScrollState) {
10199 		case kWaitForTransition:
10200 			if (CheckAutoScroll(mouseLoc, false) == false)
10201 				fAutoScrollState = kDelayAutoScroll;
10202 			break;
10203 
10204 		case kDelayAutoScroll:
10205 			if (CheckAutoScroll(mouseLoc, false) == true) {
10206 				snooze(600000);
10207 				GetMouse(&mouseLoc, &buttons);
10208 				if (CheckAutoScroll(mouseLoc, false) == true)
10209 					fAutoScrollState = kAutoScrollOn;
10210 			}
10211 			break;
10212 
10213 		case kAutoScrollOn:
10214 			CheckAutoScroll(mouseLoc, true);
10215 			break;
10216 	}
10217 }
10218 
10219 
10220 BRect
10221 BPoseView::CalcPoseRect(const BPose* pose, int32 index,
10222 	bool firstColumnOnly) const
10223 {
10224 	if (ViewMode() == kListMode)
10225 		return CalcPoseRectList(pose, index, firstColumnOnly);
10226 	else
10227 		return CalcPoseRectIcon(pose);
10228 }
10229 
10230 
10231 BRect
10232 BPoseView::CalcPoseRectIcon(const BPose* pose) const
10233 {
10234 	return pose->CalcRect(this);
10235 }
10236 
10237 
10238 BRect
10239 BPoseView::CalcPoseRectList(const BPose* pose, int32 index,
10240 	bool firstColumnOnly) const
10241 {
10242 	return pose->CalcRect(BPoint(0, index * fListElemHeight), this,
10243 		firstColumnOnly);
10244 }
10245 
10246 
10247 bool
10248 BPoseView::Represents(const node_ref* node) const
10249 {
10250 	return *(fModel->NodeRef()) == *node;
10251 }
10252 
10253 
10254 bool
10255 BPoseView::Represents(const entry_ref* ref) const
10256 {
10257 	return *fModel->EntryRef() == *ref;
10258 }
10259 
10260 
10261 void
10262 BPoseView::ShowBarberPole()
10263 {
10264 	if (fCountView) {
10265 		AutoLock<BWindow> lock(Window());
10266 		if (!lock)
10267 			return;
10268 		fCountView->StartBarberPole();
10269 	}
10270 }
10271 
10272 
10273 void
10274 BPoseView::HideBarberPole()
10275 {
10276 	if (fCountView != NULL) {
10277 		AutoLock<BWindow> lock(Window());
10278 		if (!lock)
10279 			return;
10280 		fCountView->EndBarberPole();
10281 	}
10282 }
10283 
10284 
10285 bool
10286 BPoseView::IsWatchingDateFormatChange()
10287 {
10288 	return fIsWatchingDateFormatChange;
10289 }
10290 
10291 
10292 void
10293 BPoseView::StartWatchDateFormatChange()
10294 {
10295 	BMessenger trackerMessenger(kTrackerSignature);
10296 	BHandler::StartWatching(trackerMessenger, kDateFormatChanged);
10297 	fIsWatchingDateFormatChange = true;
10298 }
10299 
10300 
10301 void
10302 BPoseView::StopWatchDateFormatChange()
10303 {
10304 	if (IsFilePanel()) {
10305 		BMessenger trackerMessenger(kTrackerSignature);
10306 		BHandler::StopWatching(trackerMessenger, kDateFormatChanged);
10307 	} else if (be_app->LockLooper()) {
10308 		be_app->StopWatching(this, kDateFormatChanged);
10309 		be_app->UnlockLooper();
10310 	}
10311 
10312 	fIsWatchingDateFormatChange = false;
10313 }
10314 
10315 
10316 void
10317 BPoseView::UpdateDateColumns(BMessage* message)
10318 {
10319 	int32 columnCount = CountColumns();
10320 	BRect columnRect(Bounds());
10321 
10322 	for (int32 i = 0; i < columnCount; i++) {
10323 		BColumn* col = ColumnAt(i);
10324 		if (col && col->AttrType() == B_TIME_TYPE) {
10325 			columnRect.left = col->Offset();
10326 			columnRect.right = columnRect.left + col->Width();
10327 			Invalidate(columnRect);
10328 		}
10329 	}
10330 }
10331 
10332 
10333 void
10334 BPoseView::AdaptToVolumeChange(BMessage*)
10335 {
10336 }
10337 
10338 
10339 void
10340 BPoseView::AdaptToDesktopIntegrationChange(BMessage*)
10341 {
10342 }
10343 
10344 
10345 bool
10346 BPoseView::WidgetTextOutline() const
10347 {
10348 	return fWidgetTextOutline;
10349 }
10350 
10351 
10352 void
10353 BPoseView::SetWidgetTextOutline(bool on)
10354 {
10355 	fWidgetTextOutline = on;
10356 }
10357 
10358 
10359 void
10360 BPoseView::EnsurePoseUnselected(BPose* pose)
10361 {
10362 	if (pose == fDropTarget)
10363 		fDropTarget = NULL;
10364 
10365 	if (pose == ActivePose())
10366 		CommitActivePose();
10367 
10368 	fSelectionList->RemoveItem(pose);
10369 	if (fSelectionPivotPose == pose)
10370 		fSelectionPivotPose = NULL;
10371 
10372 	if (fRealPivotPose == pose)
10373 		fRealPivotPose = NULL;
10374 
10375 	if (pose->IsSelected()) {
10376 		pose->Select(false);
10377 		if (fSelectionChangedHook)
10378 			ContainerWindow()->SelectionChanged();
10379 	}
10380 }
10381 
10382 
10383 void
10384 BPoseView::RemoveFilteredPose(BPose* pose, int32 index)
10385 {
10386 	EnsurePoseUnselected(pose);
10387 	fFilteredPoseList->RemoveItemAt(index);
10388 
10389 	BRect invalidRect = CalcPoseRectList(pose, index);
10390 	CloseGapInList(&invalidRect);
10391 
10392 	Invalidate(invalidRect);
10393 }
10394 
10395 
10396 void
10397 BPoseView::FilterChanged()
10398 {
10399 	if (ViewMode() != kListMode)
10400 		return;
10401 
10402 	int32 stringCount = fFilterStrings.CountItems();
10403 	int32 length = fFilterStrings.LastItem()->CountChars();
10404 
10405 	if (!fFiltering && (length > 0 || fRefFilter != NULL))
10406 		StartFiltering();
10407 	else if (fFiltering && stringCount == 1 && length == 0
10408 		&& fRefFilter == NULL) {
10409 		ClearFilter();
10410 	} else {
10411 		if (fLastFilterStringCount > stringCount
10412 			|| (fLastFilterStringCount == stringCount
10413 				&& fLastFilterStringLength > length)
10414 			|| fRefFilter != NULL) {
10415 			// something was removed, need to start over
10416 			fFilteredPoseList->MakeEmpty();
10417 			fFiltering = false;
10418 			StartFiltering();
10419 		} else {
10420 			int32 poseCount = fFilteredPoseList->CountItems();
10421 			for (int32 i = poseCount - 1; i >= 0; i--) {
10422 				BPose* pose = fFilteredPoseList->ItemAt(i);
10423 				if (!FilterPose(pose))
10424 					RemoveFilteredPose(pose, i);
10425 			}
10426 		}
10427 	}
10428 
10429 	fLastFilterStringCount = stringCount;
10430 	fLastFilterStringLength = length;
10431 	UpdateAfterFilterChange();
10432 }
10433 
10434 
10435 void
10436 BPoseView::UpdateAfterFilterChange()
10437 {
10438 	UpdateCount();
10439 
10440 	BPose* pose = fFilteredPoseList->LastItem();
10441 	if (pose == NULL)
10442 		BView::ScrollTo(0, 0);
10443 	else {
10444 		BRect bounds = Bounds();
10445 		float height = fFilteredPoseList->CountItems() * fListElemHeight;
10446 		if (bounds.top > 0 && bounds.bottom > height)
10447 			BView::ScrollTo(0, std::max(height - bounds.Height(), 0.0f));
10448 	}
10449 
10450 	UpdateScrollRange();
10451 }
10452 
10453 
10454 bool
10455 BPoseView::FilterPose(BPose* pose)
10456 {
10457 	if (!fFiltering || pose == NULL)
10458 		return false;
10459 
10460 	if (fRefFilter != NULL) {
10461 		PoseInfo poseInfo;
10462 		ReadPoseInfo(pose->TargetModel(), &poseInfo);
10463 		if (pose->TargetModel()->OpenNode() != B_OK)
10464 			return false;
10465 		if (!ShouldShowPose(pose->TargetModel(), &poseInfo))
10466 			return false;
10467 	}
10468 
10469 	int32 stringCount = fFilterStrings.CountItems();
10470 	int32 matchesLeft = stringCount;
10471 
10472 	bool found[stringCount];
10473 	memset(found, 0, sizeof(found));
10474 
10475 	ModelNodeLazyOpener modelOpener(pose->TargetModel());
10476 	for (int32 i = 0; i < CountColumns(); i++) {
10477 		BTextWidget* widget = pose->WidgetFor(ColumnAt(i), this, modelOpener);
10478 		const char* text = NULL;
10479 		if (widget == NULL)
10480 			continue;
10481 
10482 		text = widget->Text(this);
10483 		if (text == NULL)
10484 			continue;
10485 
10486 		for (int32 j = 0; j < stringCount; j++) {
10487 			if (found[j])
10488 				continue;
10489 
10490 			if (strcasestr(text, fFilterStrings.ItemAt(j)->String()) != NULL) {
10491 				if (--matchesLeft == 0)
10492 					return true;
10493 
10494 				found[j] = true;
10495 			}
10496 		}
10497 	}
10498 
10499 	return false;
10500 }
10501 
10502 
10503 void
10504 BPoseView::StartFiltering()
10505 {
10506 	if (fFiltering)
10507 		return;
10508 
10509 	fFiltering = true;
10510 	int32 poseCount = fPoseList->CountItems();
10511 	for (int32 i = 0; i < poseCount; i++) {
10512 		BPose* pose = fPoseList->ItemAt(i);
10513 		if (FilterPose(pose))
10514 			fFilteredPoseList->AddItem(pose);
10515 		else
10516 			EnsurePoseUnselected(pose);
10517 	}
10518 
10519 	Invalidate();
10520 }
10521 
10522 
10523 bool
10524 BPoseView::IsFiltering() const
10525 {
10526 	return fFiltering;
10527 }
10528 
10529 
10530 void
10531 BPoseView::StopFiltering()
10532 {
10533 	ClearFilter();
10534 	UpdateAfterFilterChange();
10535 }
10536 
10537 
10538 void
10539 BPoseView::ClearFilter()
10540 {
10541 	if (!fFiltering)
10542 		return;
10543 
10544 	fCountView->CancelFilter();
10545 
10546 	int32 stringCount = fFilterStrings.CountItems();
10547 	for (int32 i = stringCount - 1; i > 0; i--)
10548 		delete fFilterStrings.RemoveItemAt(i);
10549 
10550 	fFilterStrings.LastItem()->Truncate(0);
10551 	fLastFilterStringCount = 1;
10552 	fLastFilterStringLength = 0;
10553 
10554 	if (fRefFilter == NULL)
10555 		fFiltering = false;
10556 
10557 	fFilteredPoseList->MakeEmpty();
10558 
10559 	Invalidate();
10560 }
10561 
10562 
10563 void
10564 BPoseView::ExcludeTrashFromSelection()
10565 {
10566 	int32 selectCount = CountSelected();
10567 	for (int index = 0; index < selectCount; index++) {
10568 		BPose* pose = fSelectionList->ItemAt(index);
10569 		if (CanTrashForeignDrag(pose->TargetModel())) {
10570 			RemovePoseFromSelection(pose);
10571 			break;
10572 		}
10573 	}
10574 }
10575 
10576 
10577 //	#pragma mark - TScrollBar
10578 
10579 
10580 TScrollBar::TScrollBar(const char* name, BView* target, float min, float max)
10581 	:
10582 	BScrollBar(name, target, min, max, B_HORIZONTAL),
10583 	fTitleView(NULL)
10584 {
10585 	// We always want to be at least the preferred scrollbar size,
10586 	// no matter what layout we get placed into.
10587 	SetExplicitMinSize(PreferredSize());
10588 }
10589 
10590 
10591 void
10592 TScrollBar::ValueChanged(float value)
10593 {
10594 	if (fTitleView) {
10595 		BPoint origin = fTitleView->LeftTop();
10596 		fTitleView->ScrollTo(BPoint(value, origin.y));
10597 	}
10598 
10599 	_inherited::ValueChanged(value);
10600 }
10601 
10602 
10603 TPoseViewFilter::TPoseViewFilter(BPoseView* pose)
10604 	:
10605 	BMessageFilter(B_ANY_DELIVERY, B_ANY_SOURCE),
10606 	fPoseView(pose)
10607 {
10608 }
10609 
10610 
10611 TPoseViewFilter::~TPoseViewFilter()
10612 {
10613 }
10614 
10615 
10616 filter_result
10617 TPoseViewFilter::Filter(BMessage* message, BHandler**)
10618 {
10619 	filter_result result = B_DISPATCH_MESSAGE;
10620 
10621 	switch (message->what) {
10622 		case B_ARCHIVED_OBJECT:
10623 			bool handled = fPoseView->HandleMessageDropped(message);
10624 			if (handled)
10625 				result = B_SKIP_MESSAGE;
10626 			break;
10627 	}
10628 
10629 	return result;
10630 }
10631 
10632 
10633 //	#pragma mark - static member initializations
10634 
10635 float BPoseView::sFontHeight = -1;
10636 font_height BPoseView::sFontInfo = { 0, 0, 0 };
10637 OffscreenBitmap* BPoseView::sOffscreen = new OffscreenBitmap;
10638 BString BPoseView::sMatchString = "";
10639