1/*
2Open Tracker License
3
4Terms and Conditions
5
6Copyright (c) 1991-2000, Be Incorporated. All rights reserved.
7
8Permission is hereby granted, free of charge, to any person obtaining a copy of
9this software and associated documentation files (the "Software"), to deal in
10the Software without restriction, including without limitation the rights to
11use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12of the Software, and to permit persons to whom the Software is furnished to do
13so, subject to the following conditions:
14
15The above copyright notice and this permission notice applies to all licensees
16and shall be included in all copies or substantial portions of the Software.
17
18THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF TITLE, MERCHANTABILITY,
20FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
21BE INCORPORATED BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
22AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION
23WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
25Except as contained in this notice, the name of Be Incorporated shall not be
26used in advertising or otherwise to promote the sale, use or other dealings in
27this Software without prior written authorization from Be Incorporated.
28
29Tracker(TM), Be(R), BeOS(R), and BeIA(TM) are trademarks or registered trademarks
30of Be Incorporated in the United States and other countries. Other brand product
31names are registered trademarks or trademarks of their respective holders.
32All 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
71namespace BPrivate {
72
73class TrackingView : public BControl {
74public:
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
82private:
83	bool fMouseDown;
84	bool fMouseInView;
85};
86
87}	// namespace BPrivate
88
89
90static float sBorderMargin, sDrawMargin = 0.0f;
91
92
93const uint32 kSetPreferredApp = 'setp';
94const uint32 kSelectNewSymTarget = 'snew';
95const uint32 kOpenLinkSource = 'opls';
96const uint32 kOpenLinkTarget = 'oplt';
97
98
99static void
100OpenParentAndSelectOriginal(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
119static BWindow*
120OpenToolTipWindow(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
153GeneralInfoView::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
289GeneralInfoView::~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
302void
303GeneralInfoView::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
384void
385GeneralInfoView::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
400void
401GeneralInfoView::Pulse()
402{
403	CheckAndSetSize();
404	_inherited::Pulse();
405}
406
407
408void
409GeneralInfoView::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
510void
511GeneralInfoView::ReLinkTargetModel(Model* model)
512{
513	fModel = model;
514	InitStrings(model);
515	Invalidate(Bounds());
516}
517
518
519void
520GeneralInfoView::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
543void
544GeneralInfoView::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
645void
646GeneralInfoView::OpenLinkSource()
647{
648	OpenParentAndSelectOriginal(fModel->EntryRef());
649}
650
651
652void
653GeneralInfoView::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
681void
682GeneralInfoView::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
702void
703GeneralInfoView::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
772void
773GeneralInfoView::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
805void
806GeneralInfoView::FrameResized(float, float)
807{
808	BModelOpener opener(fModel);
809
810	// Truncate the strings according to the new width
811	InitStrings(fModel);
812}
813
814
815void
816GeneralInfoView::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
1025void
1026GeneralInfoView::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
1048float
1049GeneralInfoView::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
1060off_t
1061GeneralInfoView::LastSize() const
1062{
1063	return fLastSize;
1064}
1065
1066
1067void
1068GeneralInfoView::SetLastSize(off_t lastSize)
1069{
1070	fLastSize = lastSize;
1071}
1072
1073
1074void
1075GeneralInfoView::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
1088TrackingView::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
1099void
1100TrackingView::MouseDown(BPoint)
1101{
1102	if (Message() != NULL) {
1103		fMouseDown = true;
1104		fMouseInView = true;
1105		InvertRect(Bounds());
1106	}
1107}
1108
1109
1110void
1111TrackingView::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
1123void
1124TrackingView::MouseUp(BPoint)
1125{
1126	if (Message() != NULL) {
1127		if (fMouseInView)
1128			Invoke();
1129
1130		fMouseDown = false;
1131		Window()->Close();
1132	}
1133}
1134
1135
1136void
1137TrackingView::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