root/src/kits/shared/CalendarView.cpp
/*
 * Copyright 2007-2011, Haiku, Inc. All Rights Reserved.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Julun <host.haiku@gmx.de>
 */


#include "CalendarView.h"

#include <stdlib.h>

#include <DateFormat.h>
#include <LayoutUtils.h>
#include <Window.h>


namespace BPrivate {


static float
FontHeight(const BView* view)
{
        if (!view)
                return 0.0;

        BFont font;
        view->GetFont(&font);
        font_height fheight;
        font.GetHeight(&fheight);
        return ceilf(fheight.ascent + fheight.descent + fheight.leading);
}


// #pragma mark -


BCalendarView::BCalendarView(BRect frame, const char* name, uint32 resizeMask,
        uint32 flags)
        :
        BView(frame, name, resizeMask, flags),
        BInvoker(),
        fSelectionMessage(NULL),
        fDate(),
        fCurrentDate(BDate::CurrentDate(B_LOCAL_TIME)),
        fFocusChanged(false),
        fSelectionChanged(false),
        fCurrentDayChanged(false),
        fStartOfWeek((int32)B_WEEKDAY_MONDAY),
        fDayNameHeaderVisible(true),
        fWeekNumberHeaderVisible(true)
{
        _InitObject();
}


BCalendarView::BCalendarView(const char* name, uint32 flags)
        :
        BView(name, flags),
        BInvoker(),
        fSelectionMessage(NULL),
        fDate(),
        fCurrentDate(BDate::CurrentDate(B_LOCAL_TIME)),
        fFocusChanged(false),
        fSelectionChanged(false),
        fCurrentDayChanged(false),
        fStartOfWeek((int32)B_WEEKDAY_MONDAY),
        fDayNameHeaderVisible(true),
        fWeekNumberHeaderVisible(true)
{
        _InitObject();
}


BCalendarView::~BCalendarView()
{
        SetSelectionMessage(NULL);
}


BCalendarView::BCalendarView(BMessage* archive)
        :
        BView(archive),
        BInvoker(),
        fSelectionMessage(NULL),
        fDate(archive),
        fCurrentDate(BDate::CurrentDate(B_LOCAL_TIME)),
        fFocusChanged(false),
        fSelectionChanged(false),
        fCurrentDayChanged(false),
        fStartOfWeek((int32)B_WEEKDAY_MONDAY),
        fDayNameHeaderVisible(true),
        fWeekNumberHeaderVisible(true)
{
        if (archive->HasMessage("_invokeMsg")) {
                BMessage* invokationMessage = new BMessage;
                archive->FindMessage("_invokeMsg", invokationMessage);
                SetInvocationMessage(invokationMessage);
        }

        if (archive->HasMessage("_selectMsg")) {
                BMessage* selectionMessage = new BMessage;
                archive->FindMessage("selectMsg", selectionMessage);
                SetSelectionMessage(selectionMessage);
        }

        if (archive->FindInt32("_weekStart", &fStartOfWeek) != B_OK)
                fStartOfWeek = (int32)B_WEEKDAY_MONDAY;

        if (archive->FindBool("_dayHeader", &fDayNameHeaderVisible) != B_OK)
                fDayNameHeaderVisible = true;

        if (archive->FindBool("_weekHeader", &fWeekNumberHeaderVisible) != B_OK)
                fWeekNumberHeaderVisible = true;

        _SetupDayNames();
        _SetupDayNumbers();
        _SetupWeekNumbers();
}


BArchivable*
BCalendarView::Instantiate(BMessage* archive)
{
        if (validate_instantiation(archive, "BCalendarView"))
                return new BCalendarView(archive);

        return NULL;
}


status_t
BCalendarView::Archive(BMessage* archive, bool deep) const
{
        status_t status = BView::Archive(archive, deep);

        if (status == B_OK && InvocationMessage())
                status = archive->AddMessage("_invokeMsg", InvocationMessage());

        if (status == B_OK && SelectionMessage())
                status = archive->AddMessage("_selectMsg", SelectionMessage());

        if (status == B_OK)
                status = fDate.Archive(archive);

        if (status == B_OK)
                status = archive->AddInt32("_weekStart", fStartOfWeek);

        if (status == B_OK)
                status = archive->AddBool("_dayHeader", fDayNameHeaderVisible);

        if (status == B_OK)
                status = archive->AddBool("_weekHeader", fWeekNumberHeaderVisible);

        return status;
}


void
BCalendarView::AttachedToWindow()
{
        BView::AttachedToWindow();

        if (!Messenger().IsValid())
                SetTarget(Window(), NULL);

        SetViewUIColor(B_LIST_BACKGROUND_COLOR);
}


void
BCalendarView::FrameResized(float width, float height)
{
        _SetupDayNames();
        Invalidate(Bounds());
}


void
BCalendarView::Draw(BRect updateRect)
{
        if (LockLooper()) {
                if (fFocusChanged) {
                        _DrawFocusRect();
                        UnlockLooper();
                        return;
                }

                if (fSelectionChanged) {
                        _UpdateSelection();
                        UnlockLooper();
                        return;
                }

                if (fCurrentDayChanged) {
                        _UpdateCurrentDay();
                        UnlockLooper();
                        return;
                }

                _DrawDays();
                _DrawDayHeader();
                _DrawWeekHeader();

                rgb_color background = ui_color(B_PANEL_BACKGROUND_COLOR);
                SetHighColor(tint_color(background, B_DARKEN_3_TINT));
                StrokeRect(Bounds());

                UnlockLooper();
        }
}


void
BCalendarView::DrawDay(BView* owner, BRect frame, const char* text,
        bool isSelected, bool isEnabled, bool focus, bool highlight)
{
        _DrawItem(owner, frame, text, isSelected, isEnabled, focus, highlight);
}


void
BCalendarView::DrawDayName(BView* owner, BRect frame, const char* text)
{
        // we get the full rect, fake this as the internal function
        // shrinks the frame to work properly when drawing a day item
        _DrawItem(owner, frame.InsetByCopy(-1.0, -1.0), text, true);
}


void
BCalendarView::DrawWeekNumber(BView* owner, BRect frame, const char* text)
{
        // we get the full rect, fake this as the internal function
        // shrinks the frame to work properly when drawing a day item
        _DrawItem(owner, frame.InsetByCopy(-1.0, -1.0), text, true);
}


uint32
BCalendarView::SelectionCommand() const
{
        if (SelectionMessage())
                return SelectionMessage()->what;

        return 0;
}


BMessage*
BCalendarView::SelectionMessage() const
{
        return fSelectionMessage;
}


void
BCalendarView::SetSelectionMessage(BMessage* message)
{
        delete fSelectionMessage;
        fSelectionMessage = message;
}


uint32
BCalendarView::InvocationCommand() const
{
        return BInvoker::Command();
}


BMessage*
BCalendarView::InvocationMessage() const
{
        return BInvoker::Message();
}


void
BCalendarView::SetInvocationMessage(BMessage* message)
{
        BInvoker::SetMessage(message);
}


void
BCalendarView::MakeFocus(bool state)
{
        if (IsFocus() == state)
                return;

        BView::MakeFocus(state);

        // TODO: solve this better
        fFocusChanged = true;
        Draw(_RectOfDay(fFocusedDay));
        fFocusChanged = false;
}


status_t
BCalendarView::Invoke(BMessage* message)
{
        bool notify = false;
        uint32 kind = InvokeKind(&notify);

        BMessage clone(kind);
        status_t status = B_BAD_VALUE;

        if (!message && !notify)
                message = Message();

        if (!message) {
                if (!IsWatched())
                        return status;
        } else
                clone = *message;

        clone.AddPointer("source", this);
        clone.AddInt64("when", (int64)system_time());
        clone.AddMessenger("be:sender", BMessenger(this));

        clone.AddInt32("year", fDate.Year());
        clone.AddInt32("month", fDate.Month());
        clone.AddInt32("day", fDate.Day());

        if (message)
                status = BInvoker::Invoke(&clone);

        SendNotices(kind, &clone);

        return status;
}


void
BCalendarView::MouseDown(BPoint where)
{
        if (!IsFocus()) {
                MakeFocus();
                Sync();
                Window()->UpdateIfNeeded();
        }

        BRect frame = Bounds();
        if (fDayNameHeaderVisible)
                frame.top += frame.Height() / 7 - 1.0;

        if (fWeekNumberHeaderVisible)
                frame.left += frame.Width() / 8 - 1.0;

        if (!frame.Contains(where))
                return;

        // try to set to new day
        frame = _SetNewSelectedDay(where);

        // on success
        if (fSelectedDay != fNewSelectedDay) {
                // update focus
                fFocusChanged = true;
                fNewFocusedDay = fNewSelectedDay;
                Draw(_RectOfDay(fFocusedDay));
                fFocusChanged = false;

                // update selection
                fSelectionChanged = true;
                Draw(frame);
                Draw(_RectOfDay(fSelectedDay));
                fSelectionChanged = false;

                // notify that selection changed
                InvokeNotify(SelectionMessage(), B_CONTROL_MODIFIED);
        }

        int32 clicks;
        // on double click invoke
        BMessage* message = Looper()->CurrentMessage();
        if (message->FindInt32("clicks", &clicks) == B_OK && clicks > 1)
                Invoke();
}


void
BCalendarView::KeyDown(const char* bytes, int32 numBytes)
{
        const int32 kRows = 6;
        const int32 kColumns = 7;

        int32 row = fFocusedDay.row;
        int32 column = fFocusedDay.column;

        switch (bytes[0]) {
                case B_LEFT_ARROW:
                        column -= 1;
                        if (column < 0) {
                                column = kColumns - 1;
                                row -= 1;
                                if (row >= 0)
                                        fFocusChanged = true;
                        } else
                                fFocusChanged = true;
                        break;

                case B_RIGHT_ARROW:
                        column += 1;
                        if (column == kColumns) {
                                column = 0;
                                row += 1;
                                if (row < kRows)
                                        fFocusChanged = true;
                        } else
                                fFocusChanged = true;
                        break;

                case B_UP_ARROW:
                        row -= 1;
                        if (row >= 0)
                                fFocusChanged = true;
                        break;

                case B_DOWN_ARROW:
                        row += 1;
                        if (row < kRows)
                                fFocusChanged = true;
                        break;

                case B_PAGE_UP:
                {
                        BDate date(fDate);
                        date.AddMonths(-1);
                        SetDate(date);

                        Invoke();
                        break;
                }

                case B_PAGE_DOWN:
                {
                        BDate date(fDate);
                        date.AddMonths(1);
                        SetDate(date);

                        Invoke();
                        break;
                }

                case B_RETURN:
                case B_SPACE:
                {
                        fSelectionChanged = true;
                        BPoint pt = _RectOfDay(fFocusedDay).LeftTop();
                        Draw(_SetNewSelectedDay(pt + BPoint(4.0, 4.0)));
                        Draw(_RectOfDay(fSelectedDay));
                        fSelectionChanged = false;

                        Invoke();
                        break;
                }

                default:
                        BView::KeyDown(bytes, numBytes);
                        break;
        }

        if (fFocusChanged) {
                fNewFocusedDay.SetTo(row, column);
                Draw(_RectOfDay(fFocusedDay));
                Draw(_RectOfDay(fNewFocusedDay));
                fFocusChanged = false;
        }
}


void
BCalendarView::Pulse()
{
        _UpdateCurrentDate();
}


void
BCalendarView::ResizeToPreferred()
{
        float width;
        float height;

        GetPreferredSize(&width, &height);
        BView::ResizeTo(width, height);
}


void
BCalendarView::GetPreferredSize(float* width, float* height)
{
        _GetPreferredSize(width, height);
}


BSize
BCalendarView::MaxSize()
{
        return BLayoutUtils::ComposeSize(ExplicitMaxSize(),
                BSize(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED));
}


BSize
BCalendarView::MinSize()
{
        float width, height;
        _GetPreferredSize(&width, &height);
        return BLayoutUtils::ComposeSize(ExplicitMinSize(), BSize(width, height));
}


BSize
BCalendarView::PreferredSize()
{
        return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), MinSize());
}


