1/*
2 * Copyright (C) 2015 Dan Leinir Turthra Jensen <admin@leinir.dk>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) version 3, or any
8 * later version accepted by the membership of KDE e.V. (or its
9 * successor approved by the membership of KDE e.V.), which shall
10 * act as a proxy defined in Section 6 of version 3 of the license.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
19 *
20 */
21
22import QtQuick 2.12
23import QtQuick.Controls 2.12 as QtControls
24
25import org.kde.kirigami 2.7 as Kirigami
26import org.kde.okular 2.0 as Okular
27
28import "helpers" as Helpers
29
30/**
31 * @brief a ViewerBase intended as a fallback for unsupported books.
32 *
33 * It is called from Book when the opened book has no other specialised viewers.
34 *
35 * It does not use the ImageBrowser because it needs to access
36 * Okular Page items for the images.
37 */
38ViewerBase {
39    id: root;
40    title: documentItem.windowTitleForDocument;
41    onFileChanged: documentItem.url = "file://" + file;
42    onCurrentPageChanged: {
43        if(documentItem.currentPage !== currentPage) {
44            documentItem.currentPage = currentPage;
45        }
46        if(currentPage !== imageBrowser.currentIndex) {
47            pageChangeAnimation.running = false;
48            var currentPos = imageBrowser.contentX;
49            var newPos;
50            imageBrowser.positionViewAtIndex(currentPage, ListView.Center);
51            imageBrowser.currentIndex = currentPage;
52            newPos = imageBrowser.contentX;
53            pageChangeAnimation.from = currentPos;
54            pageChangeAnimation.to = newPos;
55            pageChangeAnimation.running = true;
56        }
57    }
58    NumberAnimation { id: pageChangeAnimation; target: imageBrowser; property: "contentX"; duration: applicationWindow().animationDuration; easing.type: Easing.InOutQuad; }
59    onRtlModeChanged: {
60        if(rtlMode === true) {
61            imageBrowser.layoutDirection = Qt.RightToLeft;
62        }
63        else {
64            imageBrowser.layoutDirection = Qt.LeftToRight;
65        }
66        root.restoreCurrentPage();
67    }
68    onRestoreCurrentPage: {
69        // This is un-pretty, quite obviously. But thanks to the ListView's inability to
70        // stay in place when the geometry changes, well, this makes things simple.
71        imageBrowser.positionViewAtIndex(imageBrowser.currentIndex, ListView.Center);
72
73    }
74    pageCount: documentItem.pageCount;
75    thumbnailComponent: thumbnailComponent;
76    pagesModel: documentItem.matchingPages;
77    Component {
78        id: thumbnailComponent;
79        Item {
80            width: parent.width;
81            height: Kirigami.Units.gridUnit * 6;
82            MouseArea {
83                anchors.fill: parent;
84                onClicked: viewLoader.item.currentPage = model.index;
85            }
86            Rectangle {
87                anchors.fill: parent;
88                color: Kirigami.Theme.highlightColor;
89                opacity: root.currentPage === model.index ? 1 : 0;
90                Behavior on opacity { NumberAnimation { duration: Kirigami.Units.shortDuration; } }
91            }
92            Okular.ThumbnailItem {
93                id: thumbnail
94                anchors {
95                    top: parent.top;
96                    horizontalCenter: parent.horizontalCenter;
97                    margins: Kirigami.Units.smallSpacing;
98                }
99                document: documentItem
100                pageNumber: modelData
101                height: parent.height - pageTitle.height - Kirigami.Units.smallSpacing * 2;
102                function updateWidth() {
103                    width = Math.round(height * (implicitWidth / implicitHeight));
104                }
105                Component.onCompleted: updateWidth();
106                onHeightChanged: updateWidth();
107                onImplicitHeightChanged: updateWidth();
108            }
109            QtControls.Label {
110                id: pageTitle;
111                anchors {
112                    left: parent.left;
113                    right: parent.right;
114                    bottom: parent.bottom;
115                }
116                height: paintedHeight;
117                text: modelData + 1;
118                elide: Text.ElideMiddle;
119                horizontalAlignment: Text.AlignHCenter;
120            }
121        }
122    }
123    Okular.DocumentItem {
124        id: documentItem
125        onOpenedChanged: {
126            if(opened === true) {
127                root.loadingCompleted(true);
128                initialPageChange.start();
129            } else {
130                console.debug("Well then, error loading the file...");
131            }
132        }
133        onCurrentPageChanged: {
134            if(root.currentPage !== currentPage) {
135                root.currentPage = currentPage;
136            }
137        }
138    }
139
140    Timer {
141        id: initialPageChange;
142        interval: applicationWindow().animationDuration;
143        running: false;
144        repeat: false;
145        onTriggered: root.currentPage = imageBrowser.currentIndex;
146    }
147    ListView {
148        id: imageBrowser
149        anchors.fill: parent;
150        model: documentItem.matchingPages;
151
152        interactive: false // No interactive flicky stuff here, we'll handle that with the navigator instance
153        property int imageWidth: root.width + Kirigami.Units.largeSpacing;
154        property int imageHeight: root.height;
155
156        orientation: ListView.Horizontal
157        snapMode: ListView.SnapOneItem
158
159        // This ensures that the current index is always up to date, which we need to ensure we can track the current page
160        // as required by the thumbnail navigator, and the resume-reading-from functionality
161        onMovementEnded: {
162            var indexHere = indexAt(contentX + width / 2, contentY + height / 2);
163            if(currentIndex !== indexHere) {
164                currentIndex = indexHere;
165            }
166        }
167
168        delegate: Flickable {
169            id: flick
170            width: imageBrowser.imageWidth
171            height: imageBrowser.imageHeight
172            contentWidth: imageBrowser.imageWidth
173            contentHeight: imageBrowser.imageHeight
174            interactive: contentWidth > width || contentHeight > height
175            z: interactive ? 1000 : 0
176            PinchArea {
177                width: Math.max(flick.contentWidth, flick.width)
178                height: Math.max(flick.contentHeight, flick.height)
179
180                property real initialWidth
181                property real initialHeight
182
183                onPinchStarted: {
184                    initialWidth = flick.contentWidth
185                    initialHeight = flick.contentHeight
186                }
187
188                onPinchUpdated: {
189                    // adjust content pos due to drag
190                    flick.contentX += pinch.previousCenter.x - pinch.center.x
191                    flick.contentY += pinch.previousCenter.y - pinch.center.y
192
193                    // resize content
194                    flick.resizeContent(Math.max(imageBrowser.imageWidth, initialWidth * pinch.scale), Math.max(imageBrowser.imageHeight, initialHeight * pinch.scale), pinch.center)
195                }
196
197                onPinchFinished: {
198                    // Move its content within bounds.
199                    flick.returnToBounds();
200                }
201
202                Item {
203                    implicitWidth: page.implicitWidth
204                    implicitHeight: page.implicitHeight
205                    width: flick.contentWidth
206                    height: flick.contentHeight
207                    Okular.PageItem {
208                        id: page;
209                        document: documentItem;
210                        pageNumber: index;
211                        anchors.centerIn: parent;
212                        property real pageRatio: implicitWidth / implicitHeight
213                        property bool sameOrientation: root.width / root.height > pageRatio
214                        width: sameOrientation ? parent.height * pageRatio : parent.width
215                        height: !sameOrientation ? parent.width / pageRatio : parent.height
216                    }
217                    MouseArea {
218                        anchors.fill: parent;
219                        enabled: flick.interactive
220                        onClicked: startToggleControls()
221                        onDoubleClicked: {
222                            abortToggleControls();
223                            flick.resizeContent(imageBrowser.imageWidth, imageBrowser.imageHeight, Qt.point(imageBrowser.imageWidth/2, imageBrowser.imageHeight/2));
224                            flick.returnToBounds();
225                        }
226                    }
227                }
228            }
229        }
230    }
231
232    Helpers.Navigator {
233        enabled: !imageBrowser.currentItem.interactive;
234        anchors.fill: parent;
235        onLeftRequested: imageBrowser.layoutDirection == Qt.RightToLeft? root.goNextPage(): root.goPreviousPage();
236        onRightRequested: imageBrowser.layoutDirection == Qt.RightToLeft? root.goPreviousPage(): root.goNextPage();
237        onTapped: startToggleControls();
238        onDoubleTapped: {
239            abortToggleControls();
240            imageBrowser.currentItem.resizeContent(imageBrowser.imageWidth * 2, imageBrowser.imageHeight * 2, Qt.point(eventPoint.x, eventPoint.y));
241            imageBrowser.currentItem.returnToBounds();
242        }
243    }
244}
245