xref: /haiku/src/kits/tracker/Pose.cpp (revision 13581b3d2a71545960b98fefebc5225b5bf29072)
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 		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 
365 void
366 BPose::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 
377 void
378 BPose::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 
400 void
401 BPose::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 
424 void
425 BPose::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 
465 void
466 BPose::EditNextWidget(BPoseView* poseView)
467 {
468 	EditPreviousNextWidgetCommon(poseView, true);
469 }
470 
471 
472 void
473 BPose::EditPreviousWidget(BPoseView* poseView)
474 {
475 	EditPreviousNextWidgetCommon(poseView, false);
476 }
477 
478 
479 bool
480 BPose::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 + ActualFontHeight(poseView);
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 
522 bool
523 BPose::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 
553 void
554 BPose::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 
692 void
693 BPose::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 
719 void
720 BPose::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 
762 BTextWidget*
763 BPose::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 
775 BTextWidget*
776 BPose::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 
793 BTextWidget*
794 BPose::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 
805 void
806 BPose::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 
826 void
827 BPose::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 
885 void
886 BPose::DrawToggleSwitch(BRect, BPoseView*)
887 {
888 }
889 
890 
891 BPoint
892 BPose::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 
902 void
903 BPose::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 
917 BRect
918 BPose::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 
941 BRect
942 BPose::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 + ActualFontHeight(poseView)) / 2) + 1;
971 		rect.top = rect.bottom - floorf(ActualFontHeight(poseView));
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 
981 BRect
982 BPose::_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
995 void
996 BPose::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