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 "TextWidget.h" 37 38 #include <string.h> 39 #include <stdlib.h> 40 41 #include <Alert.h> 42 #include <Catalog.h> 43 #include <Clipboard.h> 44 #include <Debug.h> 45 #include <Directory.h> 46 #include <MessageFilter.h> 47 #include <ScrollView.h> 48 #include <TextView.h> 49 #include <Volume.h> 50 #include <Window.h> 51 52 #include "Attributes.h" 53 #include "ContainerWindow.h" 54 #include "Commands.h" 55 #include "FSUtils.h" 56 #include "PoseView.h" 57 #include "Utilities.h" 58 59 60 #undef B_TRANSLATION_CONTEXT 61 #define B_TRANSLATION_CONTEXT "TextWidget" 62 63 64 const float kWidthMargin = 20; 65 66 67 // #pragma mark - BTextWidget 68 69 70 BTextWidget::BTextWidget(Model* model, BColumn* column, BPoseView* view) 71 : 72 fText(WidgetAttributeText::NewWidgetText(model, column, view)), 73 fAttrHash(column->AttrHash()), 74 fAlignment(column->Alignment()), 75 fEditable(column->Editable()), 76 fVisible(true), 77 fActive(false), 78 fSymLink(model->IsSymLink()), 79 fMaxWidth(0), 80 fLastClickedTime(0) 81 { 82 } 83 84 85 BTextWidget::~BTextWidget() 86 { 87 if (fLastClickedTime != 0) 88 fParams.poseView->SetTextWidgetToCheck(NULL, this); 89 90 delete fText; 91 } 92 93 94 int 95 BTextWidget::Compare(const BTextWidget& with, BPoseView* view) const 96 { 97 return fText->Compare(*with.fText, view); 98 } 99 100 101 const char* 102 BTextWidget::Text(const BPoseView* view) const 103 { 104 StringAttributeText* textAttribute 105 = dynamic_cast<StringAttributeText*>(fText); 106 if (textAttribute == NULL) 107 return NULL; 108 109 return textAttribute->ValueAsText(view); 110 } 111 112 113 float 114 BTextWidget::TextWidth(const BPoseView* pose) const 115 { 116 return fText->Width(pose); 117 } 118 119 120 float 121 BTextWidget::PreferredWidth(const BPoseView* pose) const 122 { 123 return fText->PreferredWidth(pose) + 1; 124 } 125 126 127 BRect 128 BTextWidget::ColumnRect(BPoint poseLoc, const BColumn* column, 129 const BPoseView* view) 130 { 131 if (view->ViewMode() != kListMode) { 132 // ColumnRect only makes sense in list view, return 133 // CalcRect otherwise 134 return CalcRect(poseLoc, column, view); 135 } 136 BRect result; 137 result.left = column->Offset() + poseLoc.x; 138 result.right = result.left + column->Width(); 139 result.bottom = poseLoc.y 140 + roundf((view->ListElemHeight() + view->FontHeight()) / 2); 141 result.top = result.bottom - floorf(view->FontHeight()); 142 return result; 143 } 144 145 146 BRect 147 BTextWidget::CalcRectCommon(BPoint poseLoc, const BColumn* column, 148 const BPoseView* view, float textWidth) 149 { 150 BRect result; 151 float viewWidth = textWidth; 152 153 if (view->ViewMode() == kListMode) { 154 viewWidth = std::min(column->Width(), textWidth); 155 156 poseLoc.x += column->Offset(); 157 158 switch (fAlignment) { 159 case B_ALIGN_LEFT: 160 result.left = poseLoc.x; 161 result.right = result.left + viewWidth; 162 break; 163 164 case B_ALIGN_CENTER: 165 result.left = poseLoc.x 166 + roundf((column->Width() - viewWidth) / 2); 167 if (result.left < 0) 168 result.left = 0; 169 170 result.right = result.left + viewWidth; 171 break; 172 173 case B_ALIGN_RIGHT: 174 result.right = poseLoc.x + column->Width(); 175 result.left = result.right - viewWidth; 176 if (result.left < 0) 177 result.left = 0; 178 break; 179 180 default: 181 TRESPASS(); 182 break; 183 } 184 185 result.bottom = poseLoc.y 186 + roundf((view->ListElemHeight() + view->FontHeight()) / 2); 187 } else { 188 viewWidth = std::min(view->StringWidth("M") * 30, textWidth); 189 if (view->ViewMode() == kIconMode) { 190 // icon mode 191 result.left = poseLoc.x 192 + roundf((view->IconSizeInt() - viewWidth) / 2); 193 result.bottom = poseLoc.y + view->IconPoseHeight(); 194 } else { 195 // mini icon mode 196 result.left = poseLoc.x + B_MINI_ICON + kMiniIconSeparator; 197 result.bottom = poseLoc.y 198 + roundf((B_MINI_ICON + view->FontHeight()) / 2); 199 } 200 201 result.right = result.left + viewWidth; 202 } 203 204 result.top = result.bottom - floorf(view->FontHeight()); 205 206 return result; 207 } 208 209 210 BRect 211 BTextWidget::CalcRect(BPoint poseLoc, const BColumn* column, 212 const BPoseView* view) 213 { 214 return CalcRectCommon(poseLoc, column, view, fText->Width(view)); 215 } 216 217 218 BRect 219 BTextWidget::CalcOldRect(BPoint poseLoc, const BColumn* column, 220 const BPoseView* view) 221 { 222 return CalcRectCommon(poseLoc, column, view, fText->CurrentWidth()); 223 } 224 225 226 BRect 227 BTextWidget::CalcClickRect(BPoint poseLoc, const BColumn* column, 228 const BPoseView* view) 229 { 230 BRect result = CalcRect(poseLoc, column, view); 231 if (result.Width() < kWidthMargin) { 232 // if resulting rect too narrow, make it a bit wider 233 // for comfortable clicking 234 if (column != NULL && column->Width() < kWidthMargin) 235 result.right = result.left + column->Width(); 236 else 237 result.right = result.left + kWidthMargin; 238 } 239 240 return result; 241 } 242 243 244 void 245 BTextWidget::CheckExpiration() 246 { 247 if (IsEditable() && fParams.pose->IsSelected() && fLastClickedTime) { 248 bigtime_t doubleClickSpeed; 249 get_click_speed(&doubleClickSpeed); 250 251 bigtime_t delta = system_time() - fLastClickedTime; 252 253 if (delta > doubleClickSpeed) { 254 // at least 'doubleClickSpeed' microseconds ellapsed and no click 255 // was registered since. 256 fLastClickedTime = 0; 257 StartEdit(fParams.bounds, fParams.poseView, fParams.pose); 258 } 259 } else { 260 fLastClickedTime = 0; 261 fParams.poseView->SetTextWidgetToCheck(NULL); 262 } 263 } 264 265 266 void 267 BTextWidget::CancelWait() 268 { 269 fLastClickedTime = 0; 270 fParams.poseView->SetTextWidgetToCheck(NULL); 271 } 272 273 274 void 275 BTextWidget::MouseUp(BRect bounds, BPoseView* view, BPose* pose, BPoint) 276 { 277 // Register the time of that click. The PoseView, through its Pulse() 278 // will allow us to StartEdit() if no other click have been registered since 279 // then. 280 281 // TODO: re-enable modifiers, one should be enough 282 view->SetTextWidgetToCheck(NULL); 283 if (IsEditable() && pose->IsSelected()) { 284 bigtime_t doubleClickSpeed; 285 get_click_speed(&doubleClickSpeed); 286 287 if (fLastClickedTime == 0) { 288 fLastClickedTime = system_time(); 289 if (fLastClickedTime - doubleClickSpeed < pose->SelectionTime()) 290 fLastClickedTime = 0; 291 } else 292 fLastClickedTime = 0; 293 294 if (fLastClickedTime == 0) 295 return; 296 297 view->SetTextWidgetToCheck(this); 298 299 fParams.pose = pose; 300 fParams.bounds = bounds; 301 fParams.poseView = view; 302 } else 303 fLastClickedTime = 0; 304 } 305 306 307 static filter_result 308 TextViewKeyDownFilter(BMessage* message, BHandler**, BMessageFilter* filter) 309 { 310 uchar key; 311 if (message->FindInt8("byte", (int8*)&key) != B_OK) 312 return B_DISPATCH_MESSAGE; 313 314 ThrowOnAssert(filter != NULL); 315 316 BContainerWindow* window = dynamic_cast<BContainerWindow*>( 317 filter->Looper()); 318 ThrowOnAssert(window != NULL); 319 320 BPoseView* view = window->PoseView(); 321 ThrowOnAssert(view != NULL); 322 323 if (key == B_RETURN || key == B_ESCAPE) { 324 view->CommitActivePose(key == B_RETURN); 325 return B_SKIP_MESSAGE; 326 } 327 328 if (key == B_TAB) { 329 if (view->ActivePose()) { 330 if (message->FindInt32("modifiers") & B_SHIFT_KEY) 331 view->ActivePose()->EditPreviousWidget(view); 332 else 333 view->ActivePose()->EditNextWidget(view); 334 } 335 336 return B_SKIP_MESSAGE; 337 } 338 339 // the BTextView doesn't respect window borders when resizing itself; 340 // we try to work-around this "bug" here. 341 342 // find the text editing view 343 BView* scrollView = view->FindView("BorderView"); 344 if (scrollView != NULL) { 345 BTextView* textView = dynamic_cast<BTextView*>( 346 scrollView->FindView("WidgetTextView")); 347 if (textView != NULL) { 348 ASSERT(view->ActiveTextWidget() != NULL); 349 float maxWidth = view->ActiveTextWidget()->MaxWidth(); 350 bool tooWide = textView->TextRect().Width() > maxWidth; 351 textView->MakeResizable(!tooWide, tooWide ? NULL : scrollView); 352 } 353 } 354 355 return B_DISPATCH_MESSAGE; 356 } 357 358 359 static filter_result 360 TextViewPasteFilter(BMessage* message, BHandler**, BMessageFilter* filter) 361 { 362 ThrowOnAssert(filter != NULL); 363 364 BContainerWindow* window = dynamic_cast<BContainerWindow*>( 365 filter->Looper()); 366 ThrowOnAssert(window != NULL); 367 368 BPoseView* view = window->PoseView(); 369 ThrowOnAssert(view != NULL); 370 371 // the BTextView doesn't respect window borders when resizing itself; 372 // we try to work-around this "bug" here. 373 374 // find the text editing view 375 BView* scrollView = view->FindView("BorderView"); 376 if (scrollView != NULL) { 377 BTextView* textView = dynamic_cast<BTextView*>( 378 scrollView->FindView("WidgetTextView")); 379 if (textView != NULL) { 380 float textWidth = textView->TextRect().Width(); 381 382 // subtract out selected text region width 383 int32 start, finish; 384 textView->GetSelection(&start, &finish); 385 if (start != finish) { 386 BRegion selectedRegion; 387 textView->GetTextRegion(start, finish, &selectedRegion); 388 textWidth -= selectedRegion.Frame().Width(); 389 } 390 391 // add pasted text width 392 if (be_clipboard->Lock()) { 393 BMessage* clip = be_clipboard->Data(); 394 if (clip != NULL) { 395 const char* text = NULL; 396 ssize_t length = 0; 397 398 if (clip->FindData("text/plain", B_MIME_TYPE, 399 (const void**)&text, &length) == B_OK) { 400 textWidth += textView->StringWidth(text); 401 } 402 } 403 404 be_clipboard->Unlock(); 405 } 406 407 // check if pasted text is too wide 408 ASSERT(view->ActiveTextWidget() != NULL); 409 float maxWidth = view->ActiveTextWidget()->MaxWidth(); 410 bool tooWide = textWidth > maxWidth; 411 412 if (tooWide) { 413 // resize text view to max width 414 415 // move scroll view if not left aligned 416 float oldWidth = textView->Bounds().Width(); 417 float newWidth = maxWidth; 418 float right = oldWidth - newWidth; 419 420 if (textView->Alignment() == B_ALIGN_CENTER) 421 scrollView->MoveBy(roundf(right / 2), 0); 422 else if (textView->Alignment() == B_ALIGN_RIGHT) 423 scrollView->MoveBy(right, 0); 424 425 // resize scroll view 426 float grow = newWidth - oldWidth; 427 scrollView->ResizeBy(grow, 0); 428 } 429 430 textView->MakeResizable(!tooWide, tooWide ? NULL : scrollView); 431 } 432 } 433 434 return B_DISPATCH_MESSAGE; 435 } 436 437 438 void 439 BTextWidget::StartEdit(BRect bounds, BPoseView* view, BPose* pose) 440 { 441 view->SetTextWidgetToCheck(NULL, this); 442 if (!IsEditable() || IsActive()) 443 return; 444 445 BEntry entry(pose->TargetModel()->EntryRef()); 446 if (entry.InitCheck() == B_OK 447 && !ConfirmChangeIfWellKnownDirectory(&entry, kRename)) { 448 return; 449 } 450 451 view->SetActiveTextWidget(this); 452 453 // TODO fix text rect being off by a pixel on some files 454 455 BRect rect(bounds); 456 rect.OffsetBy(view->ViewMode() == kListMode ? -1 : 1, -4); 457 BTextView* textView = new BTextView(rect, "WidgetTextView", rect, 458 be_plain_font, 0, B_FOLLOW_ALL, B_WILL_DRAW); 459 460 textView->SetWordWrap(false); 461 textView->SetInsets(2, 2, 2, 2); 462 DisallowMetaKeys(textView); 463 fText->SetUpEditing(textView); 464 465 textView->AddFilter(new BMessageFilter(B_KEY_DOWN, TextViewKeyDownFilter)); 466 textView->AddFilter(new BMessageFilter(B_PASTE, TextViewPasteFilter)); 467 468 // get full text length 469 rect.right = rect.left + textView->LineWidth(); 470 rect.bottom = rect.top + textView->LineHeight() - 1 + 4; 471 472 if (view->ViewMode() == kListMode) { 473 // limit max width to column width in list mode 474 BColumn* column = view->ColumnFor(fAttrHash); 475 ASSERT(column != NULL); 476 fMaxWidth = column->Width(); 477 } else { 478 // limit max width to 30em in icon and mini icon mode 479 fMaxWidth = textView->StringWidth("M") * 30; 480 481 if (textView->LineWidth() > fMaxWidth 482 || view->ViewMode() == kMiniIconMode) { 483 // compensate for text going over right inset 484 rect.OffsetBy(-2, 0); 485 } 486 } 487 488 // resize textView 489 textView->MoveTo(rect.LeftTop()); 490 textView->ResizeTo(std::min(fMaxWidth, rect.Width()), rect.Height()); 491 textView->SetTextRect(rect); 492 493 // set alignment before adding textView so it doesn't redraw 494 switch (view->ViewMode()) { 495 case kIconMode: 496 textView->SetAlignment(B_ALIGN_CENTER); 497 break; 498 499 case kMiniIconMode: 500 textView->SetAlignment(B_ALIGN_LEFT); 501 break; 502 503 case kListMode: 504 textView->SetAlignment(fAlignment); 505 break; 506 } 507 508 BScrollView* scrollView = new BScrollView("BorderView", textView, 0, 0, 509 false, false, B_PLAIN_BORDER); 510 view->AddChild(scrollView); 511 512 bool tooWide = textView->TextRect().Width() > fMaxWidth; 513 textView->MakeResizable(!tooWide, tooWide ? NULL : scrollView); 514 515 view->SetActivePose(pose); 516 // tell view about pose 517 SetActive(true); 518 // for widget 519 520 textView->SelectAll(); 521 textView->ScrollToSelection(); 522 // scroll to beginning so that text is visible 523 textView->MakeFocus(); 524 525 // make this text widget invisible while we edit it 526 SetVisible(false); 527 528 ASSERT(view->Window() != NULL); 529 // how can I not have a Window here??? 530 531 if (view->Window()) { 532 // force immediate redraw so TextView appears instantly 533 view->Window()->UpdateIfNeeded(); 534 } 535 } 536 537 538 void 539 BTextWidget::StopEdit(bool saveChanges, BPoint poseLoc, BPoseView* view, 540 BPose* pose, int32 poseIndex) 541 { 542 view->SetActiveTextWidget(NULL); 543 544 // find the text editing view 545 BView* scrollView = view->FindView("BorderView"); 546 ASSERT(scrollView != NULL); 547 if (scrollView == NULL) 548 return; 549 550 BTextView* textView = dynamic_cast<BTextView*>( 551 scrollView->FindView("WidgetTextView")); 552 ASSERT(textView != NULL); 553 if (textView == NULL) 554 return; 555 556 BColumn* column = view->ColumnFor(fAttrHash); 557 ASSERT(column != NULL); 558 if (column == NULL) 559 return; 560 561 if (saveChanges && fText->CommitEditedText(textView)) { 562 // we have an actual change, re-sort 563 view->CheckPoseSortOrder(pose, poseIndex); 564 } 565 566 // make text widget visible again 567 SetVisible(true); 568 view->Invalidate(ColumnRect(poseLoc, column, view)); 569 570 // force immediate redraw so TEView disappears 571 scrollView->RemoveSelf(); 572 delete scrollView; 573 574 ASSERT(view->Window() != NULL); 575 view->Window()->UpdateIfNeeded(); 576 view->MakeFocus(); 577 578 SetActive(false); 579 } 580 581 582 void 583 BTextWidget::CheckAndUpdate(BPoint loc, const BColumn* column, 584 BPoseView* view, bool visible) 585 { 586 BRect oldRect; 587 if (view->ViewMode() != kListMode) 588 oldRect = CalcOldRect(loc, column, view); 589 590 if (fText->CheckAttributeChanged() && fText->CheckViewChanged(view) 591 && visible) { 592 BRect invalRect(ColumnRect(loc, column, view)); 593 if (view->ViewMode() != kListMode) 594 invalRect = invalRect | oldRect; 595 view->Invalidate(invalRect); 596 } 597 } 598 599 600 void 601 BTextWidget::SelectAll(BPoseView* view) 602 { 603 BTextView* text = dynamic_cast<BTextView*>( 604 view->FindView("WidgetTextView")); 605 if (text != NULL) 606 text->SelectAll(); 607 } 608 609 610 void 611 BTextWidget::Draw(BRect eraseRect, BRect textRect, float, BPoseView* view, 612 BView* drawView, bool selected, uint32 clipboardMode, BPoint offset, 613 bool direct) 614 { 615 textRect.OffsetBy(offset); 616 617 if (direct) { 618 // draw selection box if selected 619 if (selected) { 620 drawView->SetDrawingMode(B_OP_COPY); 621 // eraseRect.OffsetBy(offset); 622 // drawView->FillRect(eraseRect, B_SOLID_LOW); 623 drawView->FillRect(textRect, B_SOLID_LOW); 624 } else 625 drawView->SetDrawingMode(B_OP_OVER); 626 627 // set high color 628 rgb_color highColor; 629 // for active views, the selection is drawn as inverse text (background color for the text, 630 // solid black for the background). 631 // For inactive windows, the text is drawn normally, then the selection rect is 632 // alpha-blended on top of it. 633 // This all happens in BPose::Draw before and after calling this function, here we are 634 // only concerned with setting the correct color for the text. 635 if (selected && view->Window()->IsActive()) 636 highColor = ui_color(B_DOCUMENT_BACKGROUND_COLOR); 637 else 638 highColor = view->DeskTextColor(); 639 640 if (clipboardMode == kMoveSelectionTo && !selected) { 641 drawView->SetDrawingMode(B_OP_ALPHA); 642 drawView->SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); 643 highColor.alpha = 64; 644 } 645 drawView->SetHighColor(highColor); 646 } 647 648 BPoint loc; 649 loc.y = textRect.bottom - view->FontInfo().descent; 650 loc.x = textRect.left + 1; 651 652 const char* fittingText = fText->FittingText(view); 653 654 // TODO: Comparing view and drawView here to avoid rendering 655 // the text outline when producing a drag bitmap. The check is 656 // not fully correct, since an offscreen view is also used in some 657 // other rare cases (something to do with columns). But for now, this 658 // fixes the broken drag bitmaps when dragging icons from the Desktop. 659 if (!selected && view == drawView && view->WidgetTextOutline()) { 660 // draw a halo around the text by using the "false bold" 661 // feature for text rendering. Either black or white is used for 662 // the glow (whatever acts as contrast) with a some alpha value, 663 drawView->SetDrawingMode(B_OP_ALPHA); 664 drawView->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY); 665 666 BFont font; 667 drawView->GetFont(&font); 668 669 rgb_color textColor = ui_color(B_PANEL_TEXT_COLOR); 670 if (view->IsDesktopWindow()) 671 textColor = view->DeskTextColor(); 672 673 if (textColor.Brightness() < 100) { 674 // dark text on light outline 675 rgb_color glowColor = ui_color(B_SHINE_COLOR); 676 677 font.SetFalseBoldWidth(2.0); 678 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 679 glowColor.alpha = 30; 680 drawView->SetHighColor(glowColor); 681 682 drawView->DrawString(fittingText, loc); 683 684 font.SetFalseBoldWidth(1.0); 685 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 686 glowColor.alpha = 65; 687 drawView->SetHighColor(glowColor); 688 689 drawView->DrawString(fittingText, loc); 690 691 font.SetFalseBoldWidth(0.0); 692 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 693 } else { 694 // light text on dark outline 695 rgb_color outlineColor = kBlack; 696 697 font.SetFalseBoldWidth(1.0); 698 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 699 outlineColor.alpha = 30; 700 drawView->SetHighColor(outlineColor); 701 702 drawView->DrawString(fittingText, loc); 703 704 font.SetFalseBoldWidth(0.0); 705 drawView->SetFont(&font, B_FONT_FALSE_BOLD_WIDTH); 706 707 outlineColor.alpha = 200; 708 drawView->SetHighColor(outlineColor); 709 710 drawView->DrawString(fittingText, loc + BPoint(1, 1)); 711 } 712 713 drawView->SetDrawingMode(B_OP_OVER); 714 drawView->SetHighColor(textColor); 715 } 716 717 drawView->DrawString(fittingText, loc); 718 719 if (fSymLink && (fAttrHash == view->FirstColumn()->AttrHash())) { 720 // TODO: 721 // this should be exported to the WidgetAttribute class, probably 722 // by having a per widget kind style 723 if (direct) { 724 rgb_color underlineColor = drawView->HighColor(); 725 underlineColor.alpha = 180; 726 drawView->SetHighColor(underlineColor); 727 drawView->SetDrawingMode(B_OP_ALPHA); 728 drawView->SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY); 729 } 730 731 textRect.right = textRect.left + fText->Width(view); 732 // only underline text part 733 drawView->StrokeLine(textRect.LeftBottom(), textRect.RightBottom(), 734 B_MIXED_COLORS); 735 } 736 } 737