int32
BCalendarView::Day() const
{
        return fDate.Day();
}


int32
BCalendarView::Year() const
{
        return fDate.Year();
}


int32
BCalendarView::Month() const
{
        return fDate.Month();
}


bool
BCalendarView::SetDay(int32 day)
{
        BDate date = Date();
        date.SetDay(day);
        if (!date.IsValid())
                return false;
        SetDate(date);
        return true;
}


bool
BCalendarView::SetMonth(int32 month)
{
        if (month < 1 || month > 12)
                return false;
        BDate date = Date();
        int32 oldDay = date.Day();

        date.SetMonth(month);
        date.SetDay(1); // make sure the date is valid

        // We must make sure that the day in month fits inside the new month.
        if (oldDay > date.DaysInMonth())
                date.SetDay(date.DaysInMonth());
        else
                date.SetDay(oldDay);
        SetDate(date);
        return true;
}


bool
BCalendarView::SetYear(int32 year)
{
        BDate date = Date();

        // This can fail when going from 29 feb. on a leap year to a non-leap year.
        if (date.Month() == 2 && date.Day() == 29 && !date.IsLeapYear(year))
                date.SetDay(28);

        // TODO we should also handle the "hole" at the switch between Julian and
        // Gregorian calendars, which will result in an invalid date.

        date.SetYear(year);
        SetDate(date);
        return true;
}


