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
59
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 */
75 property int dateTimeDisplay: FormDateTimeDelegate.DateTimeDisplay.DateTime
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 spacing: 0
160
161 QQC2.Label {
162 text: root.text
163 Layout.fillWidth: true
164 padding: Kirigami.Units.gridUnit
165 bottomPadding: Kirigami.Units.largeSpacing
166 topPadding: Kirigami.Units.largeSpacing
167 visible: root.text.length > 0 && root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.DateTime
168 Accessible.ignored: true
169 }
170
171 RowLayout {
172 spacing: 0
173
174 Layout.fillWidth: true
175 Layout.minimumWidth: parent.width
176
177 QQC2.AbstractButton {
178 id: dateButton
179
180 property bool androidPickerActive: false
181
182 horizontalPadding: Private.FormCardUnits.horizontalPadding
183 verticalPadding: Private.FormCardUnits.verticalPadding
184
185 Layout.fillWidth: true
186 Layout.maximumWidth: root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.DateTime ? parent.width / 2 : parent.width
187
188 visible: root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.DateTime || root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.Date
189
190 text: if (!isNaN(root.value.valueOf())) {
191 const today = new Date();
192 if (root.value.getFullYear() === today.getFullYear()
193 && root.value.getDate() === today.getDate()
194 && root.value.getMonth() == today.getMonth()) {
195 return i18ndc("kirigami-addons6", "Displayed in place of the date if the selected day is today", "Today");
196 }
197 const locale = Qt.locale();
198 const weekDay = root.value.toLocaleDateString(locale, "ddd, ");
199 if (root.value.getFullYear() == today.getFullYear()) {
200 return weekDay + root.value.toLocaleDateString(locale, Locale.ShortFormat);
201 }
202
203 const escapeRegExp = (strToEscape) => {
204 // Escape special characters for use in a regular expression
205 return strToEscape.replace(/[\-\[\]\/\{\}\‍(\‍)\*\+\?\.\\\^\$\|]/g, "\\$&");
206 };
207
208 const trimChar = (origString, charToTrim) => {
209 charToTrim = escapeRegExp(charToTrim);
210 const regEx = new RegExp("^[" + charToTrim + "]+|[" + charToTrim + "]+$", "g");
211 return origString.replace(regEx, "");
212 };
213
214 let dateFormat = locale.dateFormat(Locale.ShortFormat)
215 .replace(root.value.getFullYear(), '')
216 .replace('yyyy', ''); // I'll be long dead when this will break and this won't be my problem anymore
217
218 dateFormat = trimChar(trimChar(trimChar(dateFormat, '-'), '.'), '/')
219
220 return weekDay + root.value.toLocaleDateString(locale, dateFormat);
221 } else {
222 i18ndc("kirigami-addons6", "Date is not set", "Not set")
223 }
224
225 contentItem: RowLayout {
226 spacing: 0
227
228 Kirigami.Icon {
229 source: "view-calendar"
230 Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
231 Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
232 Layout.rightMargin: Private.FormCardUnits.horizontalSpacing
233 }
234
235 QQC2.Label {
236 id: dateLabel
237
238 text: root.text
239 visible: root.text.length > 0 && root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.Date
240
241 Layout.fillWidth: true
242 Accessible.ignored: true
243 }
244
245 QQC2.Label {
246 text: dateButton.text
247
248 Layout.fillWidth: !dateLabel.visible
249 Accessible.ignored: true
250 }
251 }
252 onClicked: {
253 if (root.readOnly) {
254 return;
255 }
256
257 let value = root.value;
258
259 if (isNaN(value.valueOf())) {
260 value = root.initialValue;
261 }
262
263 if (root.minimumDate) {
264 root.minimumDate.setHours(0, 0, 0, 0);
265 }
266 if (root.maximumDate) {
267 root.maximumDate.setHours(0, 0, 0, 0);
268 }
269
270 if (Qt.platform.os === 'android') {
271 androidPickerActive = true;
272 DateTime.AndroidIntegration.showDatePicker(value.getTime());
273 } else {
274 const item = datePopup.createObject(root.popupParent, {
275 value: value,
276 minimumDate: root.minimumDate,
277 maximumDate: root.maximumDate,
278 });
279
280 item.accepted.connect(() => {
281 if (isNaN(root.value.valueOf())) {
282 root.value = root.initialValue;
283 }
284 root.value.setFullYear(item.value.getFullYear());
285 root.value.setMonth(item.value.getMonth());
286 root.value.setDate(item.value.getDate());
287 });
288
289 item.open();
290 }
291 }
292
293 background: FormDelegateBackground {
294 visible: !root.readOnly
295 control: dateButton
296 }
297
298 Component {
299 id: datePopup
300 DateTime.DatePopup {
301 x: parent ? Math.round((parent.width - width) / 2) : 0
302 y: parent ? Math.round((parent.height - height) / 2) : 0
303
304 width: Math.min(Kirigami.Units.gridUnit * 20, root.popupParent.width - 2 * Kirigami.Units.gridUnit)
305
306 height: Kirigami.Units.gridUnit * 20
307
308 modal: true
309
310 onClosed: destroy();
311 }
312 }
313
314 Connections {
315 enabled: Qt.platform.os === 'android' && dateButton.androidPickerActive
316 ignoreUnknownSignals: !enabled
317 target: enabled ? DateTime.AndroidIntegration : null
318 function onDatePickerFinished(accepted, newDate) {
319 dateButton.androidPickerActive = false;
320 if (accepted) {
321 if (isNaN(root.value.valueOf())) {
322 root.value = root.initialValue;
323 }
324 root.value.setFullYear(newDate.getFullYear());
325 root.value.setMonth(newDate.getMonth());
326 root.value.setDate(newDate.getDate());
327 }
328 }
329 }
330 }
331
332 Kirigami.Separator {
333 Layout.fillHeight: true
334 Layout.preferredWidth: 1
335 Layout.topMargin: Kirigami.Units.smallSpacing
336 Layout.bottomMargin: Kirigami.Units.smallSpacing
337 opacity: dateButton.hovered || timeButton.hovered || !timeButton.visible || !dateButton.visible ? 0 : 0.5
338 }
339
340 QQC2.AbstractButton {
341 id: timeButton
342
343 property bool androidPickerActive: false
344
345 visible: root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.DateTime || root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.Time
346
347 horizontalPadding: Private.FormCardUnits.horizontalPadding
348 verticalPadding: Private.FormCardUnits.verticalPadding
349
350 Layout.fillWidth: true
351 Layout.maximumWidth: root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.DateTime ? parent.width / 2 : parent.width
352
353 text: if (!isNaN(root.value.valueOf())) {
354 const locale = Qt.locale();
355 const timeFormat = locale.timeFormat(Locale.ShortFormat)
356 .replace(':ss', '');
357 return root.value.toLocaleTimeString(locale, timeFormat);
358 } else {
359 return i18ndc("kirigami-addons6", "Date is not set", "Not set");
360 }
361
362 onClicked: {
363 if (root.readOnly) {
364 return;
365 }
366
367 let value = root.value;
368 if (isNaN(value.valueOf())) {
369 value = root.initialValue;
370 }
371
372 if (Qt.platform.os === 'android') {
373 androidPickerActive = true;
374 DateTime.AndroidIntegration.showTimePicker(value.getTime());
375 } else {
376 const popup = timePopup.createObject(root.popupParent, {
377 value: value,
378 })
379 popup.open();
380 }
381 }
382
383 Component {
384 id: timePopup
385 DateTime.TimePopup {
386 id: popup
387
388 x: parent ? Math.round((parent.width - width) / 2) : 0
389 y: parent ? Math.round((parent.height - height) / 2) : 0
390
391 onClosed: popup.destroy();
392
393 parent: root.popupParent.overlay
394 modal: true
395
396 onAccepted: {
397 if (isNaN(root.value.valueOf())) {
398 root.value = root.initialValue;
399 }
400 root.value.setHours(popup.value.getHours(), popup.value.getMinutes());
401 }
402 }
403 }
404
405 Connections {
406 enabled: Qt.platform.os === 'android' && timeButton.androidPickerActive
407 ignoreUnknownSignals: !enabled
408 target: enabled ? DateTime.AndroidIntegration : null
409 function onTimePickerFinished(accepted, newDate) {
410 timeButton.androidPickerActive = false;
411 if (accepted) {
412 if (isNaN(root.value.valueOf())) {
413 root.value = root.initialValue;
414 }
415 root.value.setHours(newDate.getHours(), newDate.getMinutes());
416 }
417 }
418 }
419
420 contentItem: RowLayout {
421 spacing: 0
422
423 Kirigami.Icon {
424 source: "clock"
425
426 Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
427 Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
428 Layout.rightMargin: Private.FormCardUnits.horizontalSpacing
429 }
430
431 QQC2.Label {
432 id: timeLabel
433 text: root.text
434 visible: root.text.length > 0 && root.dateTimeDisplay === FormDateTimeDelegate.DateTimeDisplay.Time
435
436 Layout.fillWidth: true
437 Accessible.ignored: true
438 }
439
440 QQC2.Label {
441 text: timeButton.text
442 Layout.fillWidth: !timeLabel.visible
443 Accessible.ignored: true
444 }
445 }
446
447 background: FormDelegateBackground {
448 control: timeButton
449 visible: !root.readOnly
450 }
451 }
452 }
453
454 Kirigami.InlineMessage {
455 id: formErrorHandler
456 visible: root.statusMessage.length > 0
457 Layout.topMargin: visible ? Kirigami.Units.smallSpacing : 0
458 Layout.bottomMargin: visible ? Kirigami.Units.smallSpacing : 0
459 Layout.leftMargin: Kirigami.Units.gridUnit
460 Layout.rightMargin: Kirigami.Units.gridUnit
461 Layout.fillWidth: true
462 text: root.statusMessage
463 type: root.status
464 }
465 }
466}
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,...
DateTimeDisplay
Enum containing the different part of the date time that can be displayed.
A background for Form delegates.
Q_SCRIPTABLE CaptureState status()
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 Dec 6 2024 12:05:53 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.