]> rtime.felk.cvut.cz Git - coffee/qtwebbrowser.git/blob - src/qml/PageView.qml
b78feaaa95a87f25f51875c3e9f17cd94cf12d94
[coffee/qtwebbrowser.git] / src / qml / PageView.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 QtWebEngine 1.1
40 import QtQuick.Controls 1.4
41 import QtQuick.Controls.Styles 1.4
42 import QtQuick.Layouts 1.2
43 import QtGraphicalEffects 1.0
44
45 import io.qt.browser 1.0
46 import "assets"
47
48 Rectangle {
49     id: root
50
51     property int itemWidth: browserWindow.width / 2
52     property int itemHeight: browserWindow.height / 2
53
54     property bool interactive: true
55
56     property alias currentIndex: pathView.currentIndex
57     property alias count: pathView.count
58
59     property string viewState: "page"
60
61     onViewStateChanged: {
62         if (viewState == "page")
63             homeScreen.state = "disabled"
64     }
65
66     property QtObject otrProfile: WebEngineProfile {
67         offTheRecord: true
68     }
69
70     property QtObject defaultProfile: WebEngineProfile {
71         storageName: "YABProfile"
72         offTheRecord: false
73     }
74
75     Component {
76         id: tabComponent
77         Rectangle {
78             id: tabItem
79             property alias webView: webEngineView
80             property alias title: webEngineView.title
81
82             property var image: QtObject {
83                 property var snapshot: null
84                 property string url: "about:blank"
85             }
86
87             visible: opacity != 0.0
88
89             Behavior on opacity {
90                 NumberAnimation { duration: animationDuration }
91             }
92
93             anchors.fill: parent
94
95             Action {
96                 shortcut: "Ctrl+F"
97                 onTriggered: {
98                     findBar.visible = !findBar.visible
99                     if (findBar.visible) {
100                         findTextField.forceActiveFocus()
101                     }
102                 }
103             }
104
105             FeaturePermissionBar {
106                 id: permBar
107                 view: webEngineView
108                 anchors {
109                     left: parent.left
110                     right: parent.right
111                     top: parent.top
112                 }
113                 z: 3
114             }
115
116             WebEngineView {
117                 id: webEngineView
118
119                 anchors {
120                     fill: parent
121                     top: permBar.bottom
122                 }
123
124                 profile: settingsView.privateBrowsingEnabled ? otrProfile : defaultProfile
125                 enabled: root.interactive
126
127                 function takeSnapshot() {
128                     if (webEngineView.url == "" || webEngineView.url == "about:blank") {
129                         tabItem.image.url = "about:blank"
130                         tabItem.image.snapshot = null
131                         return
132                     }
133
134                     if (tabItem.image.url == webEngineView.url || tabItem.opacity != 1.0)
135                         return
136
137                     tabItem.image.url = webEngineView.url
138                     webEngineView.grabToImage(function(result) {
139                         tabItem.image.snapshot = result;
140                         console.log("takeSnapshot("+result.url+")")
141                     });
142                 }
143
144                 // Trigger a refresh to check if the new url is bookmarked.
145                 onUrlChanged: navigation.refresh()
146
147
148                 settings.autoLoadImages: settingsView.autoLoadImages
149                 settings.javascriptEnabled: !settingsView.javaScriptDisabled
150
151                 // This should be enabled as we can switch to Qt 5.6 (i.e. import QtWebEngine 1.2)
152                 // settings.pluginsEnabled: settingsView.pluginsEnabled
153
154                 onLoadingChanged: {
155                     if (loading)
156                         navigation.state = "enabled"
157                 }
158
159                 onCertificateError: {
160                     if (!acceptedCertificates.shouldAutoAccept(error)){
161                         error.defer()
162                         sslDialog.enqueue(error)
163                     } else{
164                         error.ignoreCertificateError()
165                     }
166                 }
167
168                 onNewViewRequested: {
169                     webEngineView.takeSnapshot()
170                     var tab
171                     if (!request.userInitiated) {
172                         print("Warning: Blocked a popup window.")
173                         return
174                     }
175
176                     tab = tabView.createEmptyTab()
177
178                     if (!tab)
179                         return
180
181                     if (request.destination == WebEngineView.NewViewInTab) {
182                         pathView.positionViewAtIndex(tabView.count - 1, PathView.Center)
183                         request.openIn(tab.webView)
184                     } else if (request.destination == WebEngineView.NewViewInBackgroundTab) {
185                         var index = pathView.currentIndex
186                         request.openIn(tab.webView)
187                         pathView.positionViewAtIndex(index, PathView.Center)
188                     } else if (request.destination == WebEngineView.NewViewInDialog) {
189                         request.openIn(tab.webView)
190                     } else {
191                         request.openIn(tab.webView)
192                     }
193                 }
194
195                 onFeaturePermissionRequested: {
196                     permBar.securityOrigin = securityOrigin;
197                     permBar.requestedFeature = feature;
198                     permBar.visible = true;
199                 }
200             }
201
202             Desaturate {
203                 id: desaturate
204                 visible: desaturation != 0.0
205                 anchors.fill: webEngineView
206                 source: webEngineView
207                 desaturation: root.interactive ? 0.0 : 1.0
208
209                 Behavior on desaturation {
210                     NumberAnimation { duration: animationDuration }
211                 }
212             }
213
214             FastBlur {
215                 id: blur
216                 visible: radius != 0.0
217                 anchors.fill: desaturate
218                 source: desaturate
219                 radius: desaturate.desaturation * 25
220             }
221
222             TouchTracker {
223                 id: tracker
224                 enabled: root.interactive
225                 target: webEngineView
226                 anchors.fill: parent
227                 onTouchYChanged: browserWindow.touchY = tracker.touchY
228                 onYVelocityChanged: browserWindow.velocityY = yVelocity
229                 onTouchBegin: {
230                     browserWindow.touchY = tracker.touchY
231                     browserWindow.velocityY = yVelocity
232                     browserWindow.touchReference = tracker.touchY
233                     browserWindow.touchGesture = true
234                     navigation.state = "tracking"
235                 }
236                 onTouchEnd: {
237                     browserWindow.velocityY = yVelocity
238                     browserWindow.touchGesture = false
239                     navigation.state = "tracking"
240                 }
241                 onScrollDirectionChanged: {
242                     browserWindow.velocityY = 0
243                     browserWindow.touchReference = tracker.touchY
244                 }
245             }
246
247             Rectangle {
248                 opacity: {
249                     if (inputPanel.state === "visible")
250                         return 0.0
251                     if (webEngineView.url == "" || webEngineView.url == "about:blank")
252                         return 1.0
253                     return 0.0
254                 }
255                 anchors.fill: parent
256                 visible: opacity != 0.0
257                 color: "white"
258                 Image {
259                     id: placeholder
260                     y: placeholder.height - navigation.y
261                     anchors.horizontalCenter: parent.horizontalCenter
262                     source: "qrc:///icon"
263                 }
264                 Text {
265                     id: label
266                     anchors {
267                         top: placeholder.bottom
268                         topMargin: 20
269                         horizontalCenter: placeholder.horizontalCenter
270                     }
271                     font.family: defaultFontFamily
272                     font.pixelSize: 28
273                     color: uiColor
274                     text: "Qt WebBrowser"
275                 }
276
277                 Behavior on opacity {
278                     NumberAnimation { duration: animationDuration }
279                 }
280             }
281
282             Rectangle {
283                 id: findBar
284                 anchors {
285                     right: webEngineView.right
286                     left: webEngineView.left
287                     top: webEngineView.top
288                 }
289                 height: toolBarSize / 2 + 10
290                 visible: false
291                 color: uiColor
292
293                 RowLayout {
294                     spacing: 0
295                     anchors.fill: parent
296                     Rectangle {
297                         width: 5
298                         anchors {
299                             top: parent.top
300                             bottom: parent.bottom
301                         }
302                         color: uiColor
303                     }
304                     TextField {
305                         id: findTextField
306                         Layout.fillWidth: true
307                         onAccepted: {
308                             webEngineView.findText(text)
309                         }
310                         style: TextFieldStyle {
311                             textColor: "black"
312                             font.family: defaultFontFamily
313                             font.pixelSize: 28
314                             selectionColor: uiHighlightColor
315                             selectedTextColor: "black"
316                             placeholderTextColor: placeholderColor
317                             background: Rectangle {
318                                 implicitWidth: 514
319                                 implicitHeight: toolBarSize / 2
320                                 border.color: textFieldStrokeColor
321                                 border.width: 1
322                             }
323                         }
324                     }
325                     Rectangle {
326                         width: 5
327                         anchors {
328                             top: parent.top
329                             bottom: parent.bottom
330                         }
331                         color: uiColor
332                     }
333                     Rectangle {
334                         width: 1
335                         anchors {
336                             top: parent.top
337                             bottom: parent.bottom
338                         }
339                         color: uiSeparatorColor
340                     }
341                     UIButton {
342                         id: findBackwardButton
343                         iconSource: "qrc:///back"
344                         implicitHeight: parent.height
345                         onClicked: webEngineView.findText(findTextField.text, WebEngineView.FindBackward)
346                     }
347                     Rectangle {
348                         width: 1
349                         anchors {
350                             top: parent.top
351                             bottom: parent.bottom
352                         }
353                         color: uiSeparatorColor
354                     }
355                     UIButton {
356                         id: findForwardButton
357                         iconSource: "qrc:///forward"
358                         implicitHeight: parent.height
359                         onClicked: webEngineView.findText(findTextField.text)
360                     }
361                     Rectangle {
362                         width: 1
363                         anchors {
364                             top: parent.top
365                             bottom: parent.bottom
366                         }
367                         color: uiSeparatorColor
368                     }
369                     UIButton {
370                         id: findCancelButton
371                         iconSource: "qrc:///stop"
372                         implicitHeight: parent.height
373                         onClicked: findBar.visible = false
374                     }
375                 }
376             }
377         }
378     }
379
380     ListModel {
381         id: listModel
382     }
383
384     function makeCurrent(index) {
385         viewState = "list"
386         pathView.positionViewAtIndex(index, PathView.Center)
387         viewState = "page"
388     }
389
390     function createEmptyTab() {
391         var tab = add(tabComponent)
392         return tab
393     }
394
395     function add(component) {
396         if (listModel.count === tabViewMaxTabs) {
397             homeScreen.messageBox.state = "tabsfull"
398             homeScreen.state = "enabled"
399             homeScreen.forceActiveFocus()
400             return null
401         }
402
403         var element = {"item": null }
404         element.item = component.createObject(root, { "width": root.width, "height": root.height, "opacity": 0.0 })
405
406         if (element.item == null) {
407             console.log("PageView::add(): Error creating object");
408             return
409         }
410
411         listModel.append(element)
412         return element.item
413     }
414
415     function remove(index) {
416         pathView.interactive = false
417         pathView.currentItem.state = ""
418         pathView.currentItem.visible = false
419         listModel.remove(index)
420         pathView.decrementCurrentIndex()
421         pathView.interactive = true
422         if (listModel.count == 0)
423             engine.rootWindow.close()
424     }
425
426     function get(index) {
427         return listModel.get(index)
428     }
429
430     Component {
431         id: delegate
432
433         Rectangle {
434             id: wrapper
435
436             parent: item
437
438             property real visibility: 0.0
439             property bool isCurrentItem: PathView.isCurrentItem
440
441             visible: PathView.onPath && visibility != 0.0
442             state: isCurrentItem ? root.viewState : "list"
443
444             Behavior on scale {
445                 NumberAnimation { duration: animationDuration }
446             }
447
448             states: [
449                 State {
450                     name: "page"
451                     PropertyChanges { target: wrapper; width: root.width; height: root.height; visibility: 0.0 }
452                     PropertyChanges { target: pathView; interactive: false }
453                     PropertyChanges { target: item; opacity: 1.0 }
454                     PropertyChanges { target: navigation; state: "enabled" }
455                 },
456                 State {
457                     name: "list"
458                     PropertyChanges { target: wrapper; width: itemWidth; height: itemHeight; visibility: 1.0 }
459                     PropertyChanges { target: pathView; interactive: true }
460                     PropertyChanges { target: item; opacity: 0.0 }
461                 }
462             ]
463
464             transitions: Transition {
465                 ParallelAnimation {
466                     PropertyAnimation { property: "visibility"; duration: animationDuration; easing.type : Easing.InSine }
467                     PropertyAnimation { properties: "x,y"; duration: animationDuration; easing.type: Easing.InSine }
468                     PropertyAnimation { properties: "width,height"; duration: animationDuration; easing.type: Easing.InSine }
469                 }
470             }
471
472             width: itemWidth; height: itemHeight
473             scale: {
474                 if (pathView.count == 1)
475                     return 1.0
476                 if (pathView.count < 4)
477                     return isCurrentItem ? 1.0 : 0.5
478
479                 if (isCurrentItem)
480                     return 1.0
481
482                 var index1 = pathView.currentIndex - 2
483                 var index2 = pathView.currentIndex - 1
484                 var index4 = (pathView.currentIndex + 1) % pathView.count
485                 var index5 = (pathView.currentIndex + 2) % pathView.count
486
487                 if (index1 < 0)
488                     index1 = pathView.count + index1
489                 if (index2 < 0)
490                     index2 = pathView.count + index2
491
492                 switch (index) {
493                 case index1 :
494                     return 0.25
495                 case index2:
496                     return 0.5
497                 case index4:
498                     return 0.5
499                 case index5:
500                     return 0.25
501                 }
502
503                 return 0.25
504             }
505             z: PathView.itemZ
506
507             MouseArea {
508                 enabled: pathView.interactive
509                 anchors.fill: wrapper
510                 onClicked: {
511                     mouse.accepted = true
512                     if (index < 0)
513                         return
514
515                     if (index == pathView.currentIndex) {
516                         if (root.viewState == "list")
517                             root.viewState = "page"
518                         return
519                     }
520                     pathView.currentIndex = index
521                 }
522             }
523             Rectangle {
524                 id: shadow
525                 visible: false
526                 property real size: 24
527                 anchors {
528                     top: parent.top
529                     topMargin: 9
530                     horizontalCenter: parent.horizontalCenter
531                 }
532                 color: iconOverlayColor
533                 radius: size / 2
534                 width: snapshot.width
535                 height: snapshot.height
536             }
537             GaussianBlur {
538                 anchors.fill: shadow
539                 source: shadow
540                 radius: shadow.size
541                 samples: shadow.size * 2
542                 opacity: 0.3
543                 transparentBorder: true
544                 visible: wrapper.visibility == 1.0
545             }
546
547             Rectangle {
548                 id: snapshot
549                 color: uiColor
550
551                 Image {
552                     source: {
553                         if (!item.image.snapshot)
554                             return "qrc:///about"
555                         return item.image.snapshot.url
556                     }
557                     anchors.fill: parent
558                     Rectangle {
559                         enabled: index == pathView.currentIndex && !pathView.moving && !pathView.flicking && wrapper.visibility == 1.0
560                         opacity: enabled ? 1.0 : 0.0
561                         visible: wrapper.visibility == 1.0
562                         width: image.sourceSize.width
563                         height: image.sourceSize.height - 2
564                         radius: width / 2
565                         color: iconOverlayColor
566                         anchors {
567                             horizontalCenter: parent.right
568                             verticalCenter: parent.top
569                         }
570                         Image {
571                             id: image
572                             opacity: {
573                                 if (closeButton.pressed)
574                                     return 0.70
575                                 return 1.0
576                             }
577                             anchors {
578                                 top: parent.top
579                                 left: parent.left
580                             }
581                             source: "qrc:///delete"
582                             MouseArea {
583                                 id: closeButton
584                                 anchors.fill: parent
585                                 onClicked: {
586                                     mouse.accepted = true
587                                     remove(pathView.currentIndex)
588                                 }
589                             }
590                         }
591                         Behavior on opacity {
592                             NumberAnimation { duration: animationDuration / 2 }
593                         }
594                     }
595                 }
596                 anchors.fill: wrapper
597             }
598
599             Text {
600                 anchors {
601                     topMargin: -25
602                     top: parent.top
603                     horizontalCenter: parent.horizontalCenter
604                 }
605                 horizontalAlignment: Text.AlignHCenter
606                 width: parent.width - image.width
607                 elide: Text.ElideRight
608                 text: item.title
609                 font.pixelSize: 16
610                 font.family: defaultFontFamily
611                 color: settingsView.privateBrowsingEnabled ? "white" : "#0B508C"
612                 visible: wrapper.isCurrentItem && wrapper.visibility == 1.0
613             }
614         }
615     }
616
617     Rectangle {
618         color: uiColor
619         anchors.fill: parent
620     }
621
622     PathView {
623         id: pathView
624         pathItemCount: 5
625         anchors.fill: parent
626         model: listModel
627         delegate: delegate
628         highlightMoveDuration: animationDuration
629         highlightRangeMode: PathView.StrictlyEnforceRange
630         snapMode: PathView.SnapToItem
631         preferredHighlightBegin: 0.5
632         preferredHighlightEnd: 0.5
633
634         dragMargin: itemHeight
635
636         focus: pathView.interactive
637
638         property real offset: 30
639
640         property real margin: {
641             if (count == 2)
642                 return root.width / 4 - offset
643             if (count == 3)
644                 return root.width / 8 + offset
645             if (count == 4)
646                 return root.width / 8 - offset
647
648             return offset
649         }
650
651         property real middle: {
652             if (currentItem)
653                 return (pathView.height / 2) - (currentItem.visibility * 50)
654             return (pathView.height / 2 - 50)
655         }
656
657         path: Path {
658             startX: pathView.margin
659             startY: pathView.middle
660
661             PathPercent { value: 0.0 }
662             PathAttribute { name: "itemZ"; value: 0 }
663             PathLine {
664                 x: (pathView.width - itemWidth) / 2 + 106
665                 y: pathView.middle
666             }
667             PathPercent { value: 0.49 }
668             PathAttribute { name: "itemZ"; value: 6 }
669
670             PathLine { relativeX: 0; relativeY: 0 }
671
672             PathLine {
673                 x: (pathView.width - itemWidth) / 2 + itemWidth - 106
674                 y: pathView.middle
675             }
676             PathPercent { value: 0.51 }
677
678             PathLine { relativeX: 0; relativeY: 0 }
679
680             PathAttribute { name: "itemZ"; value: 4 }
681             PathLine {
682                 x: pathView.width - pathView.margin
683                 y: pathView.middle
684             }
685             PathPercent { value: 1 }
686             PathAttribute { name: "itemZ"; value: 2 }
687         }
688
689         Keys.onLeftPressed: decrementCurrentIndex()
690         Keys.onRightPressed: incrementCurrentIndex()
691     }
692 }