diff --git a/qmlUiKirigami/AlbumView.qml b/qmlUiKirigami/AlbumView.qml index 4d8fdff..ec21350 100644 --- a/qmlUiKirigami/AlbumView.qml +++ b/qmlUiKirigami/AlbumView.qml @@ -1,136 +1,136 @@ /* * Copyright (C) 2017 Atul Sharma * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ import QtQuick 2.7 import QtQuick.Controls 2.1 as Controls import org.kde.kirigami 2.1 as Kirigami import org.kde.koko 0.1 as Koko Kirigami.ScrollablePage { id: page property alias model: gridView.model signal collectionSelected(QtObject selectedModel, string cover) signal imageSelected(int currentIndex) signal folderSelected(QtObject selectedModel, string cover) keyboardNavigationEnabled: true focus: true states: [ State { name: "browsing" when: !model.hasSelectedImages }, State { name: "selecting" when: model.hasSelectedImages && Kirigami.Settings.isMobile } ] actions { main: Kirigami.Action { iconName: "edit-select-none" text: i18n("Deselect All") tooltip: i18n("De-selects all the selected images") enabled: model.hasSelectedImages visible: model.hasSelectedImages && Kirigami.Settings.isMobile onTriggered: model.clearSelections() } contextualActions: [ Kirigami.Action { iconName: "edit-select-all" text: i18n("Select All") tooltip: i18n("Selects all the images in the current view") enabled: model.containImages onTriggered: model.selectAll() }, Kirigami.Action { iconName: "edit-select-none" text: i18n("Deselect All") tooltip: i18n("De-selects all the selected images") enabled: model.hasSelectedImages onTriggered: model.clearSelections() }, Kirigami.Action { iconName: "group-delete" text: i18n("Delete Selection") tooltip: i18n("Move selected items to trash") enabled: model.hasSelectedImages onTriggered: model.deleteSelection() } ] } background: Rectangle { color: Kirigami.Theme.viewBackgroundColor } Keys.onPressed: { switch (event.key) { case Qt.Key_Escape: gridView.model.clearSelections() break; default: break; } } leftPadding: (page.width - Math.floor(page.width / gridView.cellWidth) * gridView.cellWidth)/2 rightPadding: leftPadding GridView { id: gridView //FIXME: right now if those two objects are out of this, the whole page breaks Koko.SortModel { id: sortedListModel } Koko.ImageFolderModel { id: imageFolderModel } keyNavigationEnabled: true cellWidth: Kirigami.Units.iconSizes.enormous + Kirigami.Units.smallSpacing * 2 cellHeight: cellWidth highlight: Rectangle { color: Kirigami.Theme.highlightColor} delegate: AlbumDelegate {} Kirigami.Label { anchors.centerIn: parent text: i18n("No Images Found") visible: gridView.count == 0 font.pointSize: Kirigami.Units.gridUnit * 1 } } onCollectionSelected: pageStack.push( Qt.resolvedUrl("AlbumView.qml"), { "model": selectedModel, "title": i18n(cover)}) onFolderSelected: pageStack.push( Qt.resolvedUrl("AlbumView.qml"), { "model": selectedModel, "title": i18n(cover)}) onImageSelected: { currentImage.model = model.sourceModel currentImage.index = currentIndex - imageViewer.state = "open"; + applicationWindow().pageStack.layers.push(imageViewerComponent); } } diff --git a/qmlUiKirigami/ImageViewer.qml b/qmlUiKirigami/ImageViewer.qml index aab42a9..ee7c6e6 100644 --- a/qmlUiKirigami/ImageViewer.qml +++ b/qmlUiKirigami/ImageViewer.qml @@ -1,421 +1,311 @@ /* * Copyright (C) 2017 Marco Martin * Copyright (C) 2017 Atul Sharma * Copyright (C) 2015 Vishesh Handa * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ import QtQuick 2.7 import QtQuick.Window 2.2 import QtQuick.Controls 2.0 as Controls import org.kde.kirigami 2.0 as Kirigami import org.kde.koko 0.1 as Koko import org.kde.kquickcontrolsaddons 2.0 as KQA Kirigami.Page { id: root - + + title: i18n("Details") property alias sourceModel: imagesListModel.sourceModel property int indexValue property int imageWidth property int imageHeight leftPadding: 0 rightPadding: 0 KQA.MimeDatabase { id: mimeDB } - - Kirigami.Action { - id: backAction - iconName: "view-close" - tooltip: i18n("Close Image") - onTriggered: root.state = "closed" - } - - Kirigami.Action { - id: shareAction - iconName: "document-share" - tooltip: i18n("Share Image") - onTriggered: { - shareDialog.sheetOpen = true - shareDialog.inputData = { - "urls": [ listView.currentItem.currentImageSource.toString() ], - "mimeType": mimeDB.mimeTypeForUrl( listView.currentItem.currentImageSource).name - } - } - } - - Kirigami.Action { - id: editAction - iconName: "editimage" - tooltip: i18n("Edit Image") - } - - mainAction: root.state == "open" ? shareAction : null - leftAction: root.state == "open" ? backAction : null - rightAction: root.state == "open" ? editAction : null - states: [ - State { - name: "open" - PropertyChanges { - target: root - visible: true - } - PropertyChanges { - target: root - opacity: 1 - } - PropertyChanges { - target: root - focus: true - } - PropertyChanges { - target: listView - focus: true - } - PropertyChanges { - target: applicationWindow() - visibility: Window.Windowed - } - }, - State { - name: "closed" - PropertyChanges { - target: root - opacity: 0 - } - PropertyChanges { - target: root - visible: false - } - PropertyChanges { - target: shareDialog - sheetOpen: false - } - }, - State { - name: "fullscreen" - PropertyChanges { - target: root - focus: true - } - PropertyChanges { - target: listView - focus: true - } - PropertyChanges { - target: applicationWindow() - visibility: Window.FullScreen - } + actions { + left: Kirigami.Action { + id: backAction + iconName: "view-close" + tooltip: i18n("Close Image") + onTriggered: root.close(); } - ] - - transitions: [ - Transition { - from: "*" - to: "closed" - SequentialAnimation { - OpacityAnimator { - target: root - duration: Kirigami.Units.longDuration - easing.type: Easing.InQuad - } - PropertyAnimation { - target: root - property: "visible" - duration: Kirigami.Units.longDuration - } - ScriptAction { - script: applicationWindow().pageStack.forceActiveFocus(); + main: Kirigami.Action { + id: shareAction + iconName: "document-share" + tooltip: i18n("Share Image") + onTriggered: { + shareDialog.open(); + shareDialog.inputData = { + "urls": [ listView.currentItem.currentImageSource.toString() ], + "mimeType": mimeDB.mimeTypeForUrl( listView.currentItem.currentImageSource).name } } - }, - Transition { - from: "closed" - to: "open" - OpacityAnimator { - target: root - duration: Kirigami.Units.longDuration - easing.type: Easing.OutQuad - } } - ] + right: Kirigami.Action { + id: editAction + iconName: "editimage" + tooltip: i18n("Edit Image") + } + } + + //FIXME: HACK + property bool wasDrawerOpen + Component.onCompleted: { + applicationWindow().controlsVisible = false; + listView.forceActiveFocus(); + applicationWindow().header.visible = false; + applicationWindow().footer.visible = false; + wasDrawerOpen = applicationWindow().globalDrawer.visible; + applicationWindow().globalDrawer.visible = false; + applicationWindow().globalDrawer.enabled = false; + } + function close() { + applicationWindow().controlsVisible = true; + applicationWindow().header.visible = true; + applicationWindow().footer.visible = true; + applicationWindow().globalDrawer.visible = wasDrawerOpen; + applicationWindow().globalDrawer.enabled = true; + applicationWindow().visibility = Window.Windowed; + applicationWindow().pageStack.layers.pop(); + } + background: Rectangle { color: "black" } Keys.onPressed: { switch(event.key) { case Qt.Key_Escape: - root.state = "closed" + root.close(); break; case Qt.Key_F: - root.state = root.state == "open" ? "fullscreen" : "open" + applicationWindow().visibility = applicationWindow().visibility == Window.FullScreen ? Window.Windowed : Window.FullScreen break; default: break; } } ListView { id: listView anchors.fill: parent orientation: Qt.Horizontal snapMode: ListView.SnapOneItem onMovementEnded: currentImage.index = model.sourceIndex(indexAt(contentX+1, 1)) model: Koko.SortModel { id: imagesListModel filterRole: Koko.Roles.MimeTypeRole filterRegExp: /image\// } currentIndex: model.proxyIndex( indexValue) - Timer { - id: timer - interval: 2000 - onTriggered: footerList.opacity = 0 - } - onCurrentIndexChanged: { currentImage.index = model.sourceIndex( currentIndex) listView.positionViewAtIndex(currentIndex, ListView.Beginning) - if( footerList.visible == true) { - footerList.opacity = 1.0 - } - timer.restart() - shareDialog.sheetOpen = false + + shareDialog.close(); } delegate: Flickable { id: flick property alias currentImageSource: image.source width: imageWidth height: imageHeight contentWidth: imageWidth contentHeight: imageHeight interactive: contentWidth > width || contentHeight > height onInteractiveChanged: listView.interactive = !interactive; clip: true z: index == listView.currentIndex ? 1000 : 0 Controls.ScrollBar.vertical: Controls.ScrollBar {} Controls.ScrollBar.horizontal: Controls.ScrollBar {} PinchArea { width: Math.max(flick.contentWidth, flick.width) height: Math.max(flick.contentHeight, flick.height) property real initialWidth property real initialHeight onPinchStarted: { initialWidth = flick.contentWidth initialHeight = flick.contentHeight } onPinchUpdated: { // adjust content pos due to drag flick.contentX += pinch.previousCenter.x - pinch.center.x flick.contentY += pinch.previousCenter.y - pinch.center.y // resize content flick.resizeContent(Math.max(imageWidth*0.7, initialWidth * pinch.scale), Math.max(imageHeight*0.7, initialHeight * pinch.scale), pinch.center) } onPinchFinished: { // Move its content within bounds. if (flick.contentWidth < root.imageWidth || flick.contentHeight < root.imageHeight) { zoomAnim.x = 0; zoomAnim.y = 0; zoomAnim.width = root.imageWidth; zoomAnim.height = root.imageHeight; zoomAnim.running = true; } else { flick.returnToBounds(); } } ParallelAnimation { id: zoomAnim property real x: 0 property real y: 0 property real width: root.imageWidth property real height: root.imageHeight NumberAnimation { target: flick property: "contentWidth" from: flick.contentWidth to: zoomAnim.width duration: Kirigami.Units.longDuration easing.type: Easing.InOutQuad } NumberAnimation { target: flick property: "contentHeight" from: flick.contentHeight to: zoomAnim.height duration: Kirigami.Units.longDuration easing.type: Easing.InOutQuad } NumberAnimation { target: flick property: "contentY" from: flick.contentY to: zoomAnim.y duration: Kirigami.Units.longDuration easing.type: Easing.InOutQuad } NumberAnimation { target: flick property: "contentX" from: flick.contentX to: zoomAnim.x duration: Kirigami.Units.longDuration easing.type: Easing.InOutQuad } } Image { id: image width: flick.contentWidth height: flick.contentHeight source: model.imageurl fillMode: Image.PreserveAspectFit asynchronous: true autoTransform: true sourceSize.width: imageWidth * 2 sourceSize.height: imageHeight * 2 + Timer { + id: doubleClickTimer + interval: 150 + onTriggered: applicationWindow().controlsVisible = !applicationWindow().controlsVisible + } MouseArea { anchors.fill: parent + onClicked: { + doubleClickTimer.restart(); + } onDoubleClicked: { + doubleClickTimer.running = false; + applicationWindow().controlsVisible = false; if (flick.interactive) { zoomAnim.x = 0; zoomAnim.y = 0; zoomAnim.width = root.imageWidth; zoomAnim.height = root.imageHeight; zoomAnim.running = true; } else { zoomAnim.x = mouse.x * 2; zoomAnim.y = mouse.y *2; zoomAnim.width = root.imageWidth * 3; zoomAnim.height = root.imageHeight * 3; zoomAnim.running = true; } } onWheel: { if (wheel.modifiers & Qt.ControlModifier) { if (wheel.angleDelta.y != 0) { var factor = 1 + wheel.angleDelta.y / 600; zoomAnim.running = false; zoomAnim.width = Math.min(Math.max(root.imageWidth, zoomAnim.width * factor), root.imageWidth * 4); zoomAnim.height = Math.min(Math.max(root.imageHeight, zoomAnim.height * factor), root.imageHeight * 4); //actual factors, may be less than factor var xFactor = zoomAnim.width / flick.contentWidth; var yFactor = zoomAnim.height / flick.contentHeight; zoomAnim.x = flick.contentX * xFactor + (((wheel.x - flick.contentX) * xFactor) - (wheel.x - flick.contentX)) zoomAnim.y = flick.contentY * yFactor + (((wheel.y - flick.contentY) * yFactor) - (wheel.y - flick.contentY)) zoomAnim.running = true; } else if (wheel.pixelDelta.y != 0) { flick.resizeContent(Math.min(Math.max(root.imageWidth, flick.contentWidth + wheel.pixelDelta.y), root.imageWidth * 4), Math.min(Math.max(root.imageHeight, flick.contentHeight + wheel.pixelDelta.y), root.imageHeight * 4), wheel); } } } } } } } } - PathView { - id: footerList - visible: root.state == "open" ? true : false - height: Kirigami.Units.gridUnit * 4 - model: listView.model - currentIndex: listView.currentIndex - pathItemCount: applicationWindow().width / (Kirigami.Units.gridUnit * 5) - interactive: false - - preferredHighlightBegin: 0.5 - preferredHighlightEnd: 0.5 - highlightRangeMode: PathView.StrictlyEnforceRange - - Behavior on opacity { OpacityAnimator { duration: 500}} - - path: Path { - startX: 0 - startY: applicationWindow().height - (Kirigami.Units.gridUnit * 5) - PathAttribute { name: "iconScale"; value: 0.5 } - PathLine { - x: applicationWindow().width / 2 - y: applicationWindow().height - (Kirigami.Units.gridUnit * 5) - } - PathAttribute { name: "iconScale"; value: 1.5 } - PathLine { - x: applicationWindow().width - y: applicationWindow().height - (Kirigami.Units.gridUnit * 5) - } - PathAttribute { name: "iconScale"; value: 0.5 } - } - - delegate: Item { - scale: PathView.iconScale - height: Kirigami.Units.gridUnit * 4 - width: height - KQA.QImageItem { - height: Kirigami.Units.gridUnit * 3.8 - width: height - anchors.centerIn: parent - image: model.thumbnail - } - } - } - ShareDialog { id: shareDialog + x: (root.width - width) / 2 + y: root.height - height - Kirigami.Units.gridUnit * 3 inputData: { urls: [] } - sheetOpen: false onFinished: { if (error==0 && output.url !== "") { console.assert(output.url !== undefined); var resultUrl = output.url; console.log("Received", resultUrl) notificationManager.showNotification( true, resultUrl); } else { notificationManager.showNotification( false); } } } Koko.NotificationManager { id: notificationManager } } diff --git a/qmlUiKirigami/Main.qml b/qmlUiKirigami/Main.qml index 42b4a1b..80615b2 100644 --- a/qmlUiKirigami/Main.qml +++ b/qmlUiKirigami/Main.qml @@ -1,206 +1,205 @@ /* * Copyright (C) 2017 Atul Sharma * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * */ import QtQuick 2.1 import QtQuick.Controls 2.0 as Controls import org.kde.kirigami 2.0 as Kirigami import org.kde.koko 0.1 as Koko Kirigami.ApplicationWindow { id: root header: Kirigami.ApplicationHeader {} /* * currentImage now stores the information related to the source model */ QtObject { id: currentImage property int index property var model property GridView view : pageStack.currentItem.flickable onIndexChanged: { view.currentIndex = view.model.proxyIndex(currentImage.index) } } pageStack.initialPage: AlbumView { id: albumView model: imageFolderModel title: i18n("Folders") } globalDrawer: Sidebar { id: sideBar onFilterBy: { pageStack.pop(albumView) albumView.title = i18n(value) previouslySelectedAction.checked = false switch( value){ case "Countries": { albumView.model = imageLocationModelCountry; imageListModel.locationGroup = Koko.Types.Country; break; } case "States": { albumView.model = imageLocationModelState; imageListModel.locationGroup = Koko.Types.State; break; } case "Cities": { albumView.model = imageLocationModelCity; imageListModel.locationGroup = Koko.Types.City; break; } case "Years": { albumView.model = imageTimeModelYear; imageListModel.timeGroup = Koko.Types.Year; break; } case "Months": { albumView.model = imageTimeModelMonth; imageListModel.timeGroup = Koko.Types.Month; break; } case "Weeks": { albumView.model = imageTimeModelWeek; imageListModel.timeGroup = Koko.Types.Week; break; } case "Days": { albumView.model = imageTimeModelDay; imageListModel.timeGroup = Koko.Types.Day; break; } case "Folders": { albumView.model = imageFolderModel; imageListModel.locationGroup = -1; imageListModel.timeGroup = -1; break; } } } } Koko.SortModel { id: imageFolderModel sourceModel: Koko.ImageFolderModel { /** * imagePathArgument[0] sets the column 0 of the pageStack to reflect either a foreign path or ~/Pictures */ url: imagePathArgument == "" ? "" : imagePathArgument[0] /** * makes sure that operation only occurs after the model is populated */ onRowsInserted: { for( var i = 1; i < imagePathArgument.length -1 ; i++) { pageStack.push( Qt.resolvedUrl("ImageFolderAlbumView.qml"), { "sourceUrl": imagePathArgument[i] }) } /** * To set the currentImage when no folder is pushed */ if ( (currentImage.view.model.sourceModel == this) && (indexForUrl(imagePathArgument[imagePathArgument.length - 1]) != -1) ) { currentImage.model = this currentImage.index = indexForUrl(imagePathArgument[imagePathArgument.length - 1]) } } } /* * filterRole is an Item property exposed by the QSortFilterProxyModel */ filterRole: Koko.Roles.MimeTypeRole } Koko.SortModel { id: imageTimeModelYear sourceModel: Koko.ImageTimeModel { group: Koko.Types.Year } sortRoleName: "date" } Koko.SortModel { id: imageTimeModelMonth sourceModel: Koko.ImageTimeModel { group: Koko.Types.Month } sortRoleName: "date" } Koko.SortModel { id: imageTimeModelWeek sourceModel: Koko.ImageTimeModel { group: Koko.Types.Week } sortRoleName: "date" } Koko.SortModel { id: imageTimeModelDay sourceModel: Koko.ImageTimeModel { group: Koko.Types.Day } sortRoleName: "date" } Koko.SortModel { id: imageLocationModelCountry sourceModel: Koko.ImageLocationModel { group: Koko.Types.Country } } Koko.SortModel { id: imageLocationModelState sourceModel: Koko.ImageLocationModel { group: Koko.Types.State } } Koko.SortModel { id: imageLocationModelCity sourceModel: Koko.ImageLocationModel { group: Koko.Types.City } } Koko.ImageListModel { id: imageListModel } - - ImageViewer { - id: imageViewer - //go on top of the overlay drawer - //HACK on the parent and z to go on top of the handle as well - z: 1999999 - parent: root.overlay.parent - width: overlay.width - height: overlay.height - indexValue: currentImage.index - sourceModel: currentImage.model - imageWidth: root.width - imageHeight: root.height - state: imagePathArgument == "" ? "closed" : "open" + Component { + id: imageViewerComponent + ImageViewer { + id: imageViewer + indexValue: currentImage.index + sourceModel: currentImage.model + imageWidth: root.width + imageHeight: root.height + } + } + Component.onCompleted: { + if (imagePathArgument != "") { + pageStack.layers.push(imageViewerComponent); + } } - } diff --git a/qmlUiKirigami/ShareDialog.qml b/qmlUiKirigami/ShareDialog.qml index 2466727..97ca6d6 100644 --- a/qmlUiKirigami/ShareDialog.qml +++ b/qmlUiKirigami/ShareDialog.qml @@ -1,64 +1,66 @@ /* * Copyright 2017 by Atul Sharma * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.7 import QtQuick.Layouts 1.3 -import QtQuick.Controls 2.1 as Controls +import QtQuick.Controls 2.0 as Controls import org.kde.purpose 1.0 as Purpose import org.kde.kirigami 2.1 as Kirigami -Kirigami.OverlaySheet -{ +Controls.Popup { id: window + modal: true + focus: true property alias inputData: view.inputData property bool running: false signal finished(var output, int error, string message) + width: Kirigami.Units.gridUnit * 25 + height: Kirigami.Units.gridUnit * 28 Controls.BusyIndicator { visible: window.running anchors.fill: parent } - - contentItem: ColumnLayout { - height: Kirigami.Units.gridUnit * 16 - - Kirigami.Heading { - text: window.inputData.mimeType ? i18n("Shares for '%1'", window.inputData.mimeType) : "" - } + + Rectangle { + anchors.fill: parent + color: Kirigami.Theme.viewBackgroundColor Purpose.AlternativesView { id: view - Layout.fillWidth: true - Layout.fillHeight: true + anchors.fill:parent + clip: true pluginType: "Export" - + header: Kirigami.Heading { + text: window.inputData.mimeType ? i18n("Shares for '%1'", window.inputData.mimeType) : "" + } delegate: Kirigami.BasicListItem { label: model.display icon: "arrow-right" onClicked: view.createJob (model.index) Keys.onReturnPressed: view.createJob (model.index) Keys.onEnterPressed: view.createJob (model.index) } onRunningChanged: window.running = running onFinished: { window.finished(output, error, message) } } } }