BDate
BCalendarView::Date() const
{
        return fDate;
}


bool
BCalendarView::SetDate(const BDate& date)
{
        if (!date.IsValid())
                return false;

        if (fDate == date)
                return true;

        if (fDate.Year() == date.Year() && fDate.Month() == date.Month()) {
                fDate = date;

                _SetToDay();
                // update focus
                fFocusChanged = true;
                Draw(_RectOfDay(fFocusedDay));
                fFocusChanged = false;

                // update selection
                fSelectionChanged = true;
                Draw(_RectOfDay(fSelectedDay));
                Draw(_RectOfDay(fNewSelectedDay));
                fSelectionChanged = false;
        } else {
                fDate = date;

                _SetupDayNumbers();
                _SetupWeekNumbers();

                BRect frame = Bounds();
                if (fDayNameHeaderVisible)
                        frame.top += frame.Height() / 7 - 1.0;

                if (fWeekNumberHeaderVisible)
                        frame.left += frame.Width() / 8 - 1.0;

                Draw(frame.InsetBySelf(4.0, 4.0));
        }

        return true;
}


bool
BCalendarView::SetDate(int32 year, int32 month, int32 day)
{
        return SetDate(BDate(year, month, day));
}


BWeekday
BCalendarView::StartOfWeek() const
{
        return BWeekday(fStartOfWeek);
}


