# Calendar Widget Example

The Calendar Widget example shows use of `QCalendarWidget`.

QCalendarWidget displays one calendar month at a time and lets the user select a date. The calendar consists of four components: a navigation bar that lets the user change the month that is displayed, a grid where each cell represents one day in the month, and two headers that display weekday names and week numbers.

The Calendar Widget example displays a QCalendarWidget and lets the user configure its appearance and behavior using QComboBoxes, QCheckBoxes, and QDateEdits. In addition, the user can influence the formatting of individual dates and headers.

The properties of the QCalendarWidget are summarized in the table below.

Property | Description
--- | ---
selectedDate | The currently selected date.
minimumDate | The earliest date that can be selected.
maximumDate | The latest date that can be selected.
firstDayOfWeek | The day that is displayed as the first day of the week (usually Sunday or Monday).
gridVisible | Whether the grid should be shown.
selectionMode | Whether the user can select a date or not.
horizontalHeaderFormat | The format of the day names in the horizontal header (e.g., "M", "Mon", or "Monday").
verticalHeaderFormat | The format of the vertical header.
navigationBarVisible | Whether the navigation bar at the top of the calendar widget is shown.

The example consists of one class, `CalendarWidget`, which creates and lays out the QCalendarWidget and the other widgets that let the user configure the QCalendarWidget.

## The CalendarWidget Class

As is often the case with classes that represent self-contained windows, most of the API is private. We will review the private members as we stumble upon them in the implementation.

Here is the constructor of CalendarWidget:

            public CalendarWidget() {
                createPreviewGroupBox();
                createGeneralOptionsGroupBox();
                createDatesGroupBox();
                createTextFormatsGroupBox();

                QGridLayout layout = new QGridLayout();
                layout.addWidget(previewGroupBox, 0, 0);
                layout.addWidget(generalOptionsGroupBox, 0, 1);
                layout.addWidget(datesGroupBox, 1, 0);
                layout.addWidget(textFormatsGroupBox, 1, 1);
                layout.setSizeConstraint(QLayout.SizeConstraint.SetFixedSize);
                setLayout(layout);

                previewLayout.setRowMinimumHeight(0, calendar.sizeHint().height());
                previewLayout.setColumnMinimumWidth(0, calendar.sizeHint().width());

                setWindowTitle(tr("Calendar Widget"));
            }

We start by creating the four QGroupBoxes and their child widgets (including the QCalendarWidget) using four private `create...GroupBox()` methods, described below. Then we arrange the group boxes in a QGridLayout.

We set the grid layout's resize policy to QLayout::SetFixedSize to prevent the user from resizing the window. In that mode, the window's size is set automatically by QGridLayout based on the size hints of its contents widgets.

To ensure that the window isn't automatically resized every time we change a property of the QCalendarWidget (e.g., hiding the navigation bar, the vertical header, or the grid), we set the minimum height of row 0 and the minimum width of column 0 to the initial size of the QCalendarWidget.

Let's move on to the `createPreviewGroupBox()` method:

            private void createPreviewGroupBox() {
                previewGroupBox = new QGroupBox(tr("Preview"));

                calendar = new QCalendarWidget();
                calendar.setMinimumDate(new QDate(1900, 1, 1));
                calendar.setMaximumDate(new QDate(3000, 1, 1));
                calendar.setGridVisible(true);

                calendar.currentPageChanged.connect(this, "reformatCalendarPage()");

                previewLayout = new QGridLayout();
                previewLayout.addWidget(calendar, 0, 0, Qt.AlignmentFlag.AlignCenter);
                previewGroupBox.setLayout(previewLayout);

            }

The Preview group box contains only one widget: the QCalendarWidget. We set it up, connect its currentPageChanged() signal to our `reformatCalendarPage()` slot to make sure that every new page gets the formatting specified by the user.

