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 }