xref: /haiku/src/kits/tracker/infowindow/GeneralInfoView.cpp (revision 4c07199d8201fcf267e90be0d24b76799d03cea6)
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 		bool volumeHasNoCapacity = false;
709 
710 		if (fModel->IsVolume()) {
711 			BVolume volume(fModel->NodeRef()->device);
712 			freeBytes = volume.FreeBytes();
713 			capacity = volume.Capacity();
714 			volumeHasNoCapacity = capacity == 0;
715 		} else {
716 			// iterate over all volumes
717 			BVolumeRoster volumeRoster;
718 			BVolume volume;
719 			while (volumeRoster.GetNextVolume(&volume) == B_OK) {
720 				if (volume.FreeBytes() > 0)
721 					freeBytes += volume.FreeBytes();
722 				if (volume.Capacity() > 0)
723 					capacity += volume.Capacity();
724 			}
725 		}
726 
727 		if (fFreeBytes == freeBytes)
728 			return;
729 
730 		fFreeBytes = freeBytes;
731 
732 		if (volumeHasNoCapacity) {
733 			// set to "-" if capacity is 0
734 			fSizeString.SetTo("-");
735 			SetSizeString(fSizeString);
736 			return;
737 		}
738 
739 		fSizeString.SetTo(B_TRANSLATE("%capacity (%used used -- %free free)"));
740 
741 		char sizeStr[128];
742 		string_for_size(capacity, sizeStr, sizeof(sizeStr));
743 		fSizeString.ReplaceFirst("%capacity", sizeStr);
744 		string_for_size(capacity - fFreeBytes, sizeStr, sizeof(sizeStr));
745 		fSizeString.ReplaceFirst("%used", sizeStr);
746 		string_for_size(fFreeBytes, sizeStr, sizeof(sizeStr));
747 		fSizeString.ReplaceFirst("%free", sizeStr);
748 	} else if (fModel->IsFile()) {
749 		// poll for size changes because they do not get node monitored
750 		// until a file gets closed (with the old BFS)
751 		StatStruct statBuf;
752 		BModelOpener opener(fModel);
753 
754 		if (fModel->InitCheck() != B_OK
755 			|| fModel->Node()->GetStat(&statBuf) != B_OK) {
756 			return;
757 		}
758 
759 		if (fLastSize == statBuf.st_size)
760 			return;
761 
762 		fLastSize = statBuf.st_size;
763 		fSizeString = "";
764 		BInfoWindow::GetSizeString(fSizeString, fLastSize, 0);
765 	} else
766 		return;
767 
768 	SetSizeString(fSizeString);
769 }
770 
771 
772 void
773 GeneralInfoView::MessageReceived(BMessage* message)
774 {
775 	switch (message->what) {
776 		case kSetPreferredApp:
777 		{
778 			BNode node(fModel->EntryRef());
779 			BNodeInfo nodeInfo(&node);
780 
781 			const char* newSignature;
782 			if (message->FindString("signature", &newSignature) != B_OK)
783 				newSignature = NULL;
784 
785 			fModel->SetPreferredAppSignature(newSignature);
786 			nodeInfo.SetPreferredApp(newSignature);
787 			break;
788 		}
789 
790 		case kOpenLinkSource:
791 			OpenLinkSource();
792 			break;
793 
794 		case kOpenLinkTarget:
795 			OpenLinkTarget();
796 			break;
797 
798 		default:
799 			_inherited::MessageReceived(message);
800 			break;
801 	}
802 }
803 
804 
805 void
806 GeneralInfoView::FrameResized(float, float)
807 {
808 	BModelOpener opener(fModel);
809 
810 	// Truncate the strings according to the new width
811 	InitStrings(fModel);
812 }
813 
814 
815 void
816 GeneralInfoView::Draw(BRect)
817 {
818 	// Set the low color for anti-aliasing
819 	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
820 
821 	// Clear the old contents
822 	SetHighColor(ui_color(B_PANEL_BACKGROUND_COLOR));
823 	FillRect(Bounds());
824 
825 	rgb_color labelColor = ui_color(B_PANEL_TEXT_COLOR);
826 	rgb_color attributeColor = mix_color(HighColor(), labelColor, 192);
827 
828 	// Font information
829 	font_height fontMetrics;
830 	float lineHeight = 0;
831 	float lineBase = 0;
832 	// Draw the attribute font stuff
833 	SetFont(be_plain_font);
834 	GetFontHeight(&fontMetrics);
835 	lineHeight = CurrentFontHeight() + 5;
836 
837 	// Starting base line for the first string
838 	lineBase = lineHeight;
839 
840 	// Capacity/size
841 	SetHighColor(labelColor);
842 	if (fModel->IsVolume() || fModel->IsRoot()) {
843 		MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Capacity:"))),
844 			lineBase));
845 		DrawString(B_TRANSLATE("Capacity:"));
846 	} else {
847 		MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Size:"))),
848 			lineBase));
849 		fSizeRect.left = fDivider + 2;
850 		fSizeRect.top = lineBase - fontMetrics.ascent;
851 		fSizeRect.bottom = lineBase + fontMetrics.descent;
852 		DrawString(B_TRANSLATE("Size:"));
853 	}
854 
855 	MovePenTo(BPoint(fDivider + sDrawMargin, lineBase));
856 	SetHighColor(attributeColor);
857 	// Check for possible need of truncation
858 	if (StringWidth(fSizeString.String())
859 			> (Bounds().Width() - (fDivider + sBorderMargin))) {
860 		BString tmpString(fSizeString.String());
861 		TruncateString(&tmpString, B_TRUNCATE_MIDDLE,
862 			Bounds().Width() - (fDivider + sBorderMargin));
863 		DrawString(tmpString.String());
864 		fSizeRect.right = fSizeRect.left + StringWidth(tmpString.String())
865 			+ 3;
866 	} else {
867 		DrawString(fSizeString.String());
868 		fSizeRect.right = fSizeRect.left + StringWidth(fSizeString.String()) + 3;
869 	}
870 	lineBase += lineHeight;
871 
872 	// Created
873 	SetHighColor(labelColor);
874 	MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Created:"))),
875 		lineBase));
876 	DrawString(B_TRANSLATE("Created:"));
877 	MovePenTo(BPoint(fDivider + sDrawMargin, lineBase));
878 	SetHighColor(attributeColor);
879 	DrawString(fCreatedStr.String());
880 	lineBase += lineHeight;
881 
882 	// Modified
883 	MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Modified:"))),
884 		lineBase));
885 	SetHighColor(labelColor);
886 	DrawString(B_TRANSLATE("Modified:"));
887 	MovePenTo(BPoint(fDivider + sDrawMargin, lineBase));
888 	SetHighColor(attributeColor);
889 	DrawString(fModifiedStr.String());
890 	lineBase += lineHeight;
891 
892 	// Kind
893 	MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Kind:"))),
894 		lineBase));
895 	SetHighColor(labelColor);
896 	DrawString(B_TRANSLATE("Kind:"));
897 	MovePenTo(BPoint(fDivider + sDrawMargin, lineBase));
898 	SetHighColor(attributeColor);
899 	DrawString(fKindStr.String());
900 	lineBase += lineHeight;
901 
902 	BFont normalFont;
903 	GetFont(&normalFont);
904 
905 	// Path
906 	MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Location:"))),
907 		lineBase));
908 	SetHighColor(labelColor);
909 	DrawString(B_TRANSLATE("Location:"));
910 
911 	MovePenTo(BPoint(fDivider + sDrawMargin, lineBase));
912 	SetHighUIColor(fCurrentPathColorWhich);
913 
914 	// Check for truncation
915 	if (StringWidth(fPathStr.String()) > (Bounds().Width()
916 			- (fDivider + sBorderMargin))) {
917 		BString nameString(fPathStr.String());
918 		TruncateString(&nameString, B_TRUNCATE_MIDDLE,
919 			Bounds().Width() - (fDivider + sBorderMargin));
920 		DrawString(nameString.String());
921 	} else
922 		DrawString(fPathStr.String());
923 
924 	// Cache the position of the path
925 	fPathRect.top = lineBase - fontMetrics.ascent;
926 	fPathRect.bottom = lineBase + fontMetrics.descent;
927 	fPathRect.left = fDivider + 2;
928 	fPathRect.right = fPathRect.left + StringWidth(fPathStr.String()) + 3;
929 
930 	lineBase += lineHeight;
931 
932 	// Link to/version
933 	if (fModel->IsSymLink()) {
934 		MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Link to:"))),
935 			lineBase));
936 		SetHighColor(labelColor);
937 		DrawString(B_TRANSLATE("Link to:"));
938 		MovePenTo(BPoint(fDivider + sDrawMargin, lineBase));
939 		SetHighUIColor(fCurrentLinkColorWhich);
940 
941 		// Check for truncation
942 		if (StringWidth(fLinkToStr.String()) > (Bounds().Width()
943 				- (fDivider + sBorderMargin))) {
944 			BString nameString(fLinkToStr.String());
945 			TruncateString(&nameString, B_TRUNCATE_MIDDLE,
946 				Bounds().Width() - (fDivider + sBorderMargin));
947 			DrawString(nameString.String());
948 		} else
949 			DrawString(fLinkToStr.String());
950 
951 		// Cache the position of the link field
952 		fLinkRect.top = lineBase - fontMetrics.ascent;
953 		fLinkRect.bottom = lineBase + fontMetrics.descent;
954 		fLinkRect.left = fDivider + 2;
955 		fLinkRect.right = fLinkRect.left + StringWidth(fLinkToStr.String())
956 			+ 3;
957 
958 		// No description field
959 		fDescRect = BRect(-1, -1, -1, -1);
960 	} else if (fModel->IsExecutable()) {
961 		//Version
962 		MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Version:"))),
963 			lineBase));
964 		SetHighColor(labelColor);
965 		DrawString(B_TRANSLATE("Version:"));
966 		MovePenTo(BPoint(fDivider + sDrawMargin, lineBase));
967 		SetHighColor(attributeColor);
968 		BString nameString;
969 		if (fModel->GetVersionString(nameString, B_APP_VERSION_KIND) == B_OK)
970 			DrawString(nameString.String());
971 		else
972 			DrawString("-");
973 		lineBase += lineHeight;
974 
975 		// Description
976 		MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Description:"))),
977 			lineBase));
978 		SetHighColor(labelColor);
979 		DrawString(B_TRANSLATE("Description:"));
980 		MovePenTo(BPoint(fDivider + sDrawMargin, lineBase));
981 		SetHighColor(attributeColor);
982 		// Check for truncation
983 		if (StringWidth(fDescStr.String()) > (Bounds().Width()
984 				- (fDivider + sBorderMargin))) {
985 			BString nameString(fDescStr.String());
986 			TruncateString(&nameString, B_TRUNCATE_MIDDLE,
987 				Bounds().Width() - (fDivider + sBorderMargin));
988 			DrawString(nameString.String());
989 		} else
990 			DrawString(fDescStr.String());
991 
992 		// Cache the position of the description field
993 		fDescRect.top = lineBase - fontMetrics.ascent;
994 		fDescRect.bottom = lineBase + fontMetrics.descent;
995 		fDescRect.left = fDivider + 2;
996 		fDescRect.right = fDescRect.left + StringWidth(fDescStr.String()) + 3;
997 
998 		// No link field
999 		fLinkRect = BRect(-1, -1, -1, -1);
1000 	} else if (fModel->IsVolume()) {
1001 		//Filesystem
1002 		MovePenTo(BPoint(fDivider - (StringWidth(B_TRANSLATE("Filesystem:"))),
1003 			lineBase));
1004 		SetHighColor(labelColor);
1005 		DrawString(B_TRANSLATE("Filesystem:"));
1006 		MovePenTo(BPoint(fDivider + sDrawMargin, lineBase));
1007 		SetHighColor(attributeColor);
1008 		// Check for truncation
1009 		if (StringWidth(fFileSystemStr.String()) > (Bounds().Width()
1010 				- (fDivider + sBorderMargin))) {
1011 			BString nameString(fFileSystemStr.String());
1012 			TruncateString(&nameString, B_TRUNCATE_MIDDLE,
1013 				Bounds().Width() - (fDivider + sBorderMargin));
1014 			DrawString(nameString.String());
1015 		} else
1016 			DrawString(fFileSystemStr.String());
1017 
1018 		// No description field or link field
1019 		fDescRect = BRect(-1, -1, -1, -1);
1020 		fLinkRect = BRect(-1, -1, -1, -1);
1021 	}
1022 }
1023 
1024 
1025 void
1026 GeneralInfoView::WindowActivated(bool active)
1027 {
1028 	if (active)
1029 		return;
1030 
1031 	if (fPathWindow->Lock()) {
1032 		fPathWindow->Quit();
1033 		fPathWindow = NULL;
1034 	}
1035 
1036 	if (fLinkWindow->Lock()) {
1037 		fLinkWindow->Quit();
1038 		fLinkWindow = NULL;
1039 	}
1040 
1041 	if (fDescWindow->Lock()) {
1042 		fDescWindow->Quit();
1043 		fDescWindow = NULL;
1044 	}
1045 }
1046 
1047 
1048 float
1049 GeneralInfoView::CurrentFontHeight()
1050 {
1051 	BFont font;
1052 	GetFont(&font);
1053 	font_height fontHeight;
1054 	font.GetHeight(&fontHeight);
1055 
1056 	return fontHeight.ascent + fontHeight.descent + fontHeight.leading + 2;
1057 }
1058 
1059 
1060 off_t
1061 GeneralInfoView::LastSize() const
1062 {
1063 	return fLastSize;
1064 }
1065 
1066 
1067 void
1068 GeneralInfoView::SetLastSize(off_t lastSize)
1069 {
1070 	fLastSize = lastSize;
1071 }
1072 
1073 
1074 void
1075 GeneralInfoView::SetSizeString(const char* sizeString)
1076 {
1077 	fSizeString = sizeString;
1078 
1079 	float lineHeight = CurrentFontHeight() + 6;
1080 	BRect bounds(fDivider, 0, Bounds().right, lineHeight);
1081 	Invalidate(bounds);
1082 }
1083 
1084 
1085 //	#pragma mark -
1086 
1087 
1088 TrackingView::TrackingView(BRect frame, const char* str, BMessage* message)
1089 	: BControl(frame, "trackingView", str, message, B_FOLLOW_ALL,
1090 		B_WILL_DRAW),
1091 	fMouseDown(false),
1092 	fMouseInView(false)
1093 {
1094 	SetViewUIColor(B_PANEL_BACKGROUND_COLOR);
1095 	SetEventMask(B_POINTER_EVENTS, 0);
1096 }
1097 
1098 
1099 void
1100 TrackingView::MouseDown(BPoint)
1101 {
1102 	if (Message() != NULL) {
1103 		fMouseDown = true;
1104 		fMouseInView = true;
1105 		InvertRect(Bounds());
1106 	}
1107 }
1108 
1109 
1110 void
1111 TrackingView::MouseMoved(BPoint, uint32 transit, const BMessage*)
1112 {
1113 	if ((transit == B_ENTERED_VIEW || transit == B_EXITED_VIEW) && fMouseDown)
1114 		InvertRect(Bounds());
1115 
1116 	fMouseInView = (transit == B_ENTERED_VIEW || transit == B_INSIDE_VIEW);
1117 	DelayedInvalidate(16666, Bounds());
1118 	if (!fMouseInView && !fMouseDown)
1119 		Window()->Close();
1120 }
1121 
1122 
1123 void
1124 TrackingView::MouseUp(BPoint)
1125 {
1126 	if (Message() != NULL) {
1127 		if (fMouseInView)
1128 			Invoke();
1129 
1130 		fMouseDown = false;
1131 		Window()->Close();
1132 	}
1133 }
1134 
1135 
1136 void
1137 TrackingView::Draw(BRect)
1138 {
1139 	if (Message() != NULL)
1140 		SetHighUIColor(fMouseInView ? B_LINK_HOVER_COLOR
1141 			: B_LINK_TEXT_COLOR);
1142 	else
1143 		SetHighUIColor(B_PANEL_TEXT_COLOR);
1144 	SetLowColor(ViewColor());
1145 
1146 	font_height fontHeight;
1147 	GetFontHeight(&fontHeight);
1148 
1149 	DrawString(Label(), BPoint(3, Bounds().Height() - fontHeight.descent));
1150 }
1151