1 /* 2 * Copyright 2007-2011, Haiku, Inc. All Rights Reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Julun <host.haiku@gmx.de> 7 */ 8 9 10 #include "CalendarView.h" 11 12 #include <stdlib.h> 13 14 #include <DateFormat.h> 15 #include <LayoutUtils.h> 16 #include <Window.h> 17 18 19 namespace BPrivate { 20 21 22 static float 23 FontHeight(const BView* view) 24 { 25 if (!view) 26 return 0.0; 27 28 BFont font; 29 view->GetFont(&font); 30 font_height fheight; 31 font.GetHeight(&fheight); 32 return ceilf(fheight.ascent + fheight.descent + fheight.leading); 33 } 34 35 36 // #pragma mark - 37 38 39 BCalendarView::BCalendarView(BRect frame, const char* name, uint32 resizeMask, 40 uint32 flags) 41 : 42 BView(frame, name, resizeMask, flags), 43 BInvoker(), 44 fSelectionMessage(NULL), 45 fDate(), 46 fCurrentDate(BDate::CurrentDate(B_LOCAL_TIME)), 47 fFocusChanged(false), 48 fSelectionChanged(false), 49 fCurrentDayChanged(false), 50 fStartOfWeek((int32)B_WEEKDAY_MONDAY), 51 fDayNameHeaderVisible(true), 52 fWeekNumberHeaderVisible(true) 53 { 54 _InitObject(); 55 } 56 57 58 BCalendarView::BCalendarView(const char* name, uint32 flags) 59 : 60 BView(name, flags), 61 BInvoker(), 62 fSelectionMessage(NULL), 63 fDate(), 64 fCurrentDate(BDate::CurrentDate(B_LOCAL_TIME)), 65 fFocusChanged(false), 66 fSelectionChanged(false), 67 fCurrentDayChanged(false), 68 fStartOfWeek((int32)B_WEEKDAY_MONDAY), 69 fDayNameHeaderVisible(true), 70 fWeekNumberHeaderVisible(true) 71 { 72 _InitObject(); 73 } 74 75 76 BCalendarView::~BCalendarView() 77 { 78 SetSelectionMessage(NULL); 79 } 80 81 82 BCalendarView::BCalendarView(BMessage* archive) 83 : 84 BView(archive), 85 BInvoker(), 86 fSelectionMessage(NULL), 87 fDate(archive), 88 fCurrentDate(BDate::CurrentDate(B_LOCAL_TIME)), 89 fFocusChanged(false), 90 fSelectionChanged(false), 91 fCurrentDayChanged(false), 92 fStartOfWeek((int32)B_WEEKDAY_MONDAY), 93 fDayNameHeaderVisible(true), 94 fWeekNumberHeaderVisible(true) 95 { 96 if (archive->HasMessage("_invokeMsg")) { 97 BMessage* invokationMessage = new BMessage; 98 archive->FindMessage("_invokeMsg", invokationMessage); 99 SetInvocationMessage(invokationMessage); 100 } 101 102 if (archive->HasMessage("_selectMsg")) { 103 BMessage* selectionMessage = new BMessage; 104 archive->FindMessage("selectMsg", selectionMessage); 105 SetSelectionMessage(selectionMessage); 106 } 107 108 if (archive->FindInt32("_weekStart", &fStartOfWeek) != B_OK) 109 fStartOfWeek = (int32)B_WEEKDAY_MONDAY; 110 111 if (archive->FindBool("_dayHeader", &fDayNameHeaderVisible) != B_OK) 112 fDayNameHeaderVisible = true; 113 114 if (archive->FindBool("_weekHeader", &fWeekNumberHeaderVisible) != B_OK) 115 fWeekNumberHeaderVisible = true; 116 117 _SetupDayNames(); 118 _SetupDayNumbers(); 119 _SetupWeekNumbers(); 120 } 121 122 123 BArchivable* 124 BCalendarView::Instantiate(BMessage* archive) 125 { 126 if (validate_instantiation(archive, "BCalendarView")) 127 return new BCalendarView(archive); 128 129 return NULL; 130 } 131 132 133 status_t 134 BCalendarView::Archive(BMessage* archive, bool deep) const 135 { 136 status_t status = BView::Archive(archive, deep); 137 138 if (status == B_OK && InvocationMessage()) 139 status = archive->AddMessage("_invokeMsg", InvocationMessage()); 140 141 if (status == B_OK && SelectionMessage()) 142 status = archive->AddMessage("_selectMsg", SelectionMessage()); 143 144 if (status == B_OK) 145 status = fDate.Archive(archive); 146 147 if (status == B_OK) 148 status = archive->AddInt32("_weekStart", fStartOfWeek); 149 150 if (status == B_OK) 151 status = archive->AddBool("_dayHeader", fDayNameHeaderVisible); 152 153 if (status == B_OK) 154 status = archive->AddBool("_weekHeader", fWeekNumberHeaderVisible); 155 156 return status; 157 } 158 159 160 void 161 BCalendarView::AttachedToWindow() 162 { 163 BView::AttachedToWindow(); 164 165 if (!Messenger().IsValid()) 166 SetTarget(Window(), NULL); 167 168 SetViewUIColor(B_LIST_BACKGROUND_COLOR); 169 } 170 171 172 void 173 BCalendarView::FrameResized(float width, float height) 174 { 175 _SetupDayNames(); 176 Invalidate(Bounds()); 177 } 178 179 180 void 181 BCalendarView::Draw(BRect updateRect) 182 { 183 if (LockLooper()) { 184 if (fFocusChanged) { 185 _DrawFocusRect(); 186 UnlockLooper(); 187 return; 188 } 189 190 if (fSelectionChanged) { 191 _UpdateSelection(); 192 UnlockLooper(); 193 return; 194 } 195 196 if (fCurrentDayChanged) { 197 _UpdateCurrentDay(); 198 UnlockLooper(); 199 return; 200 } 201 202 _DrawDays(); 203 _DrawDayHeader(); 204 _DrawWeekHeader(); 205 206 rgb_color background = ui_color(B_PANEL_BACKGROUND_COLOR); 207 SetHighColor(tint_color(background, B_DARKEN_3_TINT)); 208 StrokeRect(Bounds()); 209 210 UnlockLooper(); 211 } 212 } 213 214 215 void 216 BCalendarView::DrawDay(BView* owner, BRect frame, const char* text, 217 bool isSelected, bool isEnabled, bool focus, bool highlight) 218 { 219 _DrawItem(owner, frame, text, isSelected, isEnabled, focus, highlight); 220 } 221 222 223 void 224 BCalendarView::DrawDayName(BView* owner, BRect frame, const char* text) 225 { 226 // we get the full rect, fake this as the internal function 227 // shrinks the frame to work properly when drawing a day item 228 _DrawItem(owner, frame.InsetByCopy(-1.0, -1.0), text, true); 229 } 230 231 232 void 233 BCalendarView::DrawWeekNumber(BView* owner, BRect frame, const char* text) 234 { 235 // we get the full rect, fake this as the internal function 236 // shrinks the frame to work properly when drawing a day item 237 _DrawItem(owner, frame.InsetByCopy(-1.0, -1.0), text, true); 238 } 239 240 241 uint32 242 BCalendarView::SelectionCommand() const 243 { 244 if (SelectionMessage()) 245 return SelectionMessage()->what; 246 247 return 0; 248 } 249 250 251 BMessage* 252 BCalendarView::SelectionMessage() const 253 { 254 return fSelectionMessage; 255 } 256 257 258 void 259 BCalendarView::SetSelectionMessage(BMessage* message) 260 { 261 delete fSelectionMessage; 262 fSelectionMessage = message; 263 } 264 265 266 uint32 267 BCalendarView::InvocationCommand() const 268 { 269 return BInvoker::Command(); 270 } 271 272 273 BMessage* 274 BCalendarView::InvocationMessage() const 275 { 276 return BInvoker::Message(); 277 } 278 279 280 void 281 BCalendarView::SetInvocationMessage(BMessage* message) 282 { 283 BInvoker::SetMessage(message); 284 } 285 286 287 void 288 BCalendarView::MakeFocus(bool state) 289 { 290 if (IsFocus() == state) 291 return; 292 293 BView::MakeFocus(state); 294 295 // TODO: solve this better 296 fFocusChanged = true; 297 Draw(_RectOfDay(fFocusedDay)); 298 fFocusChanged = false; 299 } 300 301 302 status_t 303 BCalendarView::Invoke(BMessage* message) 304 { 305 bool notify = false; 306 uint32 kind = InvokeKind(¬ify); 307 308 BMessage clone(kind); 309 status_t status = B_BAD_VALUE; 310 311 if (!message && !notify) 312 message = Message(); 313 314 if (!message) { 315 if (!IsWatched()) 316 return status; 317 } else 318 clone = *message; 319 320 clone.AddPointer("source", this); 321 clone.AddInt64("when", (int64)system_time()); 322 clone.AddMessenger("be:sender", BMessenger(this)); 323 324 int32 year; 325 int32 month; 326 _GetYearMonthForSelection(fSelectedDay, &year, &month); 327 328 clone.AddInt32("year", fDate.Year()); 329 clone.AddInt32("month", fDate.Month()); 330 clone.AddInt32("day", fDate.Day()); 331 332 if (message) 333 status = BInvoker::Invoke(&clone); 334 335 SendNotices(kind, &clone); 336 337 return status; 338 } 339 340 341 void 342 BCalendarView::MouseDown(BPoint where) 343 { 344 if (!IsFocus()) { 345 MakeFocus(); 346 Sync(); 347 Window()->UpdateIfNeeded(); 348 } 349 350 BRect frame = Bounds(); 351 if (fDayNameHeaderVisible) 352 frame.top += frame.Height() / 7 - 1.0; 353 354 if (fWeekNumberHeaderVisible) 355 frame.left += frame.Width() / 8 - 1.0; 356 357 if (!frame.Contains(where)) 358 return; 359 360 // try to set to new day 361 frame = _SetNewSelectedDay(where); 362 363 // on success 364 if (fSelectedDay != fNewSelectedDay) { 365 // update focus 366 fFocusChanged = true; 367 fNewFocusedDay = fNewSelectedDay; 368 Draw(_RectOfDay(fFocusedDay)); 369 fFocusChanged = false; 370 371 // update selection 372 fSelectionChanged = true; 373 Draw(frame); 374 Draw(_RectOfDay(fSelectedDay)); 375 fSelectionChanged = false; 376 377 // notify that selection changed 378 InvokeNotify(SelectionMessage(), B_CONTROL_MODIFIED); 379 } 380 381 int32 clicks; 382 // on double click invoke 383 BMessage* message = Looper()->CurrentMessage(); 384 if (message->FindInt32("clicks", &clicks) == B_OK && clicks > 1) 385 Invoke(); 386 } 387 388 389 void 390 BCalendarView::KeyDown(const char* bytes, int32 numBytes) 391 { 392 const int32 kRows = 6; 393 const int32 kColumns = 7; 394 395 int32 row = fFocusedDay.row; 396 int32 column = fFocusedDay.column; 397 398 switch (bytes[0]) { 399 case B_LEFT_ARROW: 400 column -= 1; 401 if (column < 0) { 402 column = kColumns - 1; 403 row -= 1; 404 if (row >= 0) 405 fFocusChanged = true; 406 } else 407 fFocusChanged = true; 408 break; 409 410 case B_RIGHT_ARROW: 411 column += 1; 412 if (column == kColumns) { 413 column = 0; 414 row += 1; 415 if (row < kRows) 416 fFocusChanged = true; 417 } else 418 fFocusChanged = true; 419 break; 420 421 case B_UP_ARROW: 422 row -= 1; 423 if (row >= 0) 424 fFocusChanged = true; 425 break; 426 427 case B_DOWN_ARROW: 428 row += 1; 429 if (row < kRows) 430 fFocusChanged = true; 431 break; 432 433 case B_PAGE_UP: 434 { 435 BDate date(fDate); 436 date.AddMonths(-1); 437 SetDate(date); 438 439 Invoke(); 440 break; 441 } 442 443 case B_PAGE_DOWN: 444 { 445 BDate date(fDate); 446 date.AddMonths(1); 447 SetDate(date); 448 449 Invoke(); 450 break; 451 } 452 453 case B_RETURN: 454 case B_SPACE: 455 { 456 fSelectionChanged = true; 457 BPoint pt = _RectOfDay(fFocusedDay).LeftTop(); 458 Draw(_SetNewSelectedDay(pt + BPoint(4.0, 4.0))); 459 Draw(_RectOfDay(fSelectedDay)); 460 fSelectionChanged = false; 461 462 Invoke(); 463 break; 464 } 465 466 default: 467 BView::KeyDown(bytes, numBytes); 468 break; 469 } 470 471 if (fFocusChanged) { 472 fNewFocusedDay.SetTo(row, column); 473 Draw(_RectOfDay(fFocusedDay)); 474 Draw(_RectOfDay(fNewFocusedDay)); 475 fFocusChanged = false; 476 } 477 } 478 479 480 void 481 BCalendarView::Pulse() 482 { 483 _UpdateCurrentDate(); 484 } 485 486 487 void 488 BCalendarView::ResizeToPreferred() 489 { 490 float width; 491 float height; 492 493 GetPreferredSize(&width, &height); 494 BView::ResizeTo(width, height); 495 } 496 497 498 void 499 BCalendarView::GetPreferredSize(float* width, float* height) 500 { 501 _GetPreferredSize(width, height); 502 } 503 504 505 BSize 506 BCalendarView::MaxSize() 507 { 508 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), 509 BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED)); 510 } 511 512 513 BSize 514 BCalendarView::MinSize() 515 { 516 float width, height; 517 _GetPreferredSize(&width, &height); 518 return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(width, height)); 519 } 520 521 522 BSize 523 BCalendarView::PreferredSize() 524 { 525 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), MinSize()); 526 } 527 528 529 int32 530 BCalendarView::Day() const 531 { 532 return fDate.Day(); 533 } 534 535 536 int32 537 BCalendarView::Year() const 538 { 539 int32 year; 540 _GetYearMonthForSelection(fSelectedDay, &year, NULL); 541 542 return year; 543 } 544 545 546 int32 547 BCalendarView::Month() const 548 { 549 int32 month; 550 _GetYearMonthForSelection(fSelectedDay, NULL, &month); 551 552 return month; 553 } 554 555 556 bool 557 BCalendarView::SetDay(int32 day) 558 { 559 BDate date = Date(); 560 date.SetDay(day); 561 if (!date.IsValid()) 562 return false; 563 SetDate(date); 564 return true; 565 } 566 567 568 bool 569 BCalendarView::SetMonth(int32 month) 570 { 571 if (month < 1 || month > 12) 572 return false; 573 BDate date = Date(); 574 int32 oldDay = date.Day(); 575 576 date.SetMonth(month); 577 date.SetDay(1); // make sure the date is valid 578 579 // We must make sure that the day in month fits inside the new month. 580 if (oldDay > date.DaysInMonth()) 581 date.SetDay(date.DaysInMonth()); 582 else 583 date.SetDay(oldDay); 584 SetDate(date); 585 return true; 586 } 587 588 589 bool 590 BCalendarView::SetYear(int32 year) 591 { 592 BDate date = Date(); 593 594 // This can fail when going from 29 feb. on a leap year to a non-leap year. 595 if (date.Month() == 2 && date.Day() == 29 && !date.IsLeapYear(year)) 596 date.SetDay(28); 597 598 // TODO we should also handle the "hole" at the switch between Julian and 599 // Gregorian calendars, which will result in an invalid date. 600 601 date.SetYear(year); 602 SetDate(date); 603 return true; 604 } 605 606 607 BDate 608 BCalendarView::Date() const 609 { 610 int32 year; 611 int32 month; 612 _GetYearMonthForSelection(fSelectedDay, &year, &month); 613 return BDate(year, month, fDate.Day()); 614 } 615 616 617 bool 618 BCalendarView::SetDate(const BDate& date) 619 { 620 if (!date.IsValid()) 621 return false; 622 623 if (fDate == date) 624 return true; 625 626 if (fDate.Year() == date.Year() && fDate.Month() == date.Month()) { 627 fDate = date; 628 629 _SetToDay(); 630 // update focus 631 fFocusChanged = true; 632 Draw(_RectOfDay(fFocusedDay)); 633 fFocusChanged = false; 634 635 // update selection 636 fSelectionChanged = true; 637 Draw(_RectOfDay(fSelectedDay)); 638 Draw(_RectOfDay(fNewSelectedDay)); 639 fSelectionChanged = false; 640 } else { 641 fDate = date; 642 643 _SetupDayNumbers(); 644 _SetupWeekNumbers(); 645 646 BRect frame = Bounds(); 647 if (fDayNameHeaderVisible) 648 frame.top += frame.Height() / 7 - 1.0; 649 650 if (fWeekNumberHeaderVisible) 651 frame.left += frame.Width() / 8 - 1.0; 652 653 Draw(frame.InsetBySelf(4.0, 4.0)); 654 } 655 656 return true; 657 } 658 659 660 bool 661 BCalendarView::SetDate(int32 year, int32 month, int32 day) 662 { 663 return SetDate(BDate(year, month, day)); 664 } 665 666 667 BWeekday 668 BCalendarView::StartOfWeek() const 669 { 670 return BWeekday(fStartOfWeek); 671 } 672 673 674 void 675 BCalendarView::SetStartOfWeek(BWeekday startOfWeek) 676 { 677 if (fStartOfWeek == (int32)startOfWeek) 678 return; 679 680 fStartOfWeek = (int32)startOfWeek; 681 682 _SetupDayNames(); 683 _SetupDayNumbers(); 684 _SetupWeekNumbers(); 685 686 Invalidate(Bounds().InsetBySelf(1.0, 1.0)); 687 } 688 689 690 bool 691 BCalendarView::IsDayNameHeaderVisible() const 692 { 693 return fDayNameHeaderVisible; 694 } 695 696 697 void 698 BCalendarView::SetDayNameHeaderVisible(bool visible) 699 { 700 if (fDayNameHeaderVisible == visible) 701 return; 702 703 fDayNameHeaderVisible = visible; 704 Invalidate(Bounds().InsetBySelf(1.0, 1.0)); 705 } 706 707 708 void 709 BCalendarView::UpdateDayNameHeader() 710 { 711 if (!fDayNameHeaderVisible) 712 return; 713 714 _SetupDayNames(); 715 Invalidate(Bounds().InsetBySelf(1.0, 1.0)); 716 } 717 718 719 bool 720 BCalendarView::IsWeekNumberHeaderVisible() const 721 { 722 return fWeekNumberHeaderVisible; 723 } 724 725 726 void 727 BCalendarView::SetWeekNumberHeaderVisible(bool visible) 728 { 729 if (fWeekNumberHeaderVisible == visible) 730 return; 731 732 fWeekNumberHeaderVisible = visible; 733 Invalidate(Bounds().InsetBySelf(1.0, 1.0)); 734 } 735 736 737 void 738 BCalendarView::_InitObject() 739 { 740 fDate = BDate::CurrentDate(B_LOCAL_TIME); 741 742 BDateFormat().GetStartOfWeek((BWeekday*)&fStartOfWeek); 743 744 _SetupDayNames(); 745 _SetupDayNumbers(); 746 _SetupWeekNumbers(); 747 } 748 749 750 void 751 BCalendarView::_SetToDay() 752 { 753 BDate date(fDate.Year(), fDate.Month(), 1); 754 if (!date.IsValid()) 755 return; 756 757 const int32 firstDayOffset = (7 + date.DayOfWeek() - fStartOfWeek) % 7; 758 759 int32 day = 1 - firstDayOffset; 760 for (int32 row = 0; row < 6; ++row) { 761 for (int32 column = 0; column < 7; ++column) { 762 if (day == fDate.Day()) { 763 fNewFocusedDay.SetTo(row, column); 764 fNewSelectedDay.SetTo(row, column); 765 return; 766 } 767 day++; 768 } 769 } 770 771 fNewFocusedDay.SetTo(0, 0); 772 fNewSelectedDay.SetTo(0, 0); 773 } 774 775 776 void 777 BCalendarView::_SetToCurrentDay() 778 { 779 BDate date(fCurrentDate.Year(), fCurrentDate.Month(), 1); 780 if (!date.IsValid()) 781 return; 782 if (fDate.Year() != date.Year() || fDate.Month() != date.Month()) { 783 fNewCurrentDay.SetTo(-1, -1); 784 return; 785 } 786 const int32 firstDayOffset = (7 + date.DayOfWeek() - fStartOfWeek) % 7; 787 788 int32 day = 1 - firstDayOffset; 789 for (int32 row = 0; row < 6; ++row) { 790 for (int32 column = 0; column < 7; ++column) { 791 if (day == fCurrentDate.Day()) { 792 fNewCurrentDay.SetTo(row, column); 793 return; 794 } 795 day++; 796 } 797 } 798 799 fNewCurrentDay.SetTo(-1, -1); 800 } 801 802 803 void 804 BCalendarView::_GetYearMonthForSelection(const Selection& selection, 805 int32* year, int32* month) const 806 { 807 BDate startOfMonth(fDate.Year(), fDate.Month(), 1); 808 const int32 firstDayOffset 809 = (7 + startOfMonth.DayOfWeek() - fStartOfWeek) % 7; 810 const int32 daysInMonth = startOfMonth.DaysInMonth(); 811 812 BDate date(fDate); 813 const int32 dayOffset = selection.row * 7 + selection.column; 814 if (dayOffset < firstDayOffset) 815 date.AddMonths(-1); 816 else if (dayOffset >= firstDayOffset + daysInMonth) 817 date.AddMonths(1); 818 if (year != NULL) 819 *year = date.Year(); 820 if (month != NULL) 821 *month = date.Month(); 822 } 823 824 825 void 826 BCalendarView::_GetPreferredSize(float* _width, float* _height) 827 { 828 BFont font; 829 GetFont(&font); 830 font_height fontHeight; 831 font.GetHeight(&fontHeight); 832 833 const float height = FontHeight(this) + 4.0; 834 835 int32 rows = 7; 836 if (!fDayNameHeaderVisible) 837 rows = 6; 838 839 // height = font height * rows + 8 px border 840 *_height = height * rows + 8.0; 841 842 float width = 0.0; 843 for (int32 column = 0; column < 7; ++column) { 844 float tmp = StringWidth(fDayNames[column].String()) + 2.0; 845 width = tmp > width ? tmp : width; 846 } 847 848 int32 columns = 8; 849 if (!fWeekNumberHeaderVisible) 850 columns = 7; 851 852 // width = max width day name * 8 column + 8 px border 853 *_width = width * columns + 8.0; 854 } 855 856 857 void 858 BCalendarView::_SetupDayNames() 859 { 860 BDateFormatStyle style = B_LONG_DATE_FORMAT; 861 float width, height; 862 while (style != B_DATE_FORMAT_STYLE_COUNT) { 863 _PopulateDayNames(style); 864 GetPreferredSize(&width, &height); 865 if (width < Bounds().Width()) 866 return; 867 style = static_cast<BDateFormatStyle>(static_cast<int>(style) + 1); 868 } 869 } 870 871 872 void 873 BCalendarView::_PopulateDayNames(BDateFormatStyle style) 874 { 875 for (int32 i = 0; i <= 6; ++i) { 876 fDayNames[i] = ""; 877 BDateFormat().GetDayName(1 + (fStartOfWeek - 1 + i) % 7, 878 fDayNames[i], style); 879 } 880 } 881 882 883 void 884 BCalendarView::_SetupDayNumbers() 885 { 886 BDate startOfMonth(fDate.Year(), fDate.Month(), 1); 887 if (!startOfMonth.IsValid()) 888 return; 889 890 fFocusedDay.SetTo(0, 0); 891 fSelectedDay.SetTo(0, 0); 892 fNewFocusedDay.SetTo(0, 0); 893 fCurrentDay.SetTo(-1, -1); 894 895 const int32 daysInMonth = startOfMonth.DaysInMonth(); 896 const int32 firstDayOffset 897 = (7 + startOfMonth.DayOfWeek() - fStartOfWeek) % 7; 898 899 // calc the last day one month before 900 BDate lastDayInMonthBefore(startOfMonth); 901 lastDayInMonthBefore.AddDays(-1); 902 const int32 lastDayBefore = lastDayInMonthBefore.DaysInMonth(); 903 904 int32 counter = 0; 905 int32 firstDayAfter = 1; 906 for (int32 row = 0; row < 6; ++row) { 907 for (int32 column = 0; column < 7; ++column) { 908 int32 day = 1 + counter - firstDayOffset; 909 if (counter < firstDayOffset) 910 day += lastDayBefore; 911 else if (counter >= firstDayOffset + daysInMonth) 912 day = firstDayAfter++; 913 else if (day == fDate.Day()) { 914 fFocusedDay.SetTo(row, column); 915 fSelectedDay.SetTo(row, column); 916 fNewFocusedDay.SetTo(row, column); 917 } 918 if (day == fCurrentDate.Day() && counter >= firstDayOffset 919 && counter < firstDayOffset + daysInMonth 920 && fDate.Month() == fCurrentDate.Month() 921 && fDate.Year() == fCurrentDate.Year()) 922 fCurrentDay.SetTo(row, column); 923 924 counter++; 925 fDayNumbers[row][column].Truncate(0); 926 fDayNumbers[row][column] << day; 927 } 928 } 929 } 930 931 932 void 933 BCalendarView::_SetupWeekNumbers() 934 { 935 BDate date(fDate.Year(), fDate.Month(), 1); 936 if (!date.IsValid()) 937 return; 938 939 for (int32 row = 0; row < 6; ++row) { 940 fWeekNumbers[row].SetTo(""); 941 fWeekNumbers[row] << date.WeekNumber(); 942 date.AddDays(7); 943 } 944 } 945 946 947 void 948 BCalendarView::_DrawDay(int32 currRow, int32 currColumn, int32 row, 949 int32 column, int32 counter, BRect frame, const char* text, 950 bool focus, bool highlight) 951 { 952 BDate startOfMonth(fDate.Year(), fDate.Month(), 1); 953 const int32 firstDayOffset 954 = (7 + startOfMonth.DayOfWeek() - fStartOfWeek) % 7; 955 const int32 daysMonth = startOfMonth.DaysInMonth(); 956 957 bool enabled = true; 958 bool selected = false; 959 // check for the current date 960 if (currRow == row && currColumn == column) { 961 selected = true; // draw current date selected 962 if (counter <= firstDayOffset || counter > firstDayOffset + daysMonth) { 963 enabled = false; // days of month before or after 964 selected = false; // not selected but able to get focus 965 } 966 } else { 967 if (counter <= firstDayOffset || counter > firstDayOffset + daysMonth) 968 enabled = false; // days of month before or after 969 } 970 971 DrawDay(this, frame, text, selected, enabled, focus, highlight); 972 } 973 974 975 void 976 BCalendarView::_DrawDays() 977 { 978 BRect frame = _FirstCalendarItemFrame(); 979 980 const int32 currRow = fSelectedDay.row; 981 const int32 currColumn = fSelectedDay.column; 982 983 const bool isFocus = IsFocus(); 984 const int32 focusRow = fFocusedDay.row; 985 const int32 focusColumn = fFocusedDay.column; 986 987 const int32 highlightRow = fCurrentDay.row; 988 const int32 highlightColumn = fCurrentDay.column; 989 990 int32 counter = 0; 991 for (int32 row = 0; row < 6; ++row) { 992 BRect tmp = frame; 993 for (int32 column = 0; column < 7; ++column) { 994 counter++; 995 const char* day = fDayNumbers[row][column].String(); 996 bool focus = isFocus && focusRow == row && focusColumn == column; 997 bool highlight = highlightRow == row && highlightColumn == column; 998 _DrawDay(currRow, currColumn, row, column, counter, tmp, day, 999 focus, highlight); 1000 1001 tmp.OffsetBy(tmp.Width(), 0.0); 1002 } 1003 frame.OffsetBy(0.0, frame.Height()); 1004 } 1005 } 1006 1007 1008 void 1009 BCalendarView::_DrawFocusRect() 1010 { 1011 BRect frame = _FirstCalendarItemFrame(); 1012 1013 const int32 currRow = fSelectedDay.row; 1014 const int32 currColumn = fSelectedDay.column; 1015 1016 const int32 focusRow = fFocusedDay.row; 1017 const int32 focusColumn = fFocusedDay.column; 1018 1019 const int32 highlightRow = fCurrentDay.row; 1020 const int32 highlightColumn = fCurrentDay.column; 1021 1022 int32 counter = 0; 1023 for (int32 row = 0; row < 6; ++row) { 1024 BRect tmp = frame; 1025 for (int32 column = 0; column < 7; ++column) { 1026 counter++; 1027 if (fNewFocusedDay.row == row && fNewFocusedDay.column == column) { 1028 fFocusedDay.SetTo(row, column); 1029 1030 bool focus = IsFocus() && true; 1031 bool highlight = highlightRow == row && highlightColumn == column; 1032 const char* day = fDayNumbers[row][column].String(); 1033 _DrawDay(currRow, currColumn, row, column, counter, tmp, day, 1034 focus, highlight); 1035 } else if (focusRow == row && focusColumn == column) { 1036 const char* day = fDayNumbers[row][column].String(); 1037 bool highlight = highlightRow == row && highlightColumn == column; 1038 _DrawDay(currRow, currColumn, row, column, counter, tmp, day, 1039 false, highlight); 1040 } 1041 tmp.OffsetBy(tmp.Width(), 0.0); 1042 } 1043 frame.OffsetBy(0.0, frame.Height()); 1044 } 1045 } 1046 1047 1048 void 1049 BCalendarView::_DrawDayHeader() 1050 { 1051 if (!fDayNameHeaderVisible) 1052 return; 1053 1054 int32 offset = 1; 1055 int32 columns = 8; 1056 if (!fWeekNumberHeaderVisible) { 1057 offset = 0; 1058 columns = 7; 1059 } 1060 1061 BRect frame = Bounds(); 1062 frame.right = frame.Width() / columns - 1.0; 1063 frame.bottom = frame.Height() / 7.0 - 2.0; 1064 frame.OffsetBy(4.0, 4.0); 1065 1066 for (int32 i = 0; i < columns; ++i) { 1067 if (i == 0 && fWeekNumberHeaderVisible) { 1068 DrawDayName(this, frame, ""); 1069 frame.OffsetBy(frame.Width(), 0.0); 1070 continue; 1071 } 1072 DrawDayName(this, frame, fDayNames[i - offset].String()); 1073 frame.OffsetBy(frame.Width(), 0.0); 1074 } 1075 } 1076 1077 1078 void 1079 BCalendarView::_DrawWeekHeader() 1080 { 1081 if (!fWeekNumberHeaderVisible) 1082 return; 1083 1084 int32 rows = 7; 1085 if (!fDayNameHeaderVisible) 1086 rows = 6; 1087 1088 BRect frame = Bounds(); 1089 frame.right = frame.Width() / 8.0 - 2.0; 1090 frame.bottom = frame.Height() / rows - 1.0; 1091 1092 float offsetY = 4.0; 1093 if (fDayNameHeaderVisible) 1094 offsetY += frame.Height(); 1095 1096 frame.OffsetBy(4.0, offsetY); 1097 1098 for (int32 row = 0; row < 6; ++row) { 1099 DrawWeekNumber(this, frame, fWeekNumbers[row].String()); 1100 frame.OffsetBy(0.0, frame.Height()); 1101 } 1102 } 1103 1104 1105 void 1106 BCalendarView::_DrawItem(BView* owner, BRect frame, const char* text, 1107 bool isSelected, bool isEnabled, bool focus, bool isHighlight) 1108 { 1109 rgb_color lColor = LowColor(); 1110 rgb_color highColor = HighColor(); 1111 1112 rgb_color textColor = ui_color(B_LIST_ITEM_TEXT_COLOR); 1113 rgb_color bgColor = ui_color(B_LIST_BACKGROUND_COLOR); 1114 float tintDisabled = B_LIGHTEN_2_TINT; 1115 float tintHighlight = B_LIGHTEN_1_TINT; 1116 1117 if (textColor.red + textColor.green + textColor.blue > 125 * 3) 1118 tintDisabled = B_DARKEN_2_TINT; 1119 1120 if (bgColor.red + bgColor.green + bgColor.blue > 125 * 3) 1121 tintHighlight = B_DARKEN_1_TINT; 1122 1123 if (isSelected) { 1124 SetHighColor(ui_color(B_LIST_SELECTED_BACKGROUND_COLOR)); 1125 textColor = ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR); 1126 } else if (isHighlight) 1127 SetHighColor(tint_color(bgColor, tintHighlight)); 1128 else 1129 SetHighColor(bgColor); 1130 1131 SetLowColor(HighColor()); 1132 1133 FillRect(frame.InsetByCopy(1.0, 1.0)); 1134 1135 if (focus) { 1136 rgb_color focusColor = keyboard_navigation_color(); 1137 SetHighColor(focusColor); 1138 StrokeRect(frame.InsetByCopy(1.0, 1.0)); 1139 1140 if (!isSelected) 1141 textColor = focusColor; 1142 } 1143 1144 SetHighColor(textColor); 1145 if (!isEnabled) 1146 SetHighColor(tint_color(textColor, tintDisabled)); 1147 1148 float offsetH = frame.Width() / 2.0; 1149 float offsetV = frame.Height() / 2.0 + FontHeight(owner) / 4.0; 1150 1151 BFont font(be_plain_font); 1152 if (isHighlight) 1153 font.SetFace(B_BOLD_FACE); 1154 else 1155 font.SetFace(B_REGULAR_FACE); 1156 SetFont(&font); 1157 1158 DrawString(text, BPoint(frame.right - offsetH - StringWidth(text) / 2.0, 1159 frame.top + offsetV)); 1160 1161 SetLowColor(lColor); 1162 SetHighColor(highColor); 1163 } 1164 1165 1166 void 1167 BCalendarView::_UpdateSelection() 1168 { 1169 BRect frame = _FirstCalendarItemFrame(); 1170 1171 const int32 currRow = fSelectedDay.row; 1172 const int32 currColumn = fSelectedDay.column; 1173 1174 const int32 focusRow = fFocusedDay.row; 1175 const int32 focusColumn = fFocusedDay.column; 1176 1177 const int32 highlightRow = fCurrentDay.row; 1178 const int32 highlightColumn = fCurrentDay.column; 1179 1180 int32 counter = 0; 1181 for (int32 row = 0; row < 6; ++row) { 1182 BRect tmp = frame; 1183 for (int32 column = 0; column < 7; ++column) { 1184 counter++; 1185 if (fNewSelectedDay.row == row 1186 && fNewSelectedDay.column == column) { 1187 fSelectedDay.SetTo(row, column); 1188 1189 const char* day = fDayNumbers[row][column].String(); 1190 bool focus = IsFocus() && focusRow == row 1191 && focusColumn == column; 1192 bool highlight = highlightRow == row && highlightColumn == column; 1193 _DrawDay(row, column, row, column, counter, tmp, day, focus, highlight); 1194 } else if (currRow == row && currColumn == column) { 1195 const char* day = fDayNumbers[row][column].String(); 1196 bool focus = IsFocus() && focusRow == row 1197 && focusColumn == column; 1198 bool highlight = highlightRow == row && highlightColumn == column; 1199 _DrawDay(currRow, currColumn, -1, -1, counter, tmp, day, focus, highlight); 1200 } 1201 tmp.OffsetBy(tmp.Width(), 0.0); 1202 } 1203 frame.OffsetBy(0.0, frame.Height()); 1204 } 1205 } 1206 1207 1208 void 1209 BCalendarView::_UpdateCurrentDay() 1210 { 1211 BRect frame = _FirstCalendarItemFrame(); 1212 1213 const int32 selectRow = fSelectedDay.row; 1214 const int32 selectColumn = fSelectedDay.column; 1215 1216 const int32 focusRow = fFocusedDay.row; 1217 const int32 focusColumn = fFocusedDay.column; 1218 1219 const int32 currRow = fCurrentDay.row; 1220 const int32 currColumn = fCurrentDay.column; 1221 1222 int32 counter = 0; 1223 for (int32 row = 0; row < 6; ++row) { 1224 BRect tmp = frame; 1225 for (int32 column = 0; column < 7; ++column) { 1226 counter++; 1227 if (fNewCurrentDay.row == row 1228 && fNewCurrentDay.column == column) { 1229 fCurrentDay.SetTo(row, column); 1230 1231 const char* day = fDayNumbers[row][column].String(); 1232 bool focus = IsFocus() && focusRow == row 1233 && focusColumn == column; 1234 bool isSelected = selectRow == row && selectColumn == column; 1235 if (isSelected) 1236 _DrawDay(row, column, row, column, counter, tmp, day, focus, true); 1237 else 1238 _DrawDay(row, column, -1, -1, counter, tmp, day, focus, true); 1239 1240 } else if (currRow == row && currColumn == column) { 1241 const char* day = fDayNumbers[row][column].String(); 1242 bool focus = IsFocus() && focusRow == row 1243 && focusColumn == column; 1244 bool isSelected = selectRow == row && selectColumn == column; 1245 if(isSelected) 1246 _DrawDay(currRow, currColumn, row, column, counter, tmp, day, focus, false); 1247 else 1248 _DrawDay(currRow, currColumn, -1, -1, counter, tmp, day, focus, false); 1249 } 1250 tmp.OffsetBy(tmp.Width(), 0.0); 1251 } 1252 frame.OffsetBy(0.0, frame.Height()); 1253 } 1254 } 1255 1256 1257 void 1258 BCalendarView::_UpdateCurrentDate() 1259 { 1260 BDate date = BDate::CurrentDate(B_LOCAL_TIME); 1261 1262 if (!date.IsValid()) 1263 return; 1264 if (date == fCurrentDate) 1265 return; 1266 1267 fCurrentDate = date; 1268 1269 _SetToCurrentDay(); 1270 fCurrentDayChanged = true; 1271 Draw(_RectOfDay(fCurrentDay)); 1272 Draw(_RectOfDay(fNewCurrentDay)); 1273 fCurrentDayChanged = false; 1274 1275 return; 1276 } 1277 1278 1279 BRect 1280 BCalendarView::_FirstCalendarItemFrame() const 1281 { 1282 int32 rows = 7; 1283 int32 columns = 8; 1284 1285 if (!fDayNameHeaderVisible) 1286 rows = 6; 1287 1288 if (!fWeekNumberHeaderVisible) 1289 columns = 7; 1290 1291 BRect frame = Bounds(); 1292 frame.right = frame.Width() / columns - 1.0; 1293 frame.bottom = frame.Height() / rows - 1.0; 1294 1295 float offsetY = 4.0; 1296 if (fDayNameHeaderVisible) 1297 offsetY += frame.Height(); 1298 1299 float offsetX = 4.0; 1300 if (fWeekNumberHeaderVisible) 1301 offsetX += frame.Width(); 1302 1303 return frame.OffsetBySelf(offsetX, offsetY); 1304 } 1305 1306 1307 BRect 1308 BCalendarView::_SetNewSelectedDay(const BPoint& where) 1309 { 1310 BRect frame = _FirstCalendarItemFrame(); 1311 1312 int32 counter = 0; 1313 for (int32 row = 0; row < 6; ++row) { 1314 BRect tmp = frame; 1315 for (int32 column = 0; column < 7; ++column) { 1316 counter++; 1317 if (tmp.Contains(where)) { 1318 fNewSelectedDay.SetTo(row, column); 1319 int32 year; 1320 int32 month; 1321 _GetYearMonthForSelection(fNewSelectedDay, &year, &month); 1322 if (month == fDate.Month()) { 1323 // only change date if a day in the current month has been 1324 // selected 1325 int32 day = atoi(fDayNumbers[row][column].String()); 1326 fDate.SetDate(year, month, day); 1327 } 1328 return tmp; 1329 } 1330 tmp.OffsetBy(tmp.Width(), 0.0); 1331 } 1332 frame.OffsetBy(0.0, frame.Height()); 1333 } 1334 1335 return frame; 1336 } 1337 1338 1339 BRect 1340 BCalendarView::_RectOfDay(const Selection& selection) const 1341 { 1342 BRect frame = _FirstCalendarItemFrame(); 1343 1344 int32 counter = 0; 1345 for (int32 row = 0; row < 6; ++row) { 1346 BRect tmp = frame; 1347 for (int32 column = 0; column < 7; ++column) { 1348 counter++; 1349 if (selection.row == row && selection.column == column) 1350 return tmp; 1351 tmp.OffsetBy(tmp.Width(), 0.0); 1352 } 1353 frame.OffsetBy(0.0, frame.Height()); 1354 } 1355 1356 return frame; 1357 } 1358 1359 1360 } // namespace BPrivate 1361