xref: /haiku/src/kits/tracker/infowindow/HeaderView.cpp (revision 8c78892580f132d10e624aef96f835df8d94bf19)
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 lineBase = 0;
306 
307 	// Draw the main title if the user is not currently editing it
308 	if (fTitleEditView == NULL) {
309 		SetFont(be_bold_font);
310 		SetFontSize(be_bold_font->Size());
311 		GetFont(&currentFont);
312 		currentFont.GetHeight(&fontMetrics);
313 		lineBase = fTitleRect.bottom - fontMetrics.descent;
314 		SetHighColor(labelColor);
315 		MovePenTo(BPoint(fIconRect.right + 6, lineBase));
316 
317 		// Recalculate the rect width
318 		fTitleRect.right = min_c(
319 				fTitleRect.left + currentFont.StringWidth(fModel->Name()),
320 			Bounds().Width() - 5);
321 		// Check for possible need of truncation
322 		if (StringWidth(fModel->Name()) > fTitleRect.Width()) {
323 			BString nameString(fModel->Name());
324 			TruncateString(&nameString, B_TRUNCATE_END,
325 				fTitleRect.Width() - 2);
326 			DrawString(nameString.String());
327 		} else
328 			DrawString(fModel->Name());
329 	}
330 
331 }
332 
333 
334 void
335 HeaderView::MakeFocus(bool focus)
336 {
337 	if (!focus && fTitleEditView != NULL)
338 		FinishEditingTitle(true);
339 }
340 
341 
342 void
343 HeaderView::WindowActivated(bool active)
344 {
345 	if (active)
346 		return;
347 
348 	if (fTitleEditView != NULL)
349 		FinishEditingTitle(true);
350 }
351 
352 
353 void
354 HeaderView::MouseDown(BPoint where)
355 {
356 	// Assume this isn't part of a double click
357 	fDoubleClick = false;
358 
359 	BEntry entry;
360 	fModel->GetEntry(&entry);
361 
362 	if (fTitleRect.Contains(where)) {
363 		if (!fModel->HasLocalizedName()
364 			&& ConfirmChangeIfWellKnownDirectory(&entry, kRename, true)) {
365 			BeginEditingTitle();
366 		}
367 	} else if (fTitleEditView) {
368 		FinishEditingTitle(true);
369 	} else if (fIconRect.Contains(where)) {
370 		uint32 buttons;
371 		Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons);
372 		if (SecondaryMouseButtonDown(modifiers(), buttons)) {
373 			// Show contextual menu
374 			BPopUpMenu* contextMenu
375 				= new BPopUpMenu("FileContext", false, false);
376 			if (contextMenu) {
377 				BuildContextMenu(contextMenu);
378 				contextMenu->SetAsyncAutoDestruct(true);
379 				contextMenu->Go(ConvertToScreen(where), true, true,
380 					ConvertToScreen(fIconRect));
381 			}
382 		} else {
383 			// Check to see if the point is actually on part of the icon,
384 			// versus just in the container rect. The icons are always
385 			// the large version
386 			BPoint offsetPoint;
387 			offsetPoint.x = where.x - fIconRect.left;
388 			offsetPoint.y = where.y - fIconRect.top;
389 			if (IconCache::sIconCache->IconHitTest(offsetPoint, fIconModel,
390 					kNormalIcon, B_LARGE_ICON)) {
391 				// Can't drag the trash anywhere..
392 				fTrackingState = fModel->IsTrash()
393 					? open_only_track : icon_track;
394 
395 				// Check for possible double click
396 				if (abs((int32)(fClickPoint.x - where.x)) < kDragSlop
397 					&& abs((int32)(fClickPoint.y - where.y)) < kDragSlop) {
398 					int32 clickCount;
399 					Window()->CurrentMessage()->FindInt32("clicks",
400 						&clickCount);
401 
402 					// This checks the* previous* click point
403 					if (clickCount == 2) {
404 						offsetPoint.x = fClickPoint.x - fIconRect.left;
405 						offsetPoint.y = fClickPoint.y - fIconRect.top;
406 						fDoubleClick
407 							= IconCache::sIconCache->IconHitTest(offsetPoint,
408 							fIconModel, kNormalIcon, B_LARGE_ICON);
409 					}
410 				}
411 			}
412 		}
413 	}
414 
415 	fClickPoint = where;
416 	fMouseDown = true;
417 	SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
418 }
419 
420 
421 void
422 HeaderView::MouseMoved(BPoint where, uint32, const BMessage* dragMessage)
423 {
424 	if (dragMessage != NULL && dragMessage->ReturnAddress() != BMessenger(this)
425 		&& dragMessage->what == B_SIMPLE_DATA
426 		&& BPoseView::CanHandleDragSelection(fModel, dragMessage,
427 			(modifiers() & B_CONTROL_KEY) != 0)) {
428 		// highlight drag target
429 		bool overTarget = fIconRect.Contains(where);
430 		SetDrawingMode(B_OP_OVER);
431 		if (overTarget != fIsDropTarget) {
432 			IconCache::sIconCache->Draw(fIconModel, this, fIconRect.LeftTop(),
433 				overTarget ? kSelectedIcon : kNormalIcon, B_LARGE_ICON, true);
434 			fIsDropTarget = overTarget;
435 		}
436 	}
437 
438 	switch (fTrackingState) {
439 		case icon_track:
440 			if (fMouseDown && !fDragging
441 				&& (abs((int32)(where.x - fClickPoint.x)) > kDragSlop
442 					|| abs((int32)(where.y - fClickPoint.y)) > kDragSlop)) {
443 				// Find the required height
444 				BFont font;
445 				GetFont(&font);
446 
447 				float height = CurrentFontHeight()
448 					+ fIconRect.Height() + 8;
449 				BRect rect(0, 0, min_c(fIconRect.Width()
450 						+ font.StringWidth(fModel->Name()) + 4,
451 					fIconRect.Width() * 3), height);
452 				BBitmap* dragBitmap = new BBitmap(rect, B_RGBA32, true);
453 				dragBitmap->Lock();
454 				BView* view = new BView(dragBitmap->Bounds(), "",
455 					B_FOLLOW_NONE, 0);
456 				dragBitmap->AddChild(view);
457 				view->SetOrigin(0, 0);
458 				BRect clipRect(view->Bounds());
459 				BRegion newClip;
460 				newClip.Set(clipRect);
461 				view->ConstrainClippingRegion(&newClip);
462 
463 				// Transparent draw magic
464 				view->SetHighColor(0, 0, 0, 0);
465 				view->FillRect(view->Bounds());
466 				view->SetDrawingMode(B_OP_ALPHA);
467 				rgb_color textColor = ui_color(B_PANEL_TEXT_COLOR);
468 				textColor.alpha = 128;
469 					// set transparency by value
470 				view->SetHighColor(textColor);
471 				view->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE);
472 
473 				// Draw the icon
474 				float hIconOffset = (rect.Width() - fIconRect.Width()) / 2;
475 				IconCache::sIconCache->Draw(fIconModel, view,
476 					BPoint(hIconOffset, 0), kNormalIcon, B_LARGE_ICON, true);
477 
478 				// See if we need to truncate the string
479 				BString nameString(fModel->Name());
480 				if (view->StringWidth(fModel->Name()) > rect.Width()) {
481 					view->TruncateString(&nameString, B_TRUNCATE_END,
482 						rect.Width() - 5);
483 				}
484 
485 				// Draw the label
486 				font_height fontHeight;
487 				font.GetHeight(&fontHeight);
488 				float leftText = (view->StringWidth(nameString.String())
489 					- fIconRect.Width()) / 2;
490 				view->MovePenTo(BPoint(hIconOffset - leftText + 2,
491 					fIconRect.Height() + (fontHeight.ascent + 2)));
492 				view->DrawString(nameString.String());
493 
494 				view->Sync();
495 				dragBitmap->Unlock();
496 
497 				BMessage dragMessage(B_REFS_RECEIVED);
498 				dragMessage.AddPoint("click_pt", fClickPoint);
499 				BPoint tmpLoc;
500 				uint32 button;
501 				GetMouse(&tmpLoc, &button);
502 				if (button)
503 					dragMessage.AddInt32("buttons", (int32)button);
504 
505 				dragMessage.AddInt32("be:actions",
506 					(modifiers() & B_OPTION_KEY) != 0
507 						? B_COPY_TARGET : B_MOVE_TARGET);
508 				dragMessage.AddRef("refs", fModel->EntryRef());
509 				DragMessage(&dragMessage, dragBitmap, B_OP_ALPHA,
510 					BPoint((fClickPoint.x - fIconRect.left)
511 					+ hIconOffset, fClickPoint.y - fIconRect.top), this);
512 				fDragging = true;
513 			}
514 			break;
515 
516 		case open_only_track :
517 			// Special type of entry that can't be renamed or drag and dropped
518 			// It can only be opened by double clicking on the icon
519 			break;
520 
521 		case no_track:
522 			// No mouse tracking, do nothing
523 			break;
524 	}
525 }
526 
527 
528 void
529 HeaderView::MouseUp(BPoint where)
530 {
531 	if ((fTrackingState == icon_track
532 			|| fTrackingState == open_only_track)
533 		&& fIconRect.Contains(where)) {
534 		// If it was a double click, then tell Tracker to open the item
535 		// The CurrentMessage() here does* not* have a "clicks" field,
536 		// which is why we are tracking the clicks with this temp var
537 		if (fDoubleClick) {
538 			// Double click, launch.
539 			BMessage message(B_REFS_RECEIVED);
540 			message.AddRef("refs", fModel->EntryRef());
541 
542 			// add a messenger to the launch message that will be used to
543 			// dispatch scripting calls from apps to the PoseView
544 			message.AddMessenger("TrackerViewToken", BMessenger(this));
545 			be_app->PostMessage(&message);
546 			fDoubleClick = false;
547 		}
548 	}
549 
550 	// End mouse tracking
551 	fMouseDown = false;
552 	fDragging = false;
553 	fTrackingState = no_track;
554 }
555 
556 
557 void
558 HeaderView::MessageReceived(BMessage* message)
559 {
560 	if (message->WasDropped()
561 		&& message->what == B_SIMPLE_DATA
562 		&& message->ReturnAddress() != BMessenger(this)
563 		&& fIconRect.Contains(ConvertFromScreen(message->DropPoint()))
564 		&& BPoseView::CanHandleDragSelection(fModel, message,
565 			(modifiers() & B_CONTROL_KEY) != 0)) {
566 		BPoseView::HandleDropCommon(message, fModel, 0, this,
567 			message->DropPoint());
568 		Invalidate(fIconRect);
569 		return;
570 	}
571 
572 	BView::MessageReceived(message);
573 }
574 
575 
576 
577 status_t
578 HeaderView::BuildContextMenu(BMenu* parent)
579 {
580 	if (parent == NULL)
581 		return B_BAD_VALUE;
582 
583 	// Add navigation menu if this is not a symlink
584 	// Symlink's to directories are OK however!
585 	BEntry entry(fModel->EntryRef());
586 	entry_ref ref;
587 	entry.GetRef(&ref);
588 	Model model(&entry);
589 	bool navigate = false;
590 	if (model.InitCheck() == B_OK) {
591 		if (model.IsSymLink()) {
592 			// Check if it's to a directory
593 			if (entry.SetTo(model.EntryRef(), true) == B_OK) {
594 				navigate = entry.IsDirectory();
595 				entry.GetRef(&ref);
596 			}
597 		} else if (model.IsDirectory() || model.IsVolume())
598 			navigate = true;
599 	}
600 	ModelMenuItem* navigationItem = NULL;
601 	if (navigate) {
602 		navigationItem = new ModelMenuItem(new Model(model),
603 			new BNavMenu(model.Name(), B_REFS_RECEIVED, be_app, Window()));
604 
605 		// setup a navigation menu item which will dynamically load items
606 		// as menu items are traversed
607 		BNavMenu* navMenu = dynamic_cast<BNavMenu*>(navigationItem->Submenu());
608 		if (navMenu != NULL)
609 			navMenu->SetNavDir(&ref);
610 
611 		navigationItem->SetLabel(model.Name());
612 		navigationItem->SetEntry(&entry);
613 
614 		parent->AddItem(navigationItem, 0);
615 		parent->AddItem(new BSeparatorItem(), 1);
616 
617 		BMessage* message = new BMessage(B_REFS_RECEIVED);
618 		message->AddRef("refs", &ref);
619 		navigationItem->SetMessage(message);
620 		navigationItem->SetTarget(be_app);
621 	}
622 
623 	parent->AddItem(new BMenuItem(B_TRANSLATE("Open"),
624 		new BMessage(kOpenSelection), 'O'));
625 
626 	if (!model.IsDesktop() && !model.IsRoot() && !model.IsTrash()
627 		&& !fModel->HasLocalizedName()) {
628 		parent->AddItem(new BMenuItem(B_TRANSLATE("Edit name"),
629 			new BMessage(kEditItem), 'E'));
630 		parent->AddSeparatorItem();
631 
632 		if (fModel->IsVolume()) {
633 			BMenuItem* item = new BMenuItem(B_TRANSLATE("Unmount"),
634 				new BMessage(kUnmountVolume), 'U');
635 			parent->AddItem(item);
636 			// volume model, enable/disable the Unmount item
637 			BVolume boot;
638 			BVolumeRoster().GetBootVolume(&boot);
639 			BVolume volume;
640 			volume.SetTo(fModel->NodeRef()->device);
641 			if (volume == boot)
642 				item->SetEnabled(false);
643 		}
644 	}
645 
646 	if (!model.IsRoot() && !model.IsVolume() && !model.IsTrash())
647 		parent->AddItem(new BMenuItem(B_TRANSLATE("Identify"),
648 			new BMessage(kIdentifyEntry)));
649 
650 	if (model.IsTrash())
651 		parent->AddItem(new BMenuItem(B_TRANSLATE("Empty Trash"),
652 			new BMessage(kEmptyTrash)));
653 
654 	BMenuItem* sizeItem = NULL;
655 	if (model.IsDirectory() && !model.IsVolume() && !model.IsRoot())  {
656 		parent->AddItem(sizeItem
657 				= new BMenuItem(B_TRANSLATE("Recalculate folder size"),
658 			new BMessage(kRecalculateSize)));
659 	}
660 
661 	if (model.IsSymLink()) {
662 		parent->AddItem(sizeItem
663 				= new BMenuItem(B_TRANSLATE("Set new link target"),
664 			new BMessage(kSetLinkTarget)));
665 	}
666 
667 	parent->AddItem(new BSeparatorItem());
668 	parent->AddItem(new BMenuItem(B_TRANSLATE("Permissions"),
669 		new BMessage(kPermissionsSelected), 'P'));
670 
671 	parent->SetFont(be_plain_font);
672 	parent->SetTargetForItems(this);
673 
674 	// Reset the nav menu to be_app
675 	if (navigate)
676 		navigationItem->SetTarget(be_app);
677 	if (sizeItem)
678 		sizeItem->SetTarget(Window());
679 
680 	return B_OK;
681 }
682 
683 
684 filter_result
685 HeaderView::TextViewFilter(BMessage* message, BHandler**,
686 	BMessageFilter* filter)
687 {
688 	uchar key;
689 	HeaderView* attribView = static_cast<HeaderView*>(
690 		static_cast<BWindow*>(filter->Looper())->FindView("header"));
691 
692 	// Adjust the size of the text rect
693 	BRect nuRect(attribView->TextView()->TextRect());
694 	nuRect.right = attribView->TextView()->LineWidth() + 20;
695 	attribView->TextView()->SetTextRect(nuRect);
696 
697 	// Make sure the cursor is in view
698 	attribView->TextView()->ScrollToSelection();
699 	if (message->FindInt8("byte", (int8*)&key) != B_OK)
700 		return B_DISPATCH_MESSAGE;
701 
702 	if (key == B_RETURN || key == B_ESCAPE) {
703 		attribView->FinishEditingTitle(key == B_RETURN);
704 		return B_SKIP_MESSAGE;
705 	}
706 
707 	return B_DISPATCH_MESSAGE;
708 }
709 
710 
711 float
712 HeaderView::CurrentFontHeight()
713 {
714 	BFont font;
715 	GetFont(&font);
716 	font_height fontHeight;
717 	font.GetHeight(&fontHeight);
718 
719 	return fontHeight.ascent + fontHeight.descent + fontHeight.leading + 2;
720 }
721 
722 
723