Warning, /libraries/kirigami-addons/src/formcard/FormDateTimeDelegate.qml is written in an unsupported language. File is not indexed.

0001 // SPDX-FileCopyrightText: 2023 Carl Schwan <carl@carlschwan.eu>
0002 // SPDX-License-Identifier: LGPL-2.0-or-later
0003 
0004 import QtQuick 2.15
0005 import QtQuick.Layouts 1.15
0006 import QtQuick.Controls 2.15 as QQC2
0007 import org.kde.kirigami 2.20 as Kirigami
0008 import org.kde.kirigamiaddons.dateandtime 1.0 as DateTime
0009 import org.kde.kirigamiaddons.components 1.0 as Components
0010 
0011 /**
0012  * FormDateTimeDelegate is a delegate for FormCard that lets the user enters either
0013  * a date, a time or both.
0014  *
0015  * This component allow to define a minimumDate and maximumDate to restrict
0016  * the date that the user is allowed to enters.
0017  *
0018  * Ideally for this FormDelegate, it is better to not add a label but to
0019  * instead makes it clear from the above FormHeader to that the form delegate
0020  * refers too.
0021  *
0022  * @code{.qml}
0023  * import org.kde.kirigamiaddons.formcard 1.0 as FormCard
0024  *
0025  * FormCard.FormCardPage {
0026  *     FormCard.FormHeader {
0027  *         title: "Departure"
0028  *     }
0029  *
0030  *     FormCard.FormCard {
0031  *         FormCard.FormDateTimeDelegate {}
0032  *
0033  *         FormCard.FormDelegateSeparator {}
0034  *
0035  *         FormCard.FormTextFieldDelegate {
0036  *             label: "Location"
0037  *         }
0038  *     }
0039  * }
0040  * @endcode
0041  *
0042  * @image html formdatetimedelegate.png The form card delegate
0043  *
0044  * @image html formdatetimedelegatedatepicker.png The date picker
0045  *
0046  * @image html formdatetimedelegatetimepicker.png The time picker
0047  *
0048  * @note This component can also be used in a read only mode to display a date.
0049  *
0050  * @warning This will use the native date and time picker from the platform if
0051  * available. E.g. this happens on Android.
0052  *
0053  * @since KirigamiAddons 0.12.0
0054  */
0055 AbstractFormDelegate {
0056     id: root
0057 
0058     /**
0059      * Enum containing the different part of the date time that can be displayed.
0060      */
0061     enum DateTimeDisplay {
0062         DateTime, ///< Show the date and time
0063         Date, ///< Show only the date
0064         Time ///< Show only the time
0065     }
0066 
0067     /**
0068      * This property holds which part of the date and time selector are show to the
0069      * user.
0070      *
0071      * By default both the time and the date are shown.
0072      */
0073     property int dateTimeDisplay: FormDateTimeDelegate.DateTimeDisplay.DateTime
0074 
0075     /**
0076      * This property holds the minimum date (inclusive) that the user can select.
0077      *
0078      * By default, no limit is applied to the date selection.
0079      */
0080     property date minimumDate
0081 
0082     /**
0083      * This property holds the maximum date (inclusive) that the user can select.
0084      *
0085      * By default, no limit is applied to the date selection.
0086      */
0087     property date maximumDate
0088 
0089     /**
0090      * This property holds the the date to use as initial default when editing an
0091      * an unset date.
0092      *
0093      * By default, this is the current date/time.
0094      */
0095     property date initialValue: new Date()
0096 
0097     /**
0098      * This property holds whether this delegate is readOnly or whether the user
0099      * can select a new time and date.
0100      */
0101     property bool readOnly: false
0102 
0103     /**
0104      * @brief The current date and time selected by the user.
0105      */
0106     property date value: new Date()
0107 
0108     /**
0109      * @brief This property holds the current status message type of
0110      * the text field.
0111      *
0112      * This consists of an inline message with a colorful background
0113      * and an appropriate icon.
0114      *
0115      * The status property will affect the color of ::statusMessage used.
0116      *
0117      * Accepted values:
0118      * - `Kirigami.MessageType.Information` (blue color)
0119      * - `Kirigami.MessageType.Positive` (green color)
0120      * - `Kirigami.MessageType.Warning` (orange color)
0121      * - `Kirigami.MessageType.Error` (red color)
0122      *
0123      * default: `Kirigami.MessageType.Information` if ::statusMessage is set,
0124      * nothing otherwise.
0125      *
0126      * @see Kirigami.MessageType
0127      */
0128     property var status: Kirigami.MessageType.Information
0129 
0130     /**
0131      * @brief This property holds the current status message of
0132      * the text field.
0133      *
0134      * If this property is not set, no ::status will be shown.
0135      */
0136     property string statusMessage: ""
0137 
0138     background: null
0139 
0140     focusPolicy: text.length > 0 ? Qt.TabFocus : Qt.NoFocus
0141 
0142     padding: 0
0143     topPadding: undefined
0144     leftPadding: undefined
0145     rightPadding: undefined
0146     bottomPadding: undefined
0147     verticalPadding: undefined
0148     horizontalPadding: undefined
0149 
0150     contentItem: ColumnLayout {
0151         spacing: 0
0152 
0153         QQC2.Label {
0154             text: root.text
0155             Layout.fillWidth: true
0156             padding: Kirigami.Units.gridUnit
0157             bottomPadding: Kirigami.Units.largeSpacing
0158             topPadding: Kirigami.Units.largeSpacing
0159             visible: root.text.length > 0 && root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.DateTime
0160             Accessible.ignored: true
0161         }
0162 
0163         RowLayout {
0164             spacing: 0
0165 
0166             Layout.fillWidth: true
0167             Layout.minimumWidth: parent.width
0168 
0169             QQC2.AbstractButton {
0170                 id: dateButton
0171 
0172                 property bool androidPickerActive: false
0173 
0174                 horizontalPadding: Kirigami.Units.gridUnit
0175                 verticalPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
0176 
0177                 Layout.fillWidth: true
0178                 Layout.maximumWidth: root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.DateTime ? parent.width / 2 : parent.width
0179 
0180                 visible: root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.DateTime || root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.Date
0181 
0182                 text: if (!isNaN(root.value.valueOf())) {
0183                     const today = new Date();
0184                     if (root.value.getFullYear() === today.getFullYear()
0185                         && root.value.getDate() === today.getDate()
0186                         && root.value.getMonth() == today.getMonth()) {
0187                         return i18ndc("kirigami-addons6", "Displayed in place of the date if the selected day is today", "Today");
0188                     }
0189                     const locale = Qt.locale();
0190                     const weekDay = root.value.toLocaleDateString(locale, "ddd, ");
0191                     if (root.value.getFullYear() == today.getFullYear()) {
0192                         return weekDay + root.value.toLocaleDateString(locale, Locale.ShortFormat);
0193                     }
0194 
0195                     const escapeRegExp = (strToEscape) => {
0196                         // Escape special characters for use in a regular expression
0197                         return strToEscape.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
0198                     };
0199 
0200                     const trimChar = (origString, charToTrim) => {
0201                         charToTrim = escapeRegExp(charToTrim);
0202                         const regEx = new RegExp("^[" + charToTrim + "]+|[" + charToTrim + "]+$", "g");
0203                         return origString.replace(regEx, "");
0204                     };
0205 
0206                     let dateFormat = locale.dateFormat(Locale.ShortFormat)
0207                         .replace(root.value.getFullYear(), '')
0208                         .replace('yyyy', ''); // I'll be long dead when this will break and this won't be my problem anymore
0209 
0210                     dateFormat = trimChar(trimChar(trimChar(dateFormat, '-'), '.'), '/')
0211 
0212                     return weekDay + root.value.toLocaleDateString(locale, dateFormat);
0213                 } else {
0214                     i18ndc("kirigami-addons6", "Date is not set", "Not set")
0215                 }
0216 
0217                 contentItem: RowLayout {
0218                     spacing: 0
0219 
0220                     Kirigami.Icon {
0221                         source: "view-calendar"
0222                         Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
0223                         Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
0224                         Layout.rightMargin: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
0225                     }
0226 
0227                     QQC2.Label {
0228                         id: dateLabel
0229 
0230                         text: root.text
0231                         visible: root.text.length > 0 && root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.Date
0232 
0233                         Layout.fillWidth: true
0234                         Accessible.ignored: true
0235                     }
0236 
0237                     QQC2.Label {
0238                         text: dateButton.text
0239 
0240                         Layout.fillWidth: !dateLabel.visible
0241                         Accessible.ignored: true
0242                     }
0243                 }
0244                 onClicked: {
0245                     if (root.readOnly) {
0246                         return;
0247                     }
0248 
0249                     let value = root.value;
0250 
0251                     if (isNaN(value.valueOf())) {
0252                         value = root.initialValue;
0253                     }
0254 
0255                     if (root.minimumDate) {
0256                         root.minimumDate.setHours(0, 0, 0, 0);
0257                     }
0258                     if (root.maximumDate) {
0259                         root.maximumDate.setHours(0, 0, 0, 0);
0260                     }
0261 
0262                     if (Qt.platform.os === 'android') {
0263                         androidPickerActive = true;
0264                         DateTime.AndroidIntegration.showDatePicker(value.getTime());
0265                     } else {
0266                         const item = datePopup.createObject(applicationWindow(), {
0267                             value: value,
0268                             minimumDate: root.minimumDate,
0269                             maximumDate: root.maximumDate,
0270                         });
0271 
0272                         item.accepted.connect(() => {
0273                             if (isNaN(root.value.valueOf())) {
0274                                 root.value = root.initialValue;
0275                             }
0276                             root.value.setFullYear(item.value.getFullYear());
0277                             root.value.setMonth(item.value.getMonth());
0278                             root.value.setDate(item.value.getDate());
0279                         });
0280 
0281                         item.open();
0282                     }
0283                 }
0284 
0285                 background: FormDelegateBackground {
0286                     visible: !root.readOnly
0287                     control: dateButton
0288                 }
0289 
0290                 Component {
0291                     id: datePopup
0292                     DateTime.DatePopup {
0293                         x: parent ? Math.round((parent.width - width) / 2) : 0
0294                         y: parent ? Math.round((parent.height - height) / 2) : 0
0295 
0296                         width: Math.min(Kirigami.Units.gridUnit * 20, applicationWindow().width - 2 * Kirigami.Units.gridUnit)
0297 
0298                         height: Kirigami.Units.gridUnit * 20
0299 
0300                         modal: true
0301 
0302                         onClosed: destroy();
0303                     }
0304                 }
0305 
0306                 Connections {
0307                     enabled: Qt.platform.os === 'android' && dateButton.androidPickerActive
0308                     ignoreUnknownSignals: !enabled
0309                     target: enabled ? DateTime.AndroidIntegration : null
0310                     function onDatePickerFinished(accepted, newDate) {
0311                         dateButton.androidPickerActive = false;
0312                         if (accepted) {
0313                             if (isNaN(root.value.valueOf())) {
0314                                 root.value = root.initialValue;
0315                             }
0316                             root.value.setFullYear(newDate.getFullYear());
0317                             root.value.setMonth(newDate.getMonth());
0318                             root.value.setDate(newDate.getDate());
0319                         }
0320                     }
0321                 }
0322             }
0323 
0324             Kirigami.Separator {
0325                 Layout.fillHeight: true
0326                 Layout.preferredWidth: 1
0327                 Layout.topMargin: Kirigami.Units.smallSpacing
0328                 Layout.bottomMargin: Kirigami.Units.smallSpacing
0329                 opacity: dateButton.hovered || timeButton.hovered ? 0 : 0.5
0330             }
0331 
0332             QQC2.AbstractButton {
0333                 id: timeButton
0334 
0335                 property bool androidPickerActive: false
0336 
0337                 visible: root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.DateTime || root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.Time
0338 
0339                 horizontalPadding: Kirigami.Units.gridUnit
0340                 verticalPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
0341 
0342                 Layout.fillWidth: true
0343                 Layout.maximumWidth: root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.DateTime ? parent.width / 2 : parent.width
0344 
0345                 text: if (!isNaN(root.value.valueOf())) {
0346                     const locale = Qt.locale();
0347                     const timeFormat = locale.timeFormat(Locale.ShortFormat)
0348                         .replace(':ss', '');
0349                     return root.value.toLocaleTimeString(locale, timeFormat);
0350                 } else {
0351                     return i18ndc("kirigami-addons6", "Date is not set", "Not set");
0352                 }
0353 
0354                 onClicked: {
0355                     if (root.readOnly) {
0356                         return;
0357                     }
0358 
0359                     let value = root.value;
0360                     if (isNaN(value.valueOf())) {
0361                         value = root.initialValue;
0362                     }
0363 
0364                     if (Qt.platform.os === 'android') {
0365                         androidPickerActive = true;
0366                         DateTime.AndroidIntegration.showTimePicker(value.getTime());
0367                     } else {
0368                         const popup = timePopup.createObject(applicationWindow(), {
0369                             value: value,
0370                         })
0371                         popup.open();
0372                     }
0373                 }
0374 
0375                 Component {
0376                     id: timePopup
0377                     DateTime.TimePopup {
0378                         id: popup
0379 
0380                         x: parent ? Math.round((parent.width - width) / 2) : 0
0381                         y: parent ? Math.round((parent.height - height) / 2) : 0
0382 
0383                         onClosed: popup.destroy();
0384 
0385                         parent: applicationWindow().overlay
0386                         modal: true
0387 
0388                         onAccepted: {
0389                             if (isNaN(root.value.valueOf())) {
0390                                 root.value = root.initialValue;
0391                             }
0392                             root.value.setHours(popup.value.getHours(), popup.value.getMinutes());
0393                         }
0394                     }
0395                 }
0396 
0397                 Connections {
0398                     enabled: Qt.platform.os === 'android' && timeButton.androidPickerActive
0399                     ignoreUnknownSignals: !enabled
0400                     target: enabled ? DateTime.AndroidIntegration : null
0401                     function onTimePickerFinished(accepted, newDate) {
0402                         timeButton.androidPickerActive = false;
0403                         if (accepted) {
0404                             if (isNaN(root.value.valueOf())) {
0405                                 root.value = root.initialValue;
0406                             }
0407                             root.value.setHours(newDate.getHours(), newDate.getMinutes());
0408                         }
0409                     }
0410                 }
0411 
0412                 contentItem: RowLayout {
0413                     spacing: 0
0414 
0415                     Kirigami.Icon {
0416                         source: "clock"
0417 
0418                         Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
0419                         Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
0420                         Layout.rightMargin: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
0421                     }
0422 
0423                     QQC2.Label {
0424                         id: timeLabel
0425                         text: root.text
0426                         visible: root.text.length > 0 && root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.Time
0427 
0428                         Layout.fillWidth: true
0429                         Accessible.ignored: true
0430                     }
0431 
0432                     QQC2.Label {
0433                         text: timeButton.text
0434                         Layout.fillWidth: !timeLabel.visible
0435                         Accessible.ignored: true
0436                     }
0437                 }
0438 
0439                 background: FormDelegateBackground {
0440                     control: timeButton
0441                     visible: !root.readOnly
0442                 }
0443             }
0444         }
0445 
0446         Kirigami.InlineMessage {
0447             id: formErrorHandler
0448             visible: root.statusMessage.length > 0
0449             Layout.topMargin: visible ? Kirigami.Units.smallSpacing : 0
0450             Layout.bottomMargin: visible ? Kirigami.Units.smallSpacing : 0
0451             Layout.leftMargin: Kirigami.Units.gridUnit
0452             Layout.rightMargin: Kirigami.Units.gridUnit
0453             Layout.fillWidth: true
0454             text: root.statusMessage
0455             type: root.status
0456         }
0457     }
0458 }