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

KDE's Doxygen guidelines are available online.