The `createGeneralOptionsGroupBox()` method is somewhat large and several widgets are set up the same way; we look at parts of its implementation here and skip the rest:

            private void createGeneralOptionsGroupBox() {
                generalOptionsGroupBox = new QGroupBox(tr("General Options"));

                localeCombo = new QComboBox();
                int curLocaleIndex = -1;
                int index = 0;
                for (QLocale.Language lang : QLocale.Language.values()) {
                    List<QLocale.Country> countries = QLocale.countriesForLanguage(lang);
                    for (int i = 0; i < countries.size(); ++i) {
                        QLocale.Country country = countries.get(i);
                        String label = QLocale.languageToString(lang);
                        label += "/";
                        label += QLocale.countryToString(country);
                        QLocale locale = new QLocale(lang, country);
                        if (this.locale().language() == lang && this.locale().country() == country)
                            curLocaleIndex = index;
                        localeCombo.addItem(label, locale);
                        ++index;
                    }
                }
                if (curLocaleIndex != -1)
                    localeCombo.setCurrentIndex(curLocaleIndex);
                localeLabel = new QLabel(tr("&Locale"));
                localeLabel.setBuddy(localeCombo);

The calendar widget can display the numbers of dates and years in various languages and locales. The current language/locale used by the calendar is specified by a QLocale. We loop through all possible pairs of languages and locales and add them to the Locale combo box, from which the user selects the current locale of the calendar widget.

Note that the QLocale object for each pair is stored in each combo box item's user data. We can later retrieve this object with QComboBox's itemData() when a new item is selected.

                firstDayCombo = new QComboBox();
                firstDayCombo.addItem(tr("Sunday"), Qt.DayOfWeek.Sunday);
                firstDayCombo.addItem(tr("Monday"), Qt.DayOfWeek.Monday);
                firstDayCombo.addItem(tr("Tuesday"), Qt.DayOfWeek.Tuesday);
                firstDayCombo.addItem(tr("Wednesday"), Qt.DayOfWeek.Wednesday);
                firstDayCombo.addItem(tr("Thursday"), Qt.DayOfWeek.Thursday);
                firstDayCombo.addItem(tr("Friday"), Qt.DayOfWeek.Friday);
                firstDayCombo.addItem(tr("Saturday"), Qt.DayOfWeek.Saturday);

                firstDayLabel = new QLabel(tr("Wee&k starts on:"));
                firstDayLabel.setBuddy(firstDayCombo);
            ...

The Week starts on combobox controls which day should be displayed as the first day of the week.

            ...
                localeCombo.currentIndexChanged.connect(this, "localeChanged(int)");
                firstDayCombo.currentIndexChanged.connect(this, "firstDayChanged(int)");
                selectionModeCombo.currentIndexChanged.connect(this, "selectionModeChanged(int)");
                gridCheckBox.toggled.connect(calendar, "setGridVisible(boolean)");
                navigationCheckBox.toggled.connect(calendar, "setNavigationBarVisible(boolean)");
                horizontalHeaderCombo.currentIndexChanged.connect(this, "horizontalHeaderChanged(int)");
                verticalHeaderCombo.currentIndexChanged.connect(this, "verticalHeaderChanged(int)");
            ...

After creating the widgets, we connect the signals and slots. We connect the comboboxes to private slots of `Window` or to public slots provided by QCalendarWidget.

            ...
                firstDayChanged(firstDayCombo.currentIndex());
                selectionModeChanged(selectionModeCombo.currentIndex());
                horizontalHeaderChanged(horizontalHeaderCombo.currentIndex());
                verticalHeaderChanged(verticalHeaderCombo.currentIndex());
            }

At the end of the method, we call the slots that update the calendar to ensure that the QCalendarWidget is synchronized with the other widgets on startup.

Let's now take a look at the `createDatesGroupBox()` private method:

            private void createDatesGroupBox() {
                datesGroupBox = new QGroupBox(tr("Dates"));

                minimumDateEdit = new QDateEdit();
                minimumDateEdit.setDisplayFormat("MMM d, yyyy");
                minimumDateEdit.setDateRange(calendar.minimumDate(),
                                              calendar.maximumDate());
                minimumDateEdit.setDate(calendar.minimumDate());

                minimumDateLabel = new QLabel(tr("&Minimum Date:"));
                minimumDateLabel.setBuddy(minimumDateEdit);

                currentDateEdit = new QDateEdit();
                currentDateEdit.setDisplayFormat("MMM d, yyyy");
                currentDateEdit.setDate(calendar.selectedDate());
                currentDateEdit.setDateRange(calendar.minimumDate(),
                                              calendar.maximumDate());

                currentDateLabel = new QLabel(tr("&Current Date:"));
                currentDateLabel.setBuddy(currentDateEdit);

                maximumDateEdit = new QDateEdit();
                maximumDateEdit.setDisplayFormat("MMM d, yyyy");
                maximumDateEdit.setDateRange(calendar.minimumDate(),
                                             calendar.maximumDate());
                maximumDateEdit.setDate(calendar.maximumDate());

                maximumDateLabel = new QLabel(tr("Ma&ximum Date:"));
                maximumDateLabel.setBuddy(maximumDateEdit);

In this method, we create the Minimum Date, Maximum Date, and Current Date editor widgets, which control the calendar's minimum, maximum, and selected dates. The calendar's minimum and maximum dates have already been set in `createPrivewGroupBox()`; we can then set the widgets default values to the calendars values.

                currentDateEdit.dateChanged.connect(calendar, "setSelectedDate(QDate)");
                calendar.selectionChanged.connect(this, "selectedDateChanged()");
                minimumDateEdit.dateChanged.connect(this, "minimumDateChanged(QDate)");
                maximumDateEdit.dateChanged.connect(this, "maximumDateChanged(QDate)");
            ...
            }