void
BCalendarView::SetStartOfWeek(BWeekday startOfWeek)
{
        if (fStartOfWeek == (int32)startOfWeek)
                return;

        fStartOfWeek = (int32)startOfWeek;

        _SetupDayNames();
        _SetupDayNumbers();
        _SetupWeekNumbers();

        Invalidate(Bounds().InsetBySelf(1.0, 1.0));
}


bool
BCalendarView::IsDayNameHeaderVisible() const
{
        return fDayNameHeaderVisible;
}


void
BCalendarView::SetDayNameHeaderVisible(bool visible)
{
        if (fDayNameHeaderVisible == visible)
                return;

        fDayNameHeaderVisible = visible;
        Invalidate(Bounds().InsetBySelf(1.0, 1.0));
}


void
BCalendarView::UpdateDayNameHeader()
{
        if (!fDayNameHeaderVisible)
                return;

        _SetupDayNames();
        Invalidate(Bounds().InsetBySelf(1.0, 1.0));
}


bool
BCalendarView::IsWeekNumberHeaderVisible() const
{
        return fWeekNumberHeaderVisible;
}


void
BCalendarView::SetWeekNumberHeaderVisible(bool visible)
{
        if (fWeekNumberHeaderVisible == visible)
                return;

        fWeekNumberHeaderVisible = visible;
        Invalidate(Bounds().InsetBySelf(1.0, 1.0));
}


void
BCalendarView::_InitObject()
{
        fDate = BDate::CurrentDate(B_LOCAL_TIME);

        BDateFormat().GetStartOfWeek((BWeekday*)&fStartOfWeek);

        _SetupDayNames();
        _SetupDayNumbers();
        _SetupWeekNumbers();
}


void
BCalendarView::_SetToDay()
{
        BDate date(fDate.Year(), fDate.Month(), 1);
        if (!date.IsValid())
                return;

        const int32 firstDayOffset = (7 + date.DayOfWeek() - fStartOfWeek) % 7;

        int32 day = 1 - firstDayOffset;
        for (int32 row = 0; row < 6; ++row) {
                for (int32 column = 0; column < 7; ++column) {
                        if (day == fDate.Day()) {
                                fNewFocusedDay.SetTo(row, column);
                                fNewSelectedDay.SetTo(row, column);
                                return;
                        }
                        day++;
                }
        }

        fNewFocusedDay.SetTo(0, 0);
        fNewSelectedDay.SetTo(0, 0);
}


void
BCalendarView::_SetToCurrentDay()
{
        BDate date(fCurrentDate.Year(), fCurrentDate.Month(), 1);
        if (!date.IsValid())
                return;
        if (fDate.Year() != date.Year() || fDate.Month() != date.Month()) {
                fNewCurrentDay.SetTo(-1, -1);
                return;
        }
        const int32 firstDayOffset = (7 + date.DayOfWeek() - fStartOfWeek) % 7;

        int32 day = 1 - firstDayOffset;
        for (int32 row = 0; row < 6; ++row) {
                for (int32 column = 0; column < 7; ++column) {
                        if (day == fCurrentDate.Day()) {
                                fNewCurrentDay.SetTo(row, column);
                                return;
                        }
                        day++;
                }
        }

        fNewCurrentDay.SetTo(-1, -1);
}


