diff --git a/qmlUiKirigami/AlbumDelegate.qml b/qmlUiKirigami/AlbumDelegate.qml index 226c419..aff6bf2 100644 --- a/qmlUiKirigami/AlbumDelegate.qml +++ b/qmlUiKirigami/AlbumDelegate.qml @@ -1,112 +1,112 @@ /* * 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.kquickcontrolsaddons 2.0 as KQA import org.kde.kirigami 2.1 as Kirigami import org.kde.koko 0.1 as Koko Item { id: albumDelegate width: gridView.cellWidth height: gridView.cellHeight KQA.QImageItem { id: image anchors.centerIn: parent width: gridView.cellWidth - (Kirigami.Units.smallSpacing * 2 ) height: width smooth: true image: model.thumbnail fillMode: KQA.QImageItem.PreserveAspectCrop } Kirigami.BasicListItem { visible: model.itemType == Koko.Types.Folder || model.itemType == Koko.Types.Album label: model.fileCount ? i18np(" %2 \n 1 Image"," %2 \n %1 Images", model.fileCount, model.display) : model.display reserveSpaceForIcon: false width: image.width anchors.left: image.left anchors.top: image.top background: Rectangle { anchors.fill: parent opacity: 0.7 color: Kirigami.Theme.backgroundColor } } SelectionButton { id: selectionButton visible: ( albumThumbnailMouseArea.containsMouse || iconMouseArea.containsMouse ) && !(model.itemType == Koko.Types.Folder || model.itemType == Koko.Types.Album) } SelectionDelegateHighlight { id: selectionHighlight visible: model.selected } MouseArea { id: albumThumbnailMouseArea anchors.fill: parent hoverEnabled: true onClicked: activate(); } Keys.onPressed: { switch (event.key) { case Qt.Key_Enter: case Qt.Key_Return: case Qt.Key_Space: activate(); break; default: break; } } function activate( ) { gridView.model.clearSelections() switch( model.itemType) { case Koko.Types.Album: { imageListModel.query = imageListModel.queryForIndex( model.sourceIndex) sortedListModel.sourceModel = imageListModel collectionSelected( sortedListModel, model.display) break; } case Koko.Types.Folder: { imageFolderModel.url = model.imageurl sortedListModel.sourceModel = imageFolderModel folderSelected( sortedListModel, model.display) break; } case Koko.Types.Image: { - imageSelected(model.index) + imageSelected( model.sourceIndex) break; } default: { console.log("Unknown") break; } } } } diff --git a/qmlUiKirigami/AlbumView.qml b/qmlUiKirigami/AlbumView.qml index f9b5602..77dff3e 100644 --- a/qmlUiKirigami/AlbumView.qml +++ b/qmlUiKirigami/AlbumView.qml @@ -1,105 +1,105 @@ /* * 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 contextualActions: [ Kirigami.Action { iconName: "edit-select-all" text: i18n("Select All") enabled: model.containImages onTriggered: model.selectAll() }, Kirigami.Action { iconName: "edit-select-none" text: i18n("Deselect All") enabled: model.containImages onTriggered: model.clearSelections() }, Kirigami.Action { iconName: "group-delete" text: i18n("Delete Selection") 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 {} } 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 + currentImage.model = model.sourceModel currentImage.index = currentIndex imageViewer.state = "open"; } } diff --git a/qmlUiKirigami/ImageViewer.qml b/qmlUiKirigami/ImageViewer.qml index 5521724..7af5e90 100644 --- a/qmlUiKirigami/ImageViewer.qml +++ b/qmlUiKirigami/ImageViewer.qml @@ -1,273 +1,282 @@ /* * 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 model: listView.model - property alias currentIndex: listView.currentIndex + 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 = indexAt(contentX+1, 1); + 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); } } } } } } } } - - onCurrentIndexChanged: { - currentImage.index = currentIndex - listView.positionViewAtIndex(currentIndex, ListView.Beginning) - } + //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 0a87da6..97a5258 100644 --- a/qmlUiKirigami/main.qml +++ b/qmlUiKirigami/main.qml @@ -1,181 +1,188 @@ /* * 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 = currentImage.index + 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; } } } } contextDrawer: Kirigami.ContextDrawer {} Koko.SortModel{ id: imageFolderModel sourceModel: Koko.ImageFolderModel {} + /* + * 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 - currentIndex: currentImage.index - model: currentImage.model + indexValue: currentImage.index + sourceModel: currentImage.model imageWidth: root.width imageHeight: root.height } Component.onCompleted: { albumView.model = imageFolderModel albumView.title = i18n("Folders") } } diff --git a/src/imagelistmodel.cpp b/src/imagelistmodel.cpp index baf82b7..ab78688 100644 --- a/src/imagelistmodel.cpp +++ b/src/imagelistmodel.cpp @@ -1,163 +1,169 @@ /* * 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. */ #include "imagelistmodel.h" #include "roles.h" #include "imagestorage.h" #include #include ImageListModel::ImageListModel(QObject* parent) : QAbstractListModel(parent) { connect(this, &ImageListModel::locationGroupChanged, this, &ImageListModel::slotLocationGroupChanged); connect(this, &ImageListModel::timeGroupChanged, this, &ImageListModel::slotTimeGroupChanged); connect(this, &ImageListModel::queryChanged, this, &ImageListModel::slotResetModel); connect(ImageStorage::instance(), &ImageStorage::storageModified, this, &ImageListModel::slotResetModel); } ImageListModel::~ImageListModel() { } QHash ImageListModel::roleNames() const { QHash hash = QAbstractListModel::roleNames(); hash.insert( Roles::ImageUrlRole, "imageurl"); hash.insert( Roles::ItemTypeRole, "itemType"); + hash.insert( Roles::MimeTypeRole, "mimeType"); return hash; } QVariant ImageListModel::data(const QModelIndex& index, int role) const { if( !index.isValid()) { return QVariant(); } int indexValue = index.row(); switch( role) { case Qt::DisplayRole: //TODO: return the filename component return m_images.at(indexValue); case Roles::ImageUrlRole: return m_images.at(indexValue); case Roles::ItemTypeRole: return Types::Image; + case Roles::MimeTypeRole: { + QMimeDatabase db; + QMimeType type = db.mimeTypeForFile( m_images.at(indexValue)); + return type.name(); + } } return QVariant(); } int ImageListModel::rowCount(const QModelIndex& parent) const { Q_UNUSED(parent) return m_images.size(); } void ImageListModel::slotLocationGroupChanged() { if( m_locationGroup != -1) { m_locations = ImageStorage::instance()->locations( static_cast(m_locationGroup)); m_queryType = Types::LocationQuery; } } void ImageListModel::slotTimeGroupChanged() { if( m_timeGroup != -1) { m_times = ImageStorage::instance()->timeTypes( static_cast(m_timeGroup)); m_queryType = Types::TimeQuery; } } void ImageListModel::slotResetModel() { beginResetModel(); if(m_queryType == Types::LocationQuery) { m_images = ImageStorage::instance()->imagesForLocation( m_query, static_cast(m_locationGroup)); } else if (m_queryType == Types::TimeQuery) { m_images = ImageStorage::instance()->imagesForTime( m_query, static_cast(m_timeGroup)); } endResetModel(); } Types::LocationGroup ImageListModel::locationGroup() const { return m_locationGroup; } void ImageListModel::setLocationGroup(const Types::LocationGroup &group) { m_locationGroup = group; emit locationGroupChanged(); } Types::TimeGroup ImageListModel::timeGroup() const { return m_timeGroup; } void ImageListModel::setTimeGroup(const Types::TimeGroup &group) { m_timeGroup = group; emit timeGroupChanged(); } Types::QueryType ImageListModel::queryType() const { return m_queryType; } void ImageListModel::setQueryType(const Types::QueryType& type) { m_queryType = type; } QByteArray ImageListModel::query() const { return m_query; } void ImageListModel::setQuery(const QByteArray &statement) { m_query = statement; emit queryChanged(); } -QByteArray ImageListModel::queryForIndex(const QModelIndex &index) +QByteArray ImageListModel::queryForIndex(const int& index) { if(m_queryType == Types::LocationQuery) { - return m_locations.at( index.row()).first; + return m_locations.at( index).first; } else if( m_queryType == Types::TimeQuery) { - return m_times.at( index.row()).first; + return m_times.at( index).first; } return QByteArray(); } #include "moc_imagelistmodel.cpp" diff --git a/src/imagelistmodel.h b/src/imagelistmodel.h index 0296c70..9e0a85c 100644 --- a/src/imagelistmodel.h +++ b/src/imagelistmodel.h @@ -1,79 +1,79 @@ /* * 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. */ #ifndef IMAGELISTMODEL_H #define IMAGELISTMODEL_H #include #include "types.h" class ImageListModel : public QAbstractListModel { Q_OBJECT Q_PROPERTY(Types::LocationGroup locationGroup READ locationGroup WRITE setLocationGroup NOTIFY locationGroupChanged) Q_PROPERTY(Types::TimeGroup timeGroup READ timeGroup WRITE setTimeGroup NOTIFY timeGroupChanged) Q_PROPERTY(Types::QueryType queryType READ queryType WRITE setQueryType) Q_PROPERTY(QByteArray query READ query WRITE setQuery NOTIFY queryChanged) public: explicit ImageListModel(QObject* parent = 0); ~ImageListModel(); virtual QHash< int, QByteArray > roleNames() const; virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const; virtual int rowCount(const QModelIndex& parent = QModelIndex()) const; Types::LocationGroup locationGroup() const; void setLocationGroup(const Types::LocationGroup &group); Types::TimeGroup timeGroup() const; void setTimeGroup(const Types::TimeGroup &group); Types::QueryType queryType() const; void setQueryType( const Types::QueryType &type); QByteArray query() const; void setQuery(const QByteArray &statement); - Q_INVOKABLE QByteArray queryForIndex(const QModelIndex &index); + Q_INVOKABLE QByteArray queryForIndex(const int &index); void slotLocationGroupChanged(); void slotTimeGroupChanged(); void slotResetModel(); Q_SIGNALS: void imageListChanged(); void locationGroupChanged(); void timeGroupChanged(); void queryChanged(); private: QStringList m_images; Types::LocationGroup m_locationGroup; Types::TimeGroup m_timeGroup; Types::QueryType m_queryType; QByteArray m_query; QList< QPair > m_times; QList< QPair > m_locations; }; #endif diff --git a/src/qmlplugins.cpp b/src/qmlplugins.cpp index 28ebdde..4f594b3 100644 --- a/src/qmlplugins.cpp +++ b/src/qmlplugins.cpp @@ -1,52 +1,54 @@ /* * 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) 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 . * */ #include "qmlplugins.h" #include "tagmodel.h" #include "imagelocationmodel.h" #include "imagetimemodel.h" #include "imagefoldermodel.h" #include "sortmodel.h" #include "allimagesmodel.h" #include "fileinfo.h" #include "imagelistmodel.h" #include "types.h" +#include "roles.h" #include void QmlPlugins::initializeEngine(QQmlEngine *, const char *) { } void QmlPlugins::registerTypes(const char *uri) { qmlRegisterType (); qmlRegisterType (uri, 0, 1, "TagModel"); qmlRegisterType (uri, 0, 1, "ImageLocationModel"); qmlRegisterType (uri, 0, 1, "ImageTimeModel"); qmlRegisterType (uri, 0, 1, "ImageFolderModel"); qmlRegisterType (uri, 0, 1, "AllImagesModel"); qmlRegisterType (uri, 0, 1, "SortModel"); qmlRegisterType (uri, 0, 1, "FileInfo"); qmlRegisterType (uri, 0, 1, "ImageListModel"); qmlRegisterUncreatableType(uri, 0, 1, "Types", "Cannot instantiate the Types class"); + qmlRegisterUncreatableType(uri, 0, 1, "Roles", "Cannot instantiate the Roles class"); } diff --git a/src/roles.h b/src/roles.h index c56d854..d75b662 100644 --- a/src/roles.h +++ b/src/roles.h @@ -1,44 +1,45 @@ /* * 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. */ #ifndef ROLES_H #define ROLES_H #include class Roles : public QObject { Q_OBJECT + Q_ENUMS(RoleNames) public: Roles(QObject* parent); ~Roles(); enum RoleNames { ImageUrlRole = Qt::UserRole + 1, MimeTypeRole, Thumbnail, ItemTypeRole, FilesRole, FileCountRole, DateRole, SelectedRole, SourceIndex }; }; #endif diff --git a/src/sortmodel.cpp b/src/sortmodel.cpp index c198cd6..650740c 100644 --- a/src/sortmodel.cpp +++ b/src/sortmodel.cpp @@ -1,291 +1,303 @@ /* * 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 "sortmodel.h" #include "types.h" #include "roles.h" #include #include #include #include #include using namespace Jungle; SortModel::SortModel(QObject* parent) : QSortFilterProxyModel(parent), m_screenshotSize(256, 256), m_containImages(false) { setSortLocaleAware(true); sort(0); m_selectionModel = new QItemSelectionModel(this); m_previewTimer = new QTimer(this); m_previewTimer->setSingleShot(true); connect(m_previewTimer, &QTimer::timeout, this, &SortModel::delayedPreview); connect(this, &SortModel::rowsInserted, this, [this] (const QModelIndex &parent, int first, int last) { Q_UNUSED(parent) for (int i = first; i <= last; i++) { if (Types::Image == data(index(i, 0, QModelIndex()), Roles::ItemTypeRole).toInt() && m_containImages == false) { setContainImages(true); break; } } }); connect(this, &SortModel::sourceModelChanged, this, [this] () { if (!sourceModel()) { return; } for (int i = 0; i <= sourceModel()->rowCount(); i++) { if (Types::Image == sourceModel()->data(sourceModel()->index(i, 0, QModelIndex()), Roles::ItemTypeRole).toInt() && m_containImages == false) { setContainImages(true); break; } } }); //using the same cache of the engine, they index both by url m_imageCache = new KImageCache(QStringLiteral("org.kde.koko"), 10485760); } SortModel::~SortModel() { delete m_imageCache; } void SortModel::setContainImages(bool value) { m_containImages = value; emit containImagesChanged(); } QByteArray SortModel::sortRoleName() const { int role = sortRole(); return roleNames().value(role); } void SortModel::setSortRoleName(const QByteArray& name) { if (!sourceModel()) { m_sortRoleName = name; return; } const QHash roles = sourceModel()->roleNames(); for (auto it = roles.begin(); it != roles.end(); it++) { if (it.value() == name) { setSortRole(it.key()); return; } } qDebug() << "Sort role" << name << "not found"; } QHash SortModel::roleNames() const { QHash hash = sourceModel()->roleNames(); hash.insert( Roles::SelectedRole, "selected"); hash.insert( Roles::Thumbnail, "thumbnail"); hash.insert( Roles::SourceIndex, "sourceIndex"); return hash; } QVariant SortModel::data(const QModelIndex& index, int role) const { if( !index.isValid()) { return QVariant(); } switch( role) { case Roles::SelectedRole: { return m_selectionModel->isSelected(index); } case Roles::Thumbnail: { QUrl thumbnailSource(QString( /*"file://" + */data( index, Roles::ImageUrlRole).toString())); KFileItem item( thumbnailSource, QString() ); QImage preview = QImage(m_screenshotSize, QImage::Format_ARGB32_Premultiplied); if (m_imageCache->findImage(item.url().toString(), &preview)) { return preview; } m_previewTimer->start(100); const_cast(this)->m_filesToPreview[item.url()] = QPersistentModelIndex(index); } case Roles::SourceIndex: { - return mapToSource(index); + return mapToSource(index).row(); } } return QSortFilterProxyModel::data(index, role); } void SortModel::setSourceModel(QAbstractItemModel* sourceModel) { QSortFilterProxyModel::setSourceModel(sourceModel); if (!m_sortRoleName.isEmpty()) { setSortRoleName(m_sortRoleName); m_sortRoleName.clear(); } } bool SortModel::containImages() { return m_containImages; } - bool SortModel::hasSelectedImages() { return m_selectionModel->hasSelection(); } void SortModel::setSelected(int indexValue) { if( indexValue < 0) return; QModelIndex index = QSortFilterProxyModel::index( indexValue, 0); m_selectionModel->select( index, QItemSelectionModel::Select ); emit dataChanged( index, index); emit selectedImagesChanged(); } void SortModel::toggleSelected(int indexValue ) { if( indexValue < 0) return; QModelIndex index = QSortFilterProxyModel::index( indexValue, 0); m_selectionModel->select( index, QItemSelectionModel::Toggle ); emit dataChanged( index, index); emit selectedImagesChanged(); } void SortModel::clearSelections() { if(m_selectionModel->hasSelection()) { QModelIndexList selectedIndex = m_selectionModel->selectedIndexes(); m_selectionModel->clear(); foreach(QModelIndex indexValue, selectedIndex) { emit dataChanged( indexValue, indexValue); } } emit selectedImagesChanged(); } void SortModel::selectAll() { QModelIndexList indexList; for( int row=0; rowhasSelection()) { m_selectionModel->clear(); } foreach(QModelIndex index, indexList) { if( Types::Image == data(index, Roles::ItemTypeRole)) m_selectionModel->select( index, QItemSelectionModel::Select); } emit dataChanged( index( 0, 0, QModelIndex()), index( rowCount()-1, 0, QModelIndex()) ); emit selectedImagesChanged(); } void SortModel::deleteSelection() { QList filesToDelete; foreach(QModelIndex index, m_selectionModel->selectedIndexes()) { filesToDelete << data( index, Roles::ImageUrlRole).toUrl(); } auto trashJob = KIO::trash(filesToDelete); trashJob->exec(); } +int SortModel::proxyIndex(const int& indexValue) +{ + if( sourceModel() ) { + return mapFromSource( sourceModel()->index( indexValue, 0, QModelIndex())).row(); + } + return -1; +} + +int SortModel::sourceIndex(const int& indexValue) +{ + return mapToSource( index(indexValue, 0, QModelIndex())).row(); +} + void SortModel::delayedPreview() { QHash::const_iterator i = m_filesToPreview.constBegin(); KFileItemList list; while (i != m_filesToPreview.constEnd()) { QUrl file = i.key(); QPersistentModelIndex index = i.value(); if (!m_previewJobs.contains(file) && file.isValid()) { list.append(KFileItem(file, QString(), 0)); m_previewJobs.insert(file, QPersistentModelIndex(index)); } ++i; } if (list.size() > 0) { KIO::PreviewJob* job = KIO::filePreview(list, m_screenshotSize); job->setIgnoreMaximumSize(true); // qDebug() << "Created job" << job; connect(job, &KIO::PreviewJob::gotPreview, this, &SortModel::showPreview); connect(job, &KIO::PreviewJob::failed, this, &SortModel::previewFailed); } m_filesToPreview.clear(); } void SortModel::showPreview(const KFileItem &item, const QPixmap &preview) { QPersistentModelIndex index = m_previewJobs.value(item.url()); m_previewJobs.remove(item.url()); if (!index.isValid()) { return; } m_imageCache->insertImage(item.url().toString(), preview.toImage()); //qDebug() << "preview size:" << preview.size(); emit dataChanged(index, index); } void SortModel::previewFailed(const KFileItem &item) { m_previewJobs.remove(item.url()); } diff --git a/src/sortmodel.h b/src/sortmodel.h index 4c2ce64..a4f15f3 100644 --- a/src/sortmodel.h +++ b/src/sortmodel.h @@ -1,83 +1,85 @@ /* * 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 * */ #ifndef JUNGLE_SORTMODEL_H #define JUNGLE_SORTMODEL_H #include #include #include #include #include #include #include namespace Jungle { class SortModel : public QSortFilterProxyModel { Q_OBJECT Q_PROPERTY(QByteArray sortRoleName READ sortRoleName WRITE setSortRoleName) Q_PROPERTY(bool containImages READ containImages WRITE setContainImages NOTIFY containImagesChanged) Q_PROPERTY(bool hasSelectedImages READ hasSelectedImages NOTIFY selectedImagesChanged) public: explicit SortModel(QObject* parent = 0); virtual ~SortModel(); QByteArray sortRoleName() const; void setSortRoleName(const QByteArray& name); QHash roleNames() const; QVariant data(const QModelIndex & index, int role) const; virtual void setSourceModel(QAbstractItemModel* sourceModel); bool containImages(); bool hasSelectedImages(); Q_INVOKABLE void setSelected( int indexValue); Q_INVOKABLE void toggleSelected( int indexValue); Q_INVOKABLE void clearSelections(); Q_INVOKABLE void selectAll(); Q_INVOKABLE void deleteSelection(); + Q_INVOKABLE int proxyIndex(const int &indexValue); + Q_INVOKABLE int sourceIndex(const int &indexValue); protected Q_SLOTS: void setContainImages(bool); void showPreview(const KFileItem &item, const QPixmap &preview); void previewFailed(const KFileItem &item); void delayedPreview(); signals: void containImagesChanged(); void selectedImagesChanged(); private: QByteArray m_sortRoleName; QItemSelectionModel *m_selectionModel; QTimer *m_previewTimer; QHash m_filesToPreview; QSize m_screenshotSize; QHash m_previewJobs; KImageCache* m_imageCache; bool m_containImages; }; } #endif // JUNGLE_SORTMODEL_H