xref: /haiku/src/kits/shared/CalendarView.cpp (revision c858eda663b4e558f3062c78634e7ccd53954628)
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(&notify);
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