void
BCalendarView::_GetYearMonthForSelection(const Selection& selection,
        int32* year, int32* month) const
{
        BDate startOfMonth(fDate.Year(), fDate.Month(), 1);
        const int32 firstDayOffset
                = (7 + startOfMonth.DayOfWeek() - fStartOfWeek) % 7;
        const int32 daysInMonth = startOfMonth.DaysInMonth();

        BDate date(fDate);
        const int32 dayOffset = selection.row * 7 + selection.column;
        if (dayOffset < firstDayOffset)
                date.AddMonths(-1);
        else if (dayOffset >= firstDayOffset + daysInMonth)
                date.AddMonths(1);
        if (year != NULL)
                *year = date.Year();
        if (month != NULL)
                *month = date.Month();
}


void
BCalendarView::_GetPreferredSize(float* _width, float* _height)
{
        BFont font;
        GetFont(&font);
        font_height fontHeight;
        font.GetHeight(&fontHeight);

        const float height = FontHeight(this) + 4.0;

        int32 rows = 7;
        if (!fDayNameHeaderVisible)
                rows = 6;

        // height = font height * rows + 8 px border
        *_height = height * rows + 8.0;

        float width = 0.0;
        for (int32 column = 0; column < 7; ++column) {
                float tmp = StringWidth(fDayNames[column].String()) + 2.0;
                width = tmp > width ? tmp : width;
        }

        int32 columns = 8;
        if (!fWeekNumberHeaderVisible)
                columns = 7;

        // width = max width day name * 8 column + 8 px border
        *_width = width * columns + 8.0;
}


void
BCalendarView::_SetupDayNames()
{
        BDateFormatStyle style = B_LONG_DATE_FORMAT;
        float width, height;
        while (style !=  B_DATE_FORMAT_STYLE_COUNT) {
                _PopulateDayNames(style);
                GetPreferredSize(&width, &height);
                if (width < Bounds().Width())
                        return;
                style = static_cast<BDateFormatStyle>(static_cast<int>(style) + 1);
        }
}


void
BCalendarView::_PopulateDayNames(BDateFormatStyle style)
{
        for (int32 i = 0; i <= 6; ++i) {
                fDayNames[i] = "";
                BDateFormat().GetDayName(1 + (fStartOfWeek - 1 + i) % 7,
                        fDayNames[i], style);
        }
}


void
BCalendarView::_SetupDayNumbers()
{
        BDate startOfMonth(fDate.Year(), fDate.Month(), 1);
        if (!startOfMonth.IsValid())
                return;

        fFocusedDay.SetTo(0, 0);
        fSelectedDay.SetTo(0, 0);
        fNewFocusedDay.SetTo(0, 0);
        fCurrentDay.SetTo(-1, -1);

        const int32 daysInMonth = startOfMonth.DaysInMonth();
        const int32 firstDayOffset
                = (7 + startOfMonth.DayOfWeek() - fStartOfWeek) % 7;

        // calc the last day one month before
        BDate lastDayInMonthBefore(startOfMonth);
        lastDayInMonthBefore.AddDays(-1);
        const int32 lastDayBefore = lastDayInMonthBefore.DaysInMonth();

        int32 counter = 0;
        int32 firstDayAfter = 1;
        for (int32 row = 0; row < 6; ++row) {
                for (int32 column = 0; column < 7; ++column) {
                        int32 day = 1 + counter - firstDayOffset;
                        if (counter < firstDayOffset)
                                day += lastDayBefore;
                        else if (counter >= firstDayOffset + daysInMonth)
                                day = firstDayAfter++;
                        else if (day == fDate.Day()) {
                                fFocusedDay.SetTo(row, column);
                                fSelectedDay.SetTo(row, column);
                                fNewFocusedDay.SetTo(row, column);
                        }
                        if (day == fCurrentDate.Day() && counter >= firstDayOffset
                                && counter < firstDayOffset + daysInMonth
                                && fDate.Month() == fCurrentDate.Month()
                                && fDate.Year() == fCurrentDate.Year())
                                fCurrentDay.SetTo(row, column);

                        counter++;
                        fDayNumbers[row][column].Truncate(0);
                        fDayNumbers[row][column] << day;
                }
        }
}


void
BCalendarView::_SetupWeekNumbers()
{
        BDate date(fDate.Year(), fDate.Month(), 1);
        if (!date.IsValid())
                return;

        for (int32 row = 0; row < 6; ++row) {
                fWeekNumbers[row].SetTo("");
                fWeekNumbers[row] << date.WeekNumber();
                date.AddDays(7);
        }
}


