diff --git a/qmlUiKirigami/ImageViewer.qml b/qmlUiKirigami/ImageViewer.qml index 7af5e90..5eadee0 100644 --- a/qmlUiKirigami/ImageViewer.qml +++ b/qmlUiKirigami/ImageViewer.qml @@ -1,282 +1,281 @@ /* * 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.Layouts 1.3 import QtQuick.Controls 2.0 as Controls import org.kde.kirigami 2.0 as Kirigami import org.kde.koko 0.1 as Koko Kirigami.Page { id: root property alias sourceModel: imagesListModel.sourceModel property int indexValue property int imageWidth property int imageHeight leftPadding: 0 rightPadding: 0 - state: "closed" states: [ State { name: "open" PropertyChanges { target: root visible: true } PropertyChanges { target: root opacity: 1 } PropertyChanges { target: root focus: true } PropertyChanges { target: listView focus: true } }, State { name: "closed" PropertyChanges { target: root opacity: 0 } PropertyChanges { target: root visible: false } } ] transitions: [ Transition { from: "open" 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(); } } }, Transition { from: "closed" to: "open" OpacityAnimator { target: root duration: Kirigami.Units.longDuration easing.type: Easing.OutQuad } } ] background: Rectangle { color: "black" } Keys.onEscapePressed: root.state = "closed"; 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) onCurrentIndexChanged: { currentImage.index = model.sourceIndex( currentIndex) listView.positionViewAtIndex(currentIndex, ListView.Beginning) } delegate: Flickable { id: flick 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 MouseArea { anchors.fill: parent onDoubleClicked: { 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); } } } } } } } } //FIXME: placeholder, will have to use the state machine Controls.Button { text: i18n("Back") onClicked: root.state = "closed" } } diff --git a/qmlUiKirigami/Main.qml b/qmlUiKirigami/Main.qml index 9782ca3..0f50a47 100644 --- a/qmlUiKirigami/Main.qml +++ b/qmlUiKirigami/Main.qml @@ -1,198 +1,198 @@ /* * 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 } 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 { url: imagePathArgument /** * makes sure that operation only occurs after the model is populated */ onRowsInserted: { if( indexForUrl(imagePathArgument) != -1) { currentImage.model = this currentImage.index = indexForUrl(imagePathArgument) - imageViewer.state = "open" } } } /* * 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: 2000002 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.onCompleted: { albumView.model = imageFolderModel albumView.title = i18n("Folders") } } diff --git a/src/imagefoldermodel.cpp b/src/imagefoldermodel.cpp index cb5c1f1..b735f16 100644 --- a/src/imagefoldermodel.cpp +++ b/src/imagefoldermodel.cpp @@ -1,164 +1,166 @@ /* * Copyright 2017 by Marco Martin * Copyright (C) 2017 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. */ #include "imagefoldermodel.h" #include "types.h" #include "roles.h" #include #include #include #include #include #include #include #include #include ImageFolderModel::ImageFolderModel(QObject *parent) : KDirModel(parent) { QMimeDatabase db; QList mimeList = db.allMimeTypes(); m_mimeTypes << "inode/directory"; foreach (const QMimeType &mime, mimeList) { if (mime.name().startsWith(QStringLiteral("image/"))) { m_mimeTypes << mime.name(); } } dirLister()->setMimeFilter(m_mimeTypes); connect(this, &QAbstractItemModel::rowsInserted, this, &ImageFolderModel::countChanged); connect(this, &QAbstractItemModel::rowsRemoved, this, &ImageFolderModel::countChanged); connect(this, &QAbstractItemModel::modelReset, this, &ImageFolderModel::countChanged); } ImageFolderModel::~ImageFolderModel() { } QHash ImageFolderModel::roleNames() const { return { { Qt::DisplayRole, "display" }, { Qt::DecorationRole, "decoration" }, { Roles::ImageUrlRole, "imageurl" }, { Roles::MimeTypeRole, "mimeType" }, { Roles::ItemTypeRole, "itemType"} }; } QString ImageFolderModel::url() const { return dirLister()->url().toString(); } void ImageFolderModel::setUrl(QString& url) { - if (url.isEmpty()) { - return; - } - Q_ASSERT( QUrl(url).isLocalFile()); url = QUrl(url).path(); + if (url.isEmpty()) { + QStringList locations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation); + Q_ASSERT(locations.size() > 1); + url = locations.first().append("/"); + } + QString directoryUrl; if( QDir(url).exists()) { directoryUrl = QUrl::fromLocalFile(url).toString(); } else { m_imagePath = url; directoryUrl = QUrl::fromLocalFile(url.left(url.lastIndexOf('/'))).toString(); } if (dirLister()->url().path() == directoryUrl) { dirLister()->updateDirectory(QUrl(directoryUrl)); return; } beginResetModel(); dirLister()->openUrl(QUrl(directoryUrl)); endResetModel(); emit urlChanged(); } int ImageFolderModel::indexForUrl(const QString &url) const { QModelIndex index = KDirModel::indexForUrl(QUrl(url)); return index.row(); } QVariantMap ImageFolderModel::get(int i) const { QModelIndex modelIndex = index(i, 0); KFileItem item = itemForIndex(modelIndex); QString url = item.url().toString(); QString mimeType = item.mimetype(); QVariantMap ret; ret.insert(QStringLiteral("url"), QVariant(url)); ret.insert(QStringLiteral("mimeType"), QVariant(mimeType)); return ret; } void ImageFolderModel::emptyTrash() { KIO::emptyTrash(); } QVariant ImageFolderModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } switch (role) { case Roles::ImageUrlRole: { KFileItem item = itemForIndex(index); return item.url().toString(); } case Roles::MimeTypeRole: { KFileItem item = itemForIndex(index); return item.mimetype(); } case Roles::ItemTypeRole: { KFileItem item = itemForIndex(index); if( item.isDir()) { return Types::Folder; } else { return Types::Image; } } default: return KDirModel::data(index, role); } } #include "moc_imagefoldermodel.cpp" diff --git a/src/main.cpp b/src/main.cpp index dcfc3c4..3b7031a 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,133 +1,133 @@ /* * Copyright (C) 2017 Atul Sharma * Copyright (C) 2014 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) any later version. * * 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, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "filesystemtracker.h" #include "processor.h" #include "kokoconfig.h" #include "imagestorage.h" int main(int argc, char** argv) { QApplication app(argc, argv); app.setApplicationDisplayName("Koko"); app.setOrganizationDomain("kde.org"); KDBusService service(KDBusService::Unique); QCommandLineParser parser; parser.addOption(QCommandLineOption("reset", i18n("Reset the database"))); parser.addPositionalArgument( "image", i18n("path of image you want to open")); parser.addHelpOption(); parser.process(app); if (parser.positionalArguments().size() > 1) { parser.showHelp(1); } if (parser.isSet("reset")) { KokoConfig config; config.reset(); ImageStorage::reset(); } QThread trackerThread; QStringList locations = QStandardPaths::standardLocations(QStandardPaths::PicturesLocation); Q_ASSERT(locations.size() >= 1); qDebug() << locations; QUrl currentDirPath = QUrl::fromLocalFile(QDir::currentPath().append('/')); QUrl resolvedImagePath = parser.positionalArguments().isEmpty() - ? QUrl::fromLocalFile(locations.first().append('/')) + ? QUrl() : currentDirPath.resolved( parser.positionalArguments().first()); if( !resolvedImagePath.isLocalFile()) { - resolvedImagePath = QUrl::fromLocalFile(locations.first().append('/')) ; + resolvedImagePath = QUrl() ; } FileSystemTracker tracker; tracker.setFolder(locations.first()); tracker.moveToThread(&trackerThread); Koko::Processor processor; QObject::connect(&tracker, &FileSystemTracker::imageAdded, &processor, &Koko::Processor::addFile); QObject::connect(&tracker, &FileSystemTracker::imageRemoved, &processor, &Koko::Processor::removeFile); QObject::connect(&tracker, &FileSystemTracker::initialScanComplete, &processor, &Koko::Processor::initialScanCompleted); trackerThread.start(); tracker.setSubFolder(tracker.folder()); KokoConfig config; QQmlEngine engine; QQmlContext* objectContext = engine.rootContext(); objectContext->setContextProperty("kokoProcessor", &processor); objectContext->setContextProperty("kokoConfig", &config); objectContext->setContextProperty("imagePathArgument", resolvedImagePath.toString()); QString path; //we want different main files on desktop or mobile //very small difference as they as they are subclasses of the same thing if (qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_MOBILE") && (QString::fromLatin1(qgetenv("QT_QUICK_CONTROLS_MOBILE")) == QStringLiteral("1") || QString::fromLatin1(qgetenv("QT_QUICK_CONTROLS_MOBILE")) == QStringLiteral("true"))) { path = QStandardPaths::locate(QStandardPaths::DataLocation, "ui/mobileMain.qml"); } else { path = QStandardPaths::locate(QStandardPaths::DataLocation, "ui/desktopMain.qml"); } QQuickView* view = new QQuickView( &engine, new QWindow()); view->engine()->rootContext()->setContextObject(new KLocalizedContext(view)); QQmlComponent component(&engine, path); if (component.isError()) { std::cout << component.errorString().toUtf8().constData() << std::endl; Q_ASSERT(0); } Q_ASSERT(component.status() == QQmlComponent::Ready); QObject* obj = component.create(objectContext); Q_ASSERT(obj); int rt = app.exec(); trackerThread.quit(); return rt; }