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