Merge lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/rewriteDatePickerInternal into lp:ubuntu-ui-toolkit/staging
- rewriteDatePickerInternal
- Merge into staging
Proposed by
Cris Dywan
Status: | Work in progress | ||||
---|---|---|---|---|---|
Proposed branch: | lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/rewriteDatePickerInternal | ||||
Merge into: | lp:ubuntu-ui-toolkit/staging | ||||
Diff against target: |
1468 lines (+1057/-254) 12 files modified
examples/ubuntu-ui-toolkit-gallery/Pickers.qml (+2/-0) src/imports/Components/Pickers/1.3/DatePicker.qml (+335/-254) src/imports/Components/Pickers/1.3/DatePickerHeader.qml (+98/-0) src/imports/Components/Pickers/1.3/DatePickerMonthDays.qml (+115/-0) src/imports/Components/Pickers/1.3/DatePickerMonthDaysWorker.js (+61/-0) src/imports/Components/Pickers/1.3/DayNightPicker.qml (+50/-0) src/imports/Components/Pickers/1.3/DigitSeparator.qml (+10/-0) src/imports/Components/Pickers/1.3/HourPicker.qml (+114/-0) src/imports/Components/Pickers/1.3/YearListDropdown.qml (+131/-0) src/imports/Components/Pickers/1.3/calendar.js (+79/-0) src/imports/Components/Pickers/1.3/dateutils.js (+53/-0) src/imports/Components/Pickers/Pickers.pro (+9/-0) |
||||
To merge this branch: | bzr merge lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/rewriteDatePickerInternal | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu SDK team | Pending | ||
Review via email: mp+308624@code.launchpad.net |
Commit message
Rewrite DatePicker internals
Description of the change
✔️ Allow overriding individual day display for calendar events integr.
✅ show/hide month/year switcher in calendar view depending on mode
✅ week numbers yes/no
✅ minimum month doesn't work (no arrow, but year selector)
⚪ current day needs highlight
✅ time picker shows calendar
⚪ selected 'last month'-day disappears while pressed
To post a comment you must log in.
Unmerged revisions
- 2139. By Cris Dywan
-
WIP
- 2138. By Cris Dywan
-
WIP
- 2137. By Cris Dywan
-
Rewrite DatePicker internals
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'examples/ubuntu-ui-toolkit-gallery/Pickers.qml' |
2 | --- examples/ubuntu-ui-toolkit-gallery/Pickers.qml 2015-09-24 20:05:52 +0000 |
3 | +++ examples/ubuntu-ui-toolkit-gallery/Pickers.qml 2016-10-25 11:37:58 +0000 |
4 | @@ -23,6 +23,7 @@ |
5 | id: root |
6 | |
7 | property var stringListModel: ["starred", "media-record", "like", "language-chooser", "go-home", "email", "contact-group", "notification", "active-call"] |
8 | + /* |
9 | TemplateSection { |
10 | className: "Picker" |
11 | documentation: "qml-ubuntu-components-pickers-picker.html" |
12 | @@ -205,6 +206,7 @@ |
13 | } |
14 | } |
15 | } |
16 | + */ |
17 | TemplateSection { |
18 | className: "DatePicker" |
19 | documentation: "qml-ubuntu-components-pickers-datepicker.html" |
20 | |
21 | === modified file 'src/imports/Components/Pickers/1.3/DatePicker.qml' |
22 | --- src/imports/Components/Pickers/1.3/DatePicker.qml 2016-03-15 18:16:01 +0000 |
23 | +++ src/imports/Components/Pickers/1.3/DatePicker.qml 2016-10-25 11:37:58 +0000 |
24 | @@ -16,6 +16,8 @@ |
25 | |
26 | import QtQuick 2.4 |
27 | import Ubuntu.Components 1.3 |
28 | +import Ubuntu.Components.Private 1.3 |
29 | +import 'dateutils.js' as DU |
30 | |
31 | /*! |
32 | \qmltype DatePicker |
33 | @@ -167,7 +169,8 @@ |
34 | insensitive. If minimum and maximum are within the same month, the month picker |
35 | will also be insensitive. |
36 | */ |
37 | -StyledItem { |
38 | +Item { |
39 | +// StyledItem { |
40 | id: datePicker |
41 | |
42 | /*! |
43 | @@ -237,7 +240,8 @@ |
44 | The default values are the current date for the minimum, and 50 year distance |
45 | value for maximum. |
46 | */ |
47 | - property date minimum: Date.prototype.midnight.call(new Date()) |
48 | + //property date minimum: Date.prototype.midnight.call(new Date()) |
49 | + property date minimum: DU.clone(null, 'setDate', 1) |
50 | /*! |
51 | \qmlproperty int maximum |
52 | |
53 | @@ -246,11 +250,12 @@ |
54 | |
55 | See \l minimum for more details. |
56 | */ |
57 | - property date maximum: { |
58 | + /*property date maximum: { |
59 | var d = Date.prototype.midnight.call(new Date()); |
60 | d.setFullYear(d.getFullYear() + 50); |
61 | return d; |
62 | - } |
63 | + }*/ |
64 | + property date maximum: DU.clone(minimum, 'setFullYear', minimum.getFullYear() + 50) |
65 | |
66 | /*! |
67 | For convenience, the \b year value of the \l date property. |
68 | @@ -298,11 +303,49 @@ |
69 | The property holds whether the component's pickers are moving. |
70 | \sa Picker::moving |
71 | */ |
72 | - readonly property alias moving: positioner.moving |
73 | - |
74 | - implicitWidth: units.gu(36) |
75 | - implicitHeight: units.gu(20) |
76 | - activeFocusOnPress: true |
77 | + readonly property alias moving: listView.moving |
78 | + |
79 | + implicitWidth: parent.width |
80 | + implicitHeight: content.visible ? content.height : timeContent.height |
81 | + // activeFocusOnPress: true |
82 | + |
83 | + /*! |
84 | + \qmlproperty Component delegate |
85 | + Individual days are rendered by the delegate, in the case of \l mode |
86 | + containing "Months". This is useful to customize the colors, frame, or |
87 | + add visuals for calendar events. |
88 | + Roles: |
89 | + date: the date of the rendered day |
90 | + text: the day as a string |
91 | + currentMonth: the date lies in the currently selected month |
92 | + pressed: whether this day is being pressed |
93 | + */ |
94 | + property Component delegate: Label { |
95 | + id: label |
96 | + anchors.centerIn: parent |
97 | + horizontalAlignment: Text.AlignHCenter |
98 | + verticalAlignment: Text.AlignVCenter |
99 | + property color dayColor: { |
100 | + if (DU.sameDay(date, datePicker.date)) |
101 | + return theme.palette.normal.activity; |
102 | + if (currentMonth) |
103 | + return theme.palette.normal.backgroundText; |
104 | + // FIXME: Original color is too light for Qt.lighter |
105 | + return Qt.darker(theme.palette.normal.base); |
106 | + } |
107 | + color: pressed ? Qt.lighter(dayColor) : dayColor |
108 | + textSize: Label.Medium |
109 | + text: modelData.text |
110 | + |
111 | + Frame { |
112 | + anchors.fill: parent |
113 | + thickness: units.dp(2) |
114 | + radius: units.gu(1.7) |
115 | + color: theme.palette.normal.backgroundText |
116 | + property date today: new Date() |
117 | + visible: date.getFullYear() == today.getFullYear() && date.getMonth() == today.getMonth() && DU.sameDay(date, today) |
118 | + } |
119 | + } |
120 | |
121 | /*! \internal */ |
122 | onMinimumChanged: { |
123 | @@ -323,21 +366,30 @@ |
124 | date = maximum; |
125 | } |
126 | } |
127 | - /*! \internal */ |
128 | - onWidthChanged: { |
129 | - // use dayPicker narrowFormatLimit even if the dayPicker is hidden |
130 | - // and clamp the width so it cannot have less width that the sum of |
131 | - // the three tumblers' narrowFormatLimit |
132 | - var minWidth = 0.0; |
133 | - for (var i = 0; i < tumblerModel.count; i++) { |
134 | - minWidth += tumblerModel.get(i).pickerModel.narrowFormatLimit; |
135 | - } |
136 | - width = Math.max(width, minWidth); |
137 | - } |
138 | + |
139 | + onDateChanged: { |
140 | + if (!moving) |
141 | + listView.positionViewAtIndex(DU.getMonthsRange(minimum, date), ListView.Visible) |
142 | + } |
143 | + |
144 | + property int monthsRange: DU.getMonthsRange(minimum, maximum) |
145 | + // FIXME: Move to Style |
146 | + property int scrollDirection: Qt.Horizontal |
147 | + property int snapMode: ListView.SnapToItem |
148 | + state: '' |
149 | + |
150 | + function cancelFlick() { |
151 | + listView.cancelFlick() |
152 | + } |
153 | + |
154 | + function toggleYearList() { |
155 | + datePicker.state = datePicker.state === 'years-months'? '' : 'years-months' |
156 | + } |
157 | + |
158 | /*! \internal */ |
159 | onModeChanged: internals.updatePickers() |
160 | /*! \internal */ |
161 | - onLocaleChanged: internals.updatePickers() |
162 | + // onLocaleChanged: internals.updatePickers() |
163 | |
164 | Component.onCompleted: { |
165 | if (minimum === undefined) { |
166 | @@ -347,79 +399,10 @@ |
167 | internals.updatePickers(); |
168 | } |
169 | |
170 | - // models |
171 | - YearModel { |
172 | - id: yearModel |
173 | - mainComponent: datePicker |
174 | - pickerCompleted: internals.completed && internals.showYearPicker |
175 | - pickerWidth: (!pickerItem) ? 0 : narrowFormatLimit |
176 | - function syncModels() { |
177 | - dayModel.syncModels(); |
178 | - } |
179 | - } |
180 | - MonthModel { |
181 | - id: monthModel |
182 | - mainComponent: datePicker |
183 | - pickerCompleted: internals.completed && internals.showMonthPicker |
184 | - pickerWidth: { |
185 | - if (!pickerItem) { |
186 | - return 0; |
187 | - } |
188 | - return MathUtils.clamp(datePicker.width - yearModel.pickerWidth - dayModel.pickerWidth, narrowFormatLimit, longFormatLimit); |
189 | - } |
190 | - function syncModels() { |
191 | - dayModel.syncModels(); |
192 | - } |
193 | - } |
194 | - DayModel { |
195 | - id: dayModel |
196 | - mainComponent: datePicker |
197 | - pickerCompleted: internals.completed && internals.showDayPicker |
198 | - pickerWidth: { |
199 | - if (!pickerItem) { |
200 | - return 0; |
201 | - } |
202 | - var w = Math.max(datePicker.width * internals.dayPickerRatio, narrowFormatLimit); |
203 | - if (w < longFormatLimit && w >= shortFormatLimit) { |
204 | - return shortFormatLimit; |
205 | - } |
206 | - return w; |
207 | - } |
208 | - } |
209 | - HoursModel { |
210 | - id: hoursModel |
211 | - mainComponent: datePicker |
212 | - pickerCompleted: internals.completed && internals.showHoursPicker |
213 | - pickerWidth: { |
214 | - if (!pickerItem) { |
215 | - return 0; |
216 | - } |
217 | - return narrowFormatLimit; |
218 | - } |
219 | - } |
220 | - MinutesModel { |
221 | - id: minutesModel |
222 | - mainComponent: datePicker |
223 | - pickerCompleted: internals.completed && internals.showMinutesPicker |
224 | - pickerWidth: { |
225 | - if (!pickerItem) { |
226 | - return 0; |
227 | - } |
228 | - return narrowFormatLimit; |
229 | - } |
230 | - } |
231 | - SecondsModel { |
232 | - id: secondsModel |
233 | - mainComponent: datePicker |
234 | - pickerCompleted: internals.completed && internals.showSecondsPicker |
235 | - pickerWidth: { |
236 | - if (!pickerItem) { |
237 | - return 0; |
238 | - } |
239 | - return narrowFormatLimit; |
240 | - } |
241 | - } |
242 | + width: parent.width |
243 | + height: content.height |
244 | |
245 | + /* |
246 | styleName: "DatePickerStyle" |
247 | Binding { |
248 | target: __styleInstance |
249 | @@ -437,77 +420,12 @@ |
250 | value: (internals.showHoursPicker || internals.showMinutesPicker || internals.showSecondsPicker) ? |
251 | ":" : "" |
252 | } |
253 | - |
254 | - // tumbler positioner |
255 | - PickerRow { |
256 | - id: positioner |
257 | - parent: (datePicker.__styleInstance && datePicker.__styleInstance.hasOwnProperty("tumblerHolder")) ? |
258 | - datePicker.__styleInstance.tumblerHolder : datePicker |
259 | - mainComponent: datePicker |
260 | - model: tumblerModel |
261 | - margins: internals.margin |
262 | - anchors { |
263 | - top: parent.top |
264 | - bottom: parent.bottom |
265 | - horizontalCenter: parent.horizontalCenter |
266 | - } |
267 | - } |
268 | - // tumbler model |
269 | - ListModel { |
270 | - /* |
271 | - Model to hold tumbler order for repeaters. |
272 | - Roles: |
273 | - - pickerModel |
274 | - - pickerName |
275 | - */ |
276 | - id: tumblerModel |
277 | - |
278 | - /* |
279 | - Signal triggered when the model is about to remove a picker. We cannot rely on |
280 | - rowAboutToBeRemoved, as by the time the signal is called the list element is |
281 | - already removed from the model. |
282 | - */ |
283 | - signal pickerRemoved(int index) |
284 | - |
285 | - // the function checks whether a pickerModel was added or not |
286 | - // returns the index of the model object the pickerModel was found |
287 | - // or -1 on error. |
288 | - function pickerModelIndex(name) { |
289 | - for (var i = 0; i < count; i++) { |
290 | - if (get(i).pickerName === name) { |
291 | - return i; |
292 | - } |
293 | - } |
294 | - return -1; |
295 | - } |
296 | - |
297 | - // the function checks whether a pickerModel is present in the list; |
298 | - // moves the existing one to the given index or inserts it if not present |
299 | - function setPickerModel(model, name, index) { |
300 | - var idx = pickerModelIndex(name); |
301 | - if (idx >= 0) { |
302 | - move(idx, index, 1); |
303 | - } else { |
304 | - append({"pickerModel": model, "pickerName": name}); |
305 | - } |
306 | - } |
307 | - |
308 | - // removes the given picker |
309 | - function removePicker(name) { |
310 | - var idx = pickerModelIndex(name); |
311 | - if (idx >= 0) { |
312 | - pickerRemoved(idx); |
313 | - remove(idx); |
314 | - } |
315 | - } |
316 | - } |
317 | - |
318 | - // component to calculate text fitting |
319 | - Label { id: textSizer; visible: false } |
320 | + */ |
321 | + |
322 | QtObject { |
323 | id: internals |
324 | property bool completed: false |
325 | - property real margin: units.gu(1.5) |
326 | + property real margin: units.gu(2) |
327 | property real dayPickerRatio: 0.1 |
328 | |
329 | property bool showYearPicker: true |
330 | @@ -517,6 +435,7 @@ |
331 | property bool showHoursPicker: false |
332 | property bool showMinutesPicker: false |
333 | property bool showSecondsPicker: false |
334 | + property bool showDayNightPicker: showHoursPicker || showMinutesPicker || showSecondsPicker |
335 | |
336 | /* |
337 | Update pickers. |
338 | @@ -574,99 +493,261 @@ |
339 | console.warn("Date and Time picking not allowed at the same time."); |
340 | return; |
341 | } |
342 | - |
343 | - arrangeTumblers(); |
344 | - resetPickers(); |
345 | - } |
346 | - } |
347 | - |
348 | - /* |
349 | - Resets the pickers. Pickers will update their models with the given date, |
350 | - minimum and maximum values. |
351 | - */ |
352 | - function resetPickers() { |
353 | - if (!completed) return; |
354 | - for (var i = 0; i < tumblerModel.count; i++) { |
355 | - var pickerItem = tumblerModel.get(i).pickerModel.pickerItem; |
356 | - pickerItem.resetPicker(); |
357 | - } |
358 | - |
359 | - // calculate the ratio for the dayPicker |
360 | - var maxWidth = 0.0; |
361 | - maxWidth += showYearPicker ? yearModel.longFormatLimit : 0.0; |
362 | - maxWidth += showMonthPicker ? monthModel.longFormatLimit : 0.0; |
363 | - maxWidth += showDayPicker ? dayModel.longFormatLimit : 0.0; |
364 | - if (showDayPicker && maxWidth > 0.0) { |
365 | - dayPickerRatio = (dayModel.longFormatLimit / maxWidth).toPrecision(3); |
366 | - } |
367 | - } |
368 | - |
369 | - /* |
370 | - Detects the tumbler order from the date format of the locale |
371 | - */ |
372 | - function arrangeTumblers() { |
373 | - // disable completion so avoid accidental date changes |
374 | - completed = false; |
375 | - |
376 | - // use short format to exclude any extra characters |
377 | - // FIXME: The js split(/\W/g) terminates the process on armhf with Qt 5.3 (v4 js) (https://bugreports.qt-project.org/browse/QTBUG-39255) |
378 | - var format = datePicker.locale.dateFormat(Locale.ShortFormat).match(/\w+/g); |
379 | - // loop through the format to decide the position of the tumbler |
380 | - var formatIndex = 0; |
381 | - for (var i in format) { |
382 | - if (!format[i].length) continue; |
383 | - // check the first two characters |
384 | - switch (format[i].substr(0, 1).toLowerCase()) { |
385 | - case 'y': |
386 | - if (showYearPicker) { |
387 | - tumblerModel.setPickerModel(yearModel, "YearPicker", formatIndex); |
388 | - formatIndex++; |
389 | - } else { |
390 | - tumblerModel.removePicker("YearPicker"); |
391 | - } |
392 | - |
393 | - break; |
394 | - case 'm': |
395 | - if (showMonthPicker) { |
396 | - tumblerModel.setPickerModel(monthModel, "MonthPicker", formatIndex); |
397 | - formatIndex++; |
398 | - } else { |
399 | - tumblerModel.removePicker("MonthPicker"); |
400 | - } |
401 | - |
402 | - break; |
403 | - case 'd': |
404 | - if (showDayPicker) { |
405 | - tumblerModel.setPickerModel(dayModel, "DayPicker", formatIndex); |
406 | - formatIndex++; |
407 | - } else { |
408 | - tumblerModel.removePicker("DayPicker"); |
409 | - } |
410 | - break; |
411 | - } |
412 | - } |
413 | - // check hms |
414 | - if (showHoursPicker) { |
415 | - tumblerModel.setPickerModel(hoursModel, "HoursPicker", formatIndex); |
416 | - formatIndex++; |
417 | - } else { |
418 | - tumblerModel.removePicker("HoursPicker"); |
419 | - } |
420 | - if (showMinutesPicker) { |
421 | - tumblerModel.setPickerModel(minutesModel, "MinutesPicker", formatIndex); |
422 | - formatIndex++; |
423 | - } else { |
424 | - tumblerModel.removePicker("MinutesPicker"); |
425 | - } |
426 | - if (showSecondsPicker) { |
427 | - tumblerModel.setPickerModel(secondsModel, "SecondsPicker", formatIndex); |
428 | - formatIndex++; |
429 | - } else { |
430 | - tumblerModel.removePicker("SecondsPicker"); |
431 | - } |
432 | - |
433 | - // re-enable completion |
434 | - completed = true; |
435 | + } |
436 | + } |
437 | + } |
438 | + |
439 | + states: [ |
440 | + State { name: '' }, |
441 | + State { name: 'years-months' } |
442 | + ] |
443 | + |
444 | + property real dropdownAnimDuration: UbuntuAnimation.FastDuration |
445 | + |
446 | + transitions: [ |
447 | + Transition { |
448 | + from: '' |
449 | + to: 'years-months' |
450 | + ParallelAnimation { |
451 | + UbuntuNumberAnimation { |
452 | + target: headerShadow |
453 | + property: 'width' |
454 | + to: content.width |
455 | + duration: datePicker.dropdownAnimDuration / 2 |
456 | + } |
457 | + UbuntuNumberAnimation { |
458 | + target: headerShadow |
459 | + property: 'opacity' |
460 | + to: 1 |
461 | + duration: datePicker.dropdownAnimDuration / 2 |
462 | + } |
463 | + UbuntuNumberAnimation { |
464 | + target: yearsListDropdown |
465 | + property: 'y' |
466 | + to: 0 |
467 | + duration: datePicker.dropdownAnimDuration |
468 | + } |
469 | + SequentialAnimation { |
470 | + PauseAnimation { |
471 | + duration: datePicker.dropdownAnimDuration |
472 | + } |
473 | + UbuntuNumberAnimation { |
474 | + target: headerShadow |
475 | + property: 'width' |
476 | + to: 0 |
477 | + duration: datePicker.dropdownAnimDuration / 1.5 |
478 | + } |
479 | + } |
480 | + } |
481 | + }, |
482 | + Transition { |
483 | + from: 'years-months' |
484 | + to: '' |
485 | + ParallelAnimation { |
486 | + property real duration: 300 |
487 | + UbuntuNumberAnimation { |
488 | + target: headerShadow |
489 | + property: 'width' |
490 | + to: content.width |
491 | + duration: datePicker.dropdownAnimDuration / 2 |
492 | + } |
493 | + UbuntuNumberAnimation { |
494 | + target: headerShadow |
495 | + property: 'opacity' |
496 | + to: 1 |
497 | + duration: datePicker.dropdownAnimDuration / 2 |
498 | + } |
499 | + UbuntuNumberAnimation { |
500 | + target: yearsListDropdown |
501 | + property: 'y' |
502 | + to: -yearsListDropdown.height |
503 | + duration: datePicker.dropdownAnimDuration |
504 | + } |
505 | + SequentialAnimation { |
506 | + PauseAnimation { |
507 | + duration: datePicker.dropdownAnimDuration |
508 | + } |
509 | + UbuntuNumberAnimation { |
510 | + target: headerShadow |
511 | + property: 'width' |
512 | + to: 0 |
513 | + duration: datePicker.dropdownAnimDuration / 1.5 |
514 | + } |
515 | + UbuntuNumberAnimation { |
516 | + target: headerShadow |
517 | + property: 'opacity' |
518 | + to: 0 |
519 | + duration: datePicker.dropdownAnimDuration / 3 |
520 | + } |
521 | + } |
522 | + } |
523 | + } |
524 | + ] |
525 | + |
526 | + Rectangle { |
527 | + color: theme.palette.normal.background |
528 | + anchors.fill: parent |
529 | + } |
530 | + |
531 | + // TimePicker |
532 | + |
533 | + Item { |
534 | + id: timeContent |
535 | + height: container.itemHeight * 5 |
536 | + anchors.leftMargin: root.margin |
537 | + anchors.rightMargin: root.margin |
538 | + visible: internals.showHoursPicker || internals.showMinutesPicker || internals.showSecondsPicker |
539 | + |
540 | + Row { |
541 | + id: container |
542 | + anchors.fill: parent |
543 | + property real itemHeight: units.gu(2) |
544 | + property int pickerCount: 3 |
545 | + property real pickerWidth: (container.width - internals.margin * container.pickerCount - 1) / container.pickerCount |
546 | + |
547 | + HourPicker { |
548 | + type: 'hours' |
549 | + width: container.pickerWidth |
550 | + height: container.height |
551 | + ampm: internals.showDayPicker |
552 | + } |
553 | + |
554 | + HourPicker { |
555 | + type: 'minutes' |
556 | + width: container.pickerWidth |
557 | + height: container.height |
558 | + } |
559 | + |
560 | + HourPicker { |
561 | + type: 'seconds' |
562 | + width: container.pickerWidth |
563 | + height: container.height |
564 | + } |
565 | + |
566 | + DayNightPicker { |
567 | + width: container.pickerWidth |
568 | + height: container.height |
569 | + itemHeight: container.itemHeight |
570 | + } |
571 | + |
572 | + DigitSeparator { |
573 | + width: container.pickerWidth |
574 | + height: container.height |
575 | + } |
576 | + } |
577 | + } |
578 | + |
579 | + Item { |
580 | + id: selectedShape |
581 | + width: parent.width |
582 | + height: parent.height / 5 |
583 | + y: parent.height / 2 - height / 2 |
584 | + visible: internals.showHoursPicker || internals.showMinutesPicker || internals.showSecondsPicker |
585 | + |
586 | + Rectangle { |
587 | + width: parent.width |
588 | + height: units.dp(1) |
589 | + y: 0 |
590 | + color: UbuntuColors.lightGrey |
591 | + } |
592 | + |
593 | + Rectangle { |
594 | + width: parent.width |
595 | + height: units.dp(1) |
596 | + y: parent.height - height |
597 | + color: UbuntuColors.lightGrey |
598 | + } |
599 | + } |
600 | + |
601 | + // DatePicker |
602 | + |
603 | + Column { |
604 | + id: content |
605 | + width: parent.width |
606 | + visible: internals.showMonthPicker || internals.showYearPicker || internals.showDayPicker |
607 | + |
608 | + DatePickerHeader { |
609 | + id: header |
610 | + anchors.left: parent.left |
611 | + anchors.right: parent.right |
612 | + fontSize: 'large' |
613 | + color: theme.palette.normal.backgroundText |
614 | + activeColor: UbuntuColors.darkGrey |
615 | + date: DU.clone(datePicker.date, 'setDate', 1) |
616 | + minimum: datePicker.minimum |
617 | + maximum: datePicker.maximum |
618 | + onRequestNextMonth: datePicker.date = DU.clone(datePicker.date, 'setMonth', datePicker.date.getMonth() + 1) |
619 | + onRequestPreviousMonth: datePicker.date = DU.clone(datePicker.date, 'setMonth', datePicker.date.getMonth() - 1) |
620 | + onRequestToggleYearList: datePicker.toggleYearList() |
621 | + yearsListOpened: datePicker.state === 'years-months' |
622 | + visible: internals.showYearPicker |
623 | + } |
624 | + |
625 | + Item { |
626 | + width: parent.width |
627 | + height: header.height * 0.5 |
628 | + Rectangle { |
629 | + id: headerShadow |
630 | + y: parent.height |
631 | + visible: false |
632 | + opacity: 0 |
633 | + color: theme.palette.normal.baseText |
634 | + width: 0 |
635 | + height: units.gu(1/16) |
636 | + anchors.horizontalCenter: parent.horizontalCenter |
637 | + } |
638 | + visible: internals.showYearPicker || internals.showMonthPicker |
639 | + } |
640 | + |
641 | + Item { |
642 | + width: parent.width |
643 | + height: listView.height |
644 | + visible: internals.showDayPicker |
645 | + ListView { |
646 | + id: listView |
647 | + clip: true |
648 | + width: parent.width |
649 | + height: currentItem.height |
650 | + model: datePicker.monthsRange |
651 | + snapMode: datePicker.snapMode |
652 | + orientation: datePicker.scrollDirection === Qt.Horizontal ? ListView.Horizontal : ListView.Vertical |
653 | + highlightFollowsCurrentItem: true |
654 | + highlightMoveDuration: 0 |
655 | + highlightRangeMode: ListView.StrictlyEnforceRange |
656 | + property bool indexInit: false |
657 | + onCurrentIndexChanged: { |
658 | + if (!indexInit && currentIndex === 0) { |
659 | + indexInit = true; |
660 | + return; |
661 | + } |
662 | + if (!listView.moving) { |
663 | + return; |
664 | + } |
665 | + datePicker.date = DU.clone(minimum, 'setMonth', minimum.getMonth() + currentIndex); |
666 | + } |
667 | + |
668 | + delegate: DatePickerMonthDays { |
669 | + id: panel |
670 | + width: datePicker.width |
671 | + displayedMonth: DU.clone(minimum, 'setMonth', minimum.getMonth() + index) |
672 | + onRequestNextMonth: datePicker.date = DU.clone(displayedMonth, 'setMonth', displayedMonth.getMonth() + 1) |
673 | + onRequestPreviousMonth: datePicker.date = DU.clone(displayedMonth, 'setMonth', displayedMonth.getMonth() - 1) |
674 | + onRequestDateChange: datePicker.date = date |
675 | + delegate: datePicker.delegate |
676 | + } |
677 | + } |
678 | + |
679 | + Item { |
680 | + clip: true |
681 | + width: parent.width |
682 | + height: parent.height |
683 | + YearListDropdown { |
684 | + id: yearsListDropdown |
685 | + minimum: datePicker.minimum |
686 | + maximum: datePicker.maximum |
687 | + onRequestDateChange: datePicker.date = newDate |
688 | + } |
689 | + } |
690 | } |
691 | } |
692 | } |
693 | |
694 | === added file 'src/imports/Components/Pickers/1.3/DatePickerHeader.qml' |
695 | --- src/imports/Components/Pickers/1.3/DatePickerHeader.qml 1970-01-01 00:00:00 +0000 |
696 | +++ src/imports/Components/Pickers/1.3/DatePickerHeader.qml 2016-10-25 11:37:58 +0000 |
697 | @@ -0,0 +1,98 @@ |
698 | +import QtQuick 2.4 |
699 | +import Ubuntu.Components 1.3 |
700 | +import './calendar.js' as Calendar |
701 | +import './dateutils.js' as DU |
702 | + |
703 | +Item { |
704 | + id: root |
705 | + |
706 | + property string fontSize: 'large' |
707 | + property color color: 'black' |
708 | + property color activeColor: 'grey' |
709 | + |
710 | + property date date: new Date() |
711 | + property date minimum: DU.clone(null, 'setDate', 1) |
712 | + property date maximum: DU.clone(minimum, 'setFullYear', minimum.getFullYear() + 50) |
713 | + |
714 | + property bool yearsListOpened: false |
715 | + |
716 | + signal requestPreviousMonth() |
717 | + signal requestNextMonth() |
718 | + signal requestToggleYearList() |
719 | + |
720 | + width: parent.width |
721 | + height: month.height |
722 | + |
723 | + MouseArea { |
724 | + id: yearDropDown |
725 | + onReleased: if (containsMouse) requestToggleYearList() |
726 | + anchors.fill: parent |
727 | + Row { |
728 | + spacing: 0 |
729 | + anchors.horizontalCenter: parent.horizontalCenter |
730 | + height: parent.height |
731 | + Label { |
732 | + id: month |
733 | + textSize: root.fontSize === 'large' ? Label.Medium : Label.Large |
734 | + text: DU.format(root.date, 'MMMM') |
735 | + horizontalAlignment: Text.AlignHCenter |
736 | + color: yearDropDown.containsPress? root.activeColor : root.color |
737 | + } |
738 | + Item { |
739 | + width: units.gu(0.5) |
740 | + height: 1 |
741 | + } |
742 | + Label { |
743 | + id: year |
744 | + textSize: root.fontSize === 'large' ? Label.Medium : Label.Large |
745 | + text: DU.format(root.date, 'yyyy') |
746 | + horizontalAlignment: Text.AlignHCenter |
747 | + color: yearDropDown.containsPress? root.activeColor : root.color |
748 | + } |
749 | + Item { |
750 | + width: units.gu(0.5) |
751 | + height: 1 |
752 | + } |
753 | + Icon { |
754 | + name: yearsListOpened? 'up' : 'down' |
755 | + height: year.height / 1.5 |
756 | + y: parent.height / 2 - height / 2 + height * 0.1 |
757 | + color: yearDropDown.containsPress? root.activeColor : root.color |
758 | + } |
759 | + } |
760 | + } |
761 | + |
762 | + MouseArea { |
763 | + visible: !DU.sameDay(root.date, minimum) |
764 | + anchors.left: parent.left |
765 | + anchors.leftMargin: units.gu(1.7) |
766 | + width: parent.height |
767 | + height: width |
768 | + onReleased: if (containsMouse) requestPreviousMonth() |
769 | + Icon { |
770 | + anchors.left: parent.left |
771 | + anchors.verticalCenter: parent.verticalCenter |
772 | + width: parent.width |
773 | + height: width |
774 | + color: parent.containsPress? root.activeColor : root.color |
775 | + name: 'previous' |
776 | + } |
777 | + } |
778 | + |
779 | + MouseArea { |
780 | + visible: !DU.sameDay(root.date, maximum) |
781 | + anchors.right: parent.right |
782 | + anchors.rightMargin: units.gu(1.7) |
783 | + width: parent.height |
784 | + height: width |
785 | + onReleased: if (containsMouse) requestNextMonth() |
786 | + Icon { |
787 | + anchors.right: parent.right |
788 | + anchors.verticalCenter: parent.verticalCenter |
789 | + width: parent.width |
790 | + height: width |
791 | + color: parent.containsPress? root.activeColor : root.color |
792 | + name: 'next' |
793 | + } |
794 | + } |
795 | +} |
796 | |
797 | === added file 'src/imports/Components/Pickers/1.3/DatePickerMonthDays.qml' |
798 | --- src/imports/Components/Pickers/1.3/DatePickerMonthDays.qml 1970-01-01 00:00:00 +0000 |
799 | +++ src/imports/Components/Pickers/1.3/DatePickerMonthDays.qml 2016-10-25 11:37:58 +0000 |
800 | @@ -0,0 +1,115 @@ |
801 | +import QtQuick 2.4 |
802 | +import Ubuntu.Components 1.3 |
803 | +import './calendar.js' as Calendar |
804 | +import './dateutils.js' as DU |
805 | + |
806 | +MouseArea { |
807 | + id: root |
808 | + |
809 | + height: cellHeight * 7 |
810 | + onPressed: lastPressedPosition = getPosition(mouse) |
811 | + |
812 | + onReleased: { |
813 | + lastPressedPosition = null |
814 | + var pressedPosition = getPosition(mouse) |
815 | + var index = pressedPosition[1] * 7 + pressedPosition[0] |
816 | + var item = monthDaysRepeater.itemAt(index) |
817 | + |
818 | + var currDay = root.monthDays[index] |
819 | + var currDate = currDay.date |
820 | + var dispDate = root.displayedMonth |
821 | + if (currDay.surrounding) { |
822 | + if (DU.compareDates(currDate, dispDate) < 0) { |
823 | + requestPreviousMonth() |
824 | + } else { |
825 | + requestNextMonth() |
826 | + } |
827 | + } else if (root.date.getTime() !== currDate.getTime()) { |
828 | + requestDateChange(currDate) |
829 | + } |
830 | + } |
831 | + |
832 | + property date date: new Date() |
833 | + property date displayedMonth: new Date(date.getTime()) |
834 | + property int firstDayOfWeek: 1 |
835 | + property var monthDays: [] |
836 | + property Component delegate |
837 | + |
838 | + readonly property real cellWidth: width / 7 |
839 | + readonly property real cellHeight: cellWidth * 0.62 |
840 | + |
841 | + property var lastPressedPosition: null |
842 | + |
843 | + property Item pressedItem: { |
844 | + if (!(root.containsPress && root.lastPressedPosition)) { |
845 | + return null |
846 | + } |
847 | + var index = lastPressedPosition[1] * 7 + lastPressedPosition[0] |
848 | + return monthDaysRepeater.itemAt(index) |
849 | + } |
850 | + |
851 | + function getPosition(mouse) { |
852 | + return [ |
853 | + Math.floor(mouse.x / cellWidth), |
854 | + Math.floor((mouse.y - cellHeight) / cellHeight), |
855 | + ] |
856 | + } |
857 | + |
858 | + WorkerScript { |
859 | + source: 'DatePickerMonthDaysWorker.js' |
860 | + onMessage: { |
861 | + if (messageObject.name === 'monthDays') { |
862 | + root.monthDays = messageObject.monthDays |
863 | + monthDaysRepeater.model = messageObject.monthDays.length |
864 | + } |
865 | + } |
866 | + Component.onCompleted: { |
867 | + this.sendMessage({ |
868 | + name: 'requestMonthDays', |
869 | + firstDayOfWeek: firstDayOfWeek, |
870 | + displayedMonth: displayedMonth, |
871 | + }) |
872 | + } |
873 | + } |
874 | + |
875 | + signal requestPreviousMonth() |
876 | + signal requestNextMonth() |
877 | + signal requestDateChange(date date) |
878 | + |
879 | + Repeater { |
880 | + model: 7 |
881 | + Label { |
882 | + id: label |
883 | + width: cellWidth |
884 | + height: cellHeight |
885 | + x: index * cellWidth |
886 | + y: 0 |
887 | + text: DU.shortDayName(index).toUpperCase() |
888 | + horizontalAlignment: Text.AlignHCenter |
889 | + verticalAlignment: Text.AlignVCenter |
890 | + textSize: Label.XSmall |
891 | + color: theme.palette.normal.backgroundTertiaryText |
892 | + } |
893 | + } |
894 | + |
895 | + Repeater { |
896 | + id: monthDaysRepeater |
897 | + model: 0 |
898 | + Item { |
899 | + id: monthDayItem |
900 | + width: cellWidth |
901 | + height: cellHeight |
902 | + x: (index % 7) * cellWidth |
903 | + y: ((index - index % 7) / 7) * cellHeight + cellHeight |
904 | + Loader { |
905 | + property var modelData: root.monthDays[index] |
906 | + property date date: modelData.date |
907 | + property string text: modelData.text |
908 | + property bool currentMonth: !modelData.surrounding |
909 | + property bool pressed: monthDayItem === pressedItem |
910 | + anchors.fill: parent |
911 | + sourceComponent: delegate |
912 | + } |
913 | + } |
914 | + } |
915 | +} |
916 | |
917 | === added file 'src/imports/Components/Pickers/1.3/DatePickerMonthDaysWorker.js' |
918 | --- src/imports/Components/Pickers/1.3/DatePickerMonthDaysWorker.js 1970-01-01 00:00:00 +0000 |
919 | +++ src/imports/Components/Pickers/1.3/DatePickerMonthDaysWorker.js 2016-10-25 11:37:58 +0000 |
920 | @@ -0,0 +1,61 @@ |
921 | +function formatDay(d, month) { |
922 | + var dateNum = d.getDate() |
923 | + return { |
924 | + dateNum: dateNum, |
925 | + text: dateNum + '', |
926 | + surrounding: d.getMonth() !== month, |
927 | + date: d |
928 | + } |
929 | +} |
930 | + |
931 | +function getMonthDays(Calendar, displayedMonth, firstDayOfWeek) { |
932 | + |
933 | + var cal = new Calendar(firstDayOfWeek) |
934 | + var month = displayedMonth.getMonth() |
935 | + var year = displayedMonth.getFullYear() |
936 | + |
937 | + function setTime(date) { |
938 | + var d = new Date(displayedMonth.getTime()) |
939 | + d.setFullYear(date.getFullYear()) |
940 | + d.setMonth(date.getMonth()) |
941 | + d.setDate(date.getDate()) |
942 | + return d |
943 | + } |
944 | + |
945 | + var monthDates = [] |
946 | + cal.monthDates(year, month, function(d) { |
947 | + monthDates.push(formatDay(setTime(d), month)) |
948 | + }) |
949 | + |
950 | + if (monthDates.length / 7 > 5) { |
951 | + return monthDates |
952 | + } |
953 | + |
954 | + // var lastDate = new Date(year, month, 1) |
955 | + var lastDate = new Date(displayedMonth.getTime()) |
956 | + lastDate.setFullYear(year) |
957 | + lastDate.setMonth(month) |
958 | + lastDate.setDate(1) |
959 | + |
960 | + var lastDay = monthDates[monthDates.length - 1] |
961 | + if (lastDay.surrounding) lastDate.setMonth(month + 1) |
962 | + lastDate.setDate(lastDay.dateNum) |
963 | + |
964 | + for (var i = monthDates.length; i < 7 * 6; i++) { |
965 | + lastDate.setDate(lastDate.getDate() + 1) |
966 | + monthDates.push(formatDay(lastDate, month)) |
967 | + } |
968 | + |
969 | + return monthDates |
970 | +} |
971 | + |
972 | +WorkerScript.onMessage = function(msg) { |
973 | + if (msg.name === 'requestMonthDays') { |
974 | + Qt.include('calendar.js', function(status) { |
975 | + WorkerScript.sendMessage({ |
976 | + name: 'monthDays', |
977 | + monthDays: getMonthDays(Calendar, msg.displayedMonth, msg.firstDayOfWeek), |
978 | + }) |
979 | + }) |
980 | + } |
981 | +} |
982 | |
983 | === added file 'src/imports/Components/Pickers/1.3/DayNightPicker.qml' |
984 | --- src/imports/Components/Pickers/1.3/DayNightPicker.qml 1970-01-01 00:00:00 +0000 |
985 | +++ src/imports/Components/Pickers/1.3/DayNightPicker.qml 2016-10-25 11:37:58 +0000 |
986 | @@ -0,0 +1,50 @@ |
987 | +import Ubuntu.Components 1.3 |
988 | +import QtQuick 2.4 |
989 | +import 'dateutils.js' as DU |
990 | + |
991 | +ListView { |
992 | + id: ampmListView |
993 | + clip: true |
994 | + model: ['AM', 'PM'] |
995 | + cacheBuffer: 0 |
996 | + snapMode: ListView.SnapToItem |
997 | + orientation: ListView.Vertical |
998 | + property real itemHeight |
999 | + |
1000 | + highlightFollowsCurrentItem: true |
1001 | + highlightMoveDuration: UbuntuAnimation.FastDuration |
1002 | + highlightRangeMode: ListView.StrictlyEnforceRange |
1003 | + |
1004 | + preferredHighlightBegin: height / 2 - itemHeight / 2 |
1005 | + preferredHighlightEnd: height / 2 + itemHeight / 2 |
1006 | + |
1007 | + onCurrentIndexChanged: { |
1008 | + var hour = date.getHours() |
1009 | + |
1010 | + if (hour < 12 && currentIndex === 1) { |
1011 | + hour += 12 |
1012 | + } else if (hour > 12 && currentIndex === 0) { |
1013 | + hour -= 12 |
1014 | + } |
1015 | + |
1016 | + datePicker.date = DU.clone(date, 'setHours', hour); |
1017 | + } |
1018 | + |
1019 | + delegate: MouseArea { |
1020 | + property bool active: ListView.isCurrentItem |
1021 | + width: parent.width |
1022 | + height: itemHeight |
1023 | + onReleased: ampmListView.currentIndex = index |
1024 | + Label { |
1025 | + id: item |
1026 | + color: parent.active? UbuntuColors.darkGrey : UbuntuColors.lightGrey // FIXME: |
1027 | + width: parent.width - units.gu(1) |
1028 | + height: parent.height |
1029 | + x: units.gu(1) |
1030 | + text: modelData |
1031 | + textSize: Label.Medium |
1032 | + horizontalAlignment: Text.AlignLeft |
1033 | + verticalAlignment: Text.AlignVCenter |
1034 | + } |
1035 | + } |
1036 | +} |
1037 | |
1038 | === added file 'src/imports/Components/Pickers/1.3/DigitSeparator.qml' |
1039 | --- src/imports/Components/Pickers/1.3/DigitSeparator.qml 1970-01-01 00:00:00 +0000 |
1040 | +++ src/imports/Components/Pickers/1.3/DigitSeparator.qml 2016-10-25 11:37:58 +0000 |
1041 | @@ -0,0 +1,10 @@ |
1042 | +import QtQuick 2.4 |
1043 | +import Ubuntu.Components 1.3 |
1044 | + |
1045 | +Label { |
1046 | + anchors.verticalCenter: parent.verticalCenter |
1047 | + color: theme.palette.normal.backgroundText |
1048 | + text: ':' |
1049 | + textSize: Label.Medium |
1050 | + Component.onCompleted: x -= width / 2 |
1051 | +} |
1052 | |
1053 | === added file 'src/imports/Components/Pickers/1.3/HourPicker.qml' |
1054 | --- src/imports/Components/Pickers/1.3/HourPicker.qml 1970-01-01 00:00:00 +0000 |
1055 | +++ src/imports/Components/Pickers/1.3/HourPicker.qml 2016-10-25 11:37:58 +0000 |
1056 | @@ -0,0 +1,114 @@ |
1057 | +import Ubuntu.Components 1.3 |
1058 | +import QtQuick 2.4 |
1059 | +import 'dateutils.js' as DU |
1060 | + |
1061 | +PathView { |
1062 | + id: pathview |
1063 | + property date date: datePicker.date // FIXME: |
1064 | + property real itemAlign: Text.AlignHCenter |
1065 | + property string type |
1066 | + property bool ampm: true |
1067 | + clip: true |
1068 | + pathItemCount: 6 |
1069 | + model: { |
1070 | + if (type === 'hours') { |
1071 | + return ampm? 12 : 24 |
1072 | + } |
1073 | + if (type === 'minutes' || type === 'seconds') { |
1074 | + return 60 |
1075 | + } |
1076 | + return 0 |
1077 | + } |
1078 | + snapMode: PathView.SnapToItem |
1079 | + preferredHighlightBegin: 0.5 |
1080 | + preferredHighlightEnd: 0.5 |
1081 | + |
1082 | + property bool indexInit: false |
1083 | + Timer { |
1084 | + id: afterCompleted |
1085 | + interval: 0 |
1086 | + onTriggered: indexInit = true |
1087 | + } |
1088 | + onCurrentIndexChanged: { |
1089 | + if (!indexInit) return |
1090 | + |
1091 | + var method = null |
1092 | + var value = null |
1093 | + var am = date.getHours() > 0 && date.getHours() < 13 |
1094 | + |
1095 | + if (type === 'hours') { |
1096 | + method = 'setHours' |
1097 | + if (ampm) { |
1098 | + value = am? currentIndex + 1 : ((currentIndex + 13) % 24) |
1099 | + } else { |
1100 | + value = (currentIndex + 1) % 24 |
1101 | + } |
1102 | + } else if (type === 'minutes') { |
1103 | + method = 'setMinutes' |
1104 | + value = currentIndex |
1105 | + |
1106 | + } else if (type === 'seconds') { |
1107 | + method = 'setSeconds' |
1108 | + value = currentIndex |
1109 | + } |
1110 | + |
1111 | + if (method === null || value === null) { |
1112 | + return |
1113 | + } |
1114 | + |
1115 | + datePicker.date = DU.clone(date, method, value) // FIXME: |
1116 | + } |
1117 | + delegate: Item { |
1118 | + property bool active: PathView.isCurrentItem |
1119 | + width: pathview.width |
1120 | + height: datePicker.itemHeight // FIXME: |
1121 | + Label { |
1122 | + width: parent.width |
1123 | + height: parent.height |
1124 | + // FIXME: What color? |
1125 | + color: parent.active? UbuntuColors.darkGrey : UbuntuColors.lightGrey |
1126 | + text: { |
1127 | + if (pathview.type === 'hours') { |
1128 | + return DU.format(DU.clone(date, 'setHours', index + 1), 'HH') |
1129 | + } |
1130 | + if (pathview.type === 'minutes') { |
1131 | + return DU.format(DU.clone(date, 'setMinutes', index), 'mm') |
1132 | + } |
1133 | + if (pathview.type === 'seconds') { |
1134 | + return DU.format(DU.clone(date, 'setSeconds', index), 'ss') |
1135 | + } |
1136 | + return '' |
1137 | + } |
1138 | + // fontSize: 'x-large' |
1139 | + textSize: Label.Medium |
1140 | + horizontalAlignment: pathview.itemAlign |
1141 | + verticalAlignment: Text.AlignVCenter |
1142 | + } |
1143 | + } |
1144 | + path: Path { |
1145 | + startX: pathview.width / 2 |
1146 | + startY: - datePicker.itemHeight / 2 // FIXME: |
1147 | + PathLine { |
1148 | + x: pathview.width / 2 |
1149 | + y: pathview.height + datePicker.itemHeight / 2 // FIXME: |
1150 | + } |
1151 | + } |
1152 | + Component.onCompleted: { |
1153 | + afterCompleted.start() |
1154 | + switch (type) { |
1155 | + case 'hours': |
1156 | + if (ampm) { |
1157 | + currentIndex = date.getHours() - 1 |
1158 | + } else { |
1159 | + currentIndex = date.getHours() % 24 - 1 |
1160 | + } |
1161 | + break |
1162 | + case 'minutes': |
1163 | + currentIndex = date.getMinutes() |
1164 | + break |
1165 | + case 'seconds': |
1166 | + currentIndex = date.getSeconds() |
1167 | + break |
1168 | + } |
1169 | + } |
1170 | +} |
1171 | |
1172 | === added file 'src/imports/Components/Pickers/1.3/YearListDropdown.qml' |
1173 | --- src/imports/Components/Pickers/1.3/YearListDropdown.qml 1970-01-01 00:00:00 +0000 |
1174 | +++ src/imports/Components/Pickers/1.3/YearListDropdown.qml 2016-10-25 11:37:58 +0000 |
1175 | @@ -0,0 +1,131 @@ |
1176 | +import QtQuick 2.4 |
1177 | +import Ubuntu.Components 1.3 |
1178 | +import 'dateutils.js' as DU |
1179 | + |
1180 | +Rectangle { |
1181 | + id: root |
1182 | + |
1183 | + property date minimum: DU.clone(null, 'setDate', 1) |
1184 | + property date maximum: DU.clone(minimum, 'setFullYear', minimum.getFullYear() + 50) |
1185 | + property real itemHeight: height / 5 |
1186 | + property real itemWidth: width / 2 |
1187 | + |
1188 | + property alias monthsPathView: monthsPathView |
1189 | + property alias yearsListView: yearsListView |
1190 | + |
1191 | + signal requestDateChange(date newDate) |
1192 | + |
1193 | + width: parent.width |
1194 | + height: parent.height |
1195 | + y: -height |
1196 | + color: 'white' |
1197 | + |
1198 | + PathView { |
1199 | + id: monthsPathView |
1200 | + property var date: new Date(1970, 0, 1) |
1201 | + width: root.itemWidth |
1202 | + height: parent.height |
1203 | + pathItemCount: 6 |
1204 | + model: 12 |
1205 | + snapMode: PathView.SnapToItem |
1206 | + clip: true |
1207 | + |
1208 | + preferredHighlightBegin: 0.5 |
1209 | + preferredHighlightEnd: 0.5 |
1210 | + |
1211 | + onCurrentIndexChanged: { |
1212 | + var newDate = DU.clone(datePicker.date, 'setMonth', currentIndex) |
1213 | + var newDisplayedDate = DU.clone(newDate) |
1214 | + root.requestDateChange(newDate) |
1215 | + } |
1216 | + |
1217 | + delegate: Item { |
1218 | + property bool active: PathView.isCurrentItem |
1219 | + width: root.itemWidth |
1220 | + height: root.itemHeight |
1221 | + Rectangle { |
1222 | + anchors.fill: parent |
1223 | + color: 'transparent' |
1224 | + Label { |
1225 | + width: parent.width - units.gu(1) |
1226 | + height: parent.height |
1227 | + color: parent.parent.active? UbuntuColors.orange : UbuntuColors.darkGrey |
1228 | + text: DU.format(DU.clone(monthsPathView.date, 'setMonth', index), 'MMMM') |
1229 | + fontSize: 'x-large' |
1230 | + horizontalAlignment: Text.AlignRight |
1231 | + verticalAlignment: Text.AlignVCenter |
1232 | + } |
1233 | + } |
1234 | + } |
1235 | + |
1236 | + path: Path { |
1237 | + startX: root.itemWidth / 2 |
1238 | + startY: - root.itemHeight / 2 |
1239 | + PathLine { |
1240 | + x: root.itemWidth / 2 |
1241 | + y: root.height + root.itemHeight / 2 |
1242 | + } |
1243 | + } |
1244 | + |
1245 | + MouseArea { |
1246 | + anchors.fill: parent |
1247 | + } |
1248 | + } |
1249 | + |
1250 | + ListView { |
1251 | + id: yearsListView |
1252 | + |
1253 | + clip: true |
1254 | + width: root.itemWidth |
1255 | + height: parent.height |
1256 | + x: width |
1257 | + |
1258 | + model: root.maximum.getFullYear() - root.minimum.getFullYear() + 1 |
1259 | + cacheBuffer: 10 |
1260 | + snapMode: ListView.SnapToItem |
1261 | + orientation: ListView.Vertical |
1262 | + |
1263 | + highlightFollowsCurrentItem: true |
1264 | + highlightMoveDuration: UbuntuAnimation.FastDuration |
1265 | + highlightRangeMode: ListView.StrictlyEnforceRange |
1266 | + |
1267 | + preferredHighlightBegin: height / 2 - root.itemHeight / 2 |
1268 | + preferredHighlightEnd: height / 2 + root.itemHeight / 2 |
1269 | + |
1270 | + property bool isCurrentIndexInit: false |
1271 | + onCurrentIndexChanged: { |
1272 | + if (!isCurrentIndexInit) { |
1273 | + isCurrentIndexInit = true |
1274 | + return |
1275 | + } |
1276 | + var newDate = DU.clone( |
1277 | + minimum, 'setFullYear', |
1278 | + minimum.getFullYear() + currentIndex |
1279 | + ) |
1280 | + root.requestDateChange(newDate) |
1281 | + } |
1282 | + |
1283 | + delegate: MouseArea { |
1284 | + width: parent.width |
1285 | + height: root.itemHeight |
1286 | + property bool active: ListView.isCurrentItem |
1287 | + onReleased: yearsListView.currentIndex = index |
1288 | + Label { |
1289 | + id: item |
1290 | + color: parent.active? UbuntuColors.orange : UbuntuColors.darkGrey |
1291 | + width: parent.width - units.gu(1) |
1292 | + height: parent.height |
1293 | + x: units.gu(1) |
1294 | + text: root.minimum.getFullYear() + index |
1295 | + fontSize: 'x-large' |
1296 | + horizontalAlignment: Text.AlignLeft |
1297 | + verticalAlignment: Text.AlignVCenter |
1298 | + } |
1299 | + } |
1300 | + |
1301 | + Component.onCompleted: { |
1302 | + // yearsListView.currentIndex = yearsListView.currentIndex |
1303 | + yearsListView.positionViewAtIndex(yearsListView.currentIndex, ListView.SnapPosition) |
1304 | + } |
1305 | + } |
1306 | +} |
1307 | |
1308 | === added file 'src/imports/Components/Pickers/1.3/calendar.js' |
1309 | --- src/imports/Components/Pickers/1.3/calendar.js 1970-01-01 00:00:00 +0000 |
1310 | +++ src/imports/Components/Pickers/1.3/calendar.js 2016-10-25 11:37:58 +0000 |
1311 | @@ -0,0 +1,79 @@ |
1312 | +.pragma library |
1313 | + |
1314 | +/*! |
1315 | + * calendar.js: inspired by the calendar module from Python |
1316 | + * Copyright(c) 2011 Luciano Ramalho <luciano@ramalho.org> |
1317 | + * MIT Licensed |
1318 | + * |
1319 | + * From https://github.com/ramalho/calendar.js |
1320 | + */ |
1321 | + |
1322 | +var CalendarException = function CalendarException(message) { |
1323 | + this.message = message; |
1324 | + this.toString = function() { |
1325 | + return this.constructor.name + ": " + this.message |
1326 | + }; |
1327 | +} |
1328 | + |
1329 | +var Calendar = function Calendar(firstWeekDay) { |
1330 | + //properties |
1331 | + this.firstWeekDay = firstWeekDay || 0; // 0 = Sunday |
1332 | +}; |
1333 | + |
1334 | +Calendar.prototype = { |
1335 | + constructor : Calendar, |
1336 | + weekStartDate : function weekStartDate(date) { |
1337 | + var startDate = new Date(date.getTime()); |
1338 | + while (startDate.getDay() !== this.firstWeekDay) { |
1339 | + startDate.setDate(startDate.getDate() - 1); |
1340 | + } |
1341 | + return startDate; |
1342 | + }, |
1343 | + monthDates : function monthDates(year, month, dayFormatter, weekFormatter) { |
1344 | + if ((typeof year !== "number") || (year < 1970)) { |
1345 | + throw new CalendarException('year must be a number >= 1970'); |
1346 | + }; |
1347 | + if ((typeof month !== "number") || (month < 0) || (month > 11)) { |
1348 | + throw new CalendarException('month must be a number (Jan is 0)'); |
1349 | + }; |
1350 | + var weeks = [], |
1351 | + week = [], |
1352 | + i = 0, |
1353 | + date = this.weekStartDate(new Date(year, month, 1)); |
1354 | + do { |
1355 | + for (i=0; i<7; i++) { |
1356 | + week.push(dayFormatter ? dayFormatter(date) : date); |
1357 | + date = new Date(date.getTime()); |
1358 | + date.setDate(date.getDate() + 1); |
1359 | + } |
1360 | + weeks.push(weekFormatter ? weekFormatter(week) : week); |
1361 | + week = []; |
1362 | + } while ((date.getMonth()<=month) && (date.getFullYear()===year)); |
1363 | + return weeks; |
1364 | + }, |
1365 | + monthDays : function monthDays(year, month) { |
1366 | + var getDayOrZero = function getDayOrZero(date) { |
1367 | + return date.getMonth() === month ? date.getDate() : 0; |
1368 | + }; |
1369 | + return this.monthDates(year, month, getDayOrZero); |
1370 | + }, |
1371 | + monthText : function monthText(year, month) { |
1372 | + if (typeof year === "undefined") { |
1373 | + var now = new Date(); |
1374 | + year = now.getFullYear(); |
1375 | + month = now.getMonth(); |
1376 | + }; |
1377 | + var getDayOrBlank = function getDayOrBlank(date) { |
1378 | + var s = date.getMonth() === month ? date.getDate().toString() : " "; |
1379 | + while (s.length < 2) s = " "+s; |
1380 | + return s; |
1381 | + }; |
1382 | + var weeks = this.monthDates(year, month, getDayOrBlank, |
1383 | + function (week) { return week.join(" ") }); |
1384 | + return weeks.join("\n"); |
1385 | + } |
1386 | +}; |
1387 | +for (var i = 0; i < 11; i++) { |
1388 | + var month = Qt.locale().monthName(i); // FIXME:, Locale.LongFormat); |
1389 | + Calendar[month] = i; |
1390 | +} |
1391 | |
1392 | === added file 'src/imports/Components/Pickers/1.3/dateutils.js' |
1393 | --- src/imports/Components/Pickers/1.3/dateutils.js 1970-01-01 00:00:00 +0000 |
1394 | +++ src/imports/Components/Pickers/1.3/dateutils.js 2016-10-25 11:37:58 +0000 |
1395 | @@ -0,0 +1,53 @@ |
1396 | +.pragma library |
1397 | + |
1398 | +// Returns a formatted string from a date, using Qt Date.prototype.toLocaleString() |
1399 | +function format(date, format, localeStr) { |
1400 | + var locale = Qt.locale(localeStr || 'en_US') |
1401 | + return date.toLocaleString(locale, format) |
1402 | +} |
1403 | + |
1404 | +// Clone a Date object, and optionally invokes methods on it |
1405 | +function clone(date, calls) { |
1406 | + var d = date? new Date(date.getTime()) : new Date() |
1407 | + if (!calls) return d |
1408 | + if (!Array.isArray(calls)) { |
1409 | + calls = [[calls].concat([].slice.call(arguments, 2))] |
1410 | + } |
1411 | + for (var i = 0; i < calls.length; i++) { |
1412 | + if (!calls[i]) continue |
1413 | + d[calls[i][0]].apply(d, calls[i].slice(1)) |
1414 | + } |
1415 | + return d |
1416 | +} |
1417 | + |
1418 | +// Return true if date1 and date2 are the same day |
1419 | +function sameDay(date1, date2) { |
1420 | + return date1.getDate() === date2.getDate() && |
1421 | + date1.getMonth() === date2.getMonth() && |
1422 | + date1.getFullYear() === date2.getFullYear() |
1423 | +} |
1424 | + |
1425 | +// Compare two dates |
1426 | +function compareDates(date1, date2) { |
1427 | + var time1 = date1.getTime() |
1428 | + var time2 = date2.getTime() |
1429 | + if (time1 > time2) return 1 |
1430 | + if (time1 < time2) return -1 |
1431 | + return 0 |
1432 | +} |
1433 | + |
1434 | +var shortDayNames = 'Mon Tue Wed Thu Fri Sat Sun'.split(' ') |
1435 | + |
1436 | +// Return a short day name based on a date object or a day number |
1437 | +function shortDayName(date) { |
1438 | + if (typeof date === 'number') { |
1439 | + return shortDayNames[date] |
1440 | + } |
1441 | + return shortDayNames[date.getDay()] |
1442 | +} |
1443 | + |
1444 | +// Return the number of months between date1 and date2 (including both) |
1445 | +function getMonthsRange(date1, date2) { |
1446 | + return (date2.getFullYear() - date1.getFullYear()) * 12 + |
1447 | + (date2.getMonth() - date1.getMonth()) |
1448 | +} |
1449 | |
1450 | === modified file 'src/imports/Components/Pickers/Pickers.pro' |
1451 | --- src/imports/Components/Pickers/Pickers.pro 2015-05-19 09:23:01 +0000 |
1452 | +++ src/imports/Components/Pickers/Pickers.pro 2016-10-25 11:37:58 +0000 |
1453 | @@ -29,6 +29,15 @@ |
1454 | 1.3/Picker.qml \ |
1455 | 1.3/PickerRow.qml \ |
1456 | 1.3/SecondsModel.qml \ |
1457 | + 1.3/dateutils.js \ |
1458 | + 1.3/calendar.js \ |
1459 | + 1.3/DatePickerMonthDays.qml \ |
1460 | + 1.3/DatePickerMonthDaysWorker.js \ |
1461 | + 1.3/YearListDropdown.qml \ |
1462 | + 1.3/DatePickerHeader.qml \ |
1463 | + 1.3/HourPicker.qml \ |
1464 | + 1.3/DayNightPicker.qml \ |
1465 | + 1.3/DigitSeparator.qml \ |
1466 | 1.3/YearModel.qml |
1467 | |
1468 | load(ubuntu_qml_module) |