]> rtime.felk.cvut.cz Git - coffee/qtwebbrowser.git/blob - src/qml/HomeScreen.qml
Add SettingsView and refactor settings handling to be generic
[coffee/qtwebbrowser.git] / src / qml / HomeScreen.qml
1 /****************************************************************************
2 **
3 ** Copyright (C) 2015 The Qt Company Ltd.
4 ** Contact: http://www.qt.io/licensing/
5 **
6 ** This file is part of the QtBrowser project.
7 **
8 ** $QT_BEGIN_LICENSE:GPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see http://www.qt.io/terms-conditions. For further
15 ** information use the contact form at http://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 2 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.GPLv2 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU General Public License version 2 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 3.0 as published by the Free Software
28 ** Foundation and appearing in the file LICENSE.GPL included in the
29 ** packaging of this file. Please review the following information to
30 ** ensure the GNU General Public License version 3.0 requirements will be
31 ** met: http://www.gnu.org/copyleft/gpl.html.
32 **
33 **
34 ** $QT_END_LICENSE$
35 **
36 ****************************************************************************/
37
38 import QtQuick 2.5
39 import "assets"
40
41 Rectangle {
42     id: homeScreen
43
44     property int padding: 60
45     property int cellSize: width / 5 - padding
46     property alias messageBox: messageBox
47     property alias count: gridView.count
48     property alias currentIndex: gridView.currentIndex
49
50     function set(i) {
51         var p = (i - i % gridViewPageItemCount) / gridViewPageItemCount
52         gridView.contentX = p * gridView.page
53     }
54
55     state: "enabled"
56
57     signal add(string title, string url, string iconUrl, string fallbackColor)
58     onAdd: {
59         if (listModel.count === gridViewMaxBookmarks) {
60             navigation.refresh()
61             messageBox.state = "full"
62             state = "enabled"
63             return
64         }
65         var element = { "title": title, "url": url, "iconUrl": iconUrl, "fallbackColor": fallbackColor }
66         listModel.append(element)
67         set(listModel.count - 1)
68     }
69
70     signal remove(string url, int idx)
71     onRemove: {
72         var index = idx < 0 ? contains(url) : idx
73         if (index < 0)
74             return
75
76         listModel.remove(index)
77         gridView.forceLayout()
78         navigation.refresh()
79     }
80
81     function get(index) {
82         return listModel.get(index)
83     }
84
85     function contains(url) {
86         for (var idx = 0; idx < listModel.count; ++idx) {
87             if (listModel.get(idx).url === url)
88                 return idx;
89         }
90         return -1;
91     }
92
93     states: [
94         State {
95             name: "enabled"
96             AnchorChanges {
97                 target: homeScreen
98                 anchors.top: navigation.bottom
99             }
100         },
101         State {
102             name: "disabled"
103             AnchorChanges {
104                 target: homeScreen
105                 anchors.top: homeScreen.parent.bottom
106             }
107         },
108         State {
109             name: "edit"
110         }
111     ]
112
113     transitions: Transition {
114         AnchorAnimation { duration: animationDuration; easing.type : Easing.InSine }
115     }
116
117     ListModel {
118         id: listModel
119         Component.onCompleted: {
120             listModel.clear()
121             var string = engine.restoreSetting("bookmarks")
122             if (!string)
123                 return
124             var list = JSON.parse(string)
125             for (var i = 0; i < list.length; ++i) {
126                 listModel.append(list[i])
127             }
128             navigation.refresh()
129         }
130         Component.onDestruction: {
131             var list = []
132             for (var i = 0; i < listModel.count; ++i) {
133                 list[i] = listModel.get(i)
134             }
135             if (!list.length)
136                 return
137             engine.saveSetting("bookmarks", JSON.stringify(list))
138         }
139     }
140
141     GridView {
142         id: gridView
143
144         onCountChanged: {
145             if (!count)
146                 messageBox.state = "empty"
147             else
148                 messageBox.state = "disabled"
149         }
150
151         property real dragStart: 0
152         property real page: 4 * cellWidth
153
154         anchors.fill: parent
155         model: listModel
156         cellWidth: homeScreen.cellSize + homeScreen.padding
157         cellHeight: cellWidth
158         flow: GridView.FlowTopToBottom
159         boundsBehavior: Flickable.StopAtBounds
160         maximumFlickVelocity: 0
161         contentHeight: parent.height
162
163         MouseArea {
164             z: -1
165             enabled: homeScreen.state == "edit"
166             anchors.fill: parent
167             onClicked: homeScreen.state = "enabled"
168         }
169
170         rightMargin: {
171             var margin = (parent.width - 4 * gridView.cellWidth - homeScreen.padding) / 2
172             var padding = gridView.page - Math.round(gridView.count % gridViewPageItemCount / 2) * gridView.cellWidth
173
174             if (padding == gridView.page)
175                 return margin
176
177             return margin + padding
178         }
179
180         anchors {
181             topMargin: toolBarSize
182             leftMargin: (parent.width - 4 * gridView.cellWidth + homeScreen.padding) / 2
183         }
184
185         Behavior on contentX {
186             NumberAnimation { duration: 1.5 * animationDuration; easing.type : Easing.InSine}
187         }
188
189         function snapToPage() {
190             if (dragging) {
191                 dragStart = contentX
192                 return
193             }
194             if (dragStart == 2 * page && contentX < 2 * page) {
195                 contentX = page
196                 return
197             }
198             if (dragStart == page) {
199                 if (contentX < page) {
200                     contentX = 0
201                     return
202                 }
203                 if (page < contentX) {
204                     contentX = 2 * page
205                     return
206                 }
207             }
208             if (dragStart == 0 && 0 < contentX) {
209                 contentX = page
210                 return
211             }
212             contentX = 0
213         }
214
215         onDraggingChanged: snapToPage()
216         delegate: Rectangle {
217             id: square
218             property string iconColor: "#f6f6f6"
219             width: homeScreen.cellSize
220             height: width
221             border.color: iconStrokeColor
222             border.width: 1
223
224             Rectangle {
225                 id: bg
226                 anchors {
227                     horizontalCenter: parent.horizontalCenter
228                     top: parent.top
229                     margins: 1
230                 }
231                 state: "fallback"
232                 width: square.width - 2
233                 height: width
234                 states: [
235                     State {
236                         name: "fallback"
237                         PropertyChanges {
238                             target: square
239                             color: fallbackColor
240                         }
241                         PropertyChanges {
242                             target: bg
243                             color: square.color
244                         }
245                     },
246                     State {
247                         name: "normal"
248                         PropertyChanges {
249                             target: square
250                             color: iconColor
251                         }
252                         PropertyChanges {
253                             target: bg
254                             color: square.color
255                         }
256                     }
257                 ]
258
259                 Image {
260                     id: icon
261                     smooth: true
262                     anchors {
263                         top: parent.top
264                         horizontalCenter: parent.horizontalCenter
265                         topMargin: width < bg.width ? 15 : 0
266                     }
267                     width: {
268                         if (!icon.sourceSize.width)
269                             return 0
270                         if (icon.sourceSize.width < 100)
271                             return 32
272
273                         return bg.width
274                     }
275                     height: width
276                     source: iconUrl
277                     onStatusChanged: {
278                         switch (status) {
279                         case Image.Null:
280                         case Image.Loading:
281                         case Image.Error:
282                             bg.state = "fallback"
283                             break
284                         case Image.Ready:
285                             bg.state = "normal"
286                             break
287                         }
288                     }
289                 }
290                 Text {
291                     function cleanup(string) {
292                         var t = string.replace("-", " ")
293                         .replace("|", " ").replace(",", " ")
294                         .replace(/\s\s+/g, "\n")
295                         return t
296                     }
297
298                     visible: icon.width != bg.width
299                     text: cleanup(title)
300                     font.family: defaultFontFamily
301                     font.pixelSize: 18
302                     color: bg.state == "fallback" ? "white" : "black"
303                     anchors {
304                         top: icon.bottom
305                         bottom: parent.bottom
306                         left: parent.left
307                         right: parent.right
308                         leftMargin: 15
309                         rightMargin: 15
310                         bottomMargin: 15
311                     }
312                     maximumLineCount: 3
313                     elide: Text.ElideRight
314                     wrapMode: Text.Wrap
315                     verticalAlignment: Text.AlignVCenter
316                     horizontalAlignment: Text.AlignHCenter
317                 }
318             }
319
320             Rectangle {
321                 id: overlay
322                 visible: opacity != 0.0
323                 anchors.fill: parent
324                 color: iconOverlayColor
325                 opacity: {
326                     if (iconMouse.pressed) {
327                         if (homeScreen.state != "edit")
328                             return 0.1
329                         return 0.4
330                     }
331                     if (homeScreen.state == "edit")
332                         return 0.3
333                     return 0.0
334                 }
335             }
336             MouseArea {
337                 id: iconMouse
338                 anchors.fill: parent
339                 onPressAndHold: {
340                     if (homeScreen.state == "edit") {
341                         homeScreen.state = "enabled"
342                         return
343                     }
344                     homeScreen.state = "edit"
345                 }
346                 onClicked: {
347                     if (homeScreen.state == "edit") {
348                         homeScreen.state = "enabled"
349                         return
350                     }
351                     navigation.load(url)
352                 }
353             }
354             Rectangle {
355                 enabled: homeScreen.state == "edit"
356                 opacity: enabled ? 1.0 : 0.0
357                 width: image.sourceSize.width
358                 height: image.sourceSize.height - 2
359                 radius: width / 2
360                 color: iconOverlayColor
361                 anchors {
362                     horizontalCenter: parent.right
363                     verticalCenter: parent.top
364                 }
365                 Image {
366                     id: image
367                     opacity: {
368                         if (deleteButton.pressed)
369                             return 0.70
370                         return 1.0
371                     }
372                     anchors {
373                         top: parent.top
374                         left: parent.left
375                     }
376                     source: "qrc:///delete"
377                     MouseArea {
378                         id: deleteButton
379                         anchors.fill: parent
380                         onClicked: {
381                             mouse.accepted = true
382                             remove(url, index)
383                         }
384                     }
385                 }
386                 Behavior on opacity {
387                     NumberAnimation { duration: animationDuration }
388                 }
389             }
390         }
391     }
392     Rectangle {
393         width: homeScreen.cellSize - homeScreen.padding / 2 - 10
394         anchors {
395             left: parent.left
396             top: parent.top
397             bottom: parent.bottom
398         }
399         MouseArea {
400             enabled: homeScreen.state == "edit"
401             anchors.fill: parent
402             onClicked: homeScreen.state = "enabled"
403         }
404     }
405     Rectangle {
406         width: homeScreen.cellSize - homeScreen.padding / 2 - 10
407         anchors {
408             right: parent.right
409             top: parent.top
410             bottom: parent.bottom
411         }
412         MouseArea {
413             enabled: homeScreen.state == "edit"
414             anchors.fill: parent
415             onClicked: homeScreen.state = "enabled"
416         }
417     }
418     Row {
419         id: pageIndicator
420         spacing: 20
421         anchors {
422             bottomMargin: 40
423             bottom: parent.bottom
424             horizontalCenter: parent.horizontalCenter
425         }
426         Repeater {
427             model: {
428                 var c = gridView.count % gridViewPageItemCount
429                 if (c > 0)
430                     c = 1
431                 return Math.floor(gridView.count / gridViewPageItemCount) + c
432             }
433             delegate: Rectangle {
434                 property bool active: index * gridView.page <= gridView.contentX && gridView.contentX < (index + 1) * gridView.page
435                 width: 10
436                 height: width
437                 radius: width / 2
438                 color: !active ? inactivePagerColor : uiColor
439                 anchors.verticalCenter: parent.verticalCenter
440                 MouseArea {
441                     anchors.fill: parent
442                     onClicked: gridView.contentX = index * gridView.page
443                 }
444             }
445         }
446     }
447
448     Rectangle {
449         id: messageBox
450         color: "white"
451         anchors.fill: parent
452
453         Rectangle {
454             id: error
455             visible: messageBox.state != "empty"
456             anchors {
457                 top: parent.top
458                 left: parent.left
459                 right: parent.right
460             }
461             height: homeScreen.height / 2
462             Image {
463                 id: errorIcon
464                 source: "qrc:///error"
465                 anchors.centerIn: parent
466             }
467             Text {
468                 anchors {
469                     topMargin: 10
470                     top: errorIcon.bottom
471                     horizontalCenter: parent.horizontalCenter
472                 }
473                 font.family: defaultFontFamily
474                 font.pixelSize: message.font.pixelSize
475                 text: "Oops!..."
476                 color: iconOverlayColor
477             }
478         }
479
480         Text {
481             id: message
482             anchors {
483                 top: error.bottom
484                 horizontalCenter: parent.horizontalCenter
485             }
486             color: iconOverlayColor
487             font.family: defaultFontFamily
488             font.pixelSize: 28
489             verticalAlignment: Text.AlignVCenter
490             horizontalAlignment: Text.AlignHCenter
491         }
492
493         Rectangle {
494             height: 2 * toolBarSize
495             anchors {
496                 bottom: parent.bottom
497                 top: message.bottom
498                 left: parent.left
499                 right: parent.right
500                 bottomMargin: 70
501             }
502             UIButton {
503                 color: uiColor
504                 implicitWidth: 180
505                 implicitHeight: 70
506                 visible: messageBox.state != "empty"
507                 anchors {
508                     horizontalCenter: parent.horizontalCenter
509                     bottom: parent.bottom
510                 }
511                 Text {
512                     color: "white"
513                     anchors.centerIn: parent
514                     text: "OK"
515                     font.family: defaultFontFamily
516                     font.pixelSize: 28
517                 }
518                 onClicked: {
519                     if (messageBox.state == "tabsfull") {
520                         homeScreen.state = "disabled"
521                         tabView.viewState = "list"
522                         return
523                     }
524                     if (messageBox.state == "full") {
525                         messageBox.state = "disabled"
526                         homeScreen.state = "edit"
527                         return
528                     }
529                 }
530             }
531         }
532
533         state: "disabled"
534
535         states: [
536             State {
537                 name: "disabled"
538                 PropertyChanges {
539                     target: messageBox
540                     visible: false
541                 }
542             },
543             State {
544                 name: "empty"
545                 PropertyChanges {
546                     target: message
547                     text: qsTr("No bookmarks have been saved so far.")
548                 }
549                 PropertyChanges {
550                     target: messageBox
551                     color: "transparent"
552                     visible: true
553                 }
554                 PropertyChanges {
555                     target: navigation
556                     state: "enabled"
557                 }
558             },
559             State {
560                 name: "full"
561                 PropertyChanges {
562                     target: message
563                     text: qsTr("24 bookmarks is the maximum limit.\nTo bookmark a new page you must delete a bookmark first.")
564                 }
565                 PropertyChanges {
566                     target: messageBox
567                     visible: true
568                 }
569                 PropertyChanges {
570                     target: navigation
571                     state: "enabled"
572                 }
573             },
574             State {
575                 name: "tabsfull"
576                 PropertyChanges {
577                     target: message
578                     text: qsTr("10 open tabs is the maximum limit.\nTo open a new tab you must close another one first.")
579                 }
580                 PropertyChanges {
581                     target: messageBox
582                     visible: true
583                 }
584                 PropertyChanges {
585                     target: navigation
586                     state: "enabled"
587                 }
588             }
589         ]
590     }
591 }