We connect the `currentDateEdit`'s dateChanged() signal directly to the calendar's setSelectedDate() slot. When the calendar's selected date changes, either as a result of a user action or programmatically, our `selectedDateChanged()` slot updates the Current Date editor. We also need to react when the user changes the Minimum Date and Maximum Date editors.

Here is the `createTextFormatsGroup()` method:

            private void createTextFormatsGroupBox() {
                textFormatsGroupBox = new QGroupBox(tr("Text Formats"));

                weekdayColorCombo = createColorComboBox();
                weekdayColorCombo.setCurrentIndex(
                        weekdayColorCombo.findText(tr("Black")));

                weekdayColorLabel = new QLabel(tr("&Weekday color:"));
                weekdayColorLabel.setBuddy(weekdayColorCombo);

                weekendColorCombo = createColorComboBox();
                weekendColorCombo.setCurrentIndex(
                        weekendColorCombo.findText(tr("Red")));

                weekendColorLabel = new QLabel(tr("Week&end color:"));
                weekendColorLabel.setBuddy(weekendColorCombo);

We set up the Weekday Color and Weekend Color comboboxes using `createColorCombo()`, which instantiates a QComboBox and populates it with colors ("Red", "Blue", etc.).

                headerTextFormatCombo = new QComboBox();
                headerTextFormatCombo.addItem(tr("Bold"));
                headerTextFormatCombo.addItem(tr("Italic"));
                headerTextFormatCombo.addItem(tr("Plain"));

                headerTextFormatLabel = new QLabel(tr("&Header text:"));
                headerTextFormatLabel.setBuddy(headerTextFormatCombo);

                firstFridayCheckBox = new QCheckBox(tr("&First Friday in blue"));

                mayFirstCheckBox = new QCheckBox(tr("May &1 in red"));

The Header Text Format combobox lets the user change the text format (bold, italic, or plain) used for horizontal and vertical headers. The First Friday in blue and May 1 in red check box affect the rendering of specific dates.

                weekdayColorCombo.currentIndexChanged.connect(this, "weekdayFormatChanged()");
                weekendColorCombo.currentIndexChanged.connect(this, "weekendFormatChanged()");
                headerTextFormatCombo.currentStringChanged.connect(this, "reformatHeaders()");
                firstFridayCheckBox.toggled.connect(this, "reformatCalendarPage()");
                mayFirstCheckBox.toggled.connect(this, "reformatCalendarPage()");

We connect the check boxes and comboboxes to various private slots. The First Friday in blue and May 1 in red check boxes are both connected to `reformatCalendarPage()`, which is also called when the calendar switches month.

            ...
                reformatHeaders();
                reformatCalendarPage();
            }

At the end of `createTextFormatsGroupBox()`, we call private slots to synchronize the QCalendarWidget with the other widgets.

We're now done reviewing the four `create...GroupBox()` methods. Let's now take a look at the other private methods and slots.

            private QComboBox createColorComboBox() {
                QComboBox comboBox = new QComboBox();
                comboBox.addItem(tr("Red"), new QColor(Qt.GlobalColor.red));
                comboBox.addItem(tr("Blue"), new QColor(Qt.GlobalColor.blue));
                comboBox.addItem(tr("Black"), new QColor(Qt.GlobalColor.black));
                comboBox.addItem(tr("Magenta"), new QColor(Qt.GlobalColor.magenta));
                return comboBox;
            }

In `createColorCombo()`, we create a combobox and populate it with standard colors. The second argument to QComboBox::addItem() is user data stored for each item (in this case, QColor objects).

This method was used to set up the Weekday Color and Weekend Color comboboxes.

            private void firstDayChanged(int index) {
                calendar.setFirstDayOfWeek((Qt.DayOfWeek) firstDayCombo.itemData(index));
            }

When the user changes the Week starts on combobox's value, `firstDayChanged()` is invoked with the index of the combobox's new value. We retrieve the custom data item associated with the new current item using itemData() and cast it to a Qt::DayOfWeek.

