diff --git a/qmlUiKirigami/AlbumView.qml b/qmlUiKirigami/AlbumView.qml index 4d8fdff..456b64f 100644 --- a/qmlUiKirigami/AlbumView.qml +++ b/qmlUiKirigami/AlbumView.qml @@ -1,136 +1,164 @@ /* * 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: "emblem-shared-symbolic" + text: i18n("Share") + tooltip: i18n("Share the selected images") + enabled: model.hasSelectedImages + onTriggered: { + shareMenu.sheetOpen = true + shareMenu.inputData = { + "urls": model.selectedImages(), + "mimeType": "image/" + } + } + }, 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"; } + ShareDialog { + id: shareMenu + 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); + } + } + } } diff --git a/qmlUiKirigami/ImageViewer.qml b/qmlUiKirigami/ImageViewer.qml index aab42a9..651305a 100644 --- a/qmlUiKirigami/ImageViewer.qml +++ b/qmlUiKirigami/ImageViewer.qml @@ -1,421 +1,417 @@ /* * 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 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 } } ] 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(); } } }, Transition { from: "closed" to: "open" OpacityAnimator { target: root duration: Kirigami.Units.longDuration easing.type: Easing.OutQuad } } ] background: Rectangle { color: "black" } Keys.onPressed: { switch(event.key) { case Qt.Key_Escape: root.state = "closed" break; case Qt.Key_F: root.state = root.state == "open" ? "fullscreen" : "open" 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 } 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 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); } } } } } } } } 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 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..2438d38 100644 --- a/qmlUiKirigami/Main.qml +++ b/qmlUiKirigami/Main.qml @@ -1,206 +1,209 @@ /* * 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" } + Koko.NotificationManager { + id: notificationManager + } } diff --git a/src/sortmodel.cpp b/src/sortmodel.cpp index 3edaf02..5070ee8 100644 --- a/src/sortmodel.cpp +++ b/src/sortmodel.cpp @@ -1,318 +1,330 @@ /* * 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).row(); } } return QSortFilterProxyModel::data(index, role); } bool SortModel::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const { if(sourceModel()) { if( (sourceModel()->data(source_left, Roles::ItemTypeRole) == Types::Folder && sourceModel()-> data(source_right, Roles::ItemTypeRole) == Types::Folder ) || (sourceModel()->data(source_left, Roles::ItemTypeRole) != Types::Folder && sourceModel()-> data(source_right, Roles::ItemTypeRole) != Types::Folder)) { return QSortFilterProxyModel::lessThan(source_left, source_right); } else if( sourceModel()->data(source_left, Roles::ItemTypeRole) == Types::Folder && sourceModel()->data(source_right, Roles::ItemTypeRole) != Types::Folder) { return true; } else { return false; } } return false; } 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(); } +QJsonArray SortModel::selectedImages() +{ + QJsonArray arr; + + foreach( QModelIndex index, m_selectionModel->selectedIndexes()) + { + arr.push_back( QJsonValue (data( index, Roles::ImageUrlRole).toString())); + } + + return arr; +} + 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 b996ed5..24c12a9 100644 --- a/src/sortmodel.h +++ b/src/sortmodel.h @@ -1,86 +1,88 @@ /* * 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 #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; bool lessThan(const QModelIndex & source_left, const QModelIndex & source_right) const override; 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); + Q_INVOKABLE QJsonArray selectedImages(); 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