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