void
BCalendarView::_DrawDay(int32 currRow, int32 currColumn, int32 row,
        int32 column, int32 counter, BRect frame, const char* text,
        bool focus, bool highlight)
{
        BDate startOfMonth(fDate.Year(), fDate.Month(), 1);
        const int32 firstDayOffset
                = (7 + startOfMonth.DayOfWeek() - fStartOfWeek) % 7;
        const int32 daysMonth = startOfMonth.DaysInMonth();

        bool enabled = true;
        bool selected = false;
        // check for the current date
        if (currRow == row  && currColumn == column) {
                selected = true;        // draw current date selected
                if (counter <= firstDayOffset || counter > firstDayOffset + daysMonth) {
                        enabled = false;        // days of month before or after
                        selected = false;       // not selected but able to get focus
                }
        } else {
                if (counter <= firstDayOffset || counter > firstDayOffset + daysMonth)
                        enabled = false;        // days of month before or after
        }

        DrawDay(this, frame, text, selected, enabled, focus, highlight);
}


void
BCalendarView::_DrawDays()
{
        BRect frame = _FirstCalendarItemFrame();

        const int32 currRow = fSelectedDay.row;
        const int32 currColumn = fSelectedDay.column;

        const bool isFocus = IsFocus();
        const int32 focusRow = fFocusedDay.row;
        const int32 focusColumn = fFocusedDay.column;

        const int32 highlightRow = fCurrentDay.row;
        const int32 highlightColumn = fCurrentDay.column;

        int32 counter = 0;
        for (int32 row = 0; row < 6; ++row) {
                BRect tmp = frame;
                for (int32 column = 0; column < 7; ++column) {
                        counter++;
                        const char* day = fDayNumbers[row][column].String();
                        bool focus = isFocus && focusRow == row && focusColumn == column;
                        bool highlight = highlightRow == row && highlightColumn == column;
                        _DrawDay(currRow, currColumn, row, column, counter, tmp, day,
                                focus, highlight);

                        tmp.OffsetBy(tmp.Width(), 0.0);
                }
                frame.OffsetBy(0.0, frame.Height());
        }
}


void
BCalendarView::_DrawFocusRect()
{
        BRect frame = _FirstCalendarItemFrame();

        const int32 currRow = fSelectedDay.row;
        const int32 currColumn = fSelectedDay.column;

        const int32 focusRow = fFocusedDay.row;
        const int32 focusColumn = fFocusedDay.column;

        const int32 highlightRow = fCurrentDay.row;
        const int32 highlightColumn = fCurrentDay.column;

        int32 counter = 0;
        for (int32 row = 0; row < 6; ++row) {
                BRect tmp = frame;
                for (int32 column = 0; column < 7; ++column) {
                        counter++;
                        if (fNewFocusedDay.row == row && fNewFocusedDay.column == column) {
                                fFocusedDay.SetTo(row, column);

                                bool focus = IsFocus() && true;
                                bool highlight = highlightRow == row && highlightColumn == column;
                                const char* day = fDayNumbers[row][column].String();
                                _DrawDay(currRow, currColumn, row, column, counter, tmp, day,
                                        focus, highlight);
                        } else if (focusRow == row && focusColumn == column) {
                                const char* day = fDayNumbers[row][column].String();
                                bool highlight = highlightRow == row && highlightColumn == column;
                                _DrawDay(currRow, currColumn, row, column, counter, tmp, day,
                                        false, highlight);
                        }
                        tmp.OffsetBy(tmp.Width(), 0.0);
                }
                frame.OffsetBy(0.0, frame.Height());
        }
}


void
BCalendarView::_DrawDayHeader()
{
        if (!fDayNameHeaderVisible)
                return;

        int32 offset = 1;
        int32 columns = 8;
        if (!fWeekNumberHeaderVisible) {
                offset = 0;
                columns = 7;
        }

        BRect frame = Bounds();
        frame.right = frame.Width() / columns - 1.0;
        frame.bottom = frame.Height() / 7.0 - 2.0;
        frame.OffsetBy(4.0, 4.0);

        for (int32 i = 0; i < columns; ++i) {
                if (i == 0 && fWeekNumberHeaderVisible) {
                        DrawDayName(this, frame, "");
                        frame.OffsetBy(frame.Width(), 0.0);
                        continue;
                }
                DrawDayName(this, frame, fDayNames[i - offset].String());
                frame.OffsetBy(frame.Width(), 0.0);
        }
}


