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 }