xref: /haiku/src/kits/tracker/infowindow/GeneralInfoView.cpp (revision 1773f0767ed809a3c64ccc0c1037f3c8a1d5de33)
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 "GeneralInfoView.h"
37 
38 #include <algorithm>
39 
40 #include <Application.h>
41 #include <Catalog.h>
42 #include <Locale.h>
43 #include <NodeMonitor.h>
44 #include <PopUpMenu.h>
45 #include <Region.h>
46 #include <Roster.h>
47 #include <Screen.h>
48 #include <StringFormat.h>
49 #include <SymLink.h>
50 #include <Volume.h>
51 #include <VolumeRoster.h>
52 #include <Window.h>
53 
54 #include "Attributes.h"
55 #include "Commands.h"
56 #include "FSUtils.h"
57 #include "IconMenuItem.h"
58 #include "InfoWindow.h"
59 #include "Model.h"
60 #include "NavMenu.h"
61 #include "PoseView.h"
62 #include "StringForSize.h"
63 #include "Tracker.h"
64 #include "WidgetAttributeText.h"
65 
66 
67 #undef B_TRANSLATION_CONTEXT
68 #define B_TRANSLATION_CONTEXT "InfoWindow"
69 
70 
71 namespace BPrivate {
72 
73 class TrackingView : public BControl {
74 public:
75 	TrackingView(BRect, const char* str, BMessage* message);
76 
77 	virtual void MouseDown(BPoint);
78 	virtual void MouseMoved(BPoint where, uint32 transit, const BMessage*);
79 	virtual void MouseUp(BPoint);
80 	virtual void Draw(BRect);
81 
82 private:
83 	bool fMouseDown;
84 	bool fMouseInView;
85 };
86 
87 }	// namespace BPrivate
88 
89 
90 const float kBorderMargin = 15.0f;
91 const float kDrawMargin = 3.0f;
92 
93 
94 const uint32 kSetPreferredApp = 'setp';
95 const uint32 kSelectNewSymTarget = 'snew';
96 const uint32 kOpenLinkSource = 'opls';
97 const uint32 kOpenLinkTarget = 'oplt';
98 
99 
100 static void
101 OpenParentAndSelectOriginal(const entry_ref* ref)
102 {
103 	BEntry entry(ref);
104 	node_ref node;
105 	entry.GetNodeRef(&node);
106 
107 	BEntry parent;
108 	entry.GetParent(&parent);
109 	entry_ref parentRef;
110 	parent.GetRef(&parentRef);
111 
112 	BMessage message(B_REFS_RECEIVED);
113 	message.AddRef("refs", &parentRef);
114 	message.AddData("nodeRefToSelect", B_RAW_TYPE, &node, sizeof(node_ref));
115 
116 	be_app->PostMessage(&message);
117 }
118 
119 
120 static BWindow*
121 OpenToolTipWindow(BScreen& screen, BRect rect, const char* name,
122 	const char* string, BMessenger target, BMessage* message)
123 {
124 	font_height fontHeight;
125 	be_plain_font->GetHeight(&fontHeight);
126 	float height = ceilf(fontHeight.ascent + fontHeight.descent);
127 	rect.top = floorf(rect.top + (rect.Height() - height) / 2.0f);
128 	rect.bottom = rect.top + height;
129 
130 	rect.right = rect.left + ceilf(be_plain_font->StringWidth(string)) + 4;
131 	if (rect.left < 0)
132 		rect.OffsetBy(-rect.left, 0);
133 	else if (rect.right > screen.Frame().right)
134 		rect.OffsetBy(screen.Frame().right - rect.right, 0);
135 
136 	BWindow* window = new BWindow(rect, name, B_BORDERED_WINDOW_LOOK,
137 		B_FLOATING_ALL_WINDOW_FEEL,
138 		B_NOT_MOVABLE | B_NOT_CLOSABLE | B_NOT_ZOOMABLE | B_NOT_MINIMIZABLE
139 		| B_NOT_RESIZABLE | B_AVOID_FOCUS | B_NO_WORKSPACE_ACTIVATION
140 		| B_WILL_ACCEPT_FIRST_CLICK | B_ASYNCHRONOUS_CONTROLS);
141 
142 	TrackingView* trackingView = new TrackingView(window->Bounds(),
143 		string, message);
144 	trackingView->SetTarget(target);
145 	window->AddChild(trackingView);
146 
147 	window->Sync();
148 	window->Show();
149 
150 	return window;
151 }
152 
153 
154 GeneralInfoView::GeneralInfoView(Model* model)
155 	:
156 	BGroupView(B_VERTICAL),
157 	fDivider(0),
158 	fPreferredAppMenu(NULL),
159 	fModel(model),
160 	fMouseDown(false),
161 	fTrackingState(no_track),
162 	fPathWindow(NULL),
163 	fLinkWindow(NULL),
164 	fDescWindow(NULL),
165 	fCurrentLinkColorWhich(B_LINK_TEXT_COLOR),
166 	fCurrentPathColorWhich(fCurrentLinkColorWhich)
167 {
168 	const char* fieldNames[] = {
169 		B_TRANSLATE("Description:"),
170 		B_TRANSLATE("Location:"),
171 		B_TRANSLATE("Opens with:"),
172 		B_TRANSLATE("Capacity:"),
173 		B_TRANSLATE("Size:"),
174 		B_TRANSLATE("Created:"),
175 		B_TRANSLATE("Modified:"),
176 		B_TRANSLATE("Kind:"),
177 		B_TRANSLATE("Link to:"),
178 		B_TRANSLATE("Version:"),
179 		NULL
180 	};
181 
182 
183 	SetFlags(Flags() | B_WILL_DRAW | B_PULSE_NEEDED | B_FRAME_EVENTS);
184 	SetName(B_TRANSLATE("Information"));
185 	// Set view color to standard background grey
186 	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
187 	SetFont(be_plain_font);
188 	fFreeBytes = -1;
189 	fSizeString = "";
190 	fSizeRect.Set(0, 0, 0, 0);
191 
192 	// Find offset for attributes, might be overiden below if there
193 	// is a prefered handle menu displayed
194 	BFont currentFont;
195 	GetFont(&currentFont);
196 	currentFont.SetSize(currentFont.Size() - 2);
197 
198 	// The widest string depends on the locale. We should check them all, this
199 	// is only an approximation that works for English and French.
200 	float width = 0;
201 	for (int i = 0; fieldNames[i] != 0; i++)
202 		width = std::max(width, StringWidth(fieldNames[i]));
203 	fDivider = width + kBorderMargin + 1;
204 
205 	// Keep some free space for the stuff we print ourselves
206 	float lineHeight = CurrentFontHeight();
207 	int lineCount = 7;
208 	if (model->IsSymLink())
209 		lineCount += 1; // Add space for "Link to" line
210 	if (model->IsExecutable())
211 		lineCount += 2; // Add space for "Version" and "Description" lines
212 	GroupLayout()->SetInsets(kBorderMargin, lineHeight * lineCount,
213 		B_USE_WINDOW_SPACING, B_USE_WINDOW_SPACING);
214 
215 	// Add a preferred handler pop-up menu if this item
216 	// is a file...This goes in place of the Link To:
217 	// string...
218 	if (model->IsFile()) {
219 		BMimeType mime(fModel->MimeType());
220 		BNodeInfo nodeInfo(fModel->Node());
221 
222 		// But don't add the menu if the file is executable
223 		if (!fModel->IsExecutable()) {
224 			fPreferredAppMenu = new BMenuField("", "", new BPopUpMenu(""));
225 			currentFont.SetSize(currentFont.Size() + 2);
226 			fDivider = currentFont.StringWidth(B_TRANSLATE("Opens with:"))
227 				+ 5;
228 			fPreferredAppMenu->SetDivider(fDivider);
229 			fDivider += (kBorderMargin - 2);
230 			fPreferredAppMenu->SetFont(&currentFont);
231 			fPreferredAppMenu->SetHighUIColor(B_PANEL_TEXT_COLOR);
232 			fPreferredAppMenu->SetLabel(B_TRANSLATE("Opens with:"));
233 
234 			char prefSignature[B_MIME_TYPE_LENGTH];
235 			nodeInfo.GetPreferredApp(prefSignature);
236 
237 			BMessage supportingAppList;
238 			mime.GetSupportingApps(&supportingAppList);
239 
240 			// Add the default menu item and set it to marked
241 			BMenuItem* result;
242 			result = new BMenuItem(B_TRANSLATE("Default application"),
243 				new BMessage(kSetPreferredApp));
244 			result->SetTarget(this);
245 			fPreferredAppMenu->Menu()->AddItem(result);
246 			result->SetMarked(true);
247 
248 			for (int32 index = 0; ; index++) {
249 				const char* signature;
250 				if (supportingAppList.FindString("applications", index,
251 						&signature) != B_OK) {
252 					break;
253 				}
254 
255 				// Only add separator item if there are more items
256 				if (index == 0)
257 					fPreferredAppMenu->Menu()->AddSeparatorItem();
258 
259 				BMessage* itemMessage = new BMessage(kSetPreferredApp);
260 				itemMessage->AddString("signature", signature);
261 
262 				status_t err = B_ERROR;
263 				entry_ref entry;
264 
265 				if (signature && signature[0])
266 					err = be_roster->FindApp(signature, &entry);
267 
268 				if (err != B_OK)
269 					result = new BMenuItem(signature, itemMessage);
270 				else
271 					result = new BMenuItem(entry.name, itemMessage);
272 
273 				result->SetTarget(this);
274 				fPreferredAppMenu->Menu()->AddItem(result);
275 				if (strcmp(signature, prefSignature) == 0)
276 					result->SetMarked(true);
277 			}
278 
279 			AddChild(fPreferredAppMenu);
280 		}
281 	}
282 }
283 
284 
285 GeneralInfoView::~GeneralInfoView()
286 {
287 	if (fPathWindow->Lock())
288 		fPathWindow->Quit();
289 
290 	if (fLinkWindow->Lock())
291 		fLinkWindow->Quit();
292 
293 	if (fDescWindow->Lock())
294 		fDescWindow->Quit();
295 }
296 
297 
298 void
299 GeneralInfoView::InitStrings(const Model* model)
300 {
301 	BMimeType mime;
302 	char kind[B_MIME_TYPE_LENGTH];
303 
304 	ASSERT(model->IsNodeOpen());
305 
306 	BRect drawBounds(Bounds());
307 	drawBounds.left = fDivider;
308 
309 	// We'll do our own truncation later on in Draw()
310 	WidgetAttributeText::AttrAsString(model, &fCreatedStr, kAttrStatCreated,
311 		B_TIME_TYPE, drawBounds.Width() - kBorderMargin, this);
312 	WidgetAttributeText::AttrAsString(model, &fModifiedStr, kAttrStatModified,
313 		B_TIME_TYPE, drawBounds.Width() - kBorderMargin, this);
314 	WidgetAttributeText::AttrAsString(model, &fPathStr, kAttrPath,
315 		B_STRING_TYPE, 0, this);
316 
317 	// Use the same method as used to resolve fIconModel, which handles
318 	// both absolute and relative symlinks. if the link is broken, try to
319 	// get a little more information.
320 	if (model->IsSymLink()) {
321 		bool linked = false;
322 
323 		Model resolvedModel(model->EntryRef(), true, true);
324 		if (resolvedModel.InitCheck() == B_OK) {
325 			// Get the path of the link
326 			BPath traversedPath;
327 			resolvedModel.GetPath(&traversedPath);
328 
329 			// If the BPath is initialized, then check the file for existence
330 			if (traversedPath.InitCheck() == B_OK) {
331 				BEntry entry(traversedPath.Path(), false);
332 					// look at the target itself
333 				if (entry.InitCheck() == B_OK && entry.Exists())
334 					linked = true;
335 			}
336 		}
337 
338 		// always show the target as it is: absolute or relative!
339 		BSymLink symLink(model->EntryRef());
340 		char linkToPath[B_PATH_NAME_LENGTH];
341 		symLink.ReadLink(linkToPath, B_PATH_NAME_LENGTH);
342 		fLinkToStr = linkToPath;
343 		if (!linked) {
344 			// link points to missing object
345 			fLinkToStr += B_TRANSLATE(" (broken)");
346 		}
347 	} else if (model->IsExecutable()) {
348 		if (((Model*)model)->GetLongVersionString(fDescStr,
349 				B_APP_VERSION_KIND) == B_OK) {
350 			// we want a flat string, so replace all newlines and tabs
351 			// with spaces
352 			fDescStr.ReplaceAll('\n', ' ');
353 			fDescStr.ReplaceAll('\t', ' ');
354 		} else
355 			fDescStr = "-";
356 	}
357 
358 	if (mime.SetType(model->MimeType()) == B_OK
359 		&& mime.GetShortDescription(kind) == B_OK)
360 		fKindStr = kind;
361 
362 	if (fKindStr.Length() == 0)
363 		fKindStr = model->MimeType();
364 }
365 
366 
367 void
368 GeneralInfoView::AttachedToWindow()
369 {
370 	BFont font(be_plain_font);
371 
372 	font.SetSpacing(B_BITMAP_SPACING);
373 	SetFont(&font);
374 
375 	CheckAndSetSize();
376 	if (fPreferredAppMenu)
377 		fPreferredAppMenu->Menu()->SetTargetForItems(this);
378 
379 	_inherited::AttachedToWindow();
380 }
381 
382 
383 void
384 GeneralInfoView::Pulse()
385 {
386 	CheckAndSetSize();
387 	_inherited::Pulse();
388 }
389 
390 
391 void
392 GeneralInfoView::ModelChanged(Model* model, BMessage* message)
393 {
394 	BRect drawBounds(Bounds());
395 	drawBounds.left = fDivider;
396 
397 	switch (message->FindInt32("opcode")) {
398 		case B_ENTRY_MOVED:
399 		{
400 			node_ref dirNode;
401 			node_ref itemNode;
402 			dirNode.device = itemNode.device = message->FindInt32("device");
403 			message->FindInt64("to directory", &dirNode.node);
404 			message->FindInt64("node", &itemNode.node);
405 
406 			const char* name;
407 			if (message->FindString("name", &name) != B_OK)
408 				return;
409 
410 			// ensure notification is for us
411 			if (*model->NodeRef() == itemNode
412 				// For volumes, the device ID is obviously not handled in a
413 				// consistent way; the node monitor sends us the ID of the
414 				// parent device, while the model is set to the device of the
415 				// volume directly - this hack works for volumes that are
416 				// mounted in the root directory
417 				|| (model->IsVolume()
418 					&& itemNode.device == 1
419 					&& itemNode.node == model->NodeRef()->node)) {
420 				model->UpdateEntryRef(&dirNode, name);
421 				BString title;
422 				title << name << B_TRANSLATE(" info");
423 				Window()->SetTitle(title.String());
424 				WidgetAttributeText::AttrAsString(model, &fPathStr, kAttrPath,
425 					B_STRING_TYPE, 0, this);
426 				Invalidate();
427 			}
428 			break;
429 		}
430 
431 		case B_STAT_CHANGED:
432 			if (model->OpenNode() == B_OK) {
433 				WidgetAttributeText::AttrAsString(model, &fCreatedStr,
434 					kAttrStatCreated, B_TIME_TYPE, drawBounds.Width()
435 					- kBorderMargin, this);
436 				WidgetAttributeText::AttrAsString(model, &fModifiedStr,
437 					kAttrStatModified, B_TIME_TYPE, drawBounds.Width()
438 					- kBorderMargin, this);
439 
440 				// don't change the size if it's a directory
441 				if (!model->IsDirectory()) {
442 					fLastSize = model->StatBuf()->st_size;
443 					fSizeString = "";
444 					BInfoWindow::GetSizeString(fSizeString, fLastSize, 0);
445 				}
446 				model->CloseNode();
447 			}
448 			break;
449 
450 		case B_ATTR_CHANGED:
451 		{
452 			// watch for icon updates
453 			const char* attrName;
454 			if (message->FindString("attr", &attrName) == B_OK) {
455 				if (strcmp(attrName, kAttrLargeIcon) == 0
456 					|| strcmp(attrName, kAttrIcon) == 0) {
457 					IconCache::sIconCache->IconChanged(model->ResolveIfLink());
458 					Invalidate();
459 				} else if (strcmp(attrName, kAttrMIMEType) == 0) {
460 					if (model->OpenNode() == B_OK) {
461 						model->AttrChanged(attrName);
462 						InitStrings(model);
463 						model->CloseNode();
464 					}
465 					Invalidate();
466 				}
467 			}
468 			break;
469 		}
470 
471 		default:
472 			break;
473 	}
474 
475 	fModel = model;
476 	if (fModel->IsSymLink()) {
477 		InitStrings(model);
478 		Invalidate();
479 	}
480 
481 	drawBounds.left = fDivider;
482 	Invalidate(drawBounds);
483 }
484 
485 
486 // This only applies to symlinks. If the target of the symlink
487 // was changed, then we have to update the entire model.
488 // (Since in order to re-target a symlink, we had to delete
489 // the old model and create a new one; BSymLink::SetTarget(),
490 // would be nice)
491 
492 void
493 GeneralInfoView::ReLinkTargetModel(Model* model)
494 {
495 	fModel = model;
496 	InitStrings(model);
497 	Invalidate(Bounds());
498 }
499 
500 
501 void
502 GeneralInfoView::MouseDown(BPoint where)
503 {
504 	// Start tracking the mouse if we are in any of the hotspots
505 	if (fLinkRect.Contains(where)) {
506 		InvertRect(fLinkRect);
507 		fTrackingState = link_track;
508 	} else if (fPathRect.Contains(where)) {
509 		InvertRect(fPathRect);
510 		fTrackingState = path_track;
511 	} else if (fSizeRect.Contains(where)) {
512 		if (fModel->IsDirectory() && !fModel->IsVolume()
513 			&& !fModel->IsRoot()) {
514 			InvertRect(fSizeRect);
515 			fTrackingState = size_track;
516 		} else
517 			fTrackingState = no_track;
518 	}
519 
520 	fMouseDown = true;
521 	SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
522 }
523 
524 
525 void
526 GeneralInfoView::MouseMoved(BPoint where, uint32, const BMessage* dragMessage)
527 {
528 	fCurrentLinkColorWhich = B_LINK_TEXT_COLOR;
529 	fCurrentPathColorWhich = fCurrentLinkColorWhich;
530 
531 	switch (fTrackingState) {
532 		case link_track:
533 			if (fLinkRect.Contains(where) != fMouseDown) {
534 				fMouseDown = !fMouseDown;
535 				InvertRect(fLinkRect);
536 			}
537 			break;
538 
539 		case path_track:
540 			if (fPathRect.Contains(where) != fMouseDown) {
541 				fMouseDown = !fMouseDown;
542 				InvertRect(fPathRect);
543 			}
544 			break;
545 
546 		case size_track:
547 			if (fSizeRect.Contains(where) != fMouseDown) {
548 				fMouseDown = !fMouseDown;
549 				InvertRect(fSizeRect);
550 			}
551 			break;
552 
553 		default:
554 		{
555 			// Only consider this if the window is the active window.
556 			// We have to manually get the mouse here in the event that the
557 			// mouse is over a pop-up window
558 			uint32 buttons;
559 			BPoint point;
560 			GetMouse(&point, &buttons);
561 			if (Window()->IsActive() && !buttons) {
562 				// If we are down here, then that means that we're tracking
563 				// the mouse but not from a mouse down. In this case, we're
564 				// just interested in knowing whether or not we need to
565 				// display the "pop-up" version of the path or link text.
566 				BScreen screen(Window());
567 				BFont font;
568 				GetFont(&font);
569 				float maxWidth = (Bounds().Width()
570 					- (fDivider + kBorderMargin));
571 
572 				if (fPathRect.Contains(point)) {
573 					if (fCurrentPathColorWhich != B_LINK_HOVER_COLOR)
574 						fCurrentPathColorWhich = B_LINK_HOVER_COLOR;
575 
576 					if (font.StringWidth(fPathStr.String()) > maxWidth) {
577 						fTrackingState = no_track;
578 						BRect rect = ConvertToScreen(fPathRect);
579 
580 						if (fPathWindow == NULL
581 							|| BMessenger(fPathWindow).IsValid() == false) {
582 							fPathWindow = OpenToolTipWindow(screen, rect,
583 								"fPathWindow", fPathStr.String(),
584 								BMessenger(this),
585 								new BMessage(kOpenLinkSource));
586 						}
587 					}
588 				} else if (fLinkRect.Contains(point)) {
589 
590 					if (fCurrentLinkColorWhich != B_LINK_HOVER_COLOR)
591 						fCurrentLinkColorWhich = B_LINK_HOVER_COLOR;
592 
593 					if (font.StringWidth(fLinkToStr.String()) > maxWidth) {
594 						fTrackingState = no_track;
595 						BRect rect = ConvertToScreen(fLinkRect);
596 
597 						if (!fLinkWindow
598 							|| BMessenger(fLinkWindow).IsValid() == false) {
599 							fLinkWindow = OpenToolTipWindow(screen, rect,
600 								"fLinkWindow", fLinkToStr.String(),
601 								BMessenger(this),
602 								new BMessage(kOpenLinkTarget));
603 						}
604 					}
605 				} else if (fDescRect.Contains(point)
606 					&& font.StringWidth(fDescStr.String()) > maxWidth) {
607 					fTrackingState = no_track;
608 					BRect rect = ConvertToScreen(fDescRect);
609 
610 					if (!fDescWindow
611 						|| BMessenger(fDescWindow).IsValid() == false) {
612 						fDescWindow = OpenToolTipWindow(screen, rect,
613 							"fDescWindow", fDescStr.String(),
614 							BMessenger(this), NULL);
615 					}
616 				}
617 			}
618 			break;
619 		}
620 	}
621 
622 	DelayedInvalidate(16666, fPathRect);
623 	DelayedInvalidate(16666, fLinkRect);
624 }
625 
626 
627 void
628 GeneralInfoView::OpenLinkSource()
629 {
630 	OpenParentAndSelectOriginal(fModel->EntryRef());
631 }
632 
633 
634 void
635 GeneralInfoView::OpenLinkTarget()
636 {
637 	Model resolvedModel(fModel->EntryRef(), true, true);
638 	BEntry entry;
639 	if (resolvedModel.InitCheck() == B_OK) {
640 		// Get the path of the link
641 		BPath traversedPath;
642 		resolvedModel.GetPath(&traversedPath);
643 
644 		// If the BPath is initialized, then check the file for existence
645 		if (traversedPath.InitCheck() == B_OK)
646 			entry.SetTo(traversedPath.Path());
647 	}
648 	if (entry.InitCheck() != B_OK || !entry.Exists()) {
649 		// Open a file dialog panel to allow the user to relink.
650 		BInfoWindow* window = dynamic_cast<BInfoWindow*>(Window());
651 		if (window != NULL)
652 			window->OpenFilePanel(fModel->EntryRef());
653 	} else {
654 		entry_ref ref;
655 		entry.GetRef(&ref);
656 		BPath path(&ref);
657 		printf("Opening link target: %s\n", path.Path());
658 		OpenParentAndSelectOriginal(&ref);
659 	}
660 }
661 
662 
663 void
664 GeneralInfoView::MouseUp(BPoint where)
665 {
666 	// Are we in the link rect?
667 	if (fTrackingState == link_track && fLinkRect.Contains(where)) {
668 		InvertRect(fLinkRect);
669 		OpenLinkTarget();
670 	} else if (fTrackingState == path_track && fPathRect.Contains(where)) {
671 		InvertRect(fPathRect);
672 		OpenLinkSource();
673 	} else if (fTrackingState == size_track && fSizeRect.Contains(where)) {
674 		// Recalculate size
675 		Window()->PostMessage(kRecalculateSize);
676 	}
677 
678 	// End mouse tracking
679 	fMouseDown = false;
680 	fTrackingState = no_track;
681 }
682 
683 
684 void
685 GeneralInfoView::CheckAndSetSize()
686 {
687 	if (fModel->IsVolume() || fModel->IsRoot()) {
688 		off_t freeBytes = 0;
689 		off_t capacity = 0;
690 
691 		if (fModel->IsVolume()) {
692 			BVolume volume(fModel->NodeRef()->device);
693 			freeBytes = volume.FreeBytes();
694 			capacity = volume.Capacity();
695 		} else {
696 			// iterate over all volumes
697 			BVolumeRoster volumeRoster;
698 			BVolume volume;
699 			while (volumeRoster.GetNextVolume(&volume) == B_OK) {
700 				freeBytes += volume.FreeBytes();
701 				capacity += volume.Capacity();
702 			}
703 		}
704 
705 		if (fFreeBytes == freeBytes)
706 			return;
707 
708 		fFreeBytes = freeBytes;
709 
710 		fSizeString.SetTo(B_TRANSLATE("%capacity (%used used -- %free free)"));
711 
712 		char sizeStr[128];
713 		string_for_size(capacity, sizeStr, sizeof(sizeStr));
714 		fSizeString.ReplaceFirst("%capacity", sizeStr);
715 		string_for_size(capacity - fFreeBytes, sizeStr, sizeof(sizeStr));
716 		fSizeString.ReplaceFirst("%used", sizeStr);
717 		string_for_size(fFreeBytes, sizeStr, sizeof(sizeStr));
718 		fSizeString.ReplaceFirst("%free", sizeStr);
719 
720 	} else if (fModel->IsFile()) {
721 		// poll for size changes because they do not get node monitored
722 		// until a file gets closed (with the old BFS)
723 		StatStruct statBuf;
724 		BModelOpener opener(fModel);
725 
726 		if (fModel->InitCheck() != B_OK
727 			|| fModel->Node()->GetStat(&statBuf) != B_OK) {
728 			return;
729 		}
730 
731 		if (fLastSize == statBuf.st_size)
732 			return;
733 
734 		fLastSize = statBuf.st_size;
735 		fSizeString = "";
736 		BInfoWindow::GetSizeString(fSizeString, fLastSize, 0);
737 	} else
738 		return;
739 
740 	SetSizeString(fSizeString);
741 }
742 
743 
744 void
745 GeneralInfoView::MessageReceived(BMessage* message)
746 {
747 	switch (message->what) {
748 		case kSetPreferredApp:
749 		{
750 			BNode node(fModel->EntryRef());
751 			BNodeInfo nodeInfo(&node);
752 
753 			const char* newSignature;
754 			if (message->FindString("signature", &newSignature) != B_OK)
755 				newSignature = NULL;
756 
757 			fModel->SetPreferredAppSignature(newSignature);
758 			nodeInfo.SetPreferredApp(newSignature);
759 			break;
760 		}
761 
762 		case kOpenLinkSource:
763 			OpenLinkSource();
764 			break;
765 
766 		case kOpenLinkTarget:
767 			OpenLinkTarget();
768 			break;
769 
770 		default:
771 			_inherited::MessageReceived(message);
772 			break;
773 	}
774 }
775 
776 
777 void
778 GeneralInfoView::FrameResized(float, float)
779 {
780 	BModelOpener opener(fModel);
781 
782 	// Truncate the strings according to the new width
783 	InitStrings(fModel);
784 }
785 
786 
787 void
788 GeneralInfoView::Draw(BRect)
789 {
790 	// Set the low color for anti-aliasing
791 	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
792 
793 	// Clear the old contents
794 	SetHighColor(ui_color(B_PANEL_BACKGROUND_COLOR));
795 	FillRect(Bounds());
796 
797 	rgb_color labelColor = ui_color(B_PANEL_TEXT_COLOR);
798 	rgb_color attributeColor = mix_color(HighColor(), labelColor, 192);
799 
800 	// Font information
801 	font_height fontMetrics;
802 	float lineHeight = 0;
803 	float lineBase = 0;
804 	// Draw the attribute font stuff
805 	SetFont(be_plain_font);
806 	GetFontHeight(&fontMetrics);
807 	lineHeight = CurrentFontHeight() + 5;
808 
809 	// Starting base line for the first string
810 	lineBase = lineHeight;
811 
812 	// Capacity/size
813 	SetHighColor(labelColor);
814 	if (fModel->IsVolume() || fModel->IsRoot()) {
815 		MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Capacity:"))),
816 			lineBase));
817 		DrawString(B_TRANSLATE("Capacity:"));
818 	} else {
819 		MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Size:"))),
820 			lineBase));
821 		fSizeRect.left = fDivider + 2;
822 		fSizeRect.top = lineBase - fontMetrics.ascent;
823 		fSizeRect.bottom = lineBase + fontMetrics.descent;
824 		DrawString(B_TRANSLATE("Size:"));
825 	}
826 
827 	MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
828 	SetHighColor(attributeColor);
829 	// Check for possible need of truncation
830 	if (StringWidth(fSizeString.String())
831 			> (Bounds().Width() - (fDivider + kBorderMargin))) {
832 		BString tmpString(fSizeString.String());
833 		TruncateString(&tmpString, B_TRUNCATE_MIDDLE,
834 			Bounds().Width() - (fDivider + kBorderMargin));
835 		DrawString(tmpString.String());
836 		fSizeRect.right = fSizeRect.left + StringWidth(tmpString.String())
837 			+ 3;
838 	} else {
839 		DrawString(fSizeString.String());
840 		fSizeRect.right = fSizeRect.left + StringWidth(fSizeString.String()) + 3;
841 	}
842 	lineBase += lineHeight;
843 
844 	// Created
845 	SetHighColor(labelColor);
846 	MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Created:"))),
847 		lineBase));
848 	DrawString(B_TRANSLATE("Created:"));
849 	MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
850 	SetHighColor(attributeColor);
851 	DrawString(fCreatedStr.String());
852 	lineBase += lineHeight;
853 
854 	// Modified
855 	MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Modified:"))),
856 		lineBase));
857 	SetHighColor(labelColor);
858 	DrawString(B_TRANSLATE("Modified:"));
859 	MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
860 	SetHighColor(attributeColor);
861 	DrawString(fModifiedStr.String());
862 	lineBase += lineHeight;
863 
864 	// Kind
865 	MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Kind:"))),
866 		lineBase));
867 	SetHighColor(labelColor);
868 	DrawString(B_TRANSLATE("Kind:"));
869 	MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
870 	SetHighColor(attributeColor);
871 	DrawString(fKindStr.String());
872 	lineBase += lineHeight;
873 
874 	BFont normalFont;
875 	GetFont(&normalFont);
876 
877 	// Path
878 	MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Location:"))),
879 		lineBase));
880 	SetHighColor(labelColor);
881 	DrawString(B_TRANSLATE("Location:"));
882 
883 	MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
884 	SetHighUIColor(fCurrentPathColorWhich);
885 
886 	// Check for truncation
887 	if (StringWidth(fPathStr.String()) > (Bounds().Width()
888 			- (fDivider + kBorderMargin))) {
889 		BString nameString(fPathStr.String());
890 		TruncateString(&nameString, B_TRUNCATE_MIDDLE,
891 			Bounds().Width() - (fDivider + kBorderMargin));
892 		DrawString(nameString.String());
893 	} else
894 		DrawString(fPathStr.String());
895 
896 	// Cache the position of the path
897 	fPathRect.top = lineBase - fontMetrics.ascent;
898 	fPathRect.bottom = lineBase + fontMetrics.descent;
899 	fPathRect.left = fDivider + 2;
900 	fPathRect.right = fPathRect.left + StringWidth(fPathStr.String()) + 3;
901 
902 	lineBase += lineHeight;
903 
904 	// Link to/version
905 	if (fModel->IsSymLink()) {
906 		MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Link to:"))),
907 			lineBase));
908 		SetHighColor(labelColor);
909 		DrawString(B_TRANSLATE("Link to:"));
910 		MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
911 		SetHighUIColor(fCurrentLinkColorWhich);
912 
913 		// Check for truncation
914 		if (StringWidth(fLinkToStr.String()) > (Bounds().Width()
915 				- (fDivider + kBorderMargin))) {
916 			BString nameString(fLinkToStr.String());
917 			TruncateString(&nameString, B_TRUNCATE_MIDDLE,
918 				Bounds().Width() - (fDivider + kBorderMargin));
919 			DrawString(nameString.String());
920 		} else
921 			DrawString(fLinkToStr.String());
922 
923 		// Cache the position of the link field
924 		fLinkRect.top = lineBase - fontMetrics.ascent;
925 		fLinkRect.bottom = lineBase + fontMetrics.descent;
926 		fLinkRect.left = fDivider + 2;
927 		fLinkRect.right = fLinkRect.left + StringWidth(fLinkToStr.String())
928 			+ 3;
929 
930 		// No description field
931 		fDescRect = BRect(-1, -1, -1, -1);
932 	} else if (fModel->IsExecutable()) {
933 		//Version
934 		MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Version:"))),
935 			lineBase));
936 		SetHighColor(labelColor);
937 		DrawString(B_TRANSLATE("Version:"));
938 		MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
939 		SetHighColor(attributeColor);
940 		BString nameString;
941 		if (fModel->GetVersionString(nameString, B_APP_VERSION_KIND) == B_OK)
942 			DrawString(nameString.String());
943 		else
944 			DrawString("-");
945 		lineBase += lineHeight;
946 
947 		// Description
948 		MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Description:"))),
949 			lineBase));
950 		SetHighColor(labelColor);
951 		DrawString(B_TRANSLATE("Description:"));
952 		MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
953 		SetHighColor(attributeColor);
954 		// Check for truncation
955 		if (StringWidth(fDescStr.String()) > (Bounds().Width()
956 				- (fDivider + kBorderMargin))) {
957 			BString nameString(fDescStr.String());
958 			TruncateString(&nameString, B_TRUNCATE_MIDDLE,
959 				Bounds().Width() - (fDivider + kBorderMargin));
960 			DrawString(nameString.String());
961 		} else
962 			DrawString(fDescStr.String());
963 
964 		// Cache the position of the description field
965 		fDescRect.top = lineBase - fontMetrics.ascent;
966 		fDescRect.bottom = lineBase + fontMetrics.descent;
967 		fDescRect.left = fDivider + 2;
968 		fDescRect.right = fDescRect.left + StringWidth(fDescStr.String()) + 3;
969 
970 		// No link field
971 		fLinkRect = BRect(-1, -1, -1, -1);
972 	}
973 }
974 
975 
976 void
977 GeneralInfoView::WindowActivated(bool active)
978 {
979 	if (active)
980 		return;
981 
982 	if (fPathWindow->Lock()) {
983 		fPathWindow->Quit();
984 		fPathWindow = NULL;
985 	}
986 
987 	if (fLinkWindow->Lock()) {
988 		fLinkWindow->Quit();
989 		fLinkWindow = NULL;
990 	}
991 
992 	if (fDescWindow->Lock()) {
993 		fDescWindow->Quit();
994 		fDescWindow = NULL;
995 	}
996 }
997 
998 
999 float
1000 GeneralInfoView::CurrentFontHeight()
1001 {
1002 	BFont font;
1003 	GetFont(&font);
1004 	font_height fontHeight;
1005 	font.GetHeight(&fontHeight);
1006 
1007 	return fontHeight.ascent + fontHeight.descent + fontHeight.leading + 2;
1008 }
1009 
1010 
1011 off_t
1012 GeneralInfoView::LastSize() const
1013 {
1014 	return fLastSize;
1015 }
1016 
1017 
1018 void
1019 GeneralInfoView::SetLastSize(off_t lastSize)
1020 {
1021 	fLastSize = lastSize;
1022 }
1023 
1024 
1025 void
1026 GeneralInfoView::SetSizeString(const char* sizeString)
1027 {
1028 	fSizeString = sizeString;
1029 
1030 	float lineHeight = CurrentFontHeight() + 6;
1031 	BRect bounds(fDivider, 0, Bounds().right, lineHeight);
1032 	Invalidate(bounds);
1033 }
1034 
1035 
1036 //	#pragma mark -
1037 
1038 
1039 TrackingView::TrackingView(BRect frame, const char* str, BMessage* message)
1040 	: BControl(frame, "trackingView", str, message, B_FOLLOW_ALL,
1041 		B_WILL_DRAW),
1042 	fMouseDown(false),
1043 	fMouseInView(false)
1044 {
1045 	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
1046 	SetEventMask(B_POINTER_EVENTS, 0);
1047 }
1048 
1049 
1050 void
1051 TrackingView::MouseDown(BPoint)
1052 {
1053 	if (Message() != NULL) {
1054 		fMouseDown = true;
1055 		fMouseInView = true;
1056 		InvertRect(Bounds());
1057 	}
1058 }
1059 
1060 
1061 void
1062 TrackingView::MouseMoved(BPoint, uint32 transit, const BMessage*)
1063 {
1064 	if ((transit == B_ENTERED_VIEW || transit == B_EXITED_VIEW) && fMouseDown)
1065 		InvertRect(Bounds());
1066 
1067 	fMouseInView = (transit == B_ENTERED_VIEW || transit == B_INSIDE_VIEW);
1068 	DelayedInvalidate(16666, Bounds());
1069 	if (!fMouseInView && !fMouseDown)
1070 		Window()->Close();
1071 }
1072 
1073 
1074 void
1075 TrackingView::MouseUp(BPoint)
1076 {
1077 	if (Message() != NULL) {
1078 		if (fMouseInView)
1079 			Invoke();
1080 
1081 		fMouseDown = false;
1082 		Window()->Close();
1083 	}
1084 }
1085 
1086 
1087 void
1088 TrackingView::Draw(BRect)
1089 {
1090 	if (Message() != NULL)
1091 		SetHighUIColor(fMouseInView ? B_LINK_HOVER_COLOR
1092 			: B_LINK_TEXT_COLOR);
1093 	else
1094 		SetHighUIColor(B_PANEL_TEXT_COLOR);
1095 	SetLowColor(ViewColor());
1096 
1097 	font_height fontHeight;
1098 	GetFontHeight(&fontHeight);
1099 
1100 	DrawString(Label(), BPoint(3, Bounds().Height() - fontHeight.descent));
1101 }
1102