void
BCalendarView::_DrawWeekHeader()
{
        if (!fWeekNumberHeaderVisible)
                return;

        int32 rows = 7;
        if (!fDayNameHeaderVisible)
                rows = 6;

        BRect frame = Bounds();
        frame.right = frame.Width() / 8.0 - 2.0;
        frame.bottom = frame.Height() / rows - 1.0;

        float offsetY = 4.0;
        if (fDayNameHeaderVisible)
                offsetY += frame.Height();

        frame.OffsetBy(4.0, offsetY);

        for (int32 row = 0; row < 6; ++row) {
                DrawWeekNumber(this, frame, fWeekNumbers[row].String());
                frame.OffsetBy(0.0, frame.Height());
        }
}


void
BCalendarView::_DrawItem(BView* owner, BRect frame, const char* text,
        bool isSelected, bool isEnabled, bool focus, bool isHighlight)
{
        rgb_color lColor = LowColor();
        rgb_color highColor = HighColor();

        rgb_color textColor = ui_color(B_LIST_ITEM_TEXT_COLOR);
        rgb_color bgColor = ui_color(B_LIST_BACKGROUND_COLOR);
        float tintDisabled = B_LIGHTEN_2_TINT;
        float tintHighlight = B_LIGHTEN_1_TINT;

        if (textColor.red + textColor.green + textColor.blue > 125 * 3)
                tintDisabled  = B_DARKEN_2_TINT;

        if (bgColor.red + bgColor.green + bgColor.blue > 125 * 3)
                tintHighlight = B_DARKEN_1_TINT;

        if (isSelected) {
                SetHighColor(ui_color(B_LIST_SELECTED_BACKGROUND_COLOR));
                textColor = ui_color(B_LIST_SELECTED_ITEM_TEXT_COLOR);
        } else if (isHighlight)
                SetHighColor(tint_color(bgColor, tintHighlight));
        else
                SetHighColor(bgColor);

        SetLowColor(HighColor());

        FillRect(frame.InsetByCopy(1.0, 1.0));

        if (focus) {
                rgb_color focusColor = keyboard_navigation_color();
                SetHighColor(focusColor);
                StrokeRect(frame.InsetByCopy(1.0, 1.0));

                if (!isSelected)
                        textColor = focusColor;
        }

        SetHighColor(textColor);
        if (!isEnabled)
                SetHighColor(tint_color(textColor, tintDisabled));

        float offsetH = frame.Width() / 2.0;
        float offsetV = frame.Height() / 2.0 + FontHeight(owner) / 4.0;

        BFont font(be_plain_font);
        if (isHighlight)
                font.SetFace(B_BOLD_FACE);
        else
                font.SetFace(B_REGULAR_FACE);
        SetFont(&font);

        DrawString(text, BPoint(frame.right - offsetH - StringWidth(text) / 2.0,
                        frame.top + offsetV));

        SetLowColor(lColor);
        SetHighColor(highColor);
}


void
BCalendarView::_UpdateSelection()
{
        BRect frame = _FirstCalendarItemFrame();

        const int32 currRow = fSelectedDay.row;
        const int32 currColumn = fSelectedDay.column;

        const int32 focusRow = fFocusedDay.row;
        const int32 focusColumn = fFocusedDay.column;

        const int32 highlightRow = fCurrentDay.row;
        const int32 highlightColumn = fCurrentDay.column;

        int32 counter = 0;
        for (int32 row = 0; row < 6; ++row) {
                BRect tmp = frame;
                for (int32 column = 0; column < 7; ++column) {
                        counter++;
                        if (fNewSelectedDay.row == row
                                && fNewSelectedDay.column == column) {
                                fSelectedDay.SetTo(row, column);

                                const char* day = fDayNumbers[row][column].String();
                                bool focus = IsFocus() && focusRow == row
                                        && focusColumn == column;
                                bool highlight = highlightRow == row && highlightColumn == column;
                                _DrawDay(row, column, row, column, counter, tmp, day, focus, highlight);
                        } else if (currRow == row && currColumn == column) {
                                const char* day = fDayNumbers[row][column].String();
                                bool focus = IsFocus() && focusRow == row
                                        && focusColumn == column;
                                bool highlight = highlightRow == row && highlightColumn == column;
                                _DrawDay(currRow, currColumn, -1, -1, counter, tmp, day, focus, highlight);
                        }
                        tmp.OffsetBy(tmp.Width(), 0.0);
                }
                frame.OffsetBy(0.0, frame.Height());
        }
}


