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
FontHeight(const BView * view)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
BCalendarView(BRect frame,const char * name,uint32 resizeMask,uint32 flags)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
BCalendarView(const char * name,uint32 flags)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
~BCalendarView()76 BCalendarView::~BCalendarView()
77 {
78 SetSelectionMessage(NULL);
79 }
80
81
BCalendarView(BMessage * archive)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*
Instantiate(BMessage * archive)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
Archive(BMessage * archive,bool deep) const134 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
AttachedToWindow()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
FrameResized(float width,float height)173 BCalendarView::FrameResized(float width, float height)
174 {
175 _SetupDayNames();
176 Invalidate(Bounds());
177 }
178
179
180 void
Draw(BRect updateRect)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
DrawDay(BView * owner,BRect frame,const char * text,bool isSelected,bool isEnabled,bool focus,bool highlight)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
DrawDayName(BView * owner,BRect frame,const char * text)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
DrawWeekNumber(BView * owner,BRect frame,const char * text)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
SelectionCommand() const242 BCalendarView::SelectionCommand() const
243 {
244 if (SelectionMessage())
245 return SelectionMessage()->what;
246
247 return 0;
248 }
249
250
251 BMessage*
SelectionMessage() const252 BCalendarView::SelectionMessage() const
253 {
254 return fSelectionMessage;
255 }
256
257
258 void
SetSelectionMessage(BMessage * message)259 BCalendarView::SetSelectionMessage(BMessage* message)
260 {
261 delete fSelectionMessage;
262 fSelectionMessage = message;
263 }
264
265
266 uint32
InvocationCommand() const267 BCalendarView::InvocationCommand() const
268 {
269 return BInvoker::Command();
270 }
271
272
273 BMessage*
InvocationMessage() const274 BCalendarView::InvocationMessage() const
275 {
276 return BInvoker::Message();
277 }
278
279
280 void
SetInvocationMessage(BMessage * message)281 BCalendarView::SetInvocationMessage(BMessage* message)
282 {
283 BInvoker::SetMessage(message);
284 }
285
286
287 void
MakeFocus(bool state)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
Invoke(BMessage * message)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
MouseDown(BPoint where)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
KeyDown(const char * bytes,int32 numBytes)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
Pulse()481 BCalendarView::Pulse()
482 {
483 _UpdateCurrentDate();
484 }
485
486
487 void
ResizeToPreferred()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
GetPreferredSize(float * width,float * height)499 BCalendarView::GetPreferredSize(float* width, float* height)
500 {
501 _GetPreferredSize(width, height);
502 }
503
504
505 BSize
MaxSize()506 BCalendarView::MaxSize()
507 {
508 return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
509 BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
510 }
511
512
513 BSize
MinSize()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
PreferredSize()523 BCalendarView::PreferredSize()
524 {
525 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), MinSize());
526 }
527
528
529 int32
Day() const530 BCalendarView::Day() const
531 {
532 return fDate.Day();
533 }
534
535
536 int32
Year() const537 BCalendarView::Year() const
538 {
539 int32 year;
540 _GetYearMonthForSelection(fSelectedDay, &year, NULL);
541
542 return year;
543 }
544
545
546 int32
Month() const547 BCalendarView::Month() const
548 {
549 int32 month;
550 _GetYearMonthForSelection(fSelectedDay, NULL, &month);
551
552 return month;
553 }
554
555
556 bool
SetDay(int32 day)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
SetMonth(int32 month)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
SetYear(int32 year)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
Date() const608 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
SetDate(const BDate & date)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
SetDate(int32 year,int32 month,int32 day)661 BCalendarView::SetDate(int32 year, int32 month, int32 day)
662 {
663 return SetDate(BDate(year, month, day));
664 }
665
666
667 BWeekday
StartOfWeek() const668 BCalendarView::StartOfWeek() const
669 {
670 return BWeekday(fStartOfWeek);
671 }
672
673
674 void
SetStartOfWeek(BWeekday startOfWeek)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
IsDayNameHeaderVisible() const691 BCalendarView::IsDayNameHeaderVisible() const
692 {
693 return fDayNameHeaderVisible;
694 }
695
696
697 void
SetDayNameHeaderVisible(bool visible)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
UpdateDayNameHeader()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
IsWeekNumberHeaderVisible() const720 BCalendarView::IsWeekNumberHeaderVisible() const
721 {
722 return fWeekNumberHeaderVisible;
723 }
724
725
726 void
SetWeekNumberHeaderVisible(bool visible)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
_InitObject()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
_SetToDay()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
_SetToCurrentDay()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
_GetYearMonthForSelection(const Selection & selection,int32 * year,int32 * month) const804 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
_GetPreferredSize(float * _width,float * _height)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
_SetupDayNames()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
_PopulateDayNames(BDateFormatStyle style)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
_SetupDayNumbers()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
_SetupWeekNumbers()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
_DrawDay(int32 currRow,int32 currColumn,int32 row,int32 column,int32 counter,BRect frame,const char * text,bool focus,bool highlight)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
_DrawDays()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
_DrawFocusRect()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
_DrawDayHeader()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
_DrawWeekHeader()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
_DrawItem(BView * owner,BRect frame,const char * text,bool isSelected,bool isEnabled,bool focus,bool isHighlight)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
_UpdateSelection()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
_UpdateCurrentDay()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
_UpdateCurrentDate()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
_FirstCalendarItemFrame() const1280 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
_SetNewSelectedDay(const BPoint & where)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
_RectOfDay(const Selection & selection) const1340 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