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