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