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