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