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