`selectionModeChanged()`, `horizontalHeaderChanged()`, and `verticalHeaderChanged()` are very similar to `firstDayChanged()`, so they are omitted.

            private void selectedDateChanged() {
                currentDateEdit.setDate(calendar.selectedDate());
            }

The `selectedDateChanged()` updates the Current Date editor to reflect the current state of the QCalendarWidget.

            private void minimumDateChanged(QDate date) {
                calendar.setMinimumDate(date);
                maximumDateEdit.setDate(calendar.maximumDate());
            }

When the user changes the minimum date, we tell the QCalenderWidget. We also update the Maximum Date editor, because if the new minimum date is later than the current maximum date, QCalendarWidget will automatically adapt its maximum date to avoid a contradicting state.

            private void maximumDateChanged(QDate date) {
                calendar.setMaximumDate(date);
                minimumDateEdit.setDate(calendar.minimumDate());
            }

`maximumDateChanged()` is implemented similarly to `minimumDateChanged()`.

            private void weekdayFormatChanged() {
                QTextCharFormat format = new QTextCharFormat();

                format.setForeground(new QBrush((QColor) weekdayColorCombo.itemData(weekdayColorCombo.currentIndex())));
                calendar.setWeekdayTextFormat(Qt.DayOfWeek.Monday, format);
                calendar.setWeekdayTextFormat(Qt.DayOfWeek.Tuesday, format);
                calendar.setWeekdayTextFormat(Qt.DayOfWeek.Wednesday, format);
                calendar.setWeekdayTextFormat(Qt.DayOfWeek.Thursday, format);
                calendar.setWeekdayTextFormat(Qt.DayOfWeek.Friday, format);
            }

Each combobox item has a QColor object as user data corresponding to the item's text. After fetching the colors from the comboboxes, we set the text format of each day of the week.

The text format of a column in the calendar is given as a QTextCharFormat, which besides the foreground color lets us specify various character formatting information. In this example, we only show a subset of the possibilities.

            private void weekendFormatChanged() {
                QTextCharFormat format = new QTextCharFormat();

                format.setForeground(new QBrush((QColor) weekendColorCombo.itemData(weekendColorCombo.currentIndex())));
                calendar.setWeekdayTextFormat(Qt.DayOfWeek.Saturday, format);
                calendar.setWeekdayTextFormat(Qt.DayOfWeek.Sunday, format);
            }

`weekendFormatChanged()` is the same as `weekdayFormatChanged()`, except that it affects Saturday and Sunday instead of Monday to Friday.

            private void reformatHeaders() {
                String text = headerTextFormatCombo.currentText();
                QTextCharFormat format = new QTextCharFormat();

                if (text.equals(tr("Bold"))) {
                    format.setFontWeight(QFont.Weight.Bold.value());
                } else if (text.equals(tr("Italic"))) {
                    format.setFontItalic(true);
                } else if (text.equals(tr("Green"))) {
                    format.setForeground(new QBrush(new QColor(Qt.GlobalColor.green)));
                }
                calendar.setHeaderTextFormat(format);
            }

The `reformatHeaders()` slot is called when the user changes the text format of the headers. We compare the current text of the Header Text Format combobox to determine which format to apply. (An alternative would have been to store QTextCharFormat values alongside the combobox items.)

            private void reformatCalendarPage() {
                QTextCharFormat mayFirstFormat = new QTextCharFormat();
                if (mayFirstCheckBox.isChecked())
                    mayFirstFormat.setForeground(new QBrush(new QColor(Qt.GlobalColor.red)));

                QTextCharFormat firstFridayFormat = new QTextCharFormat();
                if (firstFridayCheckBox.isChecked())
                    firstFridayFormat.setForeground(new QBrush(new QColor(Qt.GlobalColor.blue)));

                QDate date = new QDate(calendar.yearShown(), calendar.monthShown(), 1);

                calendar.setDateTextFormat(new QDate(date.year(), 5, 1), mayFirstFormat);

                date.setDate(date.year(), date.month(), 1);
                while (date.dayOfWeek() != Qt.DayOfWeek.Friday.value())
                    date = date.addDays(1);
                calendar.setDateTextFormat(date, firstFridayFormat);
            }

In `reformatCalendarPage()`, we set the text format of the first Friday in the month and May 1 in the current year. The text formats that are actually used depend on which check boxes are checked.

QCalendarWidget lets us set the text format of individual dates with the setDateTextFormat(). We chose to set the dates when the calendar page changes, i.e., a new month is displayed. We check which of the `mayFirstCheckBox` and `firstDayCheckBox`, if any, are checked and set the text formats accordingly.