xref: /haiku/src/kits/tracker/Pose.cpp (revision 9a6a20d4689307142a7ed26a1437ba47e244e73f)
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 <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 
52 int32
53 CalcFreeSpace(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
73 BPose::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 
115 BPose::~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 
131 void
132 BPose::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 
144 BTextWidget*
145 BPose::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 
158 BTextWidget*
159 BPose::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 
173 void
174 BPose::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 
183 void
184 BPose::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 
198 inline bool
199 OneMouseUp(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 
217 void
218 BPose::MouseUp(BPoint poseLoc, BPoseView* poseView, BPoint where, int32)
219 {
220 	WhileEachTextWidget(this, poseView, OneMouseUp, poseLoc, where);
221 }
222 
223 
224 inline void
225 OneCheckAndUpdate(BTextWidget* widget, BPose*, BPoseView* poseView,
226 	BColumn* column, BPoint poseLoc)
227 {
228 	widget->CheckAndUpdate(poseLoc, column, poseView, true);
229 }
230 
231 
232 void
233 BPose::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 
243 void
244 BPose::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 
307 bool
308 BPose::_PeriodicUpdateCallback(BPose* pose, void* cookie)
309 {
310 	return pose->UpdateVolumeSpaceBar((BVolume*)cookie);
311 }
312 
313 
314 bool
315 BPose::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 
340 void
341 BPose::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 	} else {
353 		BPoint location = Location(poseView);
354 		rect.left = location.x;
355 		rect.top = location.y;
356 		rect.right = rect.left + iconSize;
357 		rect.bottom = rect.top + iconSize;
358 	}
359 
360 	poseView->Invalidate(rect);
361 }
362 
363 
364 void
365 BPose::UpdateBrokenSymLink(BPoint poseLoc, BPoseView* poseView)
366 {
367 	ASSERT(TargetModel()->IsSymLink());
368 	ASSERT(TargetModel()->LinkTo() == NULL);
369 	if (!TargetModel()->IsSymLink() || TargetModel()->LinkTo() != NULL)
370 		return;
371 
372 	UpdateIcon(poseLoc, poseView);
373 }
374 
375 
376 void
377 BPose::UpdateWasBrokenSymlink(BPoint poseLoc, BPoseView* poseView)
378 {
379 	if (!fModel->IsSymLink())
380 		return;
381 
382 	if (fModel->LinkTo() != NULL) {
383 		BEntry entry(fModel->EntryRef(), true);
384 		if (entry.InitCheck() != B_OK) {
385 			watch_node(fModel->LinkTo()->NodeRef(), B_STOP_WATCHING, poseView);
386 			fModel->SetLinkTo(NULL);
387 			UpdateIcon(poseLoc, poseView);
388 		}
389 		return;
390 	}
391 
392 	poseView->CreateSymlinkPoseTarget(fModel);
393 	UpdateIcon(poseLoc, poseView);
394 	if (fModel->LinkTo() != NULL)
395 		fModel->LinkTo()->CloseNode();
396 }
397 
398 
399 void
400 BPose::EditFirstWidget(BPoint poseLoc, BPoseView* poseView)
401 {
402 	// find first editable widget
403 	BColumn* column;
404 	for (int32 i = 0; (column = poseView->ColumnAt(i)) != NULL; i++) {
405 		BTextWidget* widget = WidgetFor(column->AttrHash());
406 
407 		if (widget != NULL && widget->IsEditable()) {
408 			BRect bounds;
409 			// ToDo:
410 			// fold the three StartEdit code sequences into a cover call
411 			if (poseView->ViewMode() == kListMode)
412 				bounds = widget->CalcRect(poseLoc, column, poseView);
413 			else
414 				bounds = widget->CalcRect(Location(poseView), NULL, poseView);
415 
416 			widget->StartEdit(bounds, poseView, this);
417 			break;
418 		}
419 	}
420 }
421 
422 
423 void
424 BPose::EditPreviousNextWidgetCommon(BPoseView* poseView, bool next)
425 {
426 	bool found = false;
427 	int32 delta = next ? 1 : -1;
428 	for (int32 index = next ? 0 : poseView->CountColumns() - 1; ;
429 			index += delta) {
430 		BColumn* column = poseView->ColumnAt(index);
431 		if (column == NULL) {
432 			// out of columns
433 			break;
434 		}
435 
436 		BTextWidget* widget = WidgetFor(column->AttrHash());
437 		if (widget == NULL) {
438 			// no widget for this column, next
439 			continue;
440 		}
441 
442 		if (widget->IsActive()) {
443 			poseView->CommitActivePose();
444 			found = true;
445 			continue;
446 		}
447 
448 		if (found && column->Editable()) {
449 			BRect bounds;
450 			if (poseView->ViewMode() == kListMode) {
451 				int32 poseIndex = poseView->IndexOfPose(this);
452 				BPoint poseLoc(0, poseIndex* poseView->ListElemHeight());
453 				bounds = widget->CalcRect(poseLoc, column, poseView);
454 			} else
455 				bounds = widget->CalcRect(Location(poseView), NULL, poseView);
456 
457 			widget->StartEdit(bounds, poseView, this);
458 			break;
459 		}
460 	}
461 }
462 
463 
464 void
465 BPose::EditNextWidget(BPoseView* poseView)
466 {
467 	EditPreviousNextWidgetCommon(poseView, true);
468 }
469 
470 
471 void
472 BPose::EditPreviousWidget(BPoseView* poseView)
473 {
474 	EditPreviousNextWidgetCommon(poseView, false);
475 }
476 
477 
478 bool
479 BPose::PointInPose(const BPoseView* poseView, BPoint where) const
480 {
481 	ASSERT(poseView->ViewMode() != kListMode);
482 
483 	BPoint location = Location(poseView);
484 
485 	if (poseView->ViewMode() == kIconMode) {
486 		// check icon rect, then actual icon pixel
487 		BRect rect(location, location);
488 		rect.right += poseView->IconSizeInt() - 1;
489 		rect.bottom += poseView->IconSizeInt() - 1;
490 
491 		if (rect.Contains(where)) {
492 			return IconCache::sIconCache->IconHitTest(where - location,
493 				ResolvedModel(), kNormalIcon, poseView->IconSize());
494 		}
495 
496 		BTextWidget* widget = WidgetFor(poseView->FirstColumn()->AttrHash());
497 		if (widget) {
498 			float textWidth = ceilf(widget->TextWidth(poseView) + 1);
499 			rect.left += (poseView->IconSizeInt() - textWidth) / 2;
500 			rect.right = rect.left + textWidth;
501 		}
502 
503 		rect.top = location.y + poseView->IconSizeInt();
504 		rect.bottom = rect.top + ActualFontHeight(poseView);
505 
506 		return rect.Contains(where);
507 	}
508 
509 	// MINI_ICON_MODE rect calc
510 	BRect rect(location, location);
511 	rect.right += B_MINI_ICON + kMiniIconSeparator;
512 	rect.bottom += poseView->IconPoseHeight();
513 	BTextWidget* widget = WidgetFor(poseView->FirstColumn()->AttrHash());
514 	if (widget != NULL)
515 		rect.right += ceil(widget->TextWidth(poseView) + 1);
516 
517 	return rect.Contains(where);
518 }
519 
520 
521 bool
522 BPose::PointInPose(BPoint where, const BPoseView* poseView, BPoint point,
523 	BTextWidget** hitWidget) const
524 {
525 	if (hitWidget != NULL)
526 		*hitWidget = NULL;
527 
528 	// check intersection with icon
529 	BRect rect = _IconRect(poseView, where);
530 	if (rect.Contains(point))
531 		return true;
532 
533 	for (int32 index = 0; ; index++) {
534 		BColumn* column = poseView->ColumnAt(index);
535 		if (column == NULL)
536 			break;
537 
538 		BTextWidget* widget = WidgetFor(column->AttrHash());
539 		if (widget != NULL
540 			&& widget->CalcClickRect(where, column, poseView).Contains(point)) {
541 			if (hitWidget != NULL)
542 				*hitWidget = widget;
543 
544 			return true;
545 		}
546 	}
547 
548 	return false;
549 }
550 
551 
552 void
553 BPose::Draw(BRect rect, const BRect& updateRect, BPoseView* poseView,
554 	BView* drawView, bool fullDraw, BPoint offset, bool selected)
555 {
556 	// If the background wasn't cleared and Draw() is not called after
557 	// having edited a name or similar (with fullDraw)
558 	if (!fBackgroundClean && !fullDraw) {
559 		fBackgroundClean = true;
560 		poseView->Invalidate(rect);
561 		return;
562 	} else
563 		fBackgroundClean = false;
564 
565 	bool direct = (drawView == poseView);
566 	bool windowActive = poseView->Window()->IsActive();
567 	bool showSelectionWhenInactive = poseView->fShowSelectionWhenInactive;
568 	bool isDrawingSelectionRect = poseView->fIsDrawingSelectionRect;
569 
570 	ModelNodeLazyOpener modelOpener(fModel);
571 
572 	if (poseView->ViewMode() == kListMode) {
573 		// draw in list mode
574 		BRect iconRect = _IconRect(poseView, rect.LeftTop());
575 		if (updateRect.Intersects(iconRect)) {
576 			iconRect.OffsetBy(offset);
577 			DrawIcon(iconRect.LeftTop(), drawView, poseView->IconSize(),
578 				direct, !windowActive && !showSelectionWhenInactive);
579 		}
580 
581 		// draw text
582 		int32 columnsToDraw = 1;
583 		if (fullDraw)
584 			columnsToDraw = poseView->CountColumns();
585 
586 		for (int32 index = 0; index < columnsToDraw; index++) {
587 			BColumn* column = poseView->ColumnAt(index);
588 			if (column == NULL)
589 				break;
590 
591 			// if widget doesn't exist, create it
592 			BTextWidget* widget = WidgetFor(column, poseView, modelOpener);
593 			if (widget == NULL || !widget->IsVisible())
594 				continue;
595 
596 			BRect widgetRect(widget->ColumnRect(rect.LeftTop(), column,
597 				poseView));
598 			if (!updateRect.Intersects(widgetRect))
599 				continue;
600 
601 			BRect widgetTextRect(widget->CalcRect(rect.LeftTop(),
602 				column, poseView));
603 
604 			bool selectDuringDraw = direct && selected
605 				&& windowActive;
606 
607 			if (index == 0 && selectDuringDraw) {
608 				// draw with "reverse video" to select text
609 				drawView->PushState();
610 				drawView->SetLowColor(poseView->BackColor(true));
611 			} else if (!direct && index == 0 && selected
612 				&& (windowActive || isDrawingSelectionRect)) {
613 				drawView->SetLowColor(poseView->TextColor(true)); // inverse
614 				drawView->FillRect(widgetTextRect.OffsetByCopy(offset), B_SOLID_LOW);
615 			}
616 
617 			if (index == 0) {
618 				widget->Draw(widgetRect, widgetTextRect,
619 					column->Width(), poseView, drawView, selected,
620 					fClipboardMode, offset, direct);
621 			} else {
622 				widget->Draw(widgetTextRect, widgetTextRect,
623 					column->Width(), poseView, drawView, false,
624 					fClipboardMode, offset, direct);
625 			}
626 
627 			if (index == 0 && selected) {
628 				if (selectDuringDraw)
629 					drawView->PopState();
630 				else if (windowActive || isDrawingSelectionRect) {
631 					drawView->SetLowColor(poseView->LowColor());
632 					drawView->InvertRect(widgetTextRect.OffsetByCopy(offset));
633 				} else if (!windowActive && showSelectionWhenInactive) {
634 					widgetTextRect.OffsetBy(offset);
635 					drawView->PushState();
636 					drawView->SetDrawingMode(B_OP_BLEND);
637 					drawView->SetHighColor(128, 128, 128, 255);
638 					drawView->FillRect(widgetTextRect);
639 					drawView->PopState();
640 				}
641 			}
642 		}
643 	} else {
644 		// draw in icon mode
645 		BPoint location(Location(poseView));
646 		BPoint iconOrigin(location);
647 		iconOrigin += offset;
648 
649 		DrawIcon(iconOrigin, drawView, poseView->IconSize(), direct,
650 			!windowActive && !showSelectionWhenInactive);
651 
652 		BColumn* column = poseView->FirstColumn();
653 		if (column == NULL)
654 			return;
655 
656 		BTextWidget* widget = WidgetFor(column, poseView, modelOpener);
657 		if (widget == NULL || !widget->IsVisible())
658 			return;
659 
660 		rect = widget->CalcRect(location, NULL, poseView);
661 
662 		bool selectDuringDraw = direct && selected && windowActive;
663 
664 		if (selectDuringDraw) {
665 			// draw with "reverse video" to select text
666 			drawView->PushState();
667 			drawView->SetLowColor(poseView->BackColor(true));
668 		}
669 
670 		widget->Draw(rect, rect, rect.Width(), poseView, drawView,
671 			selected, fClipboardMode, offset, direct);
672 
673 		if (selectDuringDraw)
674 			drawView->PopState();
675 		else if (selected && direct) {
676 			if (windowActive || isDrawingSelectionRect) {
677 				rect.OffsetBy(offset);
678 				drawView->InvertRect(rect);
679 			} else if (!windowActive && showSelectionWhenInactive) {
680 				drawView->PushState();
681 				drawView->SetDrawingMode(B_OP_BLEND);
682 				drawView->SetHighColor(128, 128, 128, 255);
683 				drawView->FillRect(rect);
684 				drawView->PopState();
685 			}
686 		}
687 	}
688 }
689 
690 
691 void
692 BPose::DeselectWithoutErasingBackground(BRect, BPoseView* poseView)
693 {
694 	ASSERT(poseView->ViewMode() != kListMode);
695 	ASSERT(!IsSelected());
696 
697 	BPoint location(Location(poseView));
698 
699 	// draw icon directly
700 	if (fPercent == -1)
701 		DrawIcon(location, poseView, poseView->IconSize(), true);
702 	else
703 		UpdateIcon(location, poseView);
704 
705 	BColumn* column = poseView->FirstColumn();
706 	if (column == NULL)
707 		return;
708 
709 	BTextWidget* widget = WidgetFor(column->AttrHash());
710 	if (widget == NULL || !widget->IsVisible())
711 		return;
712 
713 	// just invalidate the background, don't draw anything
714 	poseView->Invalidate(widget->CalcRect(location, NULL, poseView));
715 }
716 
717 
718 void
719 BPose::MoveTo(BPoint point, BPoseView* poseView, bool invalidate)
720 {
721 	point.x = floorf(point.x);
722 	point.y = floorf(point.y);
723 
724 	BRect oldBounds;
725 
726 	BPoint oldLocation = Location(poseView);
727 
728 	ASSERT(poseView->ViewMode() != kListMode);
729 	if (point == oldLocation || poseView->ViewMode() == kListMode)
730 		return;
731 
732 	if (invalidate)
733 		oldBounds = CalcRect(poseView);
734 
735 	// might need to move a text view if we're active
736 	if (poseView->ActivePose() == this) {
737 		BView* border_view = poseView->FindView("BorderView");
738 		if (border_view) {
739 			border_view->MoveBy(point.x - oldLocation.x,
740 				point.y - oldLocation.y);
741 		}
742 	}
743 
744 	float scale = 1.0;
745 	if (poseView->ViewMode() == kIconMode) {
746 		scale = (float)poseView->IconSizeInt() / 32.0;
747 	}
748 	fLocation.x = point.x / scale;
749 	fLocation.y = point.y / scale;
750 
751 	fHasLocation = true;
752 	fNeedsSaveLocation = true;
753 
754 	if (invalidate) {
755 		poseView->Invalidate(oldBounds);
756 		poseView->Invalidate(CalcRect(poseView));
757 	}
758 }
759 
760 
761 BTextWidget*
762 BPose::ActiveWidget() const
763 {
764 	for (int32 i = fWidgetList.CountItems(); i-- > 0;) {
765 		BTextWidget* widget = fWidgetList.ItemAt(i);
766 		if (widget->IsActive())
767 			return widget;
768 	}
769 
770 	return NULL;
771 }
772 
773 
774 BTextWidget*
775 BPose::WidgetFor(uint32 attr, int32* index) const
776 {
777 	int32 count = fWidgetList.CountItems();
778 	for (int32 i = 0; i < count; i++) {
779 		BTextWidget* widget = fWidgetList.ItemAt(i);
780 		if (widget->AttrHash() == attr) {
781 			if (index != NULL)
782 				*index = i;
783 
784 			return widget;
785 		}
786 	}
787 
788 	return NULL;
789 }
790 
791 
792 BTextWidget*
793 BPose::WidgetFor(BColumn* column, BPoseView* poseView,
794 	ModelNodeLazyOpener &opener, int32* index)
795 {
796 	BTextWidget* widget = WidgetFor(column->AttrHash(), index);
797 	if (widget == NULL)
798 		widget = AddWidget(poseView, column, opener);
799 
800 	return widget;
801 }
802 
803 
804 void
805 BPose::DrawIcon(BPoint where, BView* view, BSize size, bool direct,
806 	bool drawUnselected)
807 {
808 	if (fClipboardMode == kMoveSelectionTo) {
809 		view->SetDrawingMode(B_OP_ALPHA);
810 		view->SetHighColor(0, 0, 0, 64);
811 			// set the level of transparency
812 		view->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY);
813 	} else if (direct)
814 		view->SetDrawingMode(B_OP_OVER);
815 
816 	IconCache::sIconCache->Draw(ResolvedModel(), view, where,
817 		fIsSelected && !drawUnselected ? kSelectedIcon : kNormalIcon, size,
818 		true);
819 
820 	if (fPercent != -1)
821 		DrawBar(where, view, size);
822 }
823 
824 
825 void
826 BPose::DrawBar(BPoint where, BView* view, BSize iconSize)
827 {
828 	view->PushState();
829 
830 	int32 size = iconSize.IntegerWidth();
831 	int32 yOffset;
832 	int32 barWidth = (int32)(7.0f / 32.0f * (float)(size + 1));
833 	if (barWidth < 4) {
834 		barWidth = 4;
835 		yOffset = 0;
836 	} else
837 		yOffset = 2;
838 	int32 barHeight = size - 4 - 2 * yOffset;
839 
840 	// the black shadowed line
841 	view->SetHighColor(32, 32, 32, 92);
842 	view->MovePenTo(BPoint(where.x + size, where.y + 1 + yOffset));
843 	view->StrokeLine(BPoint(where.x + size, where.y + size - yOffset));
844 	view->StrokeLine(BPoint(where.x + size - barWidth + 1,
845 		where.y + size - yOffset));
846 
847 	view->SetDrawingMode(B_OP_ALPHA);
848 
849 	// the gray frame
850 	view->SetHighColor(76, 76, 76, 192);
851 	BRect rect(where.x + size - barWidth,where.y + yOffset,
852 		where.x + size - 1,where.y + size - 1 - yOffset);
853 	view->StrokeRect(rect);
854 
855 	// calculate bar height
856 	int32 percent = fPercent > -1 ? fPercent : -2 - fPercent;
857 	int32 barPos = int32(barHeight * percent / 100.0);
858 	if (barPos < 0)
859 		barPos = 0;
860 	else if (barPos > barHeight)
861 		barPos = barHeight;
862 
863 	// the free space bar
864 	view->SetHighColor(TrackerSettings().FreeSpaceColor());
865 
866 	rect.InsetBy(1,1);
867 	BRect bar(rect);
868 	bar.bottom = bar.top + barPos - 1;
869 	if (barPos > 0)
870 		view->FillRect(bar);
871 
872 	// the used space bar
873 	bar.top = bar.bottom + 1;
874 	bar.bottom = rect.bottom;
875 	view->SetHighColor(fPercent < -1
876 		? TrackerSettings().WarningSpaceColor()
877 		: TrackerSettings().UsedSpaceColor());
878 	view->FillRect(bar);
879 
880 	view->PopState();
881 }
882 
883 
884 void
885 BPose::DrawToggleSwitch(BRect, BPoseView*)
886 {
887 }
888 
889 
890 BPoint
891 BPose::Location(const BPoseView* poseView) const
892 {
893 	float scale = 1.0;
894 	if (poseView->ViewMode() == kIconMode)
895 		scale = (float)poseView->IconSizeInt() / 32.0;
896 
897 	return BPoint(fLocation.x * scale, fLocation.y * scale);
898 }
899 
900 
901 void
902 BPose::SetLocation(BPoint point, const BPoseView* poseView)
903 {
904 	float scale = 1.0;
905 	if (poseView->ViewMode() == kIconMode)
906 		scale = (float)poseView->IconSizeInt() / 32.0;
907 
908 	fLocation = BPoint(floorf(point.x / scale), floorf(point.y / scale));
909 	if (isinff(fLocation.x) || isinff(fLocation.y))
910 		debugger("BPose::SetLocation() - infinite location");
911 
912 	fHasLocation = true;
913 }
914 
915 
916 BRect
917 BPose::CalcRect(BPoint loc, const BPoseView* poseView, bool minimalRect) const
918 {
919 	ASSERT(poseView->ViewMode() == kListMode);
920 
921 	BColumn* column = poseView->LastColumn();
922 	BRect rect;
923 	rect.left = loc.x;
924 	rect.top = loc.y;
925 	rect.right = loc.x + column->Offset() + column->Width();
926 	rect.bottom = rect.top + poseView->ListElemHeight();
927 
928 	if (minimalRect) {
929 		BTextWidget* widget = WidgetFor(poseView->FirstColumn()->AttrHash());
930 		if (widget != NULL) {
931 			rect.right = widget->CalcRect(loc, poseView->FirstColumn(),
932 				poseView).right;
933 		}
934 	}
935 
936 	return rect;
937 }
938 
939 
940 BRect
941 BPose::CalcRect(const BPoseView* poseView) const
942 {
943 	ASSERT(poseView->ViewMode() != kListMode);
944 
945 	BRect rect;
946 	BPoint location = Location(poseView);
947 	if (poseView->ViewMode() == kIconMode) {
948 		rect.left = location.x;
949 		rect.right = rect.left + poseView->IconSizeInt();
950 
951 		BTextWidget* widget = WidgetFor(poseView->FirstColumn()->AttrHash());
952 		if (widget != NULL) {
953 			float textWidth = ceilf(widget->TextWidth(poseView) + 1);
954 			if (textWidth > poseView->IconSizeInt()) {
955 				rect.left += (poseView->IconSizeInt() - textWidth) / 2;
956 				rect.right = rect.left + textWidth;
957 			}
958 		}
959 
960 		rect.top = location.y;
961 		rect.bottom = rect.top + poseView->IconPoseHeight();
962 	} else {
963 		// MINI_ICON_MODE rect calc
964 		rect.left = location.x;
965 		rect.right = rect.left + B_MINI_ICON + kMiniIconSeparator;
966 
967 		// big font sizes can push top above icon location top
968 		rect.bottom = location.y
969 			+ roundf((B_MINI_ICON + ActualFontHeight(poseView)) / 2) + 1;
970 		rect.top = rect.bottom - floorf(ActualFontHeight(poseView));
971 		BTextWidget* widget = WidgetFor(poseView->FirstColumn()->AttrHash());
972 		if (widget != NULL)
973 			rect.right += ceil(widget->TextWidth(poseView) + 1);
974 	}
975 
976 	return rect;
977 }
978 
979 
980 BRect
981 BPose::_IconRect(const BPoseView* poseView, BPoint location) const
982 {
983 	uint32 size = poseView->IconSizeInt();
984 	BRect rect;
985 	rect.left = location.x + poseView->ListOffset();
986 	rect.right = rect.left + size;
987 	rect.top = location.y + (poseView->ListElemHeight() - size) / 2.f;
988 	rect.bottom = rect.top + size;
989 	return rect;
990 }
991 
992 
993 #if DEBUG
994 void
995 BPose::PrintToStream()
996 {
997 	TargetModel()->PrintToStream();
998 	switch (fClipboardMode) {
999 		case kMoveSelectionTo:
1000 			PRINT(("clipboardMode: Cut\n"));
1001 			break;
1002 
1003 		case kCopySelectionTo:
1004 			PRINT(("clipboardMode: Copy\n"));
1005 			break;
1006 
1007 		default:
1008 			PRINT(("clipboardMode: 0 - not in clipboard\n"));
1009 			break;
1010 	}
1011 	PRINT(("%sselected\n", IsSelected() ? "" : "not "));
1012 	PRINT(("location %s x:%f y:%f\n", HasLocation() ? "" : "unknown ",
1013 		HasLocation() ? fLocation.x : 0,
1014 		HasLocation() ? fLocation.y : 0));
1015 	PRINT(("%s autoplaced \n", WasAutoPlaced() ? "was" : "not"));
1016 }
1017 #endif
1018