diff --git a/CMakeLists.txt b/CMakeLists.txt index eabe7bd..2c01c45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -39,6 +39,7 @@ add_executable(cutefish-filemanager helper/pathhistory.cpp helper/fm.cpp helper/shortcut.cpp + helper/thumbnailerjob.cpp desktopiconprovider.cpp diff --git a/helper/thumbnailer.cpp b/helper/thumbnailer.cpp index b3ae614..ba7c2b2 100644 --- a/helper/thumbnailer.cpp +++ b/helper/thumbnailer.cpp @@ -21,6 +21,8 @@ #include +#include "thumbnailerjob.h" + #include #include @@ -34,20 +36,35 @@ AsyncImageResponse::AsyncImageResponse(const QString &id, const QSize &requested : m_id(id) , m_requestedSize(requestedSize) { - QStringList plugins = KIO::PreviewJob::defaultPlugins(); - auto job = new KIO::PreviewJob(KFileItemList() << KFileItem(QUrl::fromUserInput(id)), requestedSize, &plugins); - - connect(job, &KIO::PreviewJob::gotPreview, [this](KFileItem, QPixmap pixmap) { + auto job_ = new ThumbnailerJob(QUrl::fromUserInput(id).toLocalFile(), requestedSize); + connect(job_, &ThumbnailerJob::gotPreview, [this] (QPixmap pixmap) { m_image = pixmap.toImage(); emit this->finished(); }); - connect(job, &KIO::PreviewJob::failed, [this](KFileItem) { + connect(job_, &ThumbnailerJob::failed, [this] { emit this->cancel(); emit this->finished(); }); - job->start(); + job_->start(); + + + +// QStringList plugins = KIO::PreviewJob::defaultPlugins(); +// auto job = new KIO::PreviewJob(KFileItemList() << KFileItem(QUrl::fromUserInput(id)), requestedSize, &plugins); + +// connect(job, &KIO::PreviewJob::gotPreview, [this](KFileItem, QPixmap pixmap) { +// m_image = pixmap.toImage(); +// emit this->finished(); +// }); + +// connect(job, &KIO::PreviewJob::failed, [this](KFileItem) { +// emit this->cancel(); +// emit this->finished(); +// }); + +// job->start(); } QQuickTextureFactory *AsyncImageResponse::textureFactory() const diff --git a/helper/thumbnailerjob.cpp b/helper/thumbnailerjob.cpp new file mode 100644 index 0000000..3c1c011 --- /dev/null +++ b/helper/thumbnailerjob.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: Reion Wong + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "thumbnailerjob.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +ThumbnailerJob::ThumbnailerJob(const QString &fileName, const QSize &size, QObject *parent) + : QThread(parent) + , m_url(QUrl::fromUserInput(fileName)) + , m_size(size) + , shmaddr(nullptr) + , shmid(-1) +{ + // http://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html#DIRECTORY + m_thumbnailsDir = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1String("/thumbnails/"); +} + +ThumbnailerJob::~ThumbnailerJob() +{ + if (shmaddr) { + shmdt((char *)shmaddr); + shmctl(shmid, IPC_RMID, nullptr); + } +} + +void ThumbnailerJob::run() +{ + if (!QFile::exists(m_url.toLocalFile())) { + emit failed(); + return; + } + + int width = m_size.width(); + int height = m_size.height(); + int cacheSize = 0; + bool needCache = false; + + // 首先需要找到目录 + if (width <= 128 && height <= 128) + cacheSize = 128; + else if (width <= 256 && height <= 256) + cacheSize = 256; + else + cacheSize = 512; + + struct CachePool { + QString path; + int minSize; + }; + + const static auto pools = { + CachePool{QStringLiteral("/normal/"), 128}, + CachePool{QStringLiteral("/large/"), 256}, + CachePool{QStringLiteral("/x-large/"), 512}, + CachePool{QStringLiteral("/xx-large/"), 1024}, + }; + + QString thumbDir; + int wants = /*devicePixelRatio **/ cacheSize; + for (const auto &p : pools) { + if (p.minSize < wants) { + continue; + } else { + thumbDir = p.path; + break; + } + } + + m_thumbnailsPath = m_thumbnailsDir + thumbDir; + + // 不存在需要创建路径 + if (!QDir(m_thumbnailsPath).exists()) { + if (QDir().mkpath(m_thumbnailsPath)) { + QFile f(m_thumbnailsPath); + f.setPermissions(QFile::ReadUser | QFile::WriteUser | QFile::ExeUser); // 0700 + } + } + + // 求出文件的 md5 + QByteArray origName; + const QFileInfo info(m_url.toLocalFile()); + const QString canonicalPath = info.canonicalFilePath(); + origName = QUrl::fromLocalFile(canonicalPath).toEncoded(QUrl::RemovePassword | QUrl::FullyEncoded); + + if (origName.isEmpty()) { + emit failed(); + return; + } + + QCryptographicHash md5(QCryptographicHash::Md5); + md5.addData(origName); + m_thumbnailsName = QString::fromLatin1(md5.result().toHex()) + QLatin1String(".png"); + + // 是否需要生成缓存 + needCache = !QFile::exists(m_thumbnailsPath + m_thumbnailsName); + + if (needCache) { + QFile f(m_url.toLocalFile()); + if (f.open(QIODevice::ReadOnly)) { + QByteArray data = f.readAll(); + QImage thumb; + thumb.loadFromData(data); + thumb = thumb.scaled(m_size, Qt::KeepAspectRatio, Qt::SmoothTransformation); + + // 保存 cache 文件 + QSaveFile saveFile(m_thumbnailsPath + m_thumbnailsName); + if (saveFile.open(QIODevice::WriteOnly)) { + if (thumb.save(&saveFile, "PNG")) { + saveFile.commit(); + } + } + + emitPreview(thumb); + } + } else { + QFile f(m_thumbnailsPath + m_thumbnailsName); + if (f.open(QIODevice::ReadOnly)) { + QByteArray data = f.readAll(); + QImage thumb; + thumb.loadFromData(data); + emitPreview(thumb); + } + } +} + +void ThumbnailerJob::emitPreview(const QImage &image) +{ + QPixmap pixmap; + + if (image.width() > m_size.width() || image.height() > m_size.height()) { + pixmap = QPixmap::fromImage(image.scaled(m_size, Qt::KeepAspectRatio, Qt::SmoothTransformation)); + } else { + pixmap = QPixmap::fromImage(image); + } + + emit gotPreview(pixmap); +} diff --git a/helper/thumbnailerjob.h b/helper/thumbnailerjob.h new file mode 100644 index 0000000..10ead87 --- /dev/null +++ b/helper/thumbnailerjob.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2021 CutefishOS Team. + * + * Author: Reion Wong + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef THUMBNAILERJOB_H +#define THUMBNAILERJOB_H + +#include +#include +#include +#include + +class ThumbnailerJob : public QThread +{ + Q_OBJECT + +public: + explicit ThumbnailerJob(const QString &fileName, const QSize &size, QObject *parent = nullptr); + ~ThumbnailerJob(); + + void run() override; + +signals: + void gotPreview(const QPixmap &pixmap); + void failed(); + +private: + void emitPreview(const QImage &image); + +private: + QUrl m_url; + QSize m_size; + + // thumbnail cache folder + QString m_thumbnailsDir; + QString m_thumbnailsPath; + QString m_thumbnailsName; + + uchar *shmaddr; + size_t shmsize; + int shmid; +}; + +#endif // THUMBNAILERJOB_H diff --git a/qml/FolderListItem.qml b/qml/FolderListItem.qml index 237276f..ebd176a 100644 --- a/qml/FolderListItem.qml +++ b/qml/FolderListItem.qml @@ -41,10 +41,10 @@ Item { property bool selected: model.selected property bool blank: model.blank - property color hoveredColor: FishUI.Theme.darkMode ? Qt.lighter(FishUI.Theme.backgroundColor, 1.1) - : Qt.darker(FishUI.Theme.backgroundColor, 1.05) + property color hoveredColor: FishUI.Theme.darkMode ? Qt.lighter(FishUI.Theme.backgroundColor, 2.3) + : Qt.darker(FishUI.Theme.backgroundColor, 1.05) property color selectedColor: FishUI.Theme.darkMode ? Qt.lighter(FishUI.Theme.backgroundColor, 1.2) - : Qt.darker(FishUI.Theme.backgroundColor, 1.15) + : Qt.darker(FishUI.Theme.backgroundColor, 1.15) // onSelectedChanged: { // if (selected && !blank) { // _listItem.grabToImage(function(result) { diff --git a/qml/FolderPage.qml b/qml/FolderPage.qml index 0f8866e..f8ac290 100644 --- a/qml/FolderPage.qml +++ b/qml/FolderPage.qml @@ -20,6 +20,7 @@ import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Layouts 1.12 +import QtGraphicalEffects 1.0 import Qt.labs.platform 1.0 import Cutefish.FileManager 1.0 as FM @@ -89,10 +90,26 @@ Item { Rectangle { id: _background anchors.fill: parent - anchors.rightMargin: FishUI.Units.smallSpacing * 1.5 - anchors.bottomMargin: FishUI.Units.smallSpacing * 1.5 radius: FishUI.Theme.smallRadius color: FishUI.Theme.secondBackgroundColor + + Rectangle { + id: _topRightRect + anchors.right: parent.right + anchors.top: parent.top + height: FishUI.Theme.smallRadius + width: FishUI.Theme.smallRadius + color: FishUI.Theme.secondBackgroundColor + } + + Rectangle { + id: _bottomLeftRect + anchors.left: parent.left + anchors.bottom: parent.bottom + height: FishUI.Theme.smallRadius + width: FishUI.Theme.smallRadius + color: FishUI.Theme.secondBackgroundColor + } } Label { @@ -140,7 +157,7 @@ Item { ColumnLayout { anchors.fill: parent - anchors.bottomMargin: FishUI.Theme.smallRadius + anchors.bottomMargin: 2 spacing: 0 Loader { @@ -161,9 +178,64 @@ Item { } } - Loader { - Layout.fillWidth: true - sourceComponent: _statusBar + Item { + visible: settings.viewMethod === 0 + height: statusBarHeight + } + } + + Item { + id: _statusBar + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: statusBarHeight + z: 999 + + Rectangle { + anchors.fill: parent + color: FishUI.Theme.backgroundColor + opacity: 0.7 + } + + MouseArea { + anchors.fill: parent + } + + RowLayout { + anchors.fill: parent + anchors.leftMargin: FishUI.Units.smallSpacing + anchors.rightMargin: FishUI.Units.smallSpacing + // anchors.bottomMargin: 1 + spacing: FishUI.Units.largeSpacing + + Label { + Layout.alignment: Qt.AlignLeft + font.pointSize: 10 + text: dirModel.count === 1 ? qsTr("%1 item").arg(dirModel.count) + : qsTr("%1 items").arg(dirModel.count) + } + + Label { + Layout.alignment: Qt.AlignLeft + font.pointSize: 10 + text: qsTr("%1 selected").arg(dirModel.selectionCount) + visible: dirModel.selectionCount >= 1 + } + + Item { + Layout.fillWidth: true + } + + Button { + Layout.fillHeight: true + Layout.alignment: Qt.AlignRight + text: qsTr("Empty Trash") + font.pointSize: 10 + onClicked: dirModel.emptyTrash() + visible: dirModel.url === "trash:///" + focusPolicy: Qt.NoFocus + } } } @@ -175,51 +247,6 @@ Item { dirModel.requestRename.connect(rename) } - Component { - id: _statusBar - - Item { - height: statusBarHeight - z: 999 - - RowLayout { - anchors.fill: parent - anchors.leftMargin: FishUI.Units.smallSpacing - anchors.rightMargin: FishUI.Units.largeSpacing - anchors.bottomMargin: 1 - spacing: FishUI.Units.largeSpacing - - Label { - Layout.alignment: Qt.AlignLeft - font.pointSize: 10 - text: dirModel.count === 1 ? qsTr("%1 item").arg(dirModel.count) - : qsTr("%1 items").arg(dirModel.count) - } - - Label { - Layout.alignment: Qt.AlignLeft - font.pointSize: 10 - text: qsTr("%1 selected").arg(dirModel.selectionCount) - visible: dirModel.selectionCount >= 1 - } - - Item { - Layout.fillWidth: true - } - - Button { - Layout.fillHeight: true - Layout.alignment: Qt.AlignRight - text: qsTr("Empty Trash") - font.pointSize: 10 - onClicked: dirModel.emptyTrash() - visible: dirModel.url === "trash:/" - focusPolicy: Qt.NoFocus - } - } - } - } - Component { id: _gridViewComponent @@ -253,7 +280,8 @@ Item { topMargin: FishUI.Units.smallSpacing leftMargin: FishUI.Units.largeSpacing - rightMargin: FishUI.Units.largeSpacing + FishUI.Theme.smallRadius + rightMargin: FishUI.Units.largeSpacing + bottomMargin: FishUI.Units.largeSpacing spacing: FishUI.Units.largeSpacing onCountChanged: { diff --git a/qml/SideBar.qml b/qml/SideBar.qml index 1f6672b..d46a136 100644 --- a/qml/SideBar.qml +++ b/qml/SideBar.qml @@ -111,7 +111,6 @@ ListView { id: _label text: model.name color: checked ? FishUI.Theme.highlightedTextColor : FishUI.Theme.textColor - elide: Text.ElideRight Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter }