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