1/*
2Open Tracker License
3
4Terms and Conditions
5
6Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7
8Permission is hereby granted, free of charge, to any person obtaining a copy of
9this software and associated documentation files (the "Software"), to deal in
10the Software without restriction, including without limitation the rights to
11use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12of the Software, and to permit persons to whom the Software is furnished to do
13so, subject to the following conditions:
14
15The above copyright notice and this permission notice applies to all licensees
16and shall be included in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25Except as contained in this notice, the name of Be Incorporated shall not be
26used in advertising or otherwise to promote the sale, use or other dealings in
27this Software without prior written authorization from Be Incorporated.
28
29Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30of Be Incorporated in the United States and other countries. Other brand product
31names are registered trademarks or trademarks of their respective holders.
32All rights reserved.
33*/
34
35
36#include <stdlib.h>
37#include <string.h>
38
39#include <Debug.h>
40#include <NodeMonitor.h>
41#include <Volume.h>
42#include <fs_info.h>
43
44#include "Attributes.h"
45#include "Commands.h"
46#include "FSClipboard.h"
47#include "IconCache.h"
48#include "Pose.h"
49#include "PoseView.h"
50
51
52int32
53CalcFreeSpace(BVolume* volume)
54{
55	off_t capacity = volume->Capacity();
56	if (capacity == 0)
57		return 100;
58
59	int32 percent
60		= static_cast<int32>(volume->FreeBytes() / (capacity / 100));
61
62	// warn below 5% of free space
63	if (percent < 5)
64		return -2 - percent;
65	return percent;
66}
67
68
69// SymLink handling:
70// symlink pose uses the resolved model to retrieve the icon, if not broken
71// everything else, like the attributes, etc. is retrieved directly from the
72// symlink itself
73BPose::BPose(Model* model, BPoseView* view, uint32 clipboardMode,
74	bool selected)
75	:
76	fModel(model),
77	fWidgetList(4, false),
78	fClipboardMode(clipboardMode),
79	fPercent(-1),
80	fSelectionTime(0),
81	fIsSelected(selected),
82	fHasLocation(false),
83	fNeedsSaveLocation(false),
84	fListModeInited(false),
85	fWasAutoPlaced(false),
86	fBrokenSymLink(false),
87	fBackgroundClean(false)
88{
89	CreateWidgets(view);
90
91	if (model->IsVolume()) {
92		fs_info info;
93		dev_t device = model->NodeRef()->device;
94		BVolume* volume = new BVolume(device);
95		if (volume->InitCheck() == B_OK
96			&& fs_stat_dev(device, &info) == B_OK) {
97			// Philosophy here:
98			// Bars go on all read/write volumes
99			// Exceptions: Not on CDDA
100			if (strcmp(info.fsh_name,"cdda") != 0
101				&& !volume->IsReadOnly()) {
102				// The volume is ok and we want space bars on it
103				gPeriodicUpdatePoses.AddPose(this, view,
104					_PeriodicUpdateCallback, volume);
105				if (TrackerSettings().ShowVolumeSpaceBar())
106					fPercent = CalcFreeSpace(volume);
107			} else
108				delete volume;
109		} else
110			delete volume;
111	}
112}
113
114
115BPose::~BPose()
116{
117	if (fModel->IsVolume()) {
118		// we might be registered for periodic updates
119		BVolume* volume = NULL;
120		if (gPeriodicUpdatePoses.RemovePose(this, (void**)&volume))
121			delete volume;
122	}
123	int32 count = fWidgetList.CountItems();
124	for (int32 i = 0; i < count; i++)
125		delete fWidgetList.ItemAt(i);
126
127	delete fModel;
128}
129
130
131void
132BPose::CreateWidgets(BPoseView* poseView)
133{
134	for (int32 index = 0; ; index++) {
135		BColumn* column = poseView->ColumnAt(index);
136		if (column == NULL)
137			break;
138
139		fWidgetList.AddItem(new BTextWidget(fModel, column, poseView));
140	}
141}
142
143
144BTextWidget*
145BPose::AddWidget(BPoseView* poseView, BColumn* column)
146{
147	BModelOpener opener(fModel);
148	if (fModel->InitCheck() != B_OK)
149		return NULL;
150
151	BTextWidget* widget = new BTextWidget(fModel, column, poseView);
152	fWidgetList.AddItem(widget);
153
154	return widget;
155}
156
157
158BTextWidget*
159BPose::AddWidget(BPoseView* poseView, BColumn* column,
160	ModelNodeLazyOpener &opener)
161{
162	opener.OpenNode();
163	if (fModel->InitCheck() != B_OK)
164		return NULL;
165
166	BTextWidget* widget = new BTextWidget(fModel, column, poseView);
167	fWidgetList.AddItem(widget);
168
169	return widget;
170}
171
172
173void
174BPose::RemoveWidget(BPoseView*, BColumn* column)
175{
176	int32 index;
177	BTextWidget* widget = WidgetFor(column->AttrHash(), &index);
178	if (widget != NULL)
179		delete fWidgetList.RemoveItemAt(index);
180}
181
182
183void
184BPose::Commit(bool saveChanges, BPoint loc, BPoseView* poseView,
185	int32 poseIndex)
186{
187	int32 count = fWidgetList.CountItems();
188	for (int32 index = 0; index < count; index++) {
189		BTextWidget* widget = fWidgetList.ItemAt(index);
190		if (widget != NULL && widget->IsActive()) {
191			widget->StopEdit(saveChanges, loc, poseView, this, poseIndex);
192			break;
193		}
194	}
195}
196
197
198inline bool
199OneMouseUp(BTextWidget* widget, BPose* pose, BPoseView* poseView,
200	BColumn* column, BPoint poseLoc, BPoint where)
201{
202	BRect rect;
203	if (poseView->ViewMode() == kListMode)
204		rect = widget->CalcClickRect(poseLoc, column, poseView);
205	else
206		rect = widget->CalcClickRect(pose->Location(poseView), NULL, poseView);
207
208	if (rect.Contains(where)) {
209		widget->MouseUp(rect, poseView, pose, where);
210		return true;
211	}
212
213	return false;
214}
215
216
217void
218BPose::MouseUp(BPoint poseLoc, BPoseView* poseView, BPoint where, int32)
219{
220	WhileEachTextWidget(this, poseView, OneMouseUp, poseLoc, where);
221}
222
223
224inline void
225OneCheckAndUpdate(BTextWidget* widget, BPose*, BPoseView* poseView,
226	BColumn* column, BPoint poseLoc)
227{
228	widget->CheckAndUpdate(poseLoc, column, poseView, true);
229}
230
231
232void
233BPose::UpdateAllWidgets(int32, BPoint poseLoc, BPoseView* poseView)
234{
235	if (poseView->ViewMode() != kListMode)
236		poseLoc = Location(poseView);
237
238	ASSERT(fModel->IsNodeOpen());
239	EachTextWidget(this, poseView, OneCheckAndUpdate, poseLoc);
240}
241
242
243void
244BPose::UpdateWidgetAndModel(Model* resolvedModel, const char* attrName,
245	uint32 attrType, int32, BPoint poseLoc, BPoseView* poseView, bool visible)
246{
247	if (poseView->ViewMode() != kListMode)
248		poseLoc = Location(poseView);
249
250	ASSERT(resolvedModel == NULL || resolvedModel->IsNodeOpen());
251
252	if (attrName != NULL) {
253		// pick up new attributes and find out if icon needs updating
254		if (resolvedModel->AttrChanged(attrName) && visible)
255			UpdateIcon(poseLoc, poseView);
256
257		// ToDo: the following code is wrong, because this sort of hashing
258		// may overlap and we get aliasing
259		uint32 attrHash = AttrHashString(attrName, attrType);
260		BTextWidget* widget = WidgetFor(attrHash);
261		if (widget != NULL) {
262			BColumn* column = poseView->ColumnFor(attrHash);
263			if (column != NULL)
264				widget->CheckAndUpdate(poseLoc, column, poseView, visible);
265		} else if (attrType == 0) {
266			// attribute got likely removed, so let's search the
267			// column for the matching attribute name
268			int32 count = fWidgetList.CountItems();
269			for (int32 i = 0; i < count; i++) {
270				BTextWidget* widget = fWidgetList.ItemAt(i);
271				BColumn* column = poseView->ColumnFor(widget->AttrHash());
272				if (column != NULL
273					&& strcmp(column->AttrName(), attrName) == 0) {
274					widget->CheckAndUpdate(poseLoc, column, poseView, visible);
275					break;
276				}
277			}
278		}
279	} else {
280		// no attr name means check all widgets for stat info changes
281
282		// pick up stat changes
283		if (resolvedModel && resolvedModel->StatChanged()) {
284			if (resolvedModel->InitCheck() != B_OK)
285				return;
286
287			if (visible)
288				UpdateIcon(poseLoc, poseView);
289		}
290
291		// distribute stat changes
292		for (int32 index = 0; ; index++) {
293			BColumn* column = poseView->ColumnAt(index);
294			if (column == NULL)
295				break;
296
297			if (column->StatField()) {
298				BTextWidget* widget = WidgetFor(column->AttrHash());
299				if (widget != NULL)
300					widget->CheckAndUpdate(poseLoc, column, poseView, visible);
301			}
302		}
303	}
304}
305
306
307bool
308BPose::_PeriodicUpdateCallback(BPose* pose, void* cookie)
309{
310	return pose->UpdateVolumeSpaceBar((BVolume*)cookie);
311}
312
313
314bool
315BPose::UpdateVolumeSpaceBar(BVolume* volume)
316{
317	bool enabled = TrackerSettings().ShowVolumeSpaceBar();
318	if (!enabled) {
319		if (fPercent == -1)
320			return false;
321
322		fPercent = -1;
323		return true;
324	}
325
326	int32 percent = CalcFreeSpace(volume);
327	if (fPercent != percent) {
328		if (percent > 100)
329			fPercent = 100;
330		else
331			fPercent = percent;
332
333		return true;
334	}
335
336	return false;
337}
338
339
340void
341BPose::UpdateIcon(BPoint poseLoc, BPoseView* poseView)
342{
343	IconCache::sIconCache->IconChanged(ResolvedModel());
344
345	int32 iconSize = poseView->IconSizeInt();
346
347	BRect rect;
348	if (poseView->ViewMode() == kListMode) {
349		rect = CalcRect(poseLoc, poseView);
350		rect.left += poseView->ListOffset();
351		rect.right = rect.left + iconSize;
352		rect.top = rect.bottom - iconSize;
353	} else {
354		BPoint location = Location(poseView);
355		rect.left = location.x;
356		rect.top = location.y;
357		rect.right = rect.left + iconSize;
358		rect.bottom = rect.top + iconSize;
359	}
360
361	poseView->Invalidate(rect);
362}
363
364
365void
366BPose::UpdateBrokenSymLink(BPoint poseLoc, BPoseView* poseView)
367{
368	ASSERT(TargetModel()->IsSymLink());
369	ASSERT(TargetModel()->LinkTo() == NULL);
370	if (!TargetModel()->IsSymLink() || TargetModel()->LinkTo() != NULL)
371		return;
372
373	UpdateIcon(poseLoc, poseView);
374}
375
376
377void
378BPose::UpdateWasBrokenSymlink(BPoint poseLoc, BPoseView* poseView)
379{
380	if (!fModel->IsSymLink())
381		return;
382
383	if (fModel->LinkTo() != NULL) {
384		BEntry entry(fModel->EntryRef(), true);
385		if (entry.InitCheck() != B_OK) {
386			watch_node(fModel->LinkTo()->NodeRef(), B_STOP_WATCHING, poseView);
387			fModel->SetLinkTo(NULL);
388			UpdateIcon(poseLoc, poseView);
389		}
390		return;
391	}
392
393	poseView->CreateSymlinkPoseTarget(fModel);
394	UpdateIcon(poseLoc, poseView);
395	if (fModel->LinkTo() != NULL)
396		fModel->LinkTo()->CloseNode();
397}
398
399
400void
401BPose::EditFirstWidget(BPoint poseLoc, BPoseView* poseView)
402{
403	// find first editable widget
404	BColumn* column;
405	for (int32 i = 0; (column = poseView->ColumnAt(i)) != NULL; i++) {
406		BTextWidget* widget = WidgetFor(column->AttrHash());
407
408		if (widget != NULL && widget->IsEditable()) {
409			BRect bounds;
410			// ToDo:
411			// fold the three StartEdit code sequences into a cover call
412			if (poseView->ViewMode() == kListMode)
413				bounds = widget->CalcRect(poseLoc, column, poseView);
414			else
415				bounds = widget->CalcRect(Location(poseView), NULL, poseView);
416
417			widget->StartEdit(bounds, poseView, this);
418			break;
419		}
420	}
421}
422
423
424void
425BPose::EditPreviousNextWidgetCommon(BPoseView* poseView, bool next)
426{
427	bool found = false;
428	int32 delta = next ? 1 : -1;
429	for (int32 index = next ? 0 : poseView->CountColumns() - 1; ;
430			index += delta) {
431		BColumn* column = poseView->ColumnAt(index);
432		if (column == NULL) {
433			// out of columns
434			break;
435		}
436
437		BTextWidget* widget = WidgetFor(column->AttrHash());
438		if (widget == NULL) {
439			// no widget for this column, next
440			continue;
441		}
442
443		if (widget->IsActive()) {
444			poseView->CommitActivePose();
445			found = true;
446			continue;
447		}
448
449		if (found && column->Editable()) {
450			BRect bounds;
451			if (poseView->ViewMode() == kListMode) {
452				int32 poseIndex = poseView->IndexOfPose(this);
453				BPoint poseLoc(0, poseIndex* poseView->ListElemHeight());
454				bounds = widget->CalcRect(poseLoc, column, poseView);
455			} else
456				bounds = widget->CalcRect(Location(poseView), NULL, poseView);
457
458			widget->StartEdit(bounds, poseView, this);
459			break;
460		}
461	}
462}
463
464
465void
466BPose::EditNextWidget(BPoseView* poseView)
467{
468	EditPreviousNextWidgetCommon(poseView, true);
469}
470
471
472void
473BPose::EditPreviousWidget(BPoseView* poseView)
474{
475	EditPreviousNextWidgetCommon(poseView, false);
476}
477
478
479bool
480BPose::PointInPose(const BPoseView* poseView, BPoint where) const
481{
482	ASSERT(poseView->ViewMode() != kListMode);
483
484	BPoint location = Location(poseView);
485
486	if (poseView->ViewMode() == kIconMode) {
487		// check icon rect, then actual icon pixel
488		BRect rect(location, location);
489		rect.right += poseView->IconSizeInt() - 1;
490		rect.bottom += poseView->IconSizeInt() - 1;
491
492		if (rect.Contains(where)) {
493			return IconCache::sIconCache->IconHitTest(where - location,
494				ResolvedModel(), kNormalIcon, poseView->IconSize());
495		}
496
497		BTextWidget* widget = WidgetFor(poseView->FirstColumn()->AttrHash());
498		if (widget) {
499			float textWidth = ceilf(widget->TextWidth(poseView) + 1);
500			rect.left += (poseView->IconSizeInt() - textWidth) / 2;
501			rect.right = rect.left + textWidth;
502		}
503
504		rect.top = location.y + poseView->IconSizeInt();
505		rect.bottom = rect.top + poseView->FontHeight();
506
507		return rect.Contains(where);
508	}
509
510	// MINI_ICON_MODE rect calc
511	BRect rect(location, location);
512	rect.right += B_MINI_ICON + kMiniIconSeparator;
513	rect.bottom += poseView->IconPoseHeight();
514	BTextWidget* widget = WidgetFor(poseView->FirstColumn()->AttrHash());
515	if (widget != NULL)
516		rect.right += ceil(widget->TextWidth(poseView) + 1);
517
518	return rect.Contains(where);
519}
520
521
522bool
523BPose::PointInPose(BPoint where, const BPoseView* poseView, BPoint point,
524	BTextWidget** hitWidget) const
525{
526	if (hitWidget != NULL)
527		*hitWidget = NULL;
528
529	// check intersection with icon
530	BRect rect = _IconRect(poseView, where);
531	if (rect.Contains(point))
532		return true;
533
534	for (int32 index = 0; ; index++) {
535		BColumn* column = poseView->ColumnAt(index);
536		if (column == NULL)
537			break;
538
539		BTextWidget* widget = WidgetFor(column->AttrHash());
540		if (widget != NULL
541			&& widget->CalcClickRect(where, column, poseView).Contains(point)) {
542			if (hitWidget != NULL)
543				*hitWidget = widget;
544
545			return true;
546		}
547	}
548
549	return false;
550}
551
552
553void
554BPose::Draw(BRect rect, const BRect& updateRect, BPoseView* poseView,
555	BView* drawView, bool fullDraw, BPoint offset, bool selected)
556{
557	// If the background wasn't cleared and Draw() is not called after
558	// having edited a name or similar (with fullDraw)
559	if (!fBackgroundClean && !fullDraw) {
560		fBackgroundClean = true;
561		poseView->Invalidate(rect);
562		return;
563	} else
564		fBackgroundClean = false;
565
566	bool direct = (drawView == poseView);
567	bool windowActive = poseView->Window()->IsActive();
568	bool showSelectionWhenInactive = poseView->fShowSelectionWhenInactive;
569	bool isDrawingSelectionRect = poseView->fIsDrawingSelectionRect;
570
571	ModelNodeLazyOpener modelOpener(fModel);
572
573	if (poseView->ViewMode() == kListMode) {
574		// draw in list mode
575		BRect iconRect = _IconRect(poseView, rect.LeftTop());
576		if (updateRect.Intersects(iconRect)) {
577			iconRect.OffsetBy(offset);
578			DrawIcon(iconRect.LeftTop(), drawView, poseView->IconSize(),
579				direct, !windowActive && !showSelectionWhenInactive);
580		}
581
582		// draw text
583		int32 columnsToDraw = 1;
584		if (fullDraw)
585			columnsToDraw = poseView->CountColumns();
586
587		for (int32 index = 0; index < columnsToDraw; index++) {
588			BColumn* column = poseView->ColumnAt(index);
589			if (column == NULL)
590				break;
591
592			// if widget doesn't exist, create it
593			BTextWidget* widget = WidgetFor(column, poseView, modelOpener);
594			if (widget == NULL || !widget->IsVisible())
595				continue;
596
597			BRect widgetRect(widget->ColumnRect(rect.LeftTop(), column,
598				poseView));
599			if (!updateRect.Intersects(widgetRect))
600				continue;
601
602			BRect widgetTextRect(widget->CalcRect(rect.LeftTop(),
603				column, poseView));
604
605			bool selectDuringDraw = direct && selected
606				&& windowActive;
607
608			if (index == 0 && selectDuringDraw) {
609				// draw with "reverse video" to select text
610				drawView->PushState();
611				drawView->SetLowColor(poseView->BackColor(true));
612			} else if (!direct && index == 0 && selected
613				&& (windowActive || isDrawingSelectionRect)) {
614				drawView->SetLowColor(poseView->TextColor(true)); // inverse
615				drawView->FillRect(widgetTextRect.OffsetByCopy(offset), B_SOLID_LOW);
616			}
617
618			if (index == 0) {
619				widget->Draw(widgetRect, widgetTextRect,
620					column->Width(), poseView, drawView, selected,
621					fClipboardMode, offset, direct);
622			} else {
623				widget->Draw(widgetTextRect, widgetTextRect,
624					column->Width(), poseView, drawView, false,
625					fClipboardMode, offset, direct);
626			}
627
628			if (index == 0 && selected) {
629				if (selectDuringDraw)
630					drawView->PopState();
631				else if (windowActive || isDrawingSelectionRect) {
632					drawView->SetLowColor(poseView->LowColor());
633					drawView->InvertRect(widgetTextRect.OffsetByCopy(offset));
634				} else if (!windowActive && showSelectionWhenInactive) {
635					widgetTextRect.OffsetBy(offset);
636					drawView->PushState();
637					drawView->SetDrawingMode(B_OP_BLEND);
638					drawView->SetHighColor(128, 128, 128, 255);
639					drawView->FillRect(widgetTextRect);
640					drawView->PopState();
641				}
642			}
643		}
644	} else {
645		// draw in icon mode
646		BPoint location(Location(poseView));
647		BPoint iconOrigin(location);
648		iconOrigin += offset;
649
650		DrawIcon(iconOrigin, drawView, poseView->IconSize(), direct,
651			!windowActive && !showSelectionWhenInactive);
652
653		BColumn* column = poseView->FirstColumn();
654		if (column == NULL)
655			return;
656
657		BTextWidget* widget = WidgetFor(column, poseView, modelOpener);
658		if (widget == NULL || !widget->IsVisible())
659			return;
660
661		rect = widget->CalcRect(location, NULL, poseView);
662
663		bool selectDuringDraw = direct && selected && windowActive;
664
665		if (selectDuringDraw) {
666			// draw with "reverse video" to select text
667			drawView->PushState();
668			drawView->SetLowColor(poseView->BackColor(true));
669		}
670
671		widget->Draw(rect, rect, rect.Width(), poseView, drawView,
672			selected, fClipboardMode, offset, direct);
673
674		if (selectDuringDraw)
675			drawView->PopState();
676		else if (selected && direct) {
677			if (windowActive || isDrawingSelectionRect) {
678				rect.OffsetBy(offset);
679				drawView->InvertRect(rect);
680			} else if (!windowActive && showSelectionWhenInactive) {
681				drawView->PushState();
682				drawView->SetDrawingMode(B_OP_BLEND);
683				drawView->SetHighColor(128, 128, 128, 255);
684				drawView->FillRect(rect);
685				drawView->PopState();
686			}
687		}
688	}
689}
690
691
692void
693BPose::DeselectWithoutErasingBackground(BRect, BPoseView* poseView)
694{
695	ASSERT(poseView->ViewMode() != kListMode);
696	ASSERT(!IsSelected());
697
698	BPoint location(Location(poseView));
699
700	// draw icon directly
701	if (fPercent == -1)
702		DrawIcon(location, poseView, poseView->IconSize(), true);
703	else
704		UpdateIcon(location, poseView);
705
706	BColumn* column = poseView->FirstColumn();
707	if (column == NULL)
708		return;
709
710	BTextWidget* widget = WidgetFor(column->AttrHash());
711	if (widget == NULL || !widget->IsVisible())
712		return;
713
714	// just invalidate the background, don't draw anything
715	poseView->Invalidate(widget->CalcRect(location, NULL, poseView));
716}
717
718
719void
720BPose::MoveTo(BPoint point, BPoseView* poseView, bool invalidate)
721{
722	point.x = floorf(point.x);
723	point.y = floorf(point.y);
724
725	BRect oldBounds;
726
727	BPoint oldLocation = Location(poseView);
728
729	ASSERT(poseView->ViewMode() != kListMode);
730	if (point == oldLocation || poseView->ViewMode() == kListMode)
731		return;
732
733	if (invalidate)
734		oldBounds = CalcRect(poseView);
735
736	// might need to move a text view if we're active
737	if (poseView->ActivePose() == this) {
738		BView* border_view = poseView->FindView("BorderView");
739		if (border_view) {
740			border_view->MoveBy(point.x - oldLocation.x,
741				point.y - oldLocation.y);
742		}
743	}
744
745	float scale = 1.0;
746	if (poseView->ViewMode() == kIconMode) {
747		scale = (float)poseView->IconSizeInt() / 32.0;
748	}
749	fLocation.x = point.x / scale;
750	fLocation.y = point.y / scale;
751
752	fHasLocation = true;
753	fNeedsSaveLocation = true;
754
755	if (invalidate) {
756		poseView->Invalidate(oldBounds);
757		poseView->Invalidate(CalcRect(poseView));
758	}
759}
760
761
762BTextWidget*
763BPose::ActiveWidget() const
764{
765	for (int32 i = fWidgetList.CountItems(); i-- > 0;) {
766		BTextWidget* widget = fWidgetList.ItemAt(i);
767		if (widget->IsActive())
768			return widget;
769	}
770
771	return NULL;
772}
773
774
775BTextWidget*
776BPose::WidgetFor(uint32 attr, int32* index) const
777{
778	int32 count = fWidgetList.CountItems();
779	for (int32 i = 0; i < count; i++) {
780		BTextWidget* widget = fWidgetList.ItemAt(i);
781		if (widget->AttrHash() == attr) {
782			if (index != NULL)
783				*index = i;
784
785			return widget;
786		}
787	}
788
789	return NULL;
790}
791
792
793BTextWidget*
794BPose::WidgetFor(BColumn* column, BPoseView* poseView,
795	ModelNodeLazyOpener &opener, int32* index)
796{
797	BTextWidget* widget = WidgetFor(column->AttrHash(), index);
798	if (widget == NULL)
799		widget = AddWidget(poseView, column, opener);
800
801	return widget;
802}
803
804
805void
806BPose::DrawIcon(BPoint where, BView* view, BSize size, bool direct,
807	bool drawUnselected)
808{
809	if (fClipboardMode == kMoveSelectionTo) {
810		view->SetDrawingMode(B_OP_ALPHA);
811		view->SetHighColor(0, 0, 0, 64);
812			// set the level of transparency
813		view->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY);
814	} else if (direct)
815		view->SetDrawingMode(B_OP_OVER);
816
817	IconCache::sIconCache->Draw(ResolvedModel(), view, where,
818		fIsSelected && !drawUnselected ? kSelectedIcon : kNormalIcon, size,
819		true);
820
821	if (fPercent != -1)
822		DrawBar(where, view, size);
823}
824
825
826void
827BPose::DrawBar(BPoint where, BView* view, BSize iconSize)
828{
829	view->PushState();
830
831	int32 size = iconSize.IntegerWidth();
832	int32 yOffset;
833	int32 barWidth = (int32)(7.0f / 32.0f * (float)(size + 1));
834	if (barWidth < 4) {
835		barWidth = 4;
836		yOffset = 0;
837	} else
838		yOffset = 2;
839	int32 barHeight = size - 4 - 2 * yOffset;
840
841	// the black shadowed line
842	view->SetHighColor(32, 32, 32, 92);
843	view->MovePenTo(BPoint(where.x + size, where.y + 1 + yOffset));
844	view->StrokeLine(BPoint(where.x + size, where.y + size - yOffset));
845	view->StrokeLine(BPoint(where.x + size - barWidth + 1,
846		where.y + size - yOffset));
847
848	view->SetDrawingMode(B_OP_ALPHA);
849
850	// the gray frame
851	view->SetHighColor(76, 76, 76, 192);
852	BRect rect(where.x + size - barWidth,where.y + yOffset,
853		where.x + size - 1,where.y + size - 1 - yOffset);
854	view->StrokeRect(rect);
855
856	// calculate bar height
857	int32 percent = fPercent > -1 ? fPercent : -2 - fPercent;
858	int32 barPos = int32(barHeight * percent / 100.0);
859	if (barPos < 0)
860		barPos = 0;
861	else if (barPos > barHeight)
862		barPos = barHeight;
863
864	// the free space bar
865	view->SetHighColor(TrackerSettings().FreeSpaceColor());
866
867	rect.InsetBy(1,1);
868	BRect bar(rect);
869	bar.bottom = bar.top + barPos - 1;
870	if (barPos > 0)
871		view->FillRect(bar);
872
873	// the used space bar
874	bar.top = bar.bottom + 1;
875	bar.bottom = rect.bottom;
876	view->SetHighColor(fPercent < -1
877		? TrackerSettings().WarningSpaceColor()
878		: TrackerSettings().UsedSpaceColor());
879	view->FillRect(bar);
880
881	view->PopState();
882}
883
884
885void
886BPose::DrawToggleSwitch(BRect, BPoseView*)
887{
888}
889
890
891BPoint
892BPose::Location(const BPoseView* poseView) const
893{
894	float scale = 1.0;
895	if (poseView->ViewMode() == kIconMode)
896		scale = (float)poseView->IconSizeInt() / 32.0;
897
898	return BPoint(fLocation.x * scale, fLocation.y * scale);
899}
900
901
902void
903BPose::SetLocation(BPoint point, const BPoseView* poseView)
904{
905	float scale = 1.0;
906	if (poseView->ViewMode() == kIconMode)
907		scale = (float)poseView->IconSizeInt() / 32.0;
908
909	fLocation = BPoint(floorf(point.x / scale), floorf(point.y / scale));
910	if (isinff(fLocation.x) || isinff(fLocation.y))
911		debugger("BPose::SetLocation() - infinite location");
912
913	fHasLocation = true;
914}
915
916
917BRect
918BPose::CalcRect(BPoint loc, const BPoseView* poseView, bool minimalRect) const
919{
920	ASSERT(poseView->ViewMode() == kListMode);
921
922	BColumn* column = poseView->LastColumn();
923	BRect rect;
924	rect.left = loc.x;
925	rect.top = loc.y;
926	rect.right = loc.x + column->Offset() + column->Width();
927	rect.bottom = rect.top + poseView->ListElemHeight();
928
929	if (minimalRect) {
930		BTextWidget* widget = WidgetFor(poseView->FirstColumn()->AttrHash());
931		if (widget != NULL) {
932			rect.right = widget->CalcRect(loc, poseView->FirstColumn(),
933				poseView).right;
934		}
935	}
936
937	return rect;
938}
939
940
941BRect
942BPose::CalcRect(const BPoseView* poseView) const
943{
944	ASSERT(poseView->ViewMode() != kListMode);
945
946	BRect rect;
947	BPoint location = Location(poseView);
948	if (poseView->ViewMode() == kIconMode) {
949		rect.left = location.x;
950		rect.right = rect.left + poseView->IconSizeInt();
951
952		BTextWidget* widget = WidgetFor(poseView->FirstColumn()->AttrHash());
953		if (widget != NULL) {
954			float textWidth = ceilf(widget->TextWidth(poseView) + 1);
955			if (textWidth > poseView->IconSizeInt()) {
956				rect.left += (poseView->IconSizeInt() - textWidth) / 2;
957				rect.right = rect.left + textWidth;
958			}
959		}
960
961		rect.top = location.y;
962		rect.bottom = rect.top + poseView->IconPoseHeight();
963	} else {
964		// MINI_ICON_MODE rect calc
965		rect.left = location.x;
966		rect.right = rect.left + B_MINI_ICON + kMiniIconSeparator;
967
968		// big font sizes can push top above icon location top
969		rect.bottom = location.y
970			+ roundf((B_MINI_ICON + poseView->FontHeight()) / 2);
971		rect.top = rect.bottom - floorf(poseView->FontHeight());
972		BTextWidget* widget = WidgetFor(poseView->FirstColumn()->AttrHash());
973		if (widget != NULL)
974			rect.right += ceil(widget->TextWidth(poseView) + 1);
975	}
976
977	return rect;
978}
979
980
981BRect
982BPose::_IconRect(const BPoseView* poseView, BPoint location) const
983{
984	uint32 size = poseView->IconSizeInt();
985	BRect rect;
986	rect.left = location.x + poseView->ListOffset();
987	rect.right = rect.left + size;
988	rect.top = location.y + (poseView->ListElemHeight() - size) / 2.f;
989	rect.bottom = rect.top + size;
990	return rect;
991}
992
993
994#if DEBUG
995void
996BPose::PrintToStream()
997{
998	TargetModel()->PrintToStream();
999	switch (fClipboardMode) {
1000		case kMoveSelectionTo:
1001			PRINT(("clipboardMode: Cut\n"));
1002			break;
1003
1004		case kCopySelectionTo:
1005			PRINT(("clipboardMode: Copy\n"));
1006			break;
1007
1008		default:
1009			PRINT(("clipboardMode: 0 - not in clipboard\n"));
1010			break;
1011	}
1012	PRINT(("%sselected\n", IsSelected() ? "" : "not "));
1013	PRINT(("location %s x:%f y:%f\n", HasLocation() ? "" : "unknown ",
1014		HasLocation() ? fLocation.x : 0,
1015		HasLocation() ? fLocation.y : 0));
1016	PRINT(("%s autoplaced \n", WasAutoPlaced() ? "was" : "not"));
1017}
1018#endif
1019