xref: /haiku/src/kits/tracker/infowindow/HeaderView.cpp (revision 60a6f1d5d7a8715cd3897dd0b626f2e4a64984a8)
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 "HeaderView.h"
37 
38 #include <Alert.h>
39 #include <Application.h>
40 #include <Catalog.h>
41 #include <Locale.h>
42 #include <PopUpMenu.h>
43 #include <ScrollView.h>
44 #include <Volume.h>
45 #include <VolumeRoster.h>
46 #include <Window.h>
47 
48 #include "Commands.h"
49 #include "FSUtils.h"
50 #include "GeneralInfoView.h"
51 #include "IconMenuItem.h"
52 #include "Model.h"
53 #include "NavMenu.h"
54 #include "PoseView.h"
55 #include "Tracker.h"
56 
57 
58 #undef B_TRANSLATION_CONTEXT
59 #define B_TRANSLATION_CONTEXT "InfoWindow"
60 
61 
62 // Offsets taken from TAlertView::Draw in BAlert.cpp
63 const float kIconHorizOffset = 18.0f;
64 const float kIconVertOffset = 6.0f;
65 const float kBorderWidth = 32.0f;
66 
67 // Amount you have to move the mouse before a drag starts
68 const float kDragSlop = 3.0f;
69 
70 
71 HeaderView::HeaderView(Model* model)
72 	:
73 	BView("header", B_WILL_DRAW),
74 	fModel(model),
75 	fIconModel(model),
76 	fTitleEditView(NULL),
77 	fTrackingState(no_track),
78 	fMouseDown(false),
79 	fIsDropTarget(false),
80 	fDoubleClick(false),
81 	fDragging(false)
82 {
83 	// Create the rect for displaying the icon
84 	fIconRect.Set(0, 0, B_LARGE_ICON - 1, B_LARGE_ICON - 1);
85 	// Offset taken from BAlert
86 	fIconRect.OffsetBy(kIconHorizOffset, kIconVertOffset);
87 	SetExplicitSize(BSize(B_SIZE_UNSET, B_LARGE_ICON + 2 * kIconVertOffset));
88 
89 	// The title rect
90 	// The magic numbers are used to properly calculate the rect so that
91 	// when the editing text view is displayed, the position of the text
92 	// does not change.
93 	BFont currentFont;
94 	font_height fontMetrics;
95 	GetFont(&currentFont);
96 	currentFont.GetHeight(&fontMetrics);
97 
98 	fTitleRect.left = fIconRect.right + 5;
99 	fTitleRect.top = 0;
100 	fTitleRect.bottom = fontMetrics.ascent + 1;
101 	fTitleRect.right = min_c(
102 		fTitleRect.left + currentFont.StringWidth(fModel->Name()),
103 		Bounds().Width() - 5);
104 	// Offset so that it centers with the icon
105 	fTitleRect.OffsetBy(0,
106 		fIconRect.top + ((fIconRect.Height() - fTitleRect.Height()) / 2));
107 	// Make some room for the border for when we are in edit mode
108 	// (Negative numbers increase the size of the rect)
109 	fTitleRect.InsetBy(-1, -2);
110 
111 	// If the model is a symlink, then we deference the model to
112 	// get the targets icon
113 	if (fModel->IsSymLink()) {
114 		Model* resolvedModel = new Model(model->EntryRef(), true, true);
115 		if (resolvedModel->InitCheck() == B_OK)
116 			fIconModel = resolvedModel;
117 		// broken link, just show the symlink
118 		else
119 			delete resolvedModel;
120 	}
121 
122 }
123 
124 
125 HeaderView::~HeaderView()
126 {
127 	if (fIconModel != fModel)
128 		delete fIconModel;
129 }
130 
131 
132 void
133 HeaderView::ModelChanged(Model* model, BMessage* message)
134 {
135 	// Update the icon stuff
136 	if (fIconModel != fModel) {
137 		delete fIconModel;
138 		fIconModel = NULL;
139 	}
140 
141 	fModel = model;
142 	if (fModel->IsSymLink()) {
143 		// if we are looking at a symlink, deference the model and look
144 		// at the target
145 		Model* resolvedModel = new Model(model->EntryRef(), true, true);
146 		if (resolvedModel->InitCheck() == B_OK) {
147 			if (fIconModel != fModel)
148 				delete fIconModel;
149 			fIconModel = resolvedModel;
150 		} else {
151 			fIconModel = model;
152 			delete resolvedModel;
153 		}
154 	}
155 
156 	Invalidate();
157 }
158 
159 
160 void
161 HeaderView::ReLinkTargetModel(Model* model)
162 {
163 	fModel = model;
164 	if (fModel->IsSymLink()) {
165 		Model* resolvedModel = new Model(model->EntryRef(), true, true);
166 		if (resolvedModel->InitCheck() == B_OK) {
167 			if (fIconModel != fModel)
168 				delete fIconModel;
169 			fIconModel = resolvedModel;
170 		} else {
171 			fIconModel = fModel;
172 			delete resolvedModel;
173 		}
174 	}
175 	Invalidate();
176 }
177 
178 
179 void
180 HeaderView::BeginEditingTitle()
181 {
182 	if (fTitleEditView != NULL)
183 		return;
184 
185 	BFont font(be_plain_font);
186 	font.SetSize(font.Size() + 2);
187 	BRect textFrame(fTitleRect);
188 	textFrame.right = Bounds().Width() - 5;
189 	BRect textRect(textFrame);
190 	textRect.OffsetTo(0, 0);
191 	textRect.InsetBy(1, 1);
192 
193 	// Just make it some really large size, since we don't do any line
194 	// wrapping. The text filter will make sure to scroll the cursor
195 	// into position
196 
197 	textRect.right = 2000;
198 	fTitleEditView = new BTextView(textFrame, "text_editor",
199 		textRect, &font, 0, B_FOLLOW_ALL, B_WILL_DRAW);
200 	fTitleEditView->SetText(fModel->Name());
201 	DisallowFilenameKeys(fTitleEditView);
202 
203 	// Reset the width of the text rect
204 	textRect = fTitleEditView->TextRect();
205 	textRect.right = fTitleEditView->LineWidth() + 20;
206 	fTitleEditView->SetTextRect(textRect);
207 	fTitleEditView->SetWordWrap(false);
208 	// Add filter for catching B_RETURN and B_ESCAPE key's
209 	fTitleEditView->AddFilter(
210 		new BMessageFilter(B_KEY_DOWN, HeaderView::TextViewFilter));
211 
212 	BScrollView* scrollView = new BScrollView("BorderView", fTitleEditView,
213 		0, 0, false, false, B_PLAIN_BORDER);
214 	AddChild(scrollView);
215 	fTitleEditView->SelectAll();
216 	fTitleEditView->MakeFocus();
217 
218 	Window()->UpdateIfNeeded();
219 }
220 
221 
222 void
223 HeaderView::FinishEditingTitle(bool commit)
224 {
225 	if (fTitleEditView == NULL)
226 		return;
227 
228 	bool reopen = false;
229 
230 	const char* text = fTitleEditView->Text();
231 	uint32 length = strlen(text);
232 	if (commit && strcmp(text, fModel->Name()) != 0
233 		&& length < B_FILE_NAME_LENGTH) {
234 		BEntry entry(fModel->EntryRef());
235 		BDirectory parent;
236 		if (entry.InitCheck() == B_OK
237 			&& entry.GetParent(&parent) == B_OK) {
238 			if (parent.Contains(text)) {
239 				BAlert* alert = new BAlert("",
240 					B_TRANSLATE("That name is already taken. "
241 					"Please type another one."),
242 					B_TRANSLATE("OK"),
243 					0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
244 				alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
245 				alert->Go();
246 				reopen = true;
247 			} else {
248 				if (fModel->IsVolume()) {
249 					BVolume	volume(fModel->NodeRef()->device);
250 					if (volume.InitCheck() == B_OK)
251 						volume.SetName(text);
252 				} else
253 					entry.Rename(text);
254 
255 				// Adjust the size of the text rect
256 				BFont currentFont(be_plain_font);
257 				currentFont.SetSize(currentFont.Size() + 2);
258 				fTitleRect.right = min_c(fTitleRect.left
259 						+ currentFont.StringWidth(fTitleEditView->Text()),
260 					Bounds().Width() - 5);
261 			}
262 		}
263 	} else if (length >= B_FILE_NAME_LENGTH) {
264 		BAlert* alert = new BAlert("",
265 			B_TRANSLATE("That name is too long. Please type another one."),
266 			B_TRANSLATE("OK"),
267 			0, 0, B_WIDTH_AS_USUAL, B_WARNING_ALERT);
268 		alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
269 		alert->Go();
270 		reopen = true;
271 	}
272 
273 	// Remove view
274 	BView* scrollView = fTitleEditView->Parent();
275 	RemoveChild(scrollView);
276 	delete scrollView;
277 	fTitleEditView = NULL;
278 
279 	if (reopen)
280 		BeginEditingTitle();
281 }
282 
283 
284 void
285 HeaderView::Draw(BRect)
286 {
287 	// Set the low color for anti-aliasing
288 	SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
289 
290 	// Clear the old contents
291 	SetHighColor(ui_color(B_PANEL_BACKGROUND_COLOR));
292 	FillRect(Bounds());
293 
294 	rgb_color labelColor = ui_color(B_PANEL_TEXT_COLOR);
295 
296 	// Draw the icon, straddling the border
297 	SetDrawingMode(B_OP_OVER);
298 	IconCache::sIconCache->Draw(fIconModel, this, fIconRect.LeftTop(),
299 		kNormalIcon, B_LARGE_ICON, true);
300 	SetDrawingMode(B_OP_COPY);
301 
302 	// Font information
303 	font_height fontMetrics;
304 	BFont currentFont;
305 	float lineHeight = 0;
306 	float lineBase = 0;
307 
308 	// Draw the main title if the user is not currently editing it
309 	if (fTitleEditView == NULL) {
310 		SetFont(be_bold_font);
311 		SetFontSize(be_bold_font->Size());
312 		GetFont(&currentFont);
313 		currentFont.GetHeight(&fontMetrics);
314 		lineHeight = CurrentFontHeight() + 5;
315 		lineBase = fTitleRect.bottom - fontMetrics.descent;
316 		SetHighColor(labelColor);
317 		MovePenTo(BPoint(fIconRect.right + 6, lineBase));
318 
319 		// Recalculate the rect width
320 		fTitleRect.right = min_c(
321 				fTitleRect.left + currentFont.StringWidth(fModel->Name()),
322 			Bounds().Width() - 5);
323 		// Check for possible need of truncation
324 		if (StringWidth(fModel->Name()) > fTitleRect.Width()) {
325 			BString nameString(fModel->Name());
326 			TruncateString(&nameString, B_TRUNCATE_END,
327 				fTitleRect.Width() - 2);
328 			DrawString(nameString.String());
329 		} else
330 			DrawString(fModel->Name());
331 	}
332 
333 }
334 
335 
336 void
337 HeaderView::MakeFocus(bool focus)
338 {
339 	if (!focus && fTitleEditView != NULL)
340 		FinishEditingTitle(true);
341 }
342 
343 
344 void
345 HeaderView::WindowActivated(bool active)
346 {
347 	if (active)
348 		return;
349 
350 	if (fTitleEditView != NULL)
351 		FinishEditingTitle(true);
352 }
353 
354 
355 void
356 HeaderView::MouseDown(BPoint where)
357 {
358 	// Assume this isn't part of a double click
359 	fDoubleClick = false;
360 
361 	BEntry entry;
362 	fModel->GetEntry(&entry);
363 
364 	if (fTitleRect.Contains(where)) {
365 		if (!fModel->HasLocalizedName()
366 			&& ConfirmChangeIfWellKnownDirectory(&entry, kRename, true)) {
367 			BeginEditingTitle();
368 		}
369 	} else if (fTitleEditView) {
370 		FinishEditingTitle(true);
371 	} else if (fIconRect.Contains(where)) {
372 		uint32 buttons;
373 		Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons);
374 		if (SecondaryMouseButtonDown(modifiers(), buttons)) {
375 			// Show contextual menu
376 			BPopUpMenu* contextMenu
377 				= new BPopUpMenu("FileContext", false, false);
378 			if (contextMenu) {
379 				BuildContextMenu(contextMenu);
380 				contextMenu->SetAsyncAutoDestruct(true);
381 				contextMenu->Go(ConvertToScreen(where), true, true,
382 					ConvertToScreen(fIconRect));
383 			}
384 		} else {
385 			// Check to see if the point is actually on part of the icon,
386 			// versus just in the container rect. The icons are always
387 			// the large version
388 			BPoint offsetPoint;
389 			offsetPoint.x = where.x - fIconRect.left;
390 			offsetPoint.y = where.y - fIconRect.top;
391 			if (IconCache::sIconCache->IconHitTest(offsetPoint, fIconModel,
392 					kNormalIcon, B_LARGE_ICON)) {
393 				// Can't drag the trash anywhere..
394 				fTrackingState = fModel->IsTrash()
395 					? open_only_track : icon_track;
396 
397 				// Check for possible double click
398 				if (abs((int32)(fClickPoint.x - where.x)) < kDragSlop
399 					&& abs((int32)(fClickPoint.y - where.y)) < kDragSlop) {
400 					int32 clickCount;
401 					Window()->CurrentMessage()->FindInt32("clicks",
402 						&clickCount);
403 
404 					// This checks the* previous* click point
405 					if (clickCount == 2) {
406 						offsetPoint.x = fClickPoint.x - fIconRect.left;
407 						offsetPoint.y = fClickPoint.y - fIconRect.top;
408 						fDoubleClick
409 							= IconCache::sIconCache->IconHitTest(offsetPoint,
410 							fIconModel, kNormalIcon, B_LARGE_ICON);
411 					}
412 				}
413 			}
414 		}
415 	}
416 
417 	fClickPoint = where;
418 	fMouseDown = true;
419 	SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
420 }
421 
422 
423 void
424 HeaderView::MouseMoved(BPoint where, uint32, const BMessage* dragMessage)
425 {
426 	if (dragMessage != NULL && dragMessage->ReturnAddress() != BMessenger(this)
427 		&& dragMessage->what == B_SIMPLE_DATA
428 		&& BPoseView::CanHandleDragSelection(fModel, dragMessage,
429 			(modifiers() & B_CONTROL_KEY) != 0)) {
430 		// highlight drag target
431 		bool overTarget = fIconRect.Contains(where);
432 		SetDrawingMode(B_OP_OVER);
433 		if (overTarget != fIsDropTarget) {
434 			IconCache::sIconCache->Draw(fIconModel, this, fIconRect.LeftTop(),
435 				overTarget ? kSelectedIcon : kNormalIcon, B_LARGE_ICON, true);
436 			fIsDropTarget = overTarget;
437 		}
438 	}
439 
440 	switch (fTrackingState) {
441 		case icon_track:
442 			if (fMouseDown && !fDragging
443 				&& (abs((int32)(where.x - fClickPoint.x)) > kDragSlop
444 					|| abs((int32)(where.y - fClickPoint.y)) > kDragSlop)) {
445 				// Find the required height
446 				BFont font;
447 				GetFont(&font);
448 
449 				float height = CurrentFontHeight()
450 					+ fIconRect.Height() + 8;
451 				BRect rect(0, 0, min_c(fIconRect.Width()
452 						+ font.StringWidth(fModel->Name()) + 4,
453 					fIconRect.Width() * 3), height);
454 				BBitmap* dragBitmap = new BBitmap(rect, B_RGBA32, true);
455 				dragBitmap->Lock();
456 				BView* view = new BView(dragBitmap->Bounds(), "",
457 					B_FOLLOW_NONE, 0);
458 				dragBitmap->AddChild(view);
459 				view->SetOrigin(0, 0);
460 				BRect clipRect(view->Bounds());
461 				BRegion newClip;
462 				newClip.Set(clipRect);
463 				view->ConstrainClippingRegion(&newClip);
464 
465 				// Transparent draw magic
466 				view->SetHighColor(0, 0, 0, 0);
467 				view->FillRect(view->Bounds());
468 				view->SetDrawingMode(B_OP_ALPHA);
469 				rgb_color textColor = ui_color(B_PANEL_TEXT_COLOR);
470 				textColor.alpha = 128;
471 					// set transparency by value
472 				view->SetHighColor(textColor);
473 				view->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE);
474 
475 				// Draw the icon
476 				float hIconOffset = (rect.Width() - fIconRect.Width()) / 2;
477 				IconCache::sIconCache->Draw(fIconModel, view,
478 					BPoint(hIconOffset, 0), kNormalIcon, B_LARGE_ICON, true);
479 
480 				// See if we need to truncate the string
481 				BString nameString(fModel->Name());
482 				if (view->StringWidth(fModel->Name()) > rect.Width()) {
483 					view->TruncateString(&nameString, B_TRUNCATE_END,
484 						rect.Width() - 5);
485 				}
486 
487 				// Draw the label
488 				font_height fontHeight;
489 				font.GetHeight(&fontHeight);
490 				float leftText = (view->StringWidth(nameString.String())
491 					- fIconRect.Width()) / 2;
492 				view->MovePenTo(BPoint(hIconOffset - leftText + 2,
493 					fIconRect.Height() + (fontHeight.ascent + 2)));
494 				view->DrawString(nameString.String());
495 
496 				view->Sync();
497 				dragBitmap->Unlock();
498 
499 				BMessage dragMessage(B_REFS_RECEIVED);
500 				dragMessage.AddPoint("click_pt", fClickPoint);
501 				BPoint tmpLoc;
502 				uint32 button;
503 				GetMouse(&tmpLoc, &button);
504 				if (button)
505 					dragMessage.AddInt32("buttons", (int32)button);
506 
507 				dragMessage.AddInt32("be:actions",
508 					(modifiers() & B_OPTION_KEY) != 0
509 						? B_COPY_TARGET : B_MOVE_TARGET);
510 				dragMessage.AddRef("refs", fModel->EntryRef());
511 				DragMessage(&dragMessage, dragBitmap, B_OP_ALPHA,
512 					BPoint((fClickPoint.x - fIconRect.left)
513 					+ hIconOffset, fClickPoint.y - fIconRect.top), this);
514 				fDragging = true;
515 			}
516 			break;
517 
518 		case open_only_track :
519 			// Special type of entry that can't be renamed or drag and dropped
520 			// It can only be opened by double clicking on the icon
521 			break;
522 
523 		case no_track:
524 			// No mouse tracking, do nothing
525 			break;
526 	}
527 }
528 
529 
530 void
531 HeaderView::MouseUp(BPoint where)
532 {
533 	if ((fTrackingState == icon_track
534 			|| fTrackingState == open_only_track)
535 		&& fIconRect.Contains(where)) {
536 		// If it was a double click, then tell Tracker to open the item
537 		// The CurrentMessage() here does* not* have a "clicks" field,
538 		// which is why we are tracking the clicks with this temp var
539 		if (fDoubleClick) {
540 			// Double click, launch.
541 			BMessage message(B_REFS_RECEIVED);
542 			message.AddRef("refs", fModel->EntryRef());
543 
544 			// add a messenger to the launch message that will be used to
545 			// dispatch scripting calls from apps to the PoseView
546 			message.AddMessenger("TrackerViewToken", BMessenger(this));
547 			be_app->PostMessage(&message);
548 			fDoubleClick = false;
549 		}
550 	}
551 
552 	// End mouse tracking
553 	fMouseDown = false;
554 	fDragging = false;
555 	fTrackingState = no_track;
556 }
557 
558 
559 void
560 HeaderView::MessageReceived(BMessage* message)
561 {
562 	if (message->WasDropped()
563 		&& message->what == B_SIMPLE_DATA
564 		&& message->ReturnAddress() != BMessenger(this)
565 		&& fIconRect.Contains(ConvertFromScreen(message->DropPoint()))
566 		&& BPoseView::CanHandleDragSelection(fModel, message,
567 			(modifiers() & B_CONTROL_KEY) != 0)) {
568 		BPoseView::HandleDropCommon(message, fModel, 0, this,
569 			message->DropPoint());
570 		Invalidate(fIconRect);
571 		return;
572 	}
573 
574 	BView::MessageReceived(message);
575 }
576 
577 
578 
579 status_t
580 HeaderView::BuildContextMenu(BMenu* parent)
581 {
582 	if (parent == NULL)
583 		return B_BAD_VALUE;
584 
585 	// Add navigation menu if this is not a symlink
586 	// Symlink's to directories are OK however!
587 	BEntry entry(fModel->EntryRef());
588 	entry_ref ref;
589 	entry.GetRef(&ref);
590 	Model model(&entry);
591 	bool navigate = false;
592 	if (model.InitCheck() == B_OK) {
593 		if (model.IsSymLink()) {
594 			// Check if it's to a directory
595 			if (entry.SetTo(model.EntryRef(), true) == B_OK) {
596 				navigate = entry.IsDirectory();
597 				entry.GetRef(&ref);
598 			}
599 		} else if (model.IsDirectory() || model.IsVolume())
600 			navigate = true;
601 	}
602 	ModelMenuItem* navigationItem = NULL;
603 	if (navigate) {
604 		navigationItem = new ModelMenuItem(new Model(model),
605 			new BNavMenu(model.Name(), B_REFS_RECEIVED, be_app, Window()));
606 
607 		// setup a navigation menu item which will dynamically load items
608 		// as menu items are traversed
609 		BNavMenu* navMenu = dynamic_cast<BNavMenu*>(navigationItem->Submenu());
610 		if (navMenu != NULL)
611 			navMenu->SetNavDir(&ref);
612 
613 		navigationItem->SetLabel(model.Name());
614 		navigationItem->SetEntry(&entry);
615 
616 		parent->AddItem(navigationItem, 0);
617 		parent->AddItem(new BSeparatorItem(), 1);
618 
619 		BMessage* message = new BMessage(B_REFS_RECEIVED);
620 		message->AddRef("refs", &ref);
621 		navigationItem->SetMessage(message);
622 		navigationItem->SetTarget(be_app);
623 	}
624 
625 	parent->AddItem(new BMenuItem(B_TRANSLATE("Open"),
626 		new BMessage(kOpenSelection), 'O'));
627 
628 	if (!model.IsDesktop() && !model.IsRoot() && !model.IsTrash()
629 		&& !fModel->HasLocalizedName()) {
630 		parent->AddItem(new BMenuItem(B_TRANSLATE("Edit name"),
631 			new BMessage(kEditItem), 'E'));
632 		parent->AddSeparatorItem();
633 
634 		if (fModel->IsVolume()) {
635 			BMenuItem* item = new BMenuItem(B_TRANSLATE("Unmount"),
636 				new BMessage(kUnmountVolume), 'U');
637 			parent->AddItem(item);
638 			// volume model, enable/disable the Unmount item
639 			BVolume boot;
640 			BVolumeRoster().GetBootVolume(&boot);
641 			BVolume volume;
642 			volume.SetTo(fModel->NodeRef()->device);
643 			if (volume == boot)
644 				item->SetEnabled(false);
645 		}
646 	}
647 
648 	if (!model.IsRoot() && !model.IsVolume() && !model.IsTrash())
649 		parent->AddItem(new BMenuItem(B_TRANSLATE("Identify"),
650 			new BMessage(kIdentifyEntry)));
651 
652 	if (model.IsTrash())
653 		parent->AddItem(new BMenuItem(B_TRANSLATE("Empty Trash"),
654 			new BMessage(kEmptyTrash)));
655 
656 	BMenuItem* sizeItem = NULL;
657 	if (model.IsDirectory() && !model.IsVolume() && !model.IsRoot())  {
658 		parent->AddItem(sizeItem
659 				= new BMenuItem(B_TRANSLATE("Recalculate folder size"),
660 			new BMessage(kRecalculateSize)));
661 	}
662 
663 	if (model.IsSymLink()) {
664 		parent->AddItem(sizeItem
665 				= new BMenuItem(B_TRANSLATE("Set new link target"),
666 			new BMessage(kSetLinkTarget)));
667 	}
668 
669 	parent->AddItem(new BSeparatorItem());
670 	parent->AddItem(new BMenuItem(B_TRANSLATE("Permissions"),
671 		new BMessage(kPermissionsSelected), 'P'));
672 
673 	parent->SetFont(be_plain_font);
674 	parent->SetTargetForItems(this);
675 
676 	// Reset the nav menu to be_app
677 	if (navigate)
678 		navigationItem->SetTarget(be_app);
679 	if (sizeItem)
680 		sizeItem->SetTarget(Window());
681 
682 	return B_OK;
683 }
684 
685 
686 filter_result
687 HeaderView::TextViewFilter(BMessage* message, BHandler**,
688 	BMessageFilter* filter)
689 {
690 	uchar key;
691 	HeaderView* attribView = static_cast<HeaderView*>(
692 		static_cast<BWindow*>(filter->Looper())->FindView("header"));
693 
694 	// Adjust the size of the text rect
695 	BRect nuRect(attribView->TextView()->TextRect());
696 	nuRect.right = attribView->TextView()->LineWidth() + 20;
697 	attribView->TextView()->SetTextRect(nuRect);
698 
699 	// Make sure the cursor is in view
700 	attribView->TextView()->ScrollToSelection();
701 	if (message->FindInt8("byte", (int8*)&key) != B_OK)
702 		return B_DISPATCH_MESSAGE;
703 
704 	if (key == B_RETURN || key == B_ESCAPE) {
705 		attribView->FinishEditingTitle(key == B_RETURN);
706 		return B_SKIP_MESSAGE;
707 	}
708 
709 	return B_DISPATCH_MESSAGE;
710 }
711 
712 
713 float
714 HeaderView::CurrentFontHeight()
715 {
716 	BFont font;
717 	GetFont(&font);
718 	font_height fontHeight;
719 	font.GetHeight(&fontHeight);
720 
721 	return fontHeight.ascent + fontHeight.descent + fontHeight.leading + 2;
722 }
723 
724 
725