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