Kirigami-addons

FormDateTimeDelegate.qml
1// SPDX-FileCopyrightText: 2023 Carl Schwan <carl@carlschwan.eu>
2// SPDX-License-Identifier: LGPL-2.0-or-later
3
4import QtQuick 2.15
5import QtQuick.Layouts 1.15
6import QtQuick.Controls 2.15 as QQC2
7import org.kde.kirigami 2.20 as Kirigami
8import org.kde.kirigamiaddons.dateandtime 1.0 as DateTime
9import org.kde.kirigamiaddons.components 1.0 as Components
10
11/**
12 * FormDateTimeDelegate is a delegate for FormCard that lets the user enters either
13 * a date, a time or both.
14 *
15 * This component allow to define a minimumDate and maximumDate to restrict
16 * the date that the user is allowed to enters.
17 *
18 * Ideally for this FormDelegate, it is better to not add a label but to
19 * instead makes it clear from the above FormHeader to that the form delegate
20 * refers too.
21 *
22 * @code{.qml}
23 * import org.kde.kirigamiaddons.formcard 1.0 as FormCard
24 *
25 * FormCard.FormCardPage {
26 * FormCard.FormHeader {
27 * title: "Departure"
28 * }
29 *
30 * FormCard.FormCard {
31 * FormCard.FormDateTimeDelegate {}
32 *
33 * FormCard.FormDelegateSeparator {}
34 *
35 * FormCard.FormTextFieldDelegate {
36 * label: "Location"
37 * }
38 * }
39 * }
40 * @endcode
41 *
42 * @image html formdatetimedelegate.png The form card delegate
43 *
44 * @image html formdatetimedelegatedatepicker.png The date picker
45 *
46 * @image html formdatetimedelegatetimepicker.png The time picker
47 *
48 * @note This component can also be used in a read only mode to display a date.
49 *
50 * @warning This will use the native date and time picker from the platform if
51 * available. E.g. this happens on Android.
52 *
53 * @since KirigamiAddons 0.12.0
54 */
55AbstractFormDelegate {
56 id: root
57
58 /**
59 * Enum containing the different part of the date time that can be displayed.
60 */
61 enum DateTimeDisplay {
62 DateTime, ///< Show the date and time
63 Date, ///< Show only the date
64 Time ///< Show only the time
65 }
66
67 /**
68 * This property holds which part of the date and time selector are show to the
69 * user.
70 *
71 * By default both the time and the date are shown.
72 */
73 property int dateTimeDisplay: FormDateTimeDelegate.DateTimeDisplay.DateTime
74
75 /**
76 * This property holds the minimum date (inclusive) that the user can select.
77 *
78 * By default, no limit is applied to the date selection.
79 */
80 property date minimumDate
82 /**
83 * This property holds the maximum date (inclusive) that the user can select.
84 *
85 * By default, no limit is applied to the date selection.
86 */
87 property date maximumDate
89 /**
90 * This property holds the the date to use as initial default when editing an
91 * an unset date.
92 *
93 * By default, this is the current date/time.
94 */
95 property date initialValue: new Date()
96
97 /**
98 * This property holds whether this delegate is readOnly or whether the user
99 * can select a new time and date.
100 */
101 property bool readOnly: false
102
103 /**
104 * @brief The current date and time selected by the user.
105 */
106 property date value: new Date()
107
108 /**
109 * @brief This property holds the current status message type of
110 * the text field.
111 *
112 * This consists of an inline message with a colorful background
113 * and an appropriate icon.
114 *
115 * The status property will affect the color of ::statusMessage used.
116 *
117 * Accepted values:
118 * - `Kirigami.MessageType.Information` (blue color)
119 * - `Kirigami.MessageType.Positive` (green color)
120 * - `Kirigami.MessageType.Warning` (orange color)
121 * - `Kirigami.MessageType.Error` (red color)
122 *
123 * default: `Kirigami.MessageType.Information` if ::statusMessage is set,
124 * nothing otherwise.
126 * @see Kirigami.MessageType
127 */
128 property var status: Kirigami.MessageType.Information
129
130 /**
131 * @brief This property holds the current status message of
132 * the text field.
133 *
134 * If this property is not set, no ::status will be shown.
135 */
136 property string statusMessage: ""
137
138 /**
139 * @brief This property holds the parent used for the popups
140 * of this control.
141 */
142 property var popupParent: QQC2.ApplicationWindow.window
143
144 background: null
145
146 focusPolicy: text.length > 0 ? Qt.TabFocus : Qt.NoFocus
147
148 padding: 0
149 topPadding: undefined
150 leftPadding: undefined
151 rightPadding: undefined
152 bottomPadding: undefined
153 verticalPadding: undefined
154 horizontalPadding: undefined
155
156 contentItem: ColumnLayout {
157 spacing: 0
158
159 QQC2.Label {
160 text: root.text
161 Layout.fillWidth: true
162 padding: Kirigami.Units.gridUnit
163 bottomPadding: Kirigami.Units.largeSpacing
164 topPadding: Kirigami.Units.largeSpacing
165 visible: root.text.length > 0 && root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.DateTime
166 Accessible.ignored: true
167 }
168
169 RowLayout {
170 spacing: 0
171
172 Layout.fillWidth: true
173 Layout.minimumWidth: parent.width
174
175 QQC2.AbstractButton {
176 id: dateButton
177
178 property bool androidPickerActive: false
179
180 horizontalPadding: Kirigami.Units.gridUnit
181 verticalPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
182
183 Layout.fillWidth: true
184 Layout.maximumWidth: root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.DateTime ? parent.width / 2 : parent.width
185
186 visible: root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.DateTime || root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.Date
187
188 text: if (!isNaN(root.value.valueOf())) {
189 const today = new Date();
190 if (root.value.getFullYear() === today.getFullYear()
191 && root.value.getDate() === today.getDate()
192 && root.value.getMonth() == today.getMonth()) {
193 return i18ndc("kirigami-addons6", "Displayed in place of the date if the selected day is today", "Today");
194 }
195 const locale = Qt.locale();
196 const weekDay = root.value.toLocaleDateString(locale, "ddd, ");
197 if (root.value.getFullYear() == today.getFullYear()) {
198 return weekDay + root.value.toLocaleDateString(locale, Locale.ShortFormat);
199 }
200
201 const escapeRegExp = (strToEscape) => {
202 // Escape special characters for use in a regular expression
203 return strToEscape.replace(/[\-\[\]\/\{\}\‍(\‍)\*\+\?\.\\\^\$\|]/g, "\\$&");
204 };
205
206 const trimChar = (origString, charToTrim) => {
207 charToTrim = escapeRegExp(charToTrim);
208 const regEx = new RegExp("^[" + charToTrim + "]+|[" + charToTrim + "]+$", "g");
209 return origString.replace(regEx, "");
210 };
211
212 let dateFormat = locale.dateFormat(Locale.ShortFormat)
213 .replace(root.value.getFullYear(), '')
214 .replace('yyyy', ''); // I'll be long dead when this will break and this won't be my problem anymore
215
216 dateFormat = trimChar(trimChar(trimChar(dateFormat, '-'), '.'), '/')
217
218 return weekDay + root.value.toLocaleDateString(locale, dateFormat);
219 } else {
220 i18ndc("kirigami-addons6", "Date is not set", "Not set")
221 }
222
223 contentItem: RowLayout {
224 spacing: 0
225
226 Kirigami.Icon {
227 source: "view-calendar"
228 Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
229 Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
230 Layout.rightMargin: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
231 }
232
233 QQC2.Label {
234 id: dateLabel
235
236 text: root.text
237 visible: root.text.length > 0 && root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.Date
238
239 Layout.fillWidth: true
240 Accessible.ignored: true
241 }
242
243 QQC2.Label {
244 text: dateButton.text
245
246 Layout.fillWidth: !dateLabel.visible
247 Accessible.ignored: true
248 }
249 }
250 onClicked: {
251 if (root.readOnly) {
252 return;
253 }
254
255 let value = root.value;
256
257 if (isNaN(value.valueOf())) {
258 value = root.initialValue;
259 }
260
261 if (root.minimumDate) {
262 root.minimumDate.setHours(0, 0, 0, 0);
263 }
264 if (root.maximumDate) {
265 root.maximumDate.setHours(0, 0, 0, 0);
266 }
267
268 if (Qt.platform.os === 'android') {
269 androidPickerActive = true;
270 DateTime.AndroidIntegration.showDatePicker(value.getTime());
271 } else {
272 const item = datePopup.createObject(root.popupParent, {
273 value: value,
274 minimumDate: root.minimumDate,
275 maximumDate: root.maximumDate,
276 });
277
278 item.accepted.connect(() => {
279 if (isNaN(root.value.valueOf())) {
280 root.value = root.initialValue;
281 }
282 root.value.setFullYear(item.value.getFullYear());
283 root.value.setMonth(item.value.getMonth());
284 root.value.setDate(item.value.getDate());
285 });
286
287 item.open();
288 }
289 }
290
291 background: FormDelegateBackground {
292 visible: !root.readOnly
293 control: dateButton
294 }
295
296 Component {
297 id: datePopup
298 DateTime.DatePopup {
299 x: parent ? Math.round((parent.width - width) / 2) : 0
300 y: parent ? Math.round((parent.height - height) / 2) : 0
301
302 width: Math.min(Kirigami.Units.gridUnit * 20, root.popupParent.width - 2 * Kirigami.Units.gridUnit)
303
304 height: Kirigami.Units.gridUnit * 20
305
306 modal: true
307
308 onClosed: destroy();
309 }
310 }
311
312 Connections {
313 enabled: Qt.platform.os === 'android' && dateButton.androidPickerActive
314 ignoreUnknownSignals: !enabled
315 target: enabled ? DateTime.AndroidIntegration : null
316 function onDatePickerFinished(accepted, newDate) {
317 dateButton.androidPickerActive = false;
318 if (accepted) {
319 if (isNaN(root.value.valueOf())) {
320 root.value = root.initialValue;
321 }
322 root.value.setFullYear(newDate.getFullYear());
323 root.value.setMonth(newDate.getMonth());
324 root.value.setDate(newDate.getDate());
325 }
326 }
327 }
328 }
329
330 Kirigami.Separator {
331 Layout.fillHeight: true
332 Layout.preferredWidth: 1
333 Layout.topMargin: Kirigami.Units.smallSpacing
334 Layout.bottomMargin: Kirigami.Units.smallSpacing
335 opacity: dateButton.hovered || timeButton.hovered ? 0 : 0.5
336 }
337
338 QQC2.AbstractButton {
339 id: timeButton
340
341 property bool androidPickerActive: false
342
343 visible: root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.DateTime || root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.Time
344
345 horizontalPadding: Kirigami.Units.gridUnit
346 verticalPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
347
348 Layout.fillWidth: true
349 Layout.maximumWidth: root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.DateTime ? parent.width / 2 : parent.width
350
351 text: if (!isNaN(root.value.valueOf())) {
352 const locale = Qt.locale();
353 const timeFormat = locale.timeFormat(Locale.ShortFormat)
354 .replace(':ss', '');
355 return root.value.toLocaleTimeString(locale, timeFormat);
356 } else {
357 return i18ndc("kirigami-addons6", "Date is not set", "Not set");
358 }
359
360 onClicked: {
361 if (root.readOnly) {
362 return;
363 }
364
365 let value = root.value;
366 if (isNaN(value.valueOf())) {
367 value = root.initialValue;
368 }
369
370 if (Qt.platform.os === 'android') {
371 androidPickerActive = true;
372 DateTime.AndroidIntegration.showTimePicker(value.getTime());
373 } else {
374 const popup = timePopup.createObject(root.popupParent, {
375 value: value,
376 })
377 popup.open();
378 }
379 }
380
381 Component {
382 id: timePopup
383 DateTime.TimePopup {
384 id: popup
385
386 x: parent ? Math.round((parent.width - width) / 2) : 0
387 y: parent ? Math.round((parent.height - height) / 2) : 0
388
389 onClosed: popup.destroy();
390
391 parent: root.popupParent.overlay
392 modal: true
393
394 onAccepted: {
395 if (isNaN(root.value.valueOf())) {
396 root.value = root.initialValue;
397 }
398 root.value.setHours(popup.value.getHours(), popup.value.getMinutes());
399 }
400 }
401 }
402
403 Connections {
404 enabled: Qt.platform.os === 'android' && timeButton.androidPickerActive
405 ignoreUnknownSignals: !enabled
406 target: enabled ? DateTime.AndroidIntegration : null
407 function onTimePickerFinished(accepted, newDate) {
408 timeButton.androidPickerActive = false;
409 if (accepted) {
410 if (isNaN(root.value.valueOf())) {
411 root.value = root.initialValue;
412 }
413 root.value.setHours(newDate.getHours(), newDate.getMinutes());
414 }
415 }
416 }
417
418 contentItem: RowLayout {
419 spacing: 0
420
421 Kirigami.Icon {
422 source: "clock"
423
424 Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
425 Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
426 Layout.rightMargin: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
427 }
428
429 QQC2.Label {
430 id: timeLabel
431 text: root.text
432 visible: root.text.length > 0 && root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.Time
433
434 Layout.fillWidth: true
435 Accessible.ignored: true
436 }
437
438 QQC2.Label {
439 text: timeButton.text
440 Layout.fillWidth: !timeLabel.visible
441 Accessible.ignored: true
442 }
443 }
444
445 background: FormDelegateBackground {
446 control: timeButton
447 visible: !root.readOnly
448 }
449 }
450 }
451
452 Kirigami.InlineMessage {
453 id: formErrorHandler
454 visible: root.statusMessage.length > 0
455 Layout.topMargin: visible ? Kirigami.Units.smallSpacing : 0
456 Layout.bottomMargin: visible ? Kirigami.Units.smallSpacing : 0
457 Layout.leftMargin: Kirigami.Units.gridUnit
458 Layout.rightMargin: Kirigami.Units.gridUnit
459 Layout.fillWidth: true
460 text: root.statusMessage
461 type: root.status
462 }
463 }
464}
FormDateTimeDelegate is a delegate for FormCard that lets the user enters either a date,...
DateTimeDisplay
Enum containing the different part of the date time that can be displayed.
A background for Form delegates.
QString i18ndc(const char *domain, const char *context, const char *text, const TYPE &arg...)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri May 3 2024 11:46:57 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.