1 /* 2 * Copyright 2001-2005, Haiku Inc. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Frans van Nispen (xlr8@tref.nl) 7 * Stephan Aßmus <superstippi@gmx.de> 8 */ 9 10 /** BTextControl displays text that can act like a control. */ 11 12 13 #include <stdio.h> 14 15 #include <Message.h> 16 #include <Region.h> 17 #include <TextControl.h> 18 #include <Window.h> 19 20 #include "TextInput.h" 21 22 23 BTextControl::BTextControl(BRect frame, const char *name, const char *label, 24 const char *text, BMessage *message, uint32 mask, 25 uint32 flags) 26 : BControl(frame, name, label, message, mask, flags | B_FRAME_EVENTS) 27 { 28 _InitData(label, text); 29 30 float height; 31 BTextControl::GetPreferredSize(NULL, &height); 32 33 ResizeTo(Bounds().Width(), height); 34 35 float lineHeight = ceil(fText->LineHeight(0)); 36 fText->ResizeTo(fText->Bounds().Width(), lineHeight); 37 fText->MoveTo(fText->Frame().left, (height - lineHeight) / 2); 38 39 fPreviousHeight = Bounds().Height(); 40 } 41 42 43 BTextControl::~BTextControl() 44 { 45 SetModificationMessage(NULL); 46 } 47 48 49 BTextControl::BTextControl(BMessage* archive) 50 : BControl(archive) 51 { 52 _InitData(Label(), NULL, archive); 53 54 int32 labelAlignment = B_ALIGN_LEFT; 55 int32 textAlignment = B_ALIGN_LEFT; 56 57 if (archive->HasInt32("_a_label")) 58 archive->FindInt32("_a_label", &labelAlignment); 59 60 if (archive->HasInt32("_a_text")) 61 archive->FindInt32("_a_text", &textAlignment); 62 63 SetAlignment((alignment)labelAlignment, (alignment)textAlignment); 64 65 if (archive->HasFloat("_divide")) 66 archive->FindFloat("_a_text", &fDivider); 67 68 if (archive->HasMessage("_mod_msg")) { 69 BMessage* message = new BMessage; 70 archive->FindMessage("_mod_msg", message); 71 SetModificationMessage(message); 72 } 73 } 74 75 76 BArchivable * 77 BTextControl::Instantiate(BMessage *archive) 78 { 79 if (validate_instantiation(archive, "BTextControl")) 80 return new BTextControl(archive); 81 else 82 return NULL; 83 } 84 85 86 status_t 87 BTextControl::Archive(BMessage *data, bool deep) const 88 { 89 status_t ret = BView::Archive(data, deep); 90 alignment labelAlignment, textAlignment; 91 92 GetAlignment(&labelAlignment, &textAlignment); 93 94 if (ret == B_OK) 95 ret = data->AddInt32("_a_label", labelAlignment); 96 if (ret == B_OK) 97 ret = data->AddInt32("_a_text", textAlignment); 98 if (ret == B_OK) 99 ret = data->AddFloat("_divide", Divider()); 100 101 if (ModificationMessage() && (ret == B_OK)) 102 ret = data->AddMessage("_mod_msg", ModificationMessage()); 103 104 return ret; 105 } 106 107 108 void 109 BTextControl::SetText(const char *text) 110 { 111 if (InvokeKind() != B_CONTROL_INVOKED) 112 return; 113 114 fText->SetText(text); 115 116 if (IsFocus()) 117 fText->SetInitialText(); 118 119 fText->Invalidate(); 120 } 121 122 123 const char * 124 BTextControl::Text() const 125 { 126 return fText->Text(); 127 } 128 129 130 void 131 BTextControl::SetValue(int32 value) 132 { 133 BControl::SetValue(value); 134 } 135 136 137 status_t 138 BTextControl::Invoke(BMessage *message) 139 { 140 return BControl::Invoke(message); 141 } 142 143 144 BTextView * 145 BTextControl::TextView() const 146 { 147 return fText; 148 } 149 150 151 void 152 BTextControl::SetModificationMessage(BMessage *message) 153 { 154 delete fModificationMessage; 155 fModificationMessage = message; 156 } 157 158 159 BMessage * 160 BTextControl::ModificationMessage() const 161 { 162 return fModificationMessage; 163 } 164 165 166 void 167 BTextControl::SetAlignment(alignment labelAlignment, alignment textAlignment) 168 { 169 fText->SetAlignment(textAlignment); 170 fText->AlignTextRect(); 171 172 if (fLabelAlign != labelAlignment) { 173 fLabelAlign = labelAlignment; 174 Invalidate(); 175 } 176 } 177 178 179 void 180 BTextControl::GetAlignment(alignment* _label, alignment* _text) const 181 { 182 if (_label) 183 *_label = fLabelAlign; 184 if (_text) 185 *_text = fText->Alignment(); 186 } 187 188 189 void 190 BTextControl::SetDivider(float dividingLine) 191 { 192 dividingLine = floorf(dividingLine + 0.5); 193 194 float dx = fDivider - dividingLine; 195 196 fDivider = dividingLine; 197 198 fText->MoveBy(-dx, 0.0f); 199 fText->ResizeBy(dx, 0.0f); 200 201 if (Window()) { 202 fText->Invalidate(); 203 Invalidate(); 204 } 205 } 206 207 208 float 209 BTextControl::Divider() const 210 { 211 return fDivider; 212 } 213 214 215 void 216 BTextControl::Draw(BRect updateRect) 217 { 218 rgb_color noTint = ui_color(B_PANEL_BACKGROUND_COLOR); 219 rgb_color lighten1 = tint_color(noTint, B_LIGHTEN_1_TINT); 220 rgb_color lighten2 = tint_color(noTint, B_LIGHTEN_2_TINT); 221 rgb_color lightenMax = tint_color(noTint, B_LIGHTEN_MAX_TINT); 222 rgb_color darken1 = tint_color(noTint, B_DARKEN_1_TINT); 223 rgb_color darken2 = tint_color(noTint, B_DARKEN_2_TINT); 224 rgb_color darken4 = tint_color(noTint, B_DARKEN_4_TINT); 225 rgb_color navigationColor = ui_color(B_KEYBOARD_NAVIGATION_COLOR); 226 227 bool enabled = IsEnabled(); 228 bool active = false; 229 230 if (fText->IsFocus() && Window()->IsActive()) 231 active = true; 232 233 // outer bevel 234 235 BRect rect = Bounds(); 236 rect.left = fDivider; 237 238 if (enabled) 239 SetHighColor(darken1); 240 else 241 SetHighColor(noTint); 242 243 StrokeLine(rect.LeftBottom(), rect.LeftTop()); 244 StrokeLine(rect.RightTop()); 245 246 if (enabled) 247 SetHighColor(lighten2); 248 else 249 SetHighColor(lighten1); 250 251 StrokeLine(BPoint(rect.left + 1.0f, rect.bottom), rect.RightBottom()); 252 StrokeLine(BPoint(rect.right, rect.top + 1.0f), rect.RightBottom()); 253 254 // inner bevel 255 256 rect.InsetBy(1.0f, 1.0f); 257 258 if (active) { 259 SetHighColor(navigationColor); 260 StrokeRect(rect); 261 } else { 262 if (enabled) 263 SetHighColor(darken4); 264 else 265 SetHighColor(darken2); 266 267 StrokeLine(rect.LeftTop(), rect.LeftBottom()); 268 StrokeLine(rect.LeftTop(), rect.RightTop()); 269 270 SetHighColor(noTint); 271 StrokeLine(BPoint(rect.left + 1.0f, rect.bottom), rect.RightBottom()); 272 StrokeLine(BPoint(rect.right, rect.top + 1.0f)); 273 } 274 275 // label 276 277 if (Label()) { 278 font_height fontHeight; 279 GetFontHeight(&fontHeight); 280 281 float y = fontHeight.ascent + fText->Frame().top + 1; 282 float x; 283 284 float labelWidth = StringWidth(Label()); 285 switch (fLabelAlign) { 286 case B_ALIGN_RIGHT: 287 x = fDivider - labelWidth - 3.0f; 288 break; 289 290 case B_ALIGN_CENTER: 291 x = fDivider - labelWidth / 2.0f; 292 break; 293 294 default: 295 x = 3.0f; 296 break; 297 } 298 299 BRect labelArea(x, fText->Frame().top, x + labelWidth, 300 ceilf(fontHeight.ascent + fontHeight.descent) + 1); 301 if (x < fDivider && updateRect.Intersects(labelArea)) { 302 labelArea.right = fDivider; 303 304 BRegion clipRegion(labelArea); 305 ConstrainClippingRegion(&clipRegion); 306 SetHighColor(IsEnabled() ? ui_color(B_CONTROL_TEXT_COLOR) 307 : tint_color(noTint, B_DISABLED_LABEL_TINT)); 308 DrawString(Label(), BPoint(x, y)); 309 } 310 } 311 } 312 313 314 void 315 BTextControl::MouseDown(BPoint where) 316 { 317 if (!fText->IsFocus()) { 318 fText->MakeFocus(true); 319 fText->SelectAll(); 320 } 321 } 322 323 324 void 325 BTextControl::AttachedToWindow() 326 { 327 BControl::AttachedToWindow(); 328 329 _UpdateTextViewColors(IsEnabled()); 330 fText->MakeEditable(IsEnabled()); 331 } 332 333 334 void 335 BTextControl::MakeFocus(bool state) 336 { 337 if (state != fText->IsFocus()) { 338 fText->MakeFocus(state); 339 340 if (state) 341 fText->SelectAll(); 342 } 343 } 344 345 346 void 347 BTextControl::SetEnabled(bool enabled) 348 { 349 if (IsEnabled() == enabled) 350 return; 351 352 if (Window()) { 353 fText->MakeEditable(enabled); 354 355 _UpdateTextViewColors(enabled); 356 357 fText->Invalidate(); 358 Window()->UpdateIfNeeded(); 359 } 360 361 BControl::SetEnabled(enabled); 362 } 363 364 365 void 366 BTextControl::GetPreferredSize(float *_width, float *_height) 367 { 368 if (_height) { 369 // we need enough space for the label and the child text view 370 font_height fontHeight; 371 GetFontHeight(&fontHeight); 372 float labelHeight = ceil(fontHeight.ascent + fontHeight.descent 373 + fontHeight.leading); 374 float textHeight = fText->LineHeight(0) + 4.0; 375 376 *_height = max_c(labelHeight, textHeight); 377 } 378 379 if (_width) { 380 // TODO: this one I need to find out 381 float width = 20.0f + ceilf(StringWidth(Label())); 382 if (width < Bounds().Width()) 383 width = Bounds().Width(); 384 *_width = width; 385 } 386 } 387 388 389 void 390 BTextControl::ResizeToPreferred() 391 { 392 // TODO: change divider? 393 BView::ResizeToPreferred(); 394 } 395 396 397 void 398 BTextControl::SetFlags(uint32 flags) 399 { 400 if (!fSkipSetFlags) { 401 // If the textview is navigable, set it to not navigable if needed 402 // Else if it is not navigable, set it to navigable if needed 403 if (fText->Flags() & B_NAVIGABLE) { 404 if (!(flags & B_NAVIGABLE)) 405 fText->SetFlags(fText->Flags() & ~B_NAVIGABLE); 406 407 } else { 408 if (flags & B_NAVIGABLE) 409 fText->SetFlags(fText->Flags() | B_NAVIGABLE); 410 } 411 412 // Don't make this one navigable 413 flags &= ~B_NAVIGABLE; 414 } 415 416 BView::SetFlags(flags); 417 } 418 419 420 void 421 BTextControl::MessageReceived(BMessage *msg) 422 { 423 switch(msg->what) { 424 case B_SET_PROPERTY: 425 case B_GET_PROPERTY: 426 // TODO 427 break; 428 default: 429 BControl::MessageReceived(msg); 430 break; 431 } 432 } 433 434 435 BHandler * 436 BTextControl::ResolveSpecifier(BMessage *msg, int32 index, 437 BMessage *specifier, int32 form, 438 const char *property) 439 { 440 /* 441 BPropertyInfo propInfo(prop_list); 442 BHandler *target = NULL; 443 444 if (propInfo.FindMatch(message, 0, specifier, what, property) < B_OK) 445 return BControl::ResolveSpecifier(message, index, specifier, what, 446 property); 447 else 448 return this; 449 */ 450 return BControl::ResolveSpecifier(msg, index, specifier, form, property); 451 } 452 453 454 status_t 455 BTextControl::GetSupportedSuites(BMessage *data) 456 { 457 return BControl::GetSupportedSuites(data); 458 } 459 460 461 void 462 BTextControl::MouseUp(BPoint pt) 463 { 464 BControl::MouseUp(pt); 465 } 466 467 468 void 469 BTextControl::MouseMoved(BPoint pt, uint32 code, const BMessage *msg) 470 { 471 BControl::MouseMoved(pt, code, msg); 472 } 473 474 475 void 476 BTextControl::DetachedFromWindow() 477 { 478 BControl::DetachedFromWindow(); 479 } 480 481 482 void 483 BTextControl::AllAttached() 484 { 485 BControl::AllAttached(); 486 } 487 488 489 void 490 BTextControl::AllDetached() 491 { 492 BControl::AllDetached(); 493 } 494 495 496 void 497 BTextControl::FrameMoved(BPoint newPosition) 498 { 499 BControl::FrameMoved(newPosition); 500 } 501 502 503 void 504 BTextControl::FrameResized(float width, float height) 505 { 506 BControl::FrameResized(width, height); 507 508 // changes in width 509 510 BRect bounds = Bounds(); 511 const float border = 2.0f; 512 513 if (bounds.Width() > fPreviousWidth) { 514 // invalidate the region between the old and the new right border 515 BRect rect = bounds; 516 rect.left += fPreviousWidth - border; 517 rect.right--; 518 Invalidate(rect); 519 } else if (bounds.Width() < fPreviousWidth) { 520 // invalidate the region of the new right border 521 BRect rect = bounds; 522 rect.left = rect.right - border; 523 Invalidate(rect); 524 } 525 526 // changes in height 527 528 if (bounds.Height() > fPreviousHeight) { 529 // invalidate the region between the old and the new bottom border 530 BRect rect = bounds; 531 rect.top += fPreviousHeight - border; 532 rect.bottom--; 533 Invalidate(rect); 534 } else if (bounds.Height() < fPreviousHeight) { 535 // invalidate the region of the new bottom border 536 BRect rect = bounds; 537 rect.top = rect.bottom - border; 538 Invalidate(rect); 539 } 540 541 fPreviousWidth = uint16(bounds.Width()); 542 fPreviousHeight = uint16(bounds.Height()); 543 } 544 545 546 void 547 BTextControl::WindowActivated(bool active) 548 { 549 if (fText->IsFocus()) { 550 // invalidate to remove/show focus indication 551 BRect rect = Bounds(); 552 rect.left = fDivider; 553 Invalidate(rect); 554 555 // help out embedded text view which doesn't 556 // get notified of this 557 fText->Invalidate(); 558 } 559 } 560 561 562 status_t 563 BTextControl::Perform(perform_code d, void *arg) 564 { 565 return BControl::Perform(d, arg); 566 } 567 568 569 void BTextControl::_ReservedTextControl1() {} 570 void BTextControl::_ReservedTextControl2() {} 571 void BTextControl::_ReservedTextControl3() {} 572 void BTextControl::_ReservedTextControl4() {} 573 574 575 BTextControl & 576 BTextControl::operator=(const BTextControl&) 577 { 578 return *this; 579 } 580 581 582 void 583 BTextControl::_UpdateTextViewColors(bool enabled) 584 { 585 rgb_color textColor; 586 rgb_color color; 587 BFont font; 588 589 fText->GetFontAndColor(0, &font); 590 591 if (enabled) 592 textColor = ui_color(B_DOCUMENT_TEXT_COLOR); 593 else { 594 textColor = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 595 B_DISABLED_LABEL_TINT); 596 } 597 598 fText->SetFontAndColor(&font, B_FONT_ALL, &textColor); 599 600 if (enabled) { 601 color = ui_color(B_DOCUMENT_BACKGROUND_COLOR); 602 } else { 603 color = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 604 B_LIGHTEN_2_TINT); 605 } 606 607 fText->SetViewColor(color); 608 fText->SetLowColor(color); 609 } 610 611 612 void 613 BTextControl::_CommitValue() 614 { 615 } 616 617 618 void 619 BTextControl::_InitData(const char* label, const char* initialText, 620 BMessage* archive) 621 { 622 BRect bounds(Bounds()); 623 624 fText = NULL; 625 fModificationMessage = NULL; 626 fLabelAlign = B_ALIGN_LEFT; 627 fDivider = 0.0f; 628 fPreviousWidth = bounds.Width(); 629 fPreviousHeight = bounds.Height(); 630 fSkipSetFlags = false; 631 632 int32 flags = 0; 633 634 BFont font(be_plain_font); 635 636 if (!archive || !archive->HasString("_fname")) 637 flags |= B_FONT_FAMILY_AND_STYLE; 638 639 if (!archive || !archive->HasFloat("_fflt")) 640 flags |= B_FONT_SIZE; 641 642 if (flags != 0) 643 SetFont(&font, flags); 644 645 if (label) 646 fDivider = floorf(bounds.Width() / 2.0f); 647 648 uint32 navigableFlags = Flags() & B_NAVIGABLE; 649 if (navigableFlags != 0) { 650 fSkipSetFlags = true; 651 SetFlags(Flags() & ~B_NAVIGABLE); 652 fSkipSetFlags = false; 653 } 654 655 if (archive) 656 fText = static_cast<_BTextInput_ *>(FindView("_input_")); 657 else { 658 BRect frame(fDivider, bounds.top, 659 bounds.right, bounds.bottom); 660 // we are stroking the frame around the text view, which 661 // is 2 pixels wide 662 frame.InsetBy(2.0, 3.0); 663 BRect textRect(frame.OffsetToCopy(B_ORIGIN)); 664 665 fText = new _BTextInput_(frame, textRect, 666 B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP, 667 B_WILL_DRAW | B_FRAME_EVENTS | navigableFlags); 668 AddChild(fText); 669 670 SetText(initialText); 671 fText->SetAlignment(B_ALIGN_LEFT); 672 fText->AlignTextRect(); 673 } 674 } 675