void
BCalendarView::_UpdateCurrentDay()
{
        BRect frame = _FirstCalendarItemFrame();

        const int32 selectRow = fSelectedDay.row;
        const int32 selectColumn = fSelectedDay.column;

        const int32 focusRow = fFocusedDay.row;
        const int32 focusColumn = fFocusedDay.column;

        const int32 currRow = fCurrentDay.row;
        const int32 currColumn = fCurrentDay.column;

        int32 counter = 0;
        for (int32 row = 0; row < 6; ++row) {
                BRect tmp = frame;
                for (int32 column = 0; column < 7; ++column) {
                        counter++;
                        if (fNewCurrentDay.row == row
                                && fNewCurrentDay.column == column) {
                                fCurrentDay.SetTo(row, column);

                                const char* day = fDayNumbers[row][column].String();
                                bool focus = IsFocus() && focusRow == row
                                        && focusColumn == column;
                                bool isSelected = selectRow == row && selectColumn == column;
                                if (isSelected)
                                        _DrawDay(row, column, row, column, counter, tmp, day, focus, true);
                                else
                                        _DrawDay(row, column, -1, -1, counter, tmp, day, focus, true);

                        } else if (currRow == row && currColumn == column) {
                                const char* day = fDayNumbers[row][column].String();
                                bool focus = IsFocus() && focusRow == row
                                        && focusColumn == column;
                                bool isSelected = selectRow == row && selectColumn == column;
                                if(isSelected)
                                        _DrawDay(currRow, currColumn, row, column, counter, tmp, day, focus, false);
                                else
                                        _DrawDay(currRow, currColumn, -1, -1, counter, tmp, day, focus, false);
                        }
                        tmp.OffsetBy(tmp.Width(), 0.0);
                }
                frame.OffsetBy(0.0, frame.Height());
        }
}


void
BCalendarView::_UpdateCurrentDate()
{
        BDate date = BDate::CurrentDate(B_LOCAL_TIME);

        if (!date.IsValid())
                return;
        if (date == fCurrentDate)
                return;

        fCurrentDate = date;

        _SetToCurrentDay();
        fCurrentDayChanged = true;
        Draw(_RectOfDay(fCurrentDay));
        Draw(_RectOfDay(fNewCurrentDay));
        fCurrentDayChanged = false;

        return;
}


BRect
BCalendarView::_FirstCalendarItemFrame() const
{
        int32 rows = 7;
        int32 columns = 8;

        if (!fDayNameHeaderVisible)
                rows = 6;

        if (!fWeekNumberHeaderVisible)
                columns = 7;

        BRect frame = Bounds();
        frame.right = frame.Width() / columns - 1.0;
        frame.bottom = frame.Height() / rows - 1.0;

        float offsetY = 4.0;
        if (fDayNameHeaderVisible)
                offsetY += frame.Height();

        float offsetX = 4.0;
        if (fWeekNumberHeaderVisible)
                offsetX += frame.Width();

        return frame.OffsetBySelf(offsetX, offsetY);
}


BRect
BCalendarView::_SetNewSelectedDay(const BPoint& where)
{
        BRect frame = _FirstCalendarItemFrame();

        int32 counter = 0;
        for (int32 row = 0; row < 6; ++row) {
                BRect tmp = frame;
                for (int32 column = 0; column < 7; ++column) {
                        counter++;
                        if (tmp.Contains(where)) {
                                fNewSelectedDay.SetTo(row, column);
                                int32 year;
                                int32 month;
                                _GetYearMonthForSelection(fNewSelectedDay, &year, &month);
                                if (month == fDate.Month()) {
                                        // only change date if a day in the current month has been
                                        // selected
                                        int32 day = atoi(fDayNumbers[row][column].String());
                                        fDate.SetDate(year, month, day);
                                }
                                return tmp;
                        }
                        tmp.OffsetBy(tmp.Width(), 0.0);
                }
                frame.OffsetBy(0.0, frame.Height());
        }

        return frame;
}


BRect
BCalendarView::_RectOfDay(const Selection& selection) const
{
        BRect frame = _FirstCalendarItemFrame();

        int32 counter = 0;
        for (int32 row = 0; row < 6; ++row) {
                BRect tmp = frame;
                for (int32 column = 0; column < 7; ++column) {
                        counter++;
                        if (selection.row == row && selection.column == column)
                                return tmp;
                        tmp.OffsetBy(tmp.Width(), 0.0);
                }
                frame.OffsetBy(0.0, frame.Height());
        }

        return frame;
}


}       // namespace BPrivate