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
19namespace BPrivate {
20
21
22static float
23FontHeight(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
39BCalendarView::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
58BCalendarView::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
76BCalendarView::~BCalendarView()
77{
78	SetSelectionMessage(NULL);
79}
80
81
82BCalendarView::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
123BArchivable*
124BCalendarView::Instantiate(BMessage* archive)
125{
126	if (validate_instantiation(archive, "BCalendarView"))
127		return new BCalendarView(archive);
128
129	return NULL;
130}
131
132
133status_t
134BCalendarView::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
160void
161BCalendarView::AttachedToWindow()
162{
163	BView::AttachedToWindow();
164
165	if (!Messenger().IsValid())
166		SetTarget(Window(), NULL);
167
168	SetViewUIColor(B_LIST_BACKGROUND_COLOR);
169}
170
171
172void
173BCalendarView::FrameResized(float width, float height)
174{
175	_SetupDayNames();
176	Invalidate(Bounds());
177}
178
179
180void
181BCalendarView::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
215void
216BCalendarView::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
223void
224BCalendarView::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
232void
233BCalendarView::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
241uint32
242BCalendarView::SelectionCommand() const
243{
244	if (SelectionMessage())
245		return SelectionMessage()->what;
246
247	return 0;
248}
249
250
251BMessage*
252BCalendarView::SelectionMessage() const
253{
254	return fSelectionMessage;
255}
256
257
258void
259BCalendarView::SetSelectionMessage(BMessage* message)
260{
261	delete fSelectionMessage;
262	fSelectionMessage = message;
263}
264
265
266uint32
267BCalendarView::InvocationCommand() const
268{
269	return BInvoker::Command();
270}
271
272
273BMessage*
274BCalendarView::InvocationMessage() const
275{
276	return BInvoker::Message();
277}
278
279
280void
281BCalendarView::SetInvocationMessage(BMessage* message)
282{
283	BInvoker::SetMessage(message);
284}
285
286
287void
288BCalendarView::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
302status_t
303BCalendarView::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
341void
342BCalendarView::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
389void
390BCalendarView::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
480void
481BCalendarView::Pulse()
482{
483	_UpdateCurrentDate();
484}
485
486
487void
488BCalendarView::ResizeToPreferred()
489{
490	float width;
491	float height;
492
493	GetPreferredSize(&width, &height);
494	BView::ResizeTo(width, height);
495}
496
497
498void
499BCalendarView::GetPreferredSize(float* width, float* height)
500{
501	_GetPreferredSize(width, height);
502}
503
504
505BSize
506BCalendarView::MaxSize()
507{
508	return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
509		BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
510}
511
512
513BSize
514BCalendarView::MinSize()
515{
516	float width, height;
517	_GetPreferredSize(&width, &height);
518	return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(width, height));
519}
520
521
522BSize
523BCalendarView::PreferredSize()
524{
525	return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), MinSize());
526}
527
528
529int32
530BCalendarView::Day() const
531{
532	return fDate.Day();
533}
534
535
536int32
537BCalendarView::Year() const
538{
539	int32 year;
540	_GetYearMonthForSelection(fSelectedDay, &year, NULL);
541
542	return year;
543}
544
545
546int32
547BCalendarView::Month() const
548{
549	int32 month;
550	_GetYearMonthForSelection(fSelectedDay, NULL, &month);
551
552	return month;
553}
554
555
556bool
557BCalendarView::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
568bool
569BCalendarView::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
589bool
590BCalendarView::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
607BDate
608BCalendarView::Date() const
609{
610	int32 year;
611	int32 month;
612	_GetYearMonthForSelection(fSelectedDay, &year, &month);
613	return BDate(year, month, fDate.Day());
614}
615
616
617bool
618BCalendarView::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
660bool
661BCalendarView::SetDate(int32 year, int32 month, int32 day)
662{
663	return SetDate(BDate(year, month, day));
664}
665
666
667BWeekday
668BCalendarView::StartOfWeek() const
669{
670	return BWeekday(fStartOfWeek);
671}
672
673
674void
675BCalendarView::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
690bool
691BCalendarView::IsDayNameHeaderVisible() const
692{
693	return fDayNameHeaderVisible;
694}
695
696
697void
698BCalendarView::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
708void
709BCalendarView::UpdateDayNameHeader()
710{
711	if (!fDayNameHeaderVisible)
712		return;
713
714	_SetupDayNames();
715	Invalidate(Bounds().InsetBySelf(1.0, 1.0));
716}
717
718
719bool
720BCalendarView::IsWeekNumberHeaderVisible() const
721{
722	return fWeekNumberHeaderVisible;
723}
724
725
726void
727BCalendarView::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
737void
738BCalendarView::_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
750void
751BCalendarView::_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
776void
777BCalendarView::_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
803void
804BCalendarView::_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
825void
826BCalendarView::_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
857void
858BCalendarView::_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
872void
873BCalendarView::_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
883void
884BCalendarView::_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
932void
933BCalendarView::_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
947void
948BCalendarView::_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
975void
976BCalendarView::_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
1008void
1009BCalendarView::_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
1048void
1049BCalendarView::_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
1078void
1079BCalendarView::_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
1105void
1106BCalendarView::_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) / 2.0 - 2.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
1166void
1167BCalendarView::_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
1208void
1209BCalendarView::_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
1257void
1258BCalendarView::_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
1279BRect
1280BCalendarView::_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
1307BRect
1308BCalendarView::_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
1339BRect
1340BCalendarView::_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