1/* 2 * Copyright 2004-2011, Haiku, Inc. All Rights Reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * McCall <mccall@@digitalparadise.co.uk> 7 * Mike Berg <mike@berg-net.us> 8 * Julun <host.haiku@gmx.de> 9 * Clemens <mail@Clemens-Zeidler.de> 10 * Adrien Destugues <pulkomandy@pulkomandy.cx> 11 * Hamish Morrison <hamish@lavabit.com> 12 */ 13 14 15#include "DateTimeEdit.h" 16 17#include <stdlib.h> 18 19#include <ControlLook.h> 20#include <DateFormat.h> 21#include <LayoutUtils.h> 22#include <List.h> 23#include <Locale.h> 24#include <String.h> 25#include <Window.h> 26 27 28namespace BPrivate { 29 30 31const uint32 kArrowAreaWidth = 16; 32 33 34TimeEdit::TimeEdit(const char* name, uint32 sections, BMessage* message) 35 : 36 SectionEdit(name, sections, message), 37 fLastKeyDownTime(0), 38 fFields(NULL), 39 fFieldCount(0), 40 fFieldPositions(NULL), 41 fFieldPosCount(0) 42{ 43 InitView(); 44} 45 46 47TimeEdit::~TimeEdit() 48{ 49 free(fFieldPositions); 50 free(fFields); 51} 52 53 54void 55TimeEdit::KeyDown(const char* bytes, int32 numBytes) 56{ 57 if (IsEnabled() == false) 58 return; 59 SectionEdit::KeyDown(bytes, numBytes); 60 61 // only accept valid input 62 int32 number = atoi(bytes); 63 if (number < 0 || bytes[0] < '0') 64 return; 65 66 int32 section = FocusIndex(); 67 if (section < 0 || section > 2) 68 return; 69 70 bigtime_t currentTime = system_time(); 71 if (currentTime - fLastKeyDownTime < 1000000) { 72 int32 doubleDigit = number + fLastKeyDownInt * 10; 73 if (_IsValidDoubleDigit(doubleDigit)) 74 number = doubleDigit; 75 fLastKeyDownTime = 0; 76 } else { 77 fLastKeyDownTime = currentTime; 78 fLastKeyDownInt = number; 79 } 80 81 // update display value 82 fHoldValue = number; 83 _CheckRange(); 84 _UpdateFields(); 85 86 // send message to change time 87 Invoke(); 88} 89 90 91void 92TimeEdit::InitView() 93{ 94 // make sure we call the base class method, as it 95 // will create the arrow bitmaps and the section list 96 fTime = BDateTime::CurrentDateTime(B_LOCAL_TIME); 97 _UpdateFields(); 98} 99 100 101void 102TimeEdit::DrawSection(uint32 index, BRect bounds, bool hasFocus) 103{ 104 if (fFieldPositions == NULL || index * 2 + 1 >= (uint32)fFieldPosCount) 105 return; 106 107 if (hasFocus) 108 SetLowColor(mix_color(ui_color(B_CONTROL_HIGHLIGHT_COLOR), 109 ViewColor(), 192)); 110 else 111 SetLowColor(ViewColor()); 112 113 BString field; 114 fText.CopyCharsInto(field, fFieldPositions[index * 2], 115 fFieldPositions[index * 2 + 1] - fFieldPositions[index * 2]); 116 117 BPoint point(bounds.LeftBottom()); 118 point.y -= bounds.Height() / 2.0 - 6.0; 119 point.x += (bounds.Width() - StringWidth(field)) / 2; 120 SetHighUIColor(B_PANEL_TEXT_COLOR); 121 FillRect(bounds, B_SOLID_LOW); 122 DrawString(field, point); 123} 124 125 126void 127TimeEdit::DrawSeparator(uint32 index, BRect bounds) 128{ 129 if (fFieldPositions == NULL || index * 2 + 2 >= (uint32)fFieldPosCount) 130 return; 131 132 BString field; 133 fText.CopyCharsInto(field, fFieldPositions[index * 2 + 1], 134 fFieldPositions[index * 2 + 2] - fFieldPositions[index * 2 + 1]); 135 136 BPoint point(bounds.LeftBottom()); 137 point.y -= bounds.Height() / 2.0 - 6.0; 138 point.x += (bounds.Width() - StringWidth(field)) / 2; 139 SetHighUIColor(B_PANEL_TEXT_COLOR); 140 DrawString(field, point); 141} 142 143 144float 145TimeEdit::SeparatorWidth() 146{ 147 return 10.0f; 148} 149 150 151float 152TimeEdit::MinSectionWidth() 153{ 154 return be_plain_font->StringWidth("00"); 155} 156 157 158void 159TimeEdit::SectionFocus(uint32 index) 160{ 161 fLastKeyDownTime = 0; 162 fFocus = index; 163 fHoldValue = _SectionValue(index); 164 Draw(Bounds()); 165} 166 167 168void 169TimeEdit::SetTime(int32 hour, int32 minute, int32 second) 170{ 171 // make sure to update date upon overflow 172 if (hour == 0 && minute == 0 && second == 0) 173 fTime = BDateTime::CurrentDateTime(B_LOCAL_TIME); 174 175 fTime.SetTime(BTime(hour, minute, second)); 176 177 if (LockLooper()) { 178 _UpdateFields(); 179 UnlockLooper(); 180 } 181 182 Invalidate(Bounds()); 183} 184 185 186BTime 187TimeEdit::GetTime() 188{ 189 return fTime.Time(); 190} 191 192 193void 194TimeEdit::DoUpPress() 195{ 196 if (fFocus == -1) 197 SectionFocus(0); 198 199 // update displayed value 200 fHoldValue += 1; 201 202 _CheckRange(); 203 _UpdateFields(); 204 205 // send message to change time 206 Invoke(); 207} 208 209 210void 211TimeEdit::DoDownPress() 212{ 213 if (fFocus == -1) 214 SectionFocus(0); 215 216 // update display value 217 fHoldValue -= 1; 218 219 _CheckRange(); 220 _UpdateFields(); 221 222 Invoke(); 223} 224 225 226void 227TimeEdit::PopulateMessage(BMessage* message) 228{ 229 if (fFocus < 0 || fFocus >= fFieldCount) 230 return; 231 232 message->AddBool("time", true); 233 message->AddInt32("hour", fTime.Time().Hour()); 234 message->AddInt32("minute", fTime.Time().Minute()); 235 message->AddInt32("second", fTime.Time().Second()); 236} 237 238 239void 240TimeEdit::_UpdateFields() 241{ 242 time_t time = fTime.Time_t(); 243 244 if (fFieldPositions != NULL) { 245 free(fFieldPositions); 246 fFieldPositions = NULL; 247 } 248 fTimeFormat.Format(fText, fFieldPositions, fFieldPosCount, time, 249 B_MEDIUM_TIME_FORMAT); 250 251 if (fFields != NULL) { 252 free(fFields); 253 fFields = NULL; 254 } 255 fTimeFormat.GetTimeFields(fFields, fFieldCount, B_MEDIUM_TIME_FORMAT); 256} 257 258 259void 260TimeEdit::_CheckRange() 261{ 262 if (fFocus < 0 || fFocus >= fFieldCount) 263 return; 264 265 int32 value = fHoldValue; 266 switch (fFields[fFocus]) { 267 case B_DATE_ELEMENT_HOUR: 268 if (value > 23) 269 value = 0; 270 else if (value < 0) 271 value = 23; 272 273 fTime.SetTime(BTime(value, fTime.Time().Minute(), 274 fTime.Time().Second())); 275 break; 276 277 case B_DATE_ELEMENT_MINUTE: 278 if (value> 59) 279 value = 0; 280 else if (value < 0) 281 value = 59; 282 283 fTime.SetTime(BTime(fTime.Time().Hour(), value, 284 fTime.Time().Second())); 285 break; 286 287 case B_DATE_ELEMENT_SECOND: 288 if (value > 59) 289 value = 0; 290 else if (value < 0) 291 value = 59; 292 293 fTime.SetTime(BTime(fTime.Time().Hour(), fTime.Time().Minute(), 294 value)); 295 break; 296 297 case B_DATE_ELEMENT_AM_PM: 298 value = fTime.Time().Hour(); 299 if (value < 13) 300 value += 12; 301 else 302 value -= 12; 303 if (value == 24) 304 value = 0; 305 306 // modify hour value to reflect change in am/ pm 307 fTime.SetTime(BTime(value, fTime.Time().Minute(), 308 fTime.Time().Second())); 309 break; 310 311 default: 312 return; 313 } 314 315 316 fHoldValue = value; 317 Invalidate(Bounds()); 318} 319 320 321bool 322TimeEdit::_IsValidDoubleDigit(int32 value) 323{ 324 if (fFocus < 0 || fFocus >= fFieldCount) 325 return false; 326 327 bool isInRange = false; 328 switch (fFields[fFocus]) { 329 case B_DATE_ELEMENT_HOUR: 330 if (value <= 23) 331 isInRange = true; 332 break; 333 334 case B_DATE_ELEMENT_MINUTE: 335 if (value <= 59) 336 isInRange = true; 337 break; 338 339 case B_DATE_ELEMENT_SECOND: 340 if (value <= 59) 341 isInRange = true; 342 break; 343 344 default: 345 break; 346 } 347 348 return isInRange; 349} 350 351 352int32 353TimeEdit::_SectionValue(int32 index) const 354{ 355 if (index < 0 || index >= fFieldCount) 356 return 0; 357 358 int32 value; 359 switch (fFields[index]) { 360 case B_DATE_ELEMENT_HOUR: 361 value = fTime.Time().Hour(); 362 break; 363 364 case B_DATE_ELEMENT_MINUTE: 365 value = fTime.Time().Minute(); 366 break; 367 368 case B_DATE_ELEMENT_SECOND: 369 value = fTime.Time().Second(); 370 break; 371 372 default: 373 value = 0; 374 break; 375 } 376 377 return value; 378} 379 380 381float 382TimeEdit::PreferredHeight() 383{ 384 font_height fontHeight; 385 GetFontHeight(&fontHeight); 386 return ceilf((fontHeight.ascent + fontHeight.descent) * 1.4); 387} 388 389 390// #pragma mark - 391 392 393DateEdit::DateEdit(const char* name, uint32 sections, BMessage* message) 394 : 395 SectionEdit(name, sections, message), 396 fFields(NULL), 397 fFieldCount(0), 398 fFieldPositions(NULL), 399 fFieldPosCount(0) 400{ 401 InitView(); 402} 403 404 405DateEdit::~DateEdit() 406{ 407 free(fFieldPositions); 408 free(fFields); 409} 410 411 412void 413DateEdit::KeyDown(const char* bytes, int32 numBytes) 414{ 415 if (IsEnabled() == false) 416 return; 417 SectionEdit::KeyDown(bytes, numBytes); 418 419 // only accept valid input 420 int32 number = atoi(bytes); 421 if (number < 0 || bytes[0] < '0') 422 return; 423 424 int32 section = FocusIndex(); 425 if (section < 0 || section > 2) 426 return; 427 428 bigtime_t currentTime = system_time(); 429 if (currentTime - fLastKeyDownTime < 1000000) { 430 int32 doubleDigit = number + fLastKeyDownInt * 10; 431 if (_IsValidDoubleDigit(doubleDigit)) 432 number = doubleDigit; 433 fLastKeyDownTime = 0; 434 } else { 435 fLastKeyDownTime = currentTime; 436 fLastKeyDownInt = number; 437 } 438 439 // if year add 2000 440 441 if (fFields[section] == B_DATE_ELEMENT_YEAR) { 442 int32 oldCentury = int32(fHoldValue / 100) * 100; 443 if (number < 10 && oldCentury == 1900) 444 number += 70; 445 number += oldCentury; 446 } 447 fHoldValue = number; 448 449 // update display value 450 _CheckRange(); 451 _UpdateFields(); 452 453 // send message to change time 454 Invoke(); 455} 456 457 458void 459DateEdit::InitView() 460{ 461 // make sure we call the base class method, as it 462 // will create the arrow bitmaps and the section list 463 fDate = BDate::CurrentDate(B_LOCAL_TIME); 464 _UpdateFields(); 465} 466 467 468void 469DateEdit::DrawSection(uint32 index, BRect bounds, bool hasFocus) 470{ 471 if (fFieldPositions == NULL || index * 2 + 1 >= (uint32)fFieldPosCount) 472 return; 473 474 if (hasFocus) 475 SetLowColor(mix_color(ui_color(B_CONTROL_HIGHLIGHT_COLOR), 476 ViewColor(), 192)); 477 else 478 SetLowColor(ViewColor()); 479 480 BString field; 481 fText.CopyCharsInto(field, fFieldPositions[index * 2], 482 fFieldPositions[index * 2 + 1] - fFieldPositions[index * 2]); 483 484 BPoint point(bounds.LeftBottom()); 485 point.y -= bounds.Height() / 2.0 - 6.0; 486 point.x += (bounds.Width() - StringWidth(field)) / 2; 487 SetHighUIColor(B_PANEL_TEXT_COLOR); 488 FillRect(bounds, B_SOLID_LOW); 489 DrawString(field, point); 490} 491 492 493void 494DateEdit::DrawSeparator(uint32 index, BRect bounds) 495{ 496 if (index >= 2) 497 return; 498 499 if (fFieldPositions == NULL || index * 2 + 2 >= (uint32)fFieldPosCount) 500 return; 501 502 BString field; 503 fText.CopyCharsInto(field, fFieldPositions[index * 2 + 1], 504 fFieldPositions[index * 2 + 2] - fFieldPositions[index * 2 + 1]); 505 506 BPoint point(bounds.LeftBottom()); 507 point.y -= bounds.Height() / 2.0 - 6.0; 508 point.x += (bounds.Width() - StringWidth(field)) / 2; 509 SetHighUIColor(B_PANEL_TEXT_COLOR); 510 DrawString(field, point); 511} 512 513 514void 515DateEdit::SectionFocus(uint32 index) 516{ 517 fLastKeyDownTime = 0; 518 fFocus = index; 519 fHoldValue = _SectionValue(index); 520 Draw(Bounds()); 521} 522 523 524float 525DateEdit::MinSectionWidth() 526{ 527 return be_plain_font->StringWidth("00"); 528} 529 530 531float 532DateEdit::SeparatorWidth() 533{ 534 return 10.0f; 535} 536 537 538void 539DateEdit::SetDate(int32 year, int32 month, int32 day) 540{ 541 fDate.SetDate(year, month, day); 542 543 if (LockLooper()) { 544 _UpdateFields(); 545 UnlockLooper(); 546 } 547 548 Invalidate(Bounds()); 549} 550 551 552BDate 553DateEdit::GetDate() 554{ 555 return fDate; 556} 557 558 559void 560DateEdit::DoUpPress() 561{ 562 if (fFocus == -1) 563 SectionFocus(0); 564 565 // update displayed value 566 fHoldValue += 1; 567 568 _CheckRange(); 569 _UpdateFields(); 570 571 // send message to change Date 572 Invoke(); 573} 574 575 576void 577DateEdit::DoDownPress() 578{ 579 if (fFocus == -1) 580 SectionFocus(0); 581 582 // update display value 583 fHoldValue -= 1; 584 585 _CheckRange(); 586 _UpdateFields(); 587 588 // send message to change Date 589 Invoke(); 590} 591 592 593void 594DateEdit::PopulateMessage(BMessage* message) 595{ 596 if (fFocus < 0 || fFocus >= fFieldCount) 597 return; 598 599 message->AddBool("time", false); 600 message->AddInt32("year", fDate.Year()); 601 message->AddInt32("month", fDate.Month()); 602 message->AddInt32("day", fDate.Day()); 603} 604 605 606void 607DateEdit::_UpdateFields() 608{ 609 time_t time = BDateTime(fDate, BTime()).Time_t(); 610 611 if (fFieldPositions != NULL) { 612 free(fFieldPositions); 613 fFieldPositions = NULL; 614 } 615 616 fDateFormat.Format(fText, fFieldPositions, fFieldPosCount, time, 617 B_SHORT_DATE_FORMAT); 618 619 if (fFields != NULL) { 620 free(fFields); 621 fFields = NULL; 622 } 623 fDateFormat.GetFields(fFields, fFieldCount, B_SHORT_DATE_FORMAT); 624} 625 626 627void 628DateEdit::_CheckRange() 629{ 630 if (fFocus < 0 || fFocus >= fFieldCount) 631 return; 632 633 int32 value = fHoldValue; 634 switch (fFields[fFocus]) { 635 case B_DATE_ELEMENT_DAY: 636 { 637 int32 days = fDate.DaysInMonth(); 638 if (value > days) 639 value = 1; 640 else if (value < 1) 641 value = days; 642 643 fDate.SetDate(fDate.Year(), fDate.Month(), value); 644 break; 645 } 646 647 case B_DATE_ELEMENT_MONTH: 648 { 649 if (value > 12) 650 value = 1; 651 else if (value < 1) 652 value = 12; 653 654 int32 day = fDate.Day(); 655 fDate.SetDate(fDate.Year(), value, 1); 656 657 // changing between months with differing amounts of days 658 while (day > fDate.DaysInMonth()) 659 day--; 660 fDate.SetDate(fDate.Year(), value, day); 661 break; 662 } 663 664 case B_DATE_ELEMENT_YEAR: 665 fDate.SetDate(value, fDate.Month(), fDate.Day()); 666 break; 667 668 default: 669 return; 670 } 671 672 fHoldValue = value; 673 Invalidate(Bounds()); 674} 675 676 677bool 678DateEdit::_IsValidDoubleDigit(int32 value) 679{ 680 if (fFocus < 0 || fFocus >= fFieldCount) 681 return false; 682 683 bool isInRange = false; 684 switch (fFields[fFocus]) { 685 case B_DATE_ELEMENT_DAY: 686 { 687 int32 days = fDate.DaysInMonth(); 688 if (value >= 1 && value <= days) 689 isInRange = true; 690 break; 691 } 692 693 case B_DATE_ELEMENT_MONTH: 694 { 695 if (value >= 1 && value <= 12) 696 isInRange = true; 697 break; 698 } 699 700 case B_DATE_ELEMENT_YEAR: 701 { 702 int32 year = int32(fHoldValue / 100) * 100 + value; 703 if (year >= 2000) 704 isInRange = true; 705 break; 706 } 707 708 default: 709 break; 710 } 711 712 return isInRange; 713} 714 715 716int32 717DateEdit::_SectionValue(int32 index) const 718{ 719 if (index < 0 || index >= fFieldCount) 720 return 0; 721 722 int32 value = 0; 723 switch (fFields[index]) { 724 case B_DATE_ELEMENT_YEAR: 725 value = fDate.Year(); 726 break; 727 728 case B_DATE_ELEMENT_MONTH: 729 value = fDate.Month(); 730 break; 731 732 case B_DATE_ELEMENT_DAY: 733 value = fDate.Day(); 734 break; 735 736 default: 737 break; 738 } 739 740 return value; 741} 742 743 744float 745DateEdit::PreferredHeight() 746{ 747 font_height fontHeight; 748 GetFontHeight(&fontHeight); 749 return ceilf((fontHeight.ascent + fontHeight.descent) * 1.4); 750} 751 752 753// #pragma mark - 754 755 756SectionEdit::SectionEdit(const char* name, uint32 sections, BMessage* message) 757 : 758 BControl(name, NULL, message, B_WILL_DRAW | B_NAVIGABLE), 759 fFocus(-1), 760 fSectionCount(sections), 761 fHoldValue(0) 762{ 763} 764 765 766SectionEdit::~SectionEdit() 767{ 768} 769 770 771void 772SectionEdit::AttachedToWindow() 773{ 774 AdoptParentColors(); 775 BControl::AttachedToWindow(); 776} 777 778 779void 780SectionEdit::Draw(BRect updateRect) 781{ 782 DrawBorder(updateRect); 783 784 for (uint32 idx = 0; idx < fSectionCount; idx++) { 785 DrawSection(idx, FrameForSection(idx), 786 ((uint32)fFocus == idx) && IsFocus()); 787 if (idx < fSectionCount - 1) 788 DrawSeparator(idx, FrameForSeparator(idx)); 789 } 790} 791 792 793void 794SectionEdit::MouseDown(BPoint where) 795{ 796 if (IsEnabled() == false) 797 return; 798 799 MakeFocus(true); 800 801 if (fUpRect.Contains(where)) 802 DoUpPress(); 803 else if (fDownRect.Contains(where)) 804 DoDownPress(); 805 else if (fSectionCount > 0) { 806 for (uint32 idx = 0; idx < fSectionCount; idx++) { 807 if (FrameForSection(idx).Contains(where)) { 808 SectionFocus(idx); 809 return; 810 } 811 } 812 } 813} 814 815 816BSize 817SectionEdit::MaxSize() 818{ 819 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), 820 BSize(B_SIZE_UNLIMITED, PreferredHeight())); 821} 822 823 824BSize 825SectionEdit::MinSize() 826{ 827 BSize minSize; 828 minSize.height = PreferredHeight(); 829 minSize.width = (SeparatorWidth() + MinSectionWidth()) 830 * fSectionCount; 831 return BLayoutUtils::ComposeSize(ExplicitMinSize(), 832 minSize); 833} 834 835 836BSize 837SectionEdit::PreferredSize() 838{ 839 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), 840 MinSize()); 841} 842 843 844BRect 845SectionEdit::FrameForSection(uint32 index) 846{ 847 BRect area = SectionArea(); 848 float sepWidth = SeparatorWidth(); 849 850 float width = (area.Width() - 851 sepWidth * (fSectionCount - 1)) 852 / fSectionCount; 853 area.left += index * (width + sepWidth); 854 area.right = area.left + width; 855 856 return area; 857} 858 859 860BRect 861SectionEdit::FrameForSeparator(uint32 index) 862{ 863 BRect area = SectionArea(); 864 float sepWidth = SeparatorWidth(); 865 866 float width = (area.Width() - 867 sepWidth * (fSectionCount - 1)) 868 / fSectionCount; 869 area.left += (index + 1) * width + index * sepWidth; 870 area.right = area.left + sepWidth; 871 872 return area; 873} 874 875 876void 877SectionEdit::MakeFocus(bool focused) 878{ 879 if (focused == IsFocus()) 880 return; 881 882 BControl::MakeFocus(focused); 883 884 if (fFocus == -1) 885 SectionFocus(0); 886 else 887 SectionFocus(fFocus); 888} 889 890 891void 892SectionEdit::KeyDown(const char* bytes, int32 numbytes) 893{ 894 if (IsEnabled() == false) 895 return; 896 if (fFocus == -1) 897 SectionFocus(0); 898 899 switch (bytes[0]) { 900 case B_LEFT_ARROW: 901 fFocus -= 1; 902 if (fFocus < 0) 903 fFocus = fSectionCount - 1; 904 SectionFocus(fFocus); 905 break; 906 907 case B_RIGHT_ARROW: 908 fFocus += 1; 909 if ((uint32)fFocus >= fSectionCount) 910 fFocus = 0; 911 SectionFocus(fFocus); 912 break; 913 914 case B_UP_ARROW: 915 DoUpPress(); 916 break; 917 918 case B_DOWN_ARROW: 919 DoDownPress(); 920 break; 921 922 default: 923 BControl::KeyDown(bytes, numbytes); 924 break; 925 } 926 Draw(Bounds()); 927} 928 929 930status_t 931SectionEdit::Invoke(BMessage* message) 932{ 933 if (message == NULL) 934 message = Message(); 935 if (message == NULL) 936 return BControl::Invoke(NULL); 937 938 BMessage clone(*message); 939 PopulateMessage(&clone); 940 return BControl::Invoke(&clone); 941} 942 943 944uint32 945SectionEdit::CountSections() const 946{ 947 return fSectionCount; 948} 949 950 951int32 952SectionEdit::FocusIndex() const 953{ 954 return fFocus; 955} 956 957 958BRect 959SectionEdit::SectionArea() const 960{ 961 BRect sectionArea = Bounds().InsetByCopy(2, 2); 962 sectionArea.right -= kArrowAreaWidth; 963 return sectionArea; 964} 965 966 967void 968SectionEdit::DrawBorder(const BRect& updateRect) 969{ 970 BRect bounds(Bounds()); 971 bool showFocus = (IsFocus() && Window() && Window()->IsActive()); 972 973 be_control_look->DrawBorder(this, bounds, updateRect, ViewColor(), 974 B_FANCY_BORDER, showFocus ? BControlLook::B_FOCUSED : 0); 975 976 // draw up/down control 977 978 bounds.left = bounds.right - kArrowAreaWidth; 979 bounds.right = Bounds().right - 2; 980 fUpRect.Set(bounds.left + 3, bounds.top + 2, bounds.right, 981 bounds.bottom / 2.0); 982 fDownRect = fUpRect.OffsetByCopy(0, fUpRect.Height() + 2); 983 984 BPoint middle(floorf(fUpRect.left + fUpRect.Width() / 2), 985 fUpRect.top + 1); 986 BPoint left(fUpRect.left + 3, fUpRect.bottom - 1); 987 BPoint right(left.x + 2 * (middle.x - left.x), fUpRect.bottom - 1); 988 989 SetPenSize(2); 990 SetLowColor(ViewColor()); 991 992 if (updateRect.Intersects(fUpRect)) { 993 FillRect(fUpRect, B_SOLID_LOW); 994 BeginLineArray(2); 995 AddLine(left, middle, HighColor()); 996 AddLine(middle, right, HighColor()); 997 EndLineArray(); 998 } 999 if (updateRect.Intersects(fDownRect)) { 1000 middle.y = fDownRect.bottom - 1; 1001 left.y = right.y = fDownRect.top + 1; 1002 1003 FillRect(fDownRect, B_SOLID_LOW); 1004 BeginLineArray(2); 1005 AddLine(left, middle, HighColor()); 1006 AddLine(middle, right, HighColor()); 1007 EndLineArray(); 1008 } 1009 1010 SetPenSize(1); 1011} 1012 1013 1014} // namespace BPrivate 1015