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