xref: /haiku/src/kits/tracker/infowindow/GeneralInfoView.cpp (revision cbe0a0c436162d78cc3f92a305b64918c839d079)
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.SetToFormat(B_TRANSLATE_COMMENT("%s info",
423 					"window title"), name);
424 				Window()->SetTitle(title.String());
425 				WidgetAttributeText::AttrAsString(model, &fPathStr, kAttrPath,
426 					B_STRING_TYPE, 0, this);
427 				Invalidate();
428 			}
429 			break;
430 		}
431 
432 		case B_STAT_CHANGED:
433 			if (model->OpenNode() == B_OK) {
434 				WidgetAttributeText::AttrAsString(model, &fCreatedStr,
435 					kAttrStatCreated, B_TIME_TYPE, drawBounds.Width()
436 					- kBorderMargin, this);
437 				WidgetAttributeText::AttrAsString(model, &fModifiedStr,
438 					kAttrStatModified, B_TIME_TYPE, drawBounds.Width()
439 					- kBorderMargin, this);
440 
441 				// don't change the size if it's a directory
442 				if (!model->IsDirectory()) {
443 					fLastSize = model->StatBuf()->st_size;
444 					fSizeString = "";
445 					BInfoWindow::GetSizeString(fSizeString, fLastSize, 0);
446 				}
447 				model->CloseNode();
448 			}
449 			break;
450 
451 		case B_ATTR_CHANGED:
452 		{
453 			// watch for icon updates
454 			const char* attrName;
455 			if (message->FindString("attr", &attrName) == B_OK) {
456 				if (strcmp(attrName, kAttrLargeIcon) == 0
457 					|| strcmp(attrName, kAttrIcon) == 0) {
458 					IconCache::sIconCache->IconChanged(model->ResolveIfLink());
459 					Invalidate();
460 				} else if (strcmp(attrName, kAttrMIMEType) == 0) {
461 					if (model->OpenNode() == B_OK) {
462 						model->AttrChanged(attrName);
463 						InitStrings(model);
464 						model->CloseNode();
465 					}
466 					Invalidate();
467 				}
468 			}
469 			break;
470 		}
471 
472 		default:
473 			break;
474 	}
475 
476 	fModel = model;
477 	if (fModel->IsSymLink()) {
478 		InitStrings(model);
479 		Invalidate();
480 	}
481 
482 	drawBounds.left = fDivider;
483 	Invalidate(drawBounds);
484 }
485 
486 
487 // This only applies to symlinks. If the target of the symlink
488 // was changed, then we have to update the entire model.
489 // (Since in order to re-target a symlink, we had to delete
490 // the old model and create a new one; BSymLink::SetTarget(),
491 // would be nice)
492 
493 void
494 GeneralInfoView::ReLinkTargetModel(Model* model)
495 {
496 	fModel = model;
497 	InitStrings(model);
498 	Invalidate(Bounds());
499 }
500 
501 
502 void
503 GeneralInfoView::MouseDown(BPoint where)
504 {
505 	// Start tracking the mouse if we are in any of the hotspots
506 	if (fLinkRect.Contains(where)) {
507 		InvertRect(fLinkRect);
508 		fTrackingState = link_track;
509 	} else if (fPathRect.Contains(where)) {
510 		InvertRect(fPathRect);
511 		fTrackingState = path_track;
512 	} else if (fSizeRect.Contains(where)) {
513 		if (fModel->IsDirectory() && !fModel->IsVolume()
514 			&& !fModel->IsRoot()) {
515 			InvertRect(fSizeRect);
516 			fTrackingState = size_track;
517 		} else
518 			fTrackingState = no_track;
519 	}
520 
521 	fMouseDown = true;
522 	SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
523 }
524 
525 
526 void
527 GeneralInfoView::MouseMoved(BPoint where, uint32, const BMessage* dragMessage)
528 {
529 	fCurrentLinkColorWhich = B_LINK_TEXT_COLOR;
530 	fCurrentPathColorWhich = fCurrentLinkColorWhich;
531 
532 	switch (fTrackingState) {
533 		case link_track:
534 			if (fLinkRect.Contains(where) != fMouseDown) {
535 				fMouseDown = !fMouseDown;
536 				InvertRect(fLinkRect);
537 			}
538 			break;
539 
540 		case path_track:
541 			if (fPathRect.Contains(where) != fMouseDown) {
542 				fMouseDown = !fMouseDown;
543 				InvertRect(fPathRect);
544 			}
545 			break;
546 
547 		case size_track:
548 			if (fSizeRect.Contains(where) != fMouseDown) {
549 				fMouseDown = !fMouseDown;
550 				InvertRect(fSizeRect);
551 			}
552 			break;
553 
554 		default:
555 		{
556 			// Only consider this if the window is the active window.
557 			// We have to manually get the mouse here in the event that the
558 			// mouse is over a pop-up window
559 			uint32 buttons;
560 			BPoint point;
561 			GetMouse(&point, &buttons);
562 			if (Window()->IsActive() && !buttons) {
563 				// If we are down here, then that means that we're tracking
564 				// the mouse but not from a mouse down. In this case, we're
565 				// just interested in knowing whether or not we need to
566 				// display the "pop-up" version of the path or link text.
567 				BScreen screen(Window());
568 				BFont font;
569 				GetFont(&font);
570 				float maxWidth = (Bounds().Width()
571 					- (fDivider + kBorderMargin));
572 
573 				if (fPathRect.Contains(point)) {
574 					if (fCurrentPathColorWhich != B_LINK_HOVER_COLOR)
575 						fCurrentPathColorWhich = B_LINK_HOVER_COLOR;
576 
577 					if (font.StringWidth(fPathStr.String()) > maxWidth) {
578 						fTrackingState = no_track;
579 						BRect rect = ConvertToScreen(fPathRect);
580 
581 						if (fPathWindow == NULL
582 							|| BMessenger(fPathWindow).IsValid() == false) {
583 							fPathWindow = OpenToolTipWindow(screen, rect,
584 								"fPathWindow", fPathStr.String(),
585 								BMessenger(this),
586 								new BMessage(kOpenLinkSource));
587 						}
588 					}
589 				} else if (fLinkRect.Contains(point)) {
590 
591 					if (fCurrentLinkColorWhich != B_LINK_HOVER_COLOR)
592 						fCurrentLinkColorWhich = B_LINK_HOVER_COLOR;
593 
594 					if (font.StringWidth(fLinkToStr.String()) > maxWidth) {
595 						fTrackingState = no_track;
596 						BRect rect = ConvertToScreen(fLinkRect);
597 
598 						if (!fLinkWindow
599 							|| BMessenger(fLinkWindow).IsValid() == false) {
600 							fLinkWindow = OpenToolTipWindow(screen, rect,
601 								"fLinkWindow", fLinkToStr.String(),
602 								BMessenger(this),
603 								new BMessage(kOpenLinkTarget));
604 						}
605 					}
606 				} else if (fDescRect.Contains(point)
607 					&& font.StringWidth(fDescStr.String()) > maxWidth) {
608 					fTrackingState = no_track;
609 					BRect rect = ConvertToScreen(fDescRect);
610 
611 					if (!fDescWindow
612 						|| BMessenger(fDescWindow).IsValid() == false) {
613 						fDescWindow = OpenToolTipWindow(screen, rect,
614 							"fDescWindow", fDescStr.String(),
615 							BMessenger(this), NULL);
616 					}
617 				}
618 			}
619 			break;
620 		}
621 	}
622 
623 	DelayedInvalidate(16666, fPathRect);
624 	DelayedInvalidate(16666, fLinkRect);
625 }
626 
627 
628 void
629 GeneralInfoView::OpenLinkSource()
630 {
631 	OpenParentAndSelectOriginal(fModel->EntryRef());
632 }
633 
634 
635 void
636 GeneralInfoView::OpenLinkTarget()
637 {
638 	Model resolvedModel(fModel->EntryRef(), true, true);
639 	BEntry entry;
640 	if (resolvedModel.InitCheck() == B_OK) {
641 		// Get the path of the link
642 		BPath traversedPath;
643 		resolvedModel.GetPath(&traversedPath);
644 
645 		// If the BPath is initialized, then check the file for existence
646 		if (traversedPath.InitCheck() == B_OK)
647 			entry.SetTo(traversedPath.Path());
648 	}
649 	if (entry.InitCheck() != B_OK || !entry.Exists()) {
650 		// Open a file dialog panel to allow the user to relink.
651 		BInfoWindow* window = dynamic_cast<BInfoWindow*>(Window());
652 		if (window != NULL)
653 			window->OpenFilePanel(fModel->EntryRef());
654 	} else {
655 		entry_ref ref;
656 		entry.GetRef(&ref);
657 		BPath path(&ref);
658 		printf("Opening link target: %s\n", path.Path());
659 		OpenParentAndSelectOriginal(&ref);
660 	}
661 }
662 
663 
664 void
665 GeneralInfoView::MouseUp(BPoint where)
666 {
667 	// Are we in the link rect?
668 	if (fTrackingState == link_track && fLinkRect.Contains(where)) {
669 		InvertRect(fLinkRect);
670 		OpenLinkTarget();
671 	} else if (fTrackingState == path_track && fPathRect.Contains(where)) {
672 		InvertRect(fPathRect);
673 		OpenLinkSource();
674 	} else if (fTrackingState == size_track && fSizeRect.Contains(where)) {
675 		// Recalculate size
676 		Window()->PostMessage(kRecalculateSize);
677 	}
678 
679 	// End mouse tracking
680 	fMouseDown = false;
681 	fTrackingState = no_track;
682 }
683 
684 
685 void
686 GeneralInfoView::CheckAndSetSize()
687 {
688 	if (fModel->IsVolume() || fModel->IsRoot()) {
689 		off_t freeBytes = 0;
690 		off_t capacity = 0;
691 
692 		if (fModel->IsVolume()) {
693 			BVolume volume(fModel->NodeRef()->device);
694 			freeBytes = volume.FreeBytes();
695 			capacity = volume.Capacity();
696 		} else {
697 			// iterate over all volumes
698 			BVolumeRoster volumeRoster;
699 			BVolume volume;
700 			while (volumeRoster.GetNextVolume(&volume) == B_OK) {
701 				freeBytes += volume.FreeBytes();
702 				capacity += volume.Capacity();
703 			}
704 		}
705 
706 		if (fFreeBytes == freeBytes)
707 			return;
708 
709 		fFreeBytes = freeBytes;
710 
711 		fSizeString.SetTo(B_TRANSLATE("%capacity (%used used -- %free free)"));
712 
713 		char sizeStr[128];
714 		string_for_size(capacity, sizeStr, sizeof(sizeStr));
715 		fSizeString.ReplaceFirst("%capacity", sizeStr);
716 		string_for_size(capacity - fFreeBytes, sizeStr, sizeof(sizeStr));
717 		fSizeString.ReplaceFirst("%used", sizeStr);
718 		string_for_size(fFreeBytes, sizeStr, sizeof(sizeStr));
719 		fSizeString.ReplaceFirst("%free", sizeStr);
720 
721 	} else if (fModel->IsFile()) {
722 		// poll for size changes because they do not get node monitored
723 		// until a file gets closed (with the old BFS)
724 		StatStruct statBuf;
725 		BModelOpener opener(fModel);
726 
727 		if (fModel->InitCheck() != B_OK
728 			|| fModel->Node()->GetStat(&statBuf) != B_OK) {
729 			return;
730 		}
731 
732 		if (fLastSize == statBuf.st_size)
733 			return;
734 
735 		fLastSize = statBuf.st_size;
736 		fSizeString = "";
737 		BInfoWindow::GetSizeString(fSizeString, fLastSize, 0);
738 	} else
739 		return;
740 
741 	SetSizeString(fSizeString);
742 }
743 
744 
745 void
746 GeneralInfoView::MessageReceived(BMessage* message)
747 {
748 	switch (message->what) {
749 		case kSetPreferredApp:
750 		{
751 			BNode node(fModel->EntryRef());
752 			BNodeInfo nodeInfo(&node);
753 
754 			const char* newSignature;
755 			if (message->FindString("signature", &newSignature) != B_OK)
756 				newSignature = NULL;
757 
758 			fModel->SetPreferredAppSignature(newSignature);
759 			nodeInfo.SetPreferredApp(newSignature);
760 			break;
761 		}
762 
763 		case kOpenLinkSource:
764 			OpenLinkSource();
765 			break;
766 
767 		case kOpenLinkTarget:
768 			OpenLinkTarget();
769 			break;
770 
771 		default:
772 			_inherited::MessageReceived(message);
773 			break;
774 	}
775 }
776 
777 
778 void
779 GeneralInfoView::FrameResized(float, float)
780 {
781 	BModelOpener opener(fModel);
782 
783 	// Truncate the strings according to the new width
784 	InitStrings(fModel);
785 }
786 
787 
788 void
789 GeneralInfoView::Draw(BRect)
790 {
791 	// Set the low color for anti-aliasing
792 	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
793 
794 	// Clear the old contents
795 	SetHighColor(ui_color(B_PANEL_BACKGROUND_COLOR));
796 	FillRect(Bounds());
797 
798 	rgb_color labelColor = ui_color(B_PANEL_TEXT_COLOR);
799 	rgb_color attributeColor = mix_color(HighColor(), labelColor, 192);
800 
801 	// Font information
802 	font_height fontMetrics;
803 	float lineHeight = 0;
804 	float lineBase = 0;
805 	// Draw the attribute font stuff
806 	SetFont(be_plain_font);
807 	GetFontHeight(&fontMetrics);
808 	lineHeight = CurrentFontHeight() + 5;
809 
810 	// Starting base line for the first string
811 	lineBase = lineHeight;
812 
813 	// Capacity/size
814 	SetHighColor(labelColor);
815 	if (fModel->IsVolume() || fModel->IsRoot()) {
816 		MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Capacity:"))),
817 			lineBase));
818 		DrawString(B_TRANSLATE("Capacity:"));
819 	} else {
820 		MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Size:"))),
821 			lineBase));
822 		fSizeRect.left = fDivider + 2;
823 		fSizeRect.top = lineBase - fontMetrics.ascent;
824 		fSizeRect.bottom = lineBase + fontMetrics.descent;
825 		DrawString(B_TRANSLATE("Size:"));
826 	}
827 
828 	MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
829 	SetHighColor(attributeColor);
830 	// Check for possible need of truncation
831 	if (StringWidth(fSizeString.String())
832 			> (Bounds().Width() - (fDivider + kBorderMargin))) {
833 		BString tmpString(fSizeString.String());
834 		TruncateString(&tmpString, B_TRUNCATE_MIDDLE,
835 			Bounds().Width() - (fDivider + kBorderMargin));
836 		DrawString(tmpString.String());
837 		fSizeRect.right = fSizeRect.left + StringWidth(tmpString.String())
838 			+ 3;
839 	} else {
840 		DrawString(fSizeString.String());
841 		fSizeRect.right = fSizeRect.left + StringWidth(fSizeString.String()) + 3;
842 	}
843 	lineBase += lineHeight;
844 
845 	// Created
846 	SetHighColor(labelColor);
847 	MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Created:"))),
848 		lineBase));
849 	DrawString(B_TRANSLATE("Created:"));
850 	MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
851 	SetHighColor(attributeColor);
852 	DrawString(fCreatedStr.String());
853 	lineBase += lineHeight;
854 
855 	// Modified
856 	MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Modified:"))),
857 		lineBase));
858 	SetHighColor(labelColor);
859 	DrawString(B_TRANSLATE("Modified:"));
860 	MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
861 	SetHighColor(attributeColor);
862 	DrawString(fModifiedStr.String());
863 	lineBase += lineHeight;
864 
865 	// Kind
866 	MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Kind:"))),
867 		lineBase));
868 	SetHighColor(labelColor);
869 	DrawString(B_TRANSLATE("Kind:"));
870 	MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
871 	SetHighColor(attributeColor);
872 	DrawString(fKindStr.String());
873 	lineBase += lineHeight;
874 
875 	BFont normalFont;
876 	GetFont(&normalFont);
877 
878 	// Path
879 	MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Location:"))),
880 		lineBase));
881 	SetHighColor(labelColor);
882 	DrawString(B_TRANSLATE("Location:"));
883 
884 	MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
885 	SetHighUIColor(fCurrentPathColorWhich);
886 
887 	// Check for truncation
888 	if (StringWidth(fPathStr.String()) > (Bounds().Width()
889 			- (fDivider + kBorderMargin))) {
890 		BString nameString(fPathStr.String());
891 		TruncateString(&nameString, B_TRUNCATE_MIDDLE,
892 			Bounds().Width() - (fDivider + kBorderMargin));
893 		DrawString(nameString.String());
894 	} else
895 		DrawString(fPathStr.String());
896 
897 	// Cache the position of the path
898 	fPathRect.top = lineBase - fontMetrics.ascent;
899 	fPathRect.bottom = lineBase + fontMetrics.descent;
900 	fPathRect.left = fDivider + 2;
901 	fPathRect.right = fPathRect.left + StringWidth(fPathStr.String()) + 3;
902 
903 	lineBase += lineHeight;
904 
905 	// Link to/version
906 	if (fModel->IsSymLink()) {
907 		MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Link to:"))),
908 			lineBase));
909 		SetHighColor(labelColor);
910 		DrawString(B_TRANSLATE("Link to:"));
911 		MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
912 		SetHighUIColor(fCurrentLinkColorWhich);
913 
914 		// Check for truncation
915 		if (StringWidth(fLinkToStr.String()) > (Bounds().Width()
916 				- (fDivider + kBorderMargin))) {
917 			BString nameString(fLinkToStr.String());
918 			TruncateString(&nameString, B_TRUNCATE_MIDDLE,
919 				Bounds().Width() - (fDivider + kBorderMargin));
920 			DrawString(nameString.String());
921 		} else
922 			DrawString(fLinkToStr.String());
923 
924 		// Cache the position of the link field
925 		fLinkRect.top = lineBase - fontMetrics.ascent;
926 		fLinkRect.bottom = lineBase + fontMetrics.descent;
927 		fLinkRect.left = fDivider + 2;
928 		fLinkRect.right = fLinkRect.left + StringWidth(fLinkToStr.String())
929 			+ 3;
930 
931 		// No description field
932 		fDescRect = BRect(-1, -1, -1, -1);
933 	} else if (fModel->IsExecutable()) {
934 		//Version
935 		MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Version:"))),
936 			lineBase));
937 		SetHighColor(labelColor);
938 		DrawString(B_TRANSLATE("Version:"));
939 		MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
940 		SetHighColor(attributeColor);
941 		BString nameString;
942 		if (fModel->GetVersionString(nameString, B_APP_VERSION_KIND) == B_OK)
943 			DrawString(nameString.String());
944 		else
945 			DrawString("-");
946 		lineBase += lineHeight;
947 
948 		// Description
949 		MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Description:"))),
950 			lineBase));
951 		SetHighColor(labelColor);
952 		DrawString(B_TRANSLATE("Description:"));
953 		MovePenTo(BPoint(fDivider + kDrawMargin, lineBase));
954 		SetHighColor(attributeColor);
955 		// Check for truncation
956 		if (StringWidth(fDescStr.String()) > (Bounds().Width()
957 				- (fDivider + kBorderMargin))) {
958 			BString nameString(fDescStr.String());
959 			TruncateString(&nameString, B_TRUNCATE_MIDDLE,
960 				Bounds().Width() - (fDivider + kBorderMargin));
961 			DrawString(nameString.String());
962 		} else
963 			DrawString(fDescStr.String());
964 
965 		// Cache the position of the description field
966 		fDescRect.top = lineBase - fontMetrics.ascent;
967 		fDescRect.bottom = lineBase + fontMetrics.descent;
968 		fDescRect.left = fDivider + 2;
969 		fDescRect.right = fDescRect.left + StringWidth(fDescStr.String()) + 3;
970 
971 		// No link field
972 		fLinkRect = BRect(-1, -1, -1, -1);
973 	}
974 }
975 
976 
977 void
978 GeneralInfoView::WindowActivated(bool active)
979 {
980 	if (active)
981 		return;
982 
983 	if (fPathWindow->Lock()) {
984 		fPathWindow->Quit();
985 		fPathWindow = NULL;
986 	}
987 
988 	if (fLinkWindow->Lock()) {
989 		fLinkWindow->Quit();
990 		fLinkWindow = NULL;
991 	}
992 
993 	if (fDescWindow->Lock()) {
994 		fDescWindow->Quit();
995 		fDescWindow = NULL;
996 	}
997 }
998 
999 
1000 float
1001 GeneralInfoView::CurrentFontHeight()
1002 {
1003 	BFont font;
1004 	GetFont(&font);
1005 	font_height fontHeight;
1006 	font.GetHeight(&fontHeight);
1007 
1008 	return fontHeight.ascent + fontHeight.descent + fontHeight.leading + 2;
1009 }
1010 
1011 
1012 off_t
1013 GeneralInfoView::LastSize() const
1014 {
1015 	return fLastSize;
1016 }
1017 
1018 
1019 void
1020 GeneralInfoView::SetLastSize(off_t lastSize)
1021 {
1022 	fLastSize = lastSize;
1023 }
1024 
1025 
1026 void
1027 GeneralInfoView::SetSizeString(const char* sizeString)
1028 {
1029 	fSizeString = sizeString;
1030 
1031 	float lineHeight = CurrentFontHeight() + 6;
1032 	BRect bounds(fDivider, 0, Bounds().right, lineHeight);
1033 	Invalidate(bounds);
1034 }
1035 
1036 
1037 //	#pragma mark -
1038 
1039 
1040 TrackingView::TrackingView(BRect frame, const char* str, BMessage* message)
1041 	: BControl(frame, "trackingView", str, message, B_FOLLOW_ALL,
1042 		B_WILL_DRAW),
1043 	fMouseDown(false),
1044 	fMouseInView(false)
1045 {
1046 	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
1047 	SetEventMask(B_POINTER_EVENTS, 0);
1048 }
1049 
1050 
1051 void
1052 TrackingView::MouseDown(BPoint)
1053 {
1054 	if (Message() != NULL) {
1055 		fMouseDown = true;
1056 		fMouseInView = true;
1057 		InvertRect(Bounds());
1058 	}
1059 }
1060 
1061 
1062 void
1063 TrackingView::MouseMoved(BPoint, uint32 transit, const BMessage*)
1064 {
1065 	if ((transit == B_ENTERED_VIEW || transit == B_EXITED_VIEW) && fMouseDown)
1066 		InvertRect(Bounds());
1067 
1068 	fMouseInView = (transit == B_ENTERED_VIEW || transit == B_INSIDE_VIEW);
1069 	DelayedInvalidate(16666, Bounds());
1070 	if (!fMouseInView && !fMouseDown)
1071 		Window()->Close();
1072 }
1073 
1074 
1075 void
1076 TrackingView::MouseUp(BPoint)
1077 {
1078 	if (Message() != NULL) {
1079 		if (fMouseInView)
1080 			Invoke();
1081 
1082 		fMouseDown = false;
1083 		Window()->Close();
1084 	}
1085 }
1086 
1087 
1088 void
1089 TrackingView::Draw(BRect)
1090 {
1091 	if (Message() != NULL)
1092 		SetHighUIColor(fMouseInView ? B_LINK_HOVER_COLOR
1093 			: B_LINK_TEXT_COLOR);
1094 	else
1095 		SetHighUIColor(B_PANEL_TEXT_COLOR);
1096 	SetLowColor(ViewColor());
1097 
1098 	font_height fontHeight;
1099 	GetFontHeight(&fontHeight);
1100 
1101 	DrawString(Label(), BPoint(3, Bounds().Height() - fontHeight.descent));
1102 }
1103