diff --git a/.tx/config b/.tx/config new file mode 100644 index 00000000..65d4ebb1 --- /dev/null +++ b/.tx/config @@ -0,0 +1,10 @@ +[main] +host = https://www.transifex.com +minimum_perc = 80 +mode = developer + +[o:linuxdeepin:p:libimage-viewer:r:libimage-viewer] +file_filter = src/libimageviewer/translations/libimageviewer_.ts +source_file = src/libimageviewer/translations/libimageviewer.ts +source_lang = en +type = QT diff --git a/.tx/deepin.conf b/.tx/deepin.conf index 0660d16a..c230b4f8 100644 --- a/.tx/deepin.conf +++ b/.tx/deepin.conf @@ -1 +1,2 @@ [transifex] +branch = m20 diff --git a/CMakeLists.txt b/CMakeLists.txt index 7901c7dd..99db348e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,7 +23,7 @@ message(STATUS ${CMAKE_CXX_FLAGS}) option(DOTEST "option for test" OFF) -if(DOTEST) +if(DOTEST OR CMAKE_BUILD_TYPE STREQUAL "Debug") add_subdirectory(tests) endif() diff --git a/libimageviewer/imageengine.cpp b/libimageviewer/imageengine.cpp index d49a6a20..941f0432 100644 --- a/libimageviewer/imageengine.cpp +++ b/libimageviewer/imageengine.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2022 - 2024 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -15,7 +15,9 @@ #include "service/commonservice.h" #include "unionimage/imgoperate.h" #include "unionimage/unionimage.h" +#include "service/permissionconfig.h" #include "service/imagedataservice.h" + class ImageEnginePrivate { public: @@ -25,13 +27,17 @@ class ImageEnginePrivate public: ImageEngine *const q_ptr; LibImgOperate *m_worker = nullptr; -// QString m_thumbnailSavePath;//缩略图保存路径 + Q_DECLARE_PUBLIC(ImageEngine) + Q_DISABLE_COPY(ImageEnginePrivate) }; -ImageEnginePrivate::ImageEnginePrivate(ImageEngine *parent): q_ptr(parent) +ImageEnginePrivate::ImageEnginePrivate(ImageEngine *parent) + : q_ptr(parent) { Q_Q(ImageEngine); + // 关联授权操作通知信号 + QObject::connect(PermissionConfig::instance(), &PermissionConfig::authoriseNotify, q, &ImageEngine::sigAuthoriseNotify); QThread *workerThread = new QThread(q_ptr); m_worker = new LibImgOperate(workerThread); @@ -41,10 +47,7 @@ ImageEnginePrivate::ImageEnginePrivate(ImageEngine *parent): q_ptr(parent) workerThread->start(); } -ImageEnginePrivate::~ImageEnginePrivate() -{ - -} +ImageEnginePrivate::~ImageEnginePrivate() {} ImageEngine *ImageEngine::m_ImageEngine = nullptr; @@ -61,28 +64,23 @@ ImageEngine::ImageEngine(QWidget *parent) : QObject(parent) , d_ptr(new ImageEnginePrivate(this)) { -// Q_D(ImageEngine); } -ImageEngine::~ImageEngine() -{ - -} +ImageEngine::~ImageEngine() {} +/** + @brief 构造传入图像 `paths` 的缩略图到路径 `thumbnailSavePath` , `remake` 标识是否重新创建 + @note 由于兼容性问题,此处的 `thumbnailSavePath` `paths` 没有调整为引用传递 + */ void ImageEngine::makeImgThumbnail(QString thumbnailSavePath, QStringList paths, int makeCount, bool remake) { //执行子线程制作缩略图动作 -// QMetaObject::invokeMethod(d->m_worker, "slotMakeImgThumbnail" -// , Q_ARG(QString, thumbnailSavePath) -// , Q_ARG(QStringList, paths) -// , Q_ARG(int, makeCount) -// , Q_ARG(bool, remake) -// ); Q_UNUSED(makeCount); if (paths.count() > 0) { LibImageDataService::instance()->readThumbnailByPaths(thumbnailSavePath, paths, remake); } } + //判断是否图片格式 bool ImageEngine::isImage(const QString &path) { @@ -90,8 +88,8 @@ bool ImageEngine::isImage(const QString &path) QMimeDatabase db; QMimeType mt = db.mimeTypeForFile(path, QMimeDatabase::MatchContent); QMimeType mt1 = db.mimeTypeForFile(path, QMimeDatabase::MatchExtension); - if (mt.name().startsWith("image/") || mt.name().startsWith("video/x-mng") || - mt1.name().startsWith("image/") || mt1.name().startsWith("video/x-mng")) { + if (mt.name().startsWith("image/") || mt.name().startsWith("video/x-mng") || mt1.name().startsWith("image/") || + mt1.name().startsWith("video/x-mng")) { bRet = true; } return bRet; @@ -99,7 +97,7 @@ bool ImageEngine::isImage(const QString &path) bool ImageEngine::isRotatable(const QString &path) { - //BUG#93143 + // BUG#93143 //由于底层代码判断是否可旋转的依据是文件名后缀所代表的图片是否可保存 //因此文件是否存在的判断添加在这一层 QFileInfo info(path); @@ -109,15 +107,16 @@ bool ImageEngine::isRotatable(const QString &path) return LibUnionImage_NameSpace::isImageSupportRotate(path); } } + //根据文件路径制作md5 QString ImageEngine::makeMD5(const QString &path) { QFile file(path); - QString stHashValue; - if (file.open(QIODevice::ReadOnly)) { //只读方式打开 + QString stHashValue; + if (file.open(QIODevice::ReadOnly)) { //只读方式打开 QCryptographicHash hash(QCryptographicHash::Md5); - QByteArray buf = file.read(10 * 1024 * 1024); // 每次读取10M + QByteArray buf = file.read(10 * 1024 * 1024); // 每次读取10M buf = buf.append(path.toUtf8()); hash.addData(buf); // 将数据添加到Hash中 stHashValue.append(hash.result().toHex()); diff --git a/libimageviewer/imageengine.h b/libimageviewer/imageengine.h index 7ad4f750..e54784aa 100644 --- a/libimageviewer/imageengine.h +++ b/libimageviewer/imageengine.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2022 - 2024 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -30,7 +30,6 @@ class IMAGEVIEWERSHARED_EXPORT ImageEngine : public QObject //是否是可选转的图片 bool isRotatable(const QString &path); - //根据文件路径制作md5 QString makeMD5(const QString &path); @@ -53,14 +52,14 @@ class IMAGEVIEWERSHARED_EXPORT ImageEngine : public QObject void sigGetAlbumName(const QString &path); //添加到已有相册/新建相册 void sigAddToAlbum(bool isNew, const QString &album, const QString &path); - void sigAddToAlbumWithUID(bool isNew, int UID, const QString &path); //采用UID方案的添加至相册 + void sigAddToAlbumWithUID(bool isNew, int UID, const QString &path); //采用UID方案的添加至相册 //收藏/取消收藏 void sigAddOrRemoveToFav(const QString &path, bool isAdd); //导出 void sigExport(const QString &path); //从自定义相册中移除 void sigRemoveFromCustom(const QString &path, const QString &album); - void sigRemoveFromCustomWithUID(const QString &path, int UID); //采用UID方案的从相册中删除 + void sigRemoveFromCustomWithUID(const QString &path, int UID); //采用UID方案的从相册中删除 //退出幻灯片 void exitSlideShow(); //按下ESC键 @@ -68,12 +67,14 @@ class IMAGEVIEWERSHARED_EXPORT ImageEngine : public QObject //通知旋转 void sigRotatePic(const QString &path); -private: + // 授权操作通知信号 + void sigAuthoriseNotify(const QJsonObject &data); +private: static ImageEngine *m_ImageEngine; QScopedPointer d_ptr; Q_DECLARE_PRIVATE_D(qGetPtrHelper(d_ptr), ImageEngine) }; -#endif // IMAGEENGINE_H +#endif // IMAGEENGINE_H diff --git a/libimageviewer/imageviewer.cpp b/libimageviewer/imageviewer.cpp index cb50d4e1..7864de7b 100644 --- a/libimageviewer/imageviewer.cpp +++ b/libimageviewer/imageviewer.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 - 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2022 - 2024 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -13,31 +13,52 @@ #include #include #include +#include #include "imageengine.h" #include "viewpanel/viewpanel.h" +#include "service/permissionconfig.h" #include "service/commonservice.h" #include "service/mtpfileproxy.h" #include "unionimage/imageutils.h" #include "unionimage/baseutils.h" +#ifdef DTKWIDGET_CLASS_DWaterMarkHelper +#include +#endif + #define PLUGINTRANSPATH "/usr/share/libimageviewer/translations" class ImageViewerPrivate { public: - ImageViewerPrivate(imageViewerSpace::ImgViewerType imgViewerType, QString savePath, AbstractTopToolbar *customTopToolbar, ImageViewer *parent); + ImageViewerPrivate(imageViewerSpace::ImgViewerType imgViewerType, + const QString &savePath, + AbstractTopToolbar *customTopToolbar, + ImageViewer *parent); + private: - ImageViewer *q_ptr; - LibViewPanel *m_panel = nullptr; - imageViewerSpace::ImgViewerType m_imgViewerType; + ImageViewer *q_ptr; + LibViewPanel *m_panel = nullptr; + imageViewerSpace::ImgViewerType m_imgViewerType; + Q_DECLARE_PUBLIC(ImageViewer) + Q_DISABLE_COPY(ImageViewerPrivate) }; -ImageViewerPrivate::ImageViewerPrivate(imageViewerSpace::ImgViewerType imgViewerType, QString savePath, AbstractTopToolbar *customTopToolbar, ImageViewer *parent) +ImageViewerPrivate::ImageViewerPrivate(imageViewerSpace::ImgViewerType imgViewerType, + const QString &savePath, + AbstractTopToolbar *customTopToolbar, + ImageViewer *parent) : q_ptr(parent) { - QDir dir(PLUGINTRANSPATH); + // 在界面前初始化授权配置 + if (!qApp) { + qWarning() << qPrintable("Must init authorise config after QApplication initialized!"); + } else { + PermissionConfig::instance()->initFromArguments(qApp->arguments()); + } + QDir dir(PLUGINTRANSPATH); if (dir.exists()) { QDirIterator qmIt(PLUGINTRANSPATH, QStringList() << QString("*%1.qm").arg(QLocale::system().name()), QDir::Files); @@ -52,8 +73,7 @@ ImageViewerPrivate::ImageViewerPrivate(imageViewerSpace::ImgViewerType imgViewer QStringList parseLocalNameList = QLocale::system().name().split("_", QString::SkipEmptyParts); if (parseLocalNameList.length() > 0) { - QString translateFilename = QString("/libimageviewer_%2.qm") - .arg(parseLocalNameList.at(0)); + QString translateFilename = QString("/libimageviewer_%2.qm").arg(parseLocalNameList.at(0)); QString translatePath = PLUGINTRANSPATH + translateFilename; if (QFile::exists(translatePath)) { @@ -64,7 +84,7 @@ ImageViewerPrivate::ImageViewerPrivate(imageViewerSpace::ImgViewerType imgViewer } } } -// } + Q_Q(ImageViewer); m_imgViewerType = imgViewerType; //记录当前展示模式 @@ -77,9 +97,40 @@ ImageViewerPrivate::ImageViewerPrivate(imageViewerSpace::ImgViewerType imgViewer q->setLayout(layout); m_panel = new LibViewPanel(customTopToolbar, q); layout->addWidget(m_panel); + +#ifdef DTKWIDGET_CLASS_DWaterMarkHelper + // 设置看图水印,目前仅在主要展示区域显示 + if (PermissionConfig::instance()->hasReadWaterMark()) { + auto data = PermissionConfig::instance()->readWaterMarkData(); + DWaterMarkHelper::instance()->setData(data); + DWaterMarkHelper::instance()->registerWidget(m_panel); + + // 仅权限控制图片单独展示 + QObject::connect(PermissionConfig::instance(), + &PermissionConfig::currentImagePathChanged, + q_ptr, + [this](const QString &, bool isTargetImage) { + if (!PermissionConfig::instance()->isValid()) { + return; + } + + DWaterMarkWidget *mark = m_panel->findChild(); + if (mark) { + mark->setVisible(isTargetImage); + } + }); + } +#endif } -ImageViewer::ImageViewer(imageViewerSpace::ImgViewerType imgViewerType, QString savePath, AbstractTopToolbar *customTopToolbar, QWidget *parent) +/** + @brief 构造视图展示控件 + @note 由于兼容性问题,此处的 `savePath` 没有调整为引用传递 + */ +ImageViewer::ImageViewer(imageViewerSpace::ImgViewerType imgViewerType, + QString savePath, + AbstractTopToolbar *customTopToolbar, + QWidget *parent) : DWidget(parent) , d_ptr(new ImageViewerPrivate(imgViewerType, savePath, customTopToolbar, this)) { @@ -108,7 +159,8 @@ bool ImageViewer::startdragImage(const QStringList &paths, const QString &firstP return d->m_panel->startdragImage(paths, firstPath); } -bool ImageViewer::startdragImageWithUID(const QStringList &paths, const QString &firstPath, bool isCustom, const QString &album, int UID) +bool ImageViewer::startdragImageWithUID( + const QStringList &paths, const QString &firstPath, bool isCustom, const QString &album, int UID) { Q_D(ImageViewer); d->m_panel->setIsCustomAlbumWithUID(isCustom, album, UID); @@ -128,7 +180,7 @@ void ImageViewer::startImgView(const QString ¤tPath, const QStringList &pa //启动线程制作缩略图 if (LibCommonService::instance()->getImgViewerType() == imageViewerSpace::ImgViewerTypeLocal || - LibCommonService::instance()->getImgViewerType() == imageViewerSpace::ImgViewerTypeNull) { + LibCommonService::instance()->getImgViewerType() == imageViewerSpace::ImgViewerTypeNull) { //首先生成当前图片的缓存 ImageEngine::instance()->makeImgThumbnail(LibCommonService::instance()->getImgSavePath(), QStringList(realPath), 1); //看图制作全部缩略图 diff --git a/libimageviewer/imageviewer.h b/libimageviewer/imageviewer.h index 9ab0f0e5..b336f22f 100644 --- a/libimageviewer/imageviewer.h +++ b/libimageviewer/imageviewer.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2022 - 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2022 - 2024 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -14,69 +14,74 @@ class AbstractTopToolbar; DWIDGET_USE_NAMESPACE - class ImageViewerPrivate; class IMAGEVIEWERSHARED_EXPORT ImageViewer : public DWidget { Q_OBJECT public: - //ImgViewerType:图片展示类型, savePath:缩略图保存位置,customTopToolbar:自定义标题栏,nullptr的时候使用内置方案 - explicit ImageViewer(imageViewerSpace::ImgViewerType imgViewerType, QString savePath, AbstractTopToolbar *customTopToolbar = nullptr, QWidget *parent = nullptr); + // ImgViewerType:图片展示类型, savePath:缩略图保存位置,customTopToolbar:自定义标题栏,nullptr的时候使用内置方案 + explicit ImageViewer(imageViewerSpace::ImgViewerType imgViewerType, + QString savePath, + AbstractTopToolbar *customTopToolbar = nullptr, + QWidget *parent = nullptr); ~ImageViewer() override; - //调用文件选择窗口 + // 调用文件选择窗口 bool startChooseFileDialog(); - //传入路径加载图片 paths:所有照片 firstPath:第一张 isCustom:是否是自定义相册 album:自定义相册名称 - bool startdragImage(const QStringList &paths, const QString &firstPath = "", bool isCustom = false, const QString &album = ""); + // 传入路径加载图片 paths:所有照片 firstPath:第一张 isCustom:是否是自定义相册 album:自定义相册名称 + bool + startdragImage(const QStringList &paths, const QString &firstPath = "", bool isCustom = false, const QString &album = ""); - //传入路径加载图片 paths:所有照片 firstPath:第一张 isCustom:是否是自定义相册 album:自定义相册名称 UID:相册的UID - bool startdragImageWithUID(const QStringList &paths, const QString &firstPath = "", bool isCustom = false, const QString &album = "", int UID = -1); + // 传入路径加载图片 paths:所有照片 firstPath:第一张 isCustom:是否是自定义相册 album:自定义相册名称 UID:相册的UID + bool startdragImageWithUID( + const QStringList &paths, const QString &firstPath = "", bool isCustom = false, const QString &album = "", int UID = -1); - //启动图片展示入口 + // 启动图片展示入口 void startImgView(const QString ¤tPath, const QStringList &paths = QStringList()); - //设置topbar的显示和隐藏 + // 设置topbar的显示和隐藏 void setTopBarVisible(bool visible); - //设置Bottomtoolbar的显示和隐藏 + // 设置Bottomtoolbar的显示和隐藏 void setBottomtoolbarVisible(bool visible); - //获得工具栏按钮 + // 获得工具栏按钮 DIconButton *getBottomtoolbarButton(imageViewerSpace::ButtonType type); - //获得当前展示图片路径 + // 获得当前展示图片路径 QString getCurrentPath(); - //二次开发接口 + // 二次开发接口 - //设置图片显示panel右键菜单的显示和隐藏,false为永久隐藏,true为跟随原有策略显示或隐藏 + // 设置图片显示panel右键菜单的显示和隐藏,false为永久隐藏,true为跟随原有策略显示或隐藏 void setViewPanelContextMenuItemVisible(imageViewerSpace::NormalMenuItemId id, bool visible); - //设置下方工具栏按钮的显示和隐藏,true为永久隐藏,false为跟随原有策略显示或隐藏 + // 设置下方工具栏按钮的显示和隐藏,true为永久隐藏,false为跟随原有策略显示或隐藏 void setBottomToolBarButtonAlawysNotVisible(imageViewerSpace::ButtonType id, bool notVisible); - //相册-给右键菜单传输自定义相册名称 + // 相册-给右键菜单传输自定义相册名称 void setCustomAlbumName(const QMap map, bool isFav); - //相册-给右键菜单传输自定义相册名称的UID方案接口,map的参数依次是UID,相册名,是否在此相册 + // 相册-给右键菜单传输自定义相册名称的UID方案接口,map的参数依次是UID,相册名,是否在此相册 void setCustomAlbumNameAndUID(const QMap > &map, bool isFav); - //设置全屏/退出全屏 + // 设置全屏/退出全屏 void switchFullScreen(); - //设置启动幻灯片 + // 设置启动幻灯片 void startSlideShow(const QStringList &paths, const QString &firstPath); - //设置panel拖拽使能 + // 设置panel拖拽使能 void setDropEnabled(bool enable); protected: void resizeEvent(QResizeEvent *e) override; void showEvent(QShowEvent *e) override; + private: QScopedPointer d_ptr; Q_DECLARE_PRIVATE_D(qGetPtrHelper(d_ptr), ImageViewer) }; -#endif // IMAGEVIEWER_H +#endif // IMAGEVIEWER_H diff --git a/libimageviewer/service/permissionconfig.cpp b/libimageviewer/service/permissionconfig.cpp new file mode 100644 index 00000000..41261852 --- /dev/null +++ b/libimageviewer/service/permissionconfig.cpp @@ -0,0 +1,895 @@ +// SPDX-FileCopyrightText: 2023 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "permissionconfig.h" +#include "imageengine.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// dprintpreviewsettinginfo 没有单独宏,使用 DWaterMarkHelper (版本更前)进行限制 +#ifdef DTKWIDGET_CLASS_DWaterMarkHelper +#include +#endif + +static const QString g_KeyTid = "tid"; +static const QString g_KeyOperate = "operate"; +static const QString g_KeyFilePath = "filePath"; +static const QString g_KeyRemaining = "remainingPrintCount"; + +// 打印水印插件 +static const char *g_WaterMarkEnv = "DEEPIN_WATERMARK"; +static const QString g_WaterMarkPluginName = "WaterMarkFilter"; +static const QString g_WaterMarkPluginClass = "PrintPreviewSettingsPlugin"; + +// 打破打印水印间距限制 +static const char *g_PrintRowSpacingProp = "_d_print_waterMarkRowSpacing"; +static const char *g_PrintColumnSpacingProp = "_d_print_waterMarkColumnSpacing"; +static const qreal g_PrintRowSpacingLimit = 10.0; +static const qreal g_PrintColumnSpacingLimit = 2.0; + +// 打印水印默认字体大小,用于计算转换系数 +static const qreal g_DefaultPrintFontSize = 65.0; + +/** + @brief 通过dbus接口从任务栏激活窗口 +*/ +bool activateWindowFromDock(quintptr winId) +{ + bool bRet = false; + // 优先采用V23接口,低版本DDE还未提供区分V23/V20接口 + QDBusInterface dockDbusInterfaceV23( + "org.deepin.dde.daemon.Dock1", "/org/deepin/dde/daemon/Dock1", "org.deepin.dde.daemon.Dock1"); + if (dockDbusInterfaceV23.isValid()) { + QDBusReply reply = dockDbusInterfaceV23.call("ActivateWindow", winId); + if (!reply.isValid()) { + qWarning() << qPrintable("Call v23 org.deepin.dde.daemon.Dock1 failed") << reply.error(); + } else { + return true; + } + } + + QDBusInterface dockDbusInterfaceV20( + "com.deepin.dde.daemon.Dock", "/com/deepin/dde/daemon/Dock", "com.deepin.dde.daemon.Dock"); + if (dockDbusInterfaceV20.isValid() && !bRet) { + QDBusReply reply = dockDbusInterfaceV20.call("ActivateWindow", winId); + if (!reply.isValid()) { + qWarning() << qPrintable("Call v20 com.deepin.dde.daemon.Dock failed") << reply.error(); + bRet = false; + } else { + return true; + } + } + + return bRet; +} + +/** + @class PermissionConfig + @brief 授权控制类,提供操作授权和水印配置 + @details 授权控制主要包括编辑、拷贝、删除、保存等操作,水印包括阅读水印及打印水印。 + 配置信息通过命令行参数获取,当授权控制开启时,进行主要操作将自动发送通知信息。 + @note 此类非线程安全 + */ + +/** + @brief 构造函数,构造时即初始化配置 + */ +PermissionConfig::PermissionConfig(QObject *parent) + : QObject(parent) +{ +#ifndef DTKWIDGET_CLASS_DWaterMarkHelper + qWarning() << qPrintable("Current version is not support read watermark"); +#endif +} + +/** + @brief 析构函数 + */ +PermissionConfig::~PermissionConfig() {} + +/** + @return 返回权限控制单实例 + */ +PermissionConfig *PermissionConfig::instance() +{ + static PermissionConfig config; + return &config; +} + +/** + @return 是否允许进行权限控制,未加载授权配置时返回 false + */ +bool PermissionConfig::isValid() const +{ + return valid; +} + +/** + @return 返回当前是否为权限控制目标图片 + */ +bool PermissionConfig::isCurrentIsTargetImage() const +{ + return isValid() && currentImagePath == targetImagePath; +} + +/** + @return 是否允许打印图片,无授权控制时默认返回 true + @note -1 表示无限制;0 表示无打印次数;1~表示剩余可打印次数 + */ +bool PermissionConfig::isPrintable(const QString &fileName) const +{ + if (checkAuthInvalid(fileName)) { + return true; + } + + return !!printLimitCount; +} + +/** + @return 是否存在阅读水印 + */ +bool PermissionConfig::hasReadWaterMark() const +{ + return authFlags.testFlag(EnableReadWaterMark); +} + +/** + @return 是否存在打印水印 + 当环境中存在打印水印插件时,将不再手动设置打印水印,而是通过环境变量设置调整打印水印插件的数据 + @sa detectWaterMarkPluginExists() + */ +bool PermissionConfig::hasPrintWaterMark() const +{ + return !useWaterMarkPlugin && authFlags.testFlag(EnablePrintWaterMark); +} + +/** + @brief 触发打印文件 \a fileName ,若为限权文件,向外发送权限通知信号 + */ +void PermissionConfig::triggerPrint(const QString &fileName) +{ + if (checkAuthInvalid(fileName)) { + return; + } + + // 减少打印计数 + reduceOnePrintCount(); + QJsonObject data{{g_KeyTid, TidPrint}, {g_KeyOperate, "print"}, {g_KeyFilePath, fileName}, {g_KeyRemaining, printCount()}}; + + triggerNotify(data); +} + +/** + @brief 返回当前剩余的打印次数 + @sa `isPrintable` + */ +int PermissionConfig::printCount() const +{ + return printLimitCount; +} + +/** + @brief 返回是否打印无限制 + */ +bool PermissionConfig::isUnlimitPrint() const +{ + if (checkAuthInvalid()) { + return true; + } + return -1 == printLimitCount; +} + +/** + @brief 减少一次打印计数并发送打印计数变更信号 `printCountChanged` + */ +void PermissionConfig::reduceOnePrintCount() +{ + if (printLimitCount > 0) { + printLimitCount--; + Q_EMIT printCountChanged(); + } else { + qWarning() << qPrintable("Escape print authorise check!"); + } +} + +/** + @brief 用于操作文件 \a fileName 触发不同类型 \a tid 动作,根据不同动作构造不同的 Json 通知数据, + 并通过信号向外发送。 +*/ +void PermissionConfig::triggerAction(TidType tid, const QString &fileName) +{ + if (checkAuthInvalid(fileName)) { + return; + } + + QString optName; + switch (tid) { + case TidOpen: + if (NotOpen != status) { + return; + } + status = Open; + optName = "open"; + break; + case TidClose: + if (Open != status) { + return; + } + status = Close; + // Note: 授权文件关闭后(关闭窗口或打开其他图片),权限控制无效化 + valid = false; + + optName = "close"; + break; + case TidEdit: + optName = "edit"; + break; + case TidCopy: + optName = "copy"; + break; + case TidDelete: + optName = "delete"; + break; + case TidPrint: + // 打印操作略有不同,单独处理 + triggerPrint(fileName); + return; + default: + /* + 当前未捕获的操作将不会处理,直接返回 + case TidSwitch: + optName = "pictureSwitch"; + break; + case TidSetWallpaper: + optName = "setWallpaper"; + break; + */ + return; + } + + QJsonObject data{{g_KeyTid, tid}, {g_KeyOperate, optName}, {g_KeyFilePath, fileName}}; + triggerNotify(data); +} + +/** + @brief 触发权限操作通知,将向外发送Json数据,通过DBus广播 + */ +void PermissionConfig::triggerNotify(const QJsonObject &data) +{ + enum ReportMode { Broadcast = 1, Report = 2, ReportAndBroadcast = Broadcast | Report }; + QJsonObject sendData; + sendData.insert("policy", QJsonObject{{"reportMode", ReportAndBroadcast}}); + sendData.insert("info", data); + + Q_EMIT authoriseNotify(sendData); +} + +/** + @brief 过滤 \a watched (DPrintPreviewWidget)的 DynamicPropertyChange 事件, + 用于打破 DTK 对打印间距的限制,和设置值一致,仅在设置 breakPrintSpacingLimit 后有效 + @sa installFilterPrintDialog + */ +bool PermissionConfig::eventFilter(QObject *watched, QEvent *event) +{ + if (QEvent::DynamicPropertyChange == event->type() && watched) { + QDynamicPropertyChangeEvent *propEvent = dynamic_cast(event); + if (!propEvent) { + return false; + } + + auto checkFunc = [&](const char *prop, qreal value, qreal limit) { + if (QByteArray(prop) != propEvent->propertyName() || (value <= limit)) { + return; + } + + qreal curValue = watched->property(prop).toDouble(); + if (!qFuzzyCompare(curValue, value)) { + // 强制设置间距和预期值一致 + watched->setProperty(prop, value); + } + }; + + // 检查 rowSpacing 和 columnSpacing, DynamicPropertyChange 只是事后通知,无需实际过滤 + checkFunc(g_PrintRowSpacingProp, printRowSpacing, g_PrintRowSpacingLimit); + checkFunc(g_PrintColumnSpacingProp, printColumnSpacing, g_PrintColumnSpacingLimit); + } + + return false; +} + +/** + @brief 设置当前处理的文件路径为 \a fileName + */ +void PermissionConfig::setCurrentImagePath(const QString &fileName) +{ + if (!valid) { + return; + } + + currentImagePath = fileName; + + // 通知当前展示的图片变更 + Q_EMIT currentImagePathChanged(fileName, bool(currentImagePath == targetImagePath)); +} + +/** + @return 返回当前控制的图片路径 + */ +QString PermissionConfig::targetImage() const +{ + return targetImagePath; +} + +#ifdef DTKWIDGET_CLASS_DWaterMarkHelper + +/** + @return 返回从配置中读取的阅读水印配置,用于图片展示时显示 + */ +WaterMarkData PermissionConfig::readWaterMarkData() const +{ + return readWaterMark; +} + +/** + @return 返回从配置中读取的打印水印配置,用于图片打印预览及打印时显示 + */ +WaterMarkData PermissionConfig::printWaterMarkData() const +{ + return printWaterMark; +} + +/** + @brief 添加 \a dialog 的过滤器,这是用于解除 DTK 中对打印水印间距的限制 + 仅在设置 `breakPrintSpacingLimit` 权限后,且打印水印间距系数超过 DTK 限制时有效。 + 此函数时预留的,用于预防后期新增需求。 + + 打印的插件接口在5.5.50后提供,DTKWIDGET_CLASS_DWaterMarkHelper(5.6.9)包含这部分功能 + */ +bool PermissionConfig::installFilterPrintDialog(DPrintPreviewDialog *dialog) +{ + if (!breakPrintSpacingLimit) { + return false; + } + + // 判断是否需要调整间距,若间距没有超过限制,则无需调整 + if (!(printRowSpacing > g_PrintRowSpacingLimit || printColumnSpacing > g_PrintColumnSpacingLimit)) { + return false; + } + + if (dialog) { + DPrintPreviewWidget *widget = dialog->findChild(); + if (widget) { + widget->installEventFilter(this); + return true; + } + } + + return false; +} + +/** + @brief 设置打印对话框 `dialog` 的打印水印格式,当系统环境中存在打印插件时,将不会进入此函数。 + */ +bool PermissionConfig::setDialogPrintWatermark(DPrintPreviewDialog *dialog) const +{ + if (!dialog) { + return false; + } + + // 使用适配的水印配置 + bool ret = false; + const AdapterWaterMarkData &data = printAdapterWaterMark; + DPrintPreviewSettingInfo *baseInfo = dialog->createDialogSettingInfo(DPrintPreviewWatermarkInfo::PS_Watermark); + + if (baseInfo) { + DPrintPreviewWatermarkInfo *info = dynamic_cast(baseInfo); + if (info) { + // 打印水印实现方式同阅读水印略有不同,调整参数以使得效果一致。 + info->opened = true; + + info->angle = static_cast(data.rotation); + info->transparency = static_cast(data.opacity * 100); + QFontMetrics fm(data.font); + QSize textSize = fm.size(Qt::TextSingleLine, data.text); + if (textSize.height() > 0) { + info->rowSpacing = qMax(0.0, (qreal(data.lineSpacing + textSize.height()) / textSize.height()) - 1.0); + } + if (textSize.width() > 0) { + info->columnSpacing = qMax(0.0, (qreal(data.spacing + textSize.width()) / textSize.width()) - 1.0); + } + info->layout = (data.layout == PermissionConfig::AdapterWaterMarkData::Center) ? DPrintPreviewWatermarkInfo::Center : + DPrintPreviewWatermarkInfo::Tiled; + info->currentWatermarkType = (data.type == PermissionConfig::AdapterWaterMarkData::Text) ? + DPrintPreviewWatermarkInfo::TextWatermark : + DPrintPreviewWatermarkInfo::ImageWatermark; + info->textType = DPrintPreviewWatermarkInfo::Custom; + info->customText = data.text; + info->textColor = data.color; + info->fontList.append(data.font.family()); + // 字体使用缩放滑块处理 10%~200%, 默认字体大小为65 + info->size = int(data.font.pointSizeF() / g_DefaultPrintFontSize * 100); + + dialog->updateDialogSettingInfo(info); + + ret = true; + } else { + qWarning() << qPrintable("Can't convert DPrintPreviewDialog watermark info.") << baseInfo->type(); + } + + delete baseInfo; + } else { + qWarning() << qPrintable("Can't get DPrintPreviewDialog watermark info."); + } + + // 在不使用打印插件时,需要手动设置打印插件选项显示且不可编辑,在设置后调用 + // WaterMarkFrame 和 WaterMarkContentFrame 是DTK窗口中水印设置控件的 objectName + auto widgetList = dialog->findChildren("WaterMarkFrame"); + widgetList.append(dialog->findChildren("WaterMarkContentFrame")); + for (auto wid : widgetList) { + wid->setVisible(true); + wid->setEnabled(false); + } + + return ret; +} + +#endif // DTKWIDGET_CLASS_DWaterMarkHelper + +/** + @brief 接收来自外部的调起窗口指令,当 \a pid 和当前进程pid一致时,激活主窗口 +*/ +void PermissionConfig::activateProcess(qint64 pid) +{ + qInfo() << QString("Receive DBus activate process, current pid: %1, request pid %2").arg(qApp->applicationPid()).arg(pid); + + if (pid == qApp->applicationPid()) { + QWindowList winList = qApp->topLevelWindows(); + if (winList.isEmpty()) { + return; + } + + // 看图默认一个窗口,通过 DBus 接口激活窗口(防止缩小在任务栏) + auto mainWin = winList.first(); + if (!Q_LIKELY(activateWindowFromDock(mainWin->winId()))) { + mainWin->requestActivate(); + } + } +} + +/** + @brief 从命令行参数 \a arguments 中取得授权配置 + @note 命令行参数示例: + deepin-image-viewer --config=[Base64 code data] /path/to/file + */ +void PermissionConfig::initFromArguments(const QStringList &arguments) +{ + if (valid) { + return; + } + + QString configParam; + QStringList imageList; + bool parseRet = parseConfigOption(arguments, configParam, imageList); + if (parseRet) { + // 获取带权限控制的文件路径 + for (const QString &arg : imageList) { + QFileInfo info(arg); + QString filePath = info.absoluteFilePath(); + if (ImageEngine::instance()->isImage(filePath) || info.suffix() == "dsps") { + targetImagePath = filePath; + break; + } + } + + if (targetImagePath.isEmpty()) { + qWarning() << qPrintable("Authorise config with no target image path."); + return; + } + QByteArray jsonData = QByteArray::fromBase64(configParam.toUtf8()); + qInfo() << QString("Parse authorise config, data: %1").arg(QString(jsonData)); + + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(jsonData, &error); + + if (!doc.isNull()) { + QJsonObject root = doc.object(); + initAuthorise(root.value("permission").toObject()); +#ifdef DTKWIDGET_CLASS_DWaterMarkHelper + initReadWaterMark(root.value("readWatermark").toObject()); + initPrintWaterMark(root.value("printWatermark").toObject()); +#endif // DTKWIDGET_CLASS_DWaterMarkHelper + + qInfo() << qPrintable("Current Enable permission") << authFlags; + } else { + qWarning() + << QString("Parse authorise config error at pos: %1, details: %2").arg(error.offset).arg(error.errorString()); + } + +#ifdef DTKWIDGET_CLASS_DWaterMarkHelper + // 存在打印水印设置时,检测是否存在打印水印插件,若存在则通过设置环境变量调用打印插件而不是手动设置 + if (authFlags.testFlag(EnablePrintWaterMark)) { + detectWaterMarkPluginExists(); + } +#endif // DTKWIDGET_CLASS_DWaterMarkHelper + + // 只要传入参数、图片即认为有效,无论参数是否正常解析 + valid = true; + // 首次触发打开图片 + triggerAction(TidOpen, targetImagePath); + } else { + qWarning() << qPrintable("Parse authorise config is empty."); + } + + if (valid) { + // 传入权限控制参数时,绑定DBus唤醒信号 + QDBusConnection connection = QDBusConnection::sessionBus(); + bool connRet = connection.connect( + "com.wps.cryptfs", "/com/wps/cryptfs", "cryptfs.method.Type", "activateProcess", this, SLOT(activateProcess(qint64))); + if (!connRet) { + qWarning() << qPrintable("DBus connect activateProcess failed!"); + } else { + qInfo() << qPrintable("DBus connect activateProcess success!"); + } + } +} + +/** + @return 解析命令行参数并返回设置的权限和水印配置,为设置则返回空 + */ +bool PermissionConfig::parseConfigOption(const QStringList &arguments, QString &configParam, QStringList &imageList) const +{ + // 避开以 --config=* 开头的特殊图片 + QStringList specialList; + QStringList argumentsList = arguments; + QString prefix("--config="); + for (auto itr = argumentsList.begin(); itr != argumentsList.end();) { + if ((*itr).startsWith(prefix) && (*itr).size() > prefix.size()) { + if (ImageEngine::instance()->isImage((*itr))) { + specialList.append(*itr); + itr = argumentsList.erase(itr); + continue; + } + } + + ++itr; + } + + // 取得 --config 后的数据 + QCommandLineParser parser; + QCommandLineOption configOpt("config", "Permission config json(base64 encode).", "configvalue"); + parser.addOption(configOpt); + + if (!parser.parse(argumentsList)) { + qWarning() << QString("Can't parse arguments %1").arg(parser.errorText()); + return false; + } + + imageList = parser.positionalArguments() + specialList; + if (parser.isSet(configOpt)) { + configParam = parser.value(configOpt); + return true; + } + + return false; +} + +/** + @brief 从 Json 配置 \a param 中取得授权信息 + */ +void PermissionConfig::initAuthorise(const QJsonObject ¶m) +{ + if (param.isEmpty()) { + qInfo() << qPrintable("Authorise config not contains authorise data."); + return; + } + + // 屏蔽 delete / rename ,默认无此功能 + authFlags.setFlag(EnableEdit, param.value("edit").toBool(false)); + authFlags.setFlag(EnableCopy, param.value("copy").toBool(false)); + authFlags.setFlag(EnableSwitch, param.value("pictureSwitch").toBool(false)); + authFlags.setFlag(EnableWallpaper, param.value("setWallpaper").toBool(false)); + + // Internal 内部使用,默认均为false + ignoreDevicePixelRatio = param.value("ignoreDevicePixelRatio").toBool(false); + breakPrintSpacingLimit = param.value("breakPrintSpacingLimit").toBool(false); + if (ignoreDevicePixelRatio) { + qInfo() << qPrintable("Enable internal property: ignore device pixel ratio."); + } + if (breakPrintSpacingLimit) { + qInfo() << qPrintable("Enable internal property: break print spacing limit."); + } + + printLimitCount = param.value("printCount").toInt(0); +} + +#ifdef DTKWIDGET_CLASS_DWaterMarkHelper + +/** + @brief 从 Json 配置 \a param 中取得阅读水印信息 + */ +void PermissionConfig::initReadWaterMark(const QJsonObject ¶m) +{ + if (param.isEmpty()) { + qInfo() << qPrintable("Authorise config not contains read watermark data."); + return; + } + + readAdapterWaterMark.type = AdapterWaterMarkData::Text; + readAdapterWaterMark.font.setFamily(param.value("font").toString()); + readAdapterWaterMark.font.setPointSize(param.value("fontSize").toInt()); + + QString colorName = param.value("color").toString(); + if (!colorName.startsWith('#')) { + colorName.prepend('#'); + } + readAdapterWaterMark.color.setNamedColor(colorName); + readAdapterWaterMark.opacity = param.value("opacity").toDouble() / 255; + readAdapterWaterMark.layout = param.value("layout").toInt() ? AdapterWaterMarkData::Tiled : AdapterWaterMarkData::Center; + readAdapterWaterMark.rotation = param.value("angle").toDouble(); + readAdapterWaterMark.lineSpacing = param.value("rowSpacing").toInt(); + readAdapterWaterMark.spacing = param.value("columnSpacing").toInt(); + readAdapterWaterMark.text = param.value("text").toString(); + + qreal deviceRatio = qApp->devicePixelRatio(); + if (ignoreDevicePixelRatio && !qFuzzyCompare(1.0, deviceRatio) && deviceRatio > 0) { + readAdapterWaterMark.font.setPointSizeF(readAdapterWaterMark.font.pointSizeF() / deviceRatio); + readAdapterWaterMark.lineSpacing /= deviceRatio; + readAdapterWaterMark.spacing /= deviceRatio; + } + + authFlags.setFlag(EnableReadWaterMark, true); + + // 转换为公共接口类型 + readWaterMark = convertAdapterWaterMarkData(readAdapterWaterMark); +} + +/** + @brief 从 Json 配置 \a param 中取得打印水印信息 + */ +void PermissionConfig::initPrintWaterMark(const QJsonObject ¶m) +{ + if (param.isEmpty()) { + qInfo() << qPrintable("Authorise config not contains print watermark data."); + return; + } + + printAdapterWaterMark.type = AdapterWaterMarkData::Text; + printAdapterWaterMark.font.setFamily(param.value("font").toString()); + printAdapterWaterMark.font.setPointSize(param.value("fontSize").toInt()); + + QString colorName = param.value("color").toString(); + if (!colorName.startsWith('#')) { + colorName.prepend('#'); + } + printAdapterWaterMark.color.setNamedColor(colorName); + printAdapterWaterMark.opacity = param.value("opacity").toDouble() / 255; + printAdapterWaterMark.layout = param.value("layout").toInt() ? AdapterWaterMarkData::Tiled : AdapterWaterMarkData::Center; + printAdapterWaterMark.rotation = param.value("angle").toDouble(); + printAdapterWaterMark.lineSpacing = param.value("rowSpacing").toInt(); + printAdapterWaterMark.spacing = param.value("columnSpacing").toInt(); + printAdapterWaterMark.text = param.value("text").toString(); + + authFlags.setFlag(EnablePrintWaterMark, true); + + // 计算 DTK 打印水印的间距转换系数,限制提升到 10000.0 + if (!printAdapterWaterMark.text.isEmpty()) { + QFontMetrics fm(printAdapterWaterMark.font); + QSize textSize = fm.size(Qt::TextSingleLine, printAdapterWaterMark.text); + if (textSize.height() > 0) { + printRowSpacing = (qreal(printAdapterWaterMark.lineSpacing + textSize.height()) / textSize.height()) - 1.0; + printRowSpacing = qBound(0.0, printRowSpacing, 10000.0); + } + if (textSize.width() > 0) { + printColumnSpacing = (qreal(printAdapterWaterMark.spacing + textSize.width()) / textSize.width()) - 1.0; + printColumnSpacing = qBound(0.0, printColumnSpacing, 10000.0); + } + + qInfo() << QString("Print config spacing ratio row: %1 column: %2").arg(printRowSpacing).arg(printColumnSpacing); + } + + // 转换为公共接口类型 + printWaterMark = convertAdapterWaterMarkData(printAdapterWaterMark); +} + +/** + @brief 将内部使用的数据 `adptData` 转换为可被外部使用的公共接口类型 `WaterMarkData` , + `WaterMarkData` 在主线和定制分支的接口结构不同,需要转换以兼容不同版本 + */ +WaterMarkData PermissionConfig::convertAdapterWaterMarkData(const PermissionConfig::AdapterWaterMarkData &adptData) const +{ + WaterMarkData data; + + // DTKWidget 主线和定制线的水印接口不同,通过版本进行区分 + // 主线水印接口在 5.6.9 之后引入. + // 因此,判断定制线:存在水印接口,版本不低于 5.4.42.7 且低于 5.5 +#if DTK_VERSION_CHECK(5, 4, 42, 7) <= DTK_VERSION && DTK_VERSION < DTK_VERSION_CHECK(5, 5, 0, 0) + data.type = AdapterWaterMarkData::Text == adptData.type ? WaterMarkType::Text : WaterMarkType::Image; + data.layout = AdapterWaterMarkData::Center == adptData.layout ? WaterMarkLayout::Center : WaterMarkLayout::Tiled; + data.scaleFactor = adptData.scaleFactor; + data.spacing = adptData.spacing; + data.lineSpacing = adptData.lineSpacing; + data.text = adptData.text; + data.font = adptData.font; + data.color = adptData.color; + data.rotation = adptData.rotation; + data.opacity = adptData.opacity; + data.image = adptData.image; + data.grayScale = adptData.grayScale; + +#else + data.setType(AdapterWaterMarkData::Text == adptData.type ? WaterMarkData::Text : WaterMarkData::Image); + data.setLayout(AdapterWaterMarkData::Center == adptData.layout ? WaterMarkData::Center : WaterMarkData::Tiled); + data.setScaleFactor(adptData.scaleFactor); + data.setSpacing(adptData.spacing); + data.setLineSpacing(adptData.lineSpacing); + data.setText(adptData.text); + data.setFont(adptData.font); + data.setColor(adptData.color); + data.setRotation(adptData.rotation); + data.setOpacity(adptData.opacity); + data.setImage(adptData.image); + data.setGrayScale(adptData.grayScale); + +#endif // VERSION CHECK + + return data; +} + +/** + @brief 检测当前系统环境中是否存在打印水印插件 + 通过路径下是否存在指定的插件文件决定,使用DPKG判断不一定准确, + 文件可能被移除。 + @sa initWaterMarkPluginEnvironment() + */ +void PermissionConfig::detectWaterMarkPluginExists() +{ + if (useWaterMarkPlugin) { + return; + } + + // 参考 dprintpreviewdialog.cpp loadPlugin() 加载过程判断插件是否允许加载 + // Note: 打印插件初始化在创建时加载完成,因此此处不尝试加载 +#if defined(Q_OS_LINUX) + QString pluginPath = QLatin1String("/usr/share/deepin/dtk/plugins/printsupport"); +#else + QString pluginPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QLatin1String("/printsupport") + + QLatin1String("/plugins"); +#endif + + if (!QFileInfo(pluginPath).exists()) { + return; + } + + QDir pluginDir(pluginPath); + QStringList fileList = pluginDir.entryList({"*PrintPreviewSettings*.so"}, QDir::Files); + + bool detected = false; + for (const QString &pluginName : fileList) { + // 能否正常加载 + QPluginLoader loader(pluginDir.absoluteFilePath(pluginName)); + QString className = loader.metaData().value("className").toString(); + qInfo() << qPrintable("Detect print preview plugin metadata:") << pluginName << loader.metaData(); + + if (g_WaterMarkPluginClass == className) { + detected = true; + break; + } + } + + if (detected) { + if (initWaterMarkPluginEnvironment()) { + // 成功初始化打印插件环境变量 + useWaterMarkPlugin = true; + qInfo() << qPrintable("Using watermark plugin to print preview instead of manual set."); + } else { + qWarning() << qPrintable("Detect watermark print preview plugin but init failed!"); + } + } else { + qInfo() << qPrintable("Not detect watermark print preview plugin, current plugins:") << fileList; + } +} + +/** + @brief 初始化打印水印插件环境变量 + @note 打印插件在 loadPlugin() 初始化时读取环境变量,需要注意设置环境变量时间 + @sa detectWaterMarkPluginExists() + */ +bool PermissionConfig::initWaterMarkPluginEnvironment() +{ + QJsonObject envData; + envData.insert("angle", static_cast(printAdapterWaterMark.rotation)); + envData.insert("transparency", static_cast(printAdapterWaterMark.opacity * 100)); + QFontMetrics fm(printAdapterWaterMark.font); + QSize textSize = fm.size(Qt::TextSingleLine, printAdapterWaterMark.text); + if (textSize.height() > 0) { + envData.insert("rowSpacing", + qMax(0.0, (qreal(printAdapterWaterMark.lineSpacing + textSize.height()) / textSize.height()) - 1.0)); + } + if (textSize.width() > 0) { + envData.insert("columnSpacing", + qMax(0.0, (qreal(printAdapterWaterMark.spacing + textSize.width()) / textSize.width()) - 1.0)); + } + + envData.insert("layout", + static_cast(printAdapterWaterMark.layout == AdapterWaterMarkData::Center ? + DPrintPreviewWatermarkInfo::Center : + DPrintPreviewWatermarkInfo::Tiled)); + // 仅文本水印 + envData.insert("watermarkType", static_cast(DPrintPreviewWatermarkInfo::TextWatermark)); + envData.insert("textType", static_cast(DPrintPreviewWatermarkInfo::Custom)); + envData.insert("customText", printAdapterWaterMark.text); + envData.insert("textColor", printAdapterWaterMark.color.name()); + // 兼容插件传参格式 + QJsonArray fontList; + fontList.append(printAdapterWaterMark.font.family()); + envData.insert("fontList", fontList); + // 字体使用缩放滑块处理 10%~200%, 默认字体大小为65 + envData.insert("size", int(printAdapterWaterMark.font.pointSizeF() / g_DefaultPrintFontSize * 100)); + + QJsonDocument doc; + doc.setObject(envData); + QByteArray envParam = doc.toJson(QJsonDocument::Compact); + qInfo() << qPrintable(QString("Set print preview plugin environment %1 PARAM: ").arg(g_WaterMarkEnv)) << envParam; + + if (qputenv(g_WaterMarkEnv, envParam)) { + DWIDGET_USE_NAMESPACE + // 打印水印插件可能非默认插件,设置后手动初始化及配置打印 + if (g_WaterMarkPluginName != DPrintPreviewDialog::currentPlugin()) { + QStringList plugins = DPrintPreviewDialog::availablePlugins(); + if (plugins.contains(g_WaterMarkPluginName)) { + DPrintPreviewDialog::setCurrentPlugin(g_WaterMarkPluginName); + } else { + qWarning() << qPrintable(QString("Print preview plugin not contain %1, all plugins:").arg(g_WaterMarkPluginName)) + << plugins; + return false; + } + } + + } else { + qWarning() << qPrintable("Set watermark plugin environment variable failed!"); + return false; + } + + return true; +} + +#endif // DTKWIDGET_CLASS_DWaterMarkHelper + +/** + @return 返回文件 \a fileName 是否符合权限标识 \a authFlag +*/ +bool PermissionConfig::checkAuthFlag(PermissionConfig::Authorise authFlag, const QString &fileName) const +{ + if (checkAuthInvalid(fileName)) { + return true; + } + + return authFlags.testFlag(authFlag); +} + +/** + @return 返回文件 \a fileName 是否需要被校验权限 + */ +bool PermissionConfig::checkAuthInvalid(const QString &fileName) const +{ + if (!isValid()) { + return true; + } + if (fileName.isEmpty()) { + return currentImagePath != targetImagePath; + } + + return fileName != targetImagePath; +} diff --git a/libimageviewer/service/permissionconfig.h b/libimageviewer/service/permissionconfig.h new file mode 100644 index 00000000..d94a93f6 --- /dev/null +++ b/libimageviewer/service/permissionconfig.h @@ -0,0 +1,158 @@ +// SPDX-FileCopyrightText: 2023 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef PERMISSIONCONFIG_H +#define PERMISSIONCONFIG_H + +#include +#include + +#include + +// 在 5.6.9 版本后提供 +#ifdef DTKWIDGET_CLASS_DWaterMarkHelper +#include +// 较低版本(<5.5.50)不提供插件相关接口 +#include + +DWIDGET_USE_NAMESPACE +#endif // DTKWIDGET_CLASS_DWaterMarkHelper + +class PermissionConfig : public QObject +{ + Q_OBJECT + Q_PROPERTY(int printCount READ printCount NOTIFY printCountChanged) + + explicit PermissionConfig(QObject *parent = nullptr); + ~PermissionConfig() override; + +public: + static PermissionConfig *instance(); + bool isValid() const; + bool isCurrentIsTargetImage() const; + + enum Authorise { + NoAuth = 0, + EnableEdit = 0x1, // 编辑权限,旋转图片是否允许写回文件 + EnableCopy = 0x2, + EnableDelete = 0x4, + EnableRename = 0x8, + EnableSwitch = 0x10, // 是否允许切换文件 + EnableWallpaper = 0x20, + + EnableReadWaterMark = 0x1000, + EnablePrintWaterMark = 0x2000, + }; + Q_DECLARE_FLAGS(Authorises, Authorise) + + // 权限/配置查询接口 + bool checkAuthFlag(Authorise authFlag, const QString &fileName = QString()) const; + bool isPrintable(const QString &fileName = QString()) const; + bool hasReadWaterMark() const; + bool hasPrintWaterMark() const; + + enum TidType { + TidOpen = 1000200050, + TidEdit = 1000200051, + TidCopy = 1000200052, + TidPrint = 1000200053, + TidClose = 1000200054, + TidSwitch = 1000200055, + TidSetWallpaper = 1000200056, + TidDelete = 1000200057, + TidRename = 1000200058, + }; + + // 动作触发通知接口 + void triggerAction(TidType tid, const QString &fileName); + void triggerPrint(const QString &fileName); + Q_SIGNAL void authoriseNotify(const QJsonObject &data); + + int printCount() const; + bool isUnlimitPrint() const; + Q_SIGNAL void printCountChanged(); + + void setCurrentImagePath(const QString &fileName); + Q_SIGNAL void currentImagePathChanged(const QString &fileName, bool isTargetImage); + QString targetImage() const; + +#ifdef DTKWIDGET_CLASS_DWaterMarkHelper + // 阅读/打印水印 + WaterMarkData readWaterMarkData() const; + WaterMarkData printWaterMarkData() const; + + bool installFilterPrintDialog(DPrintPreviewDialog *dialog); + bool setDialogPrintWatermark(DPrintPreviewDialog *dialog) const; +#endif // DTKWIDGET_CLASS_DWaterMarkHelper + + Q_SLOT void activateProcess(qint64 pid); + void initFromArguments(const QStringList &arguments); + +private: + // 解析配置 + bool parseConfigOption(const QStringList &arguments, QString &configParam, QStringList &imageList) const; + void initAuthorise(const QJsonObject ¶m); + +#ifdef DTKWIDGET_CLASS_DWaterMarkHelper + // 用于兼容主线/定制线的通用水印定义,适配不同接口 + struct AdapterWaterMarkData + { + enum AWaterMarkType { None, Text, Image }; // 水印类型 + enum AWaterMarkLayout { Center, Tiled }; // 水印布局 + + AWaterMarkType type = None; // 水印类型 + AWaterMarkLayout layout = Center; // 水印布局 + qreal scaleFactor = 1.0; // 整体大小缩放系数 + int spacing = 0; // 间距 + int lineSpacing = 0; // 行间距 + QString text; // 文本水印内容 + QFont font; // 文本水印字体 + QColor color; // 文本水印颜色 + qreal rotation = 0; // 水印旋转角度(0~360) + qreal opacity = 1; // 水印透明度(0~1) + QImage image; // 图片水印中的图片 + bool grayScale = true; // 是否灰度化图片 + }; + WaterMarkData convertAdapterWaterMarkData(const AdapterWaterMarkData &adptData) const; + + void initReadWaterMark(const QJsonObject ¶m); + void initPrintWaterMark(const QJsonObject ¶m); + void detectWaterMarkPluginExists(); + bool initWaterMarkPluginEnvironment(); +#endif // DTKWIDGET_CLASS_DWaterMarkHelper + + bool checkAuthInvalid(const QString &fileName = QString()) const; + void reduceOnePrintCount(); + void triggerNotify(const QJsonObject &data); + bool eventFilter(QObject *watched, QEvent *event) override; + +private: + enum Status { NotOpen, Open, Close }; + + QString currentImagePath; // 当前展示的图片文件路径 + QString targetImagePath; // 权限控制指向的文件路径 + bool valid = false; + int printLimitCount = 0; // 打印记数,-1表示无限制 + Status status = NotOpen; // 被控制权限图片的状态 + Authorises authFlags = NoAuth; + + // 内部拓展参数 + bool ignoreDevicePixelRatio = false; // 过滤设备显示比率,按照原生像素计算 + bool useWaterMarkPlugin = false; // 是否使用水印插件 + bool breakPrintSpacingLimit = false; // 打破打印间距限制 + qreal printRowSpacing = 0.0; + qreal printColumnSpacing = 0.0; + +#ifdef DTKWIDGET_CLASS_DWaterMarkHelper + AdapterWaterMarkData readAdapterWaterMark; // 水印数据,不区分DTK版本 + AdapterWaterMarkData printAdapterWaterMark; + + WaterMarkData readWaterMark; // 水印数据,根据不同DTK版本支持接口不同 + WaterMarkData printWaterMark; +#endif // DTKWIDGET_CLASS_DWaterMarkHelper + + Q_DISABLE_COPY(PermissionConfig) +}; + +#endif // PERMISSIONCONFIG_H diff --git a/libimageviewer/translations/libimageviewer.ts b/libimageviewer/translations/libimageviewer.ts index b70f2631..50d4ab9c 100644 --- a/libimageviewer/translations/libimageviewer.ts +++ b/libimageviewer/translations/libimageviewer.ts @@ -311,6 +311,14 @@ Photo info Photo info + + (Disabled) + (Disabled) + + + (Remaining %1 times) + (Remaining %1 times) + Colorize pictures diff --git a/libimageviewer/translations/libimageviewer_es.ts b/libimageviewer/translations/libimageviewer_es.ts index 18d6ef95..e9ee589f 100644 --- a/libimageviewer/translations/libimageviewer_es.ts +++ b/libimageviewer/translations/libimageviewer_es.ts @@ -240,7 +240,15 @@ Photo info - + Photo info + + + (Disabled) + (Disabled) + + + (Remaining %1 times) + (Remaining %1 times) diff --git a/libimageviewer/translations/libimageviewer_zh_CN.ts b/libimageviewer/translations/libimageviewer_zh_CN.ts index 57aa5aa7..788d29c9 100644 --- a/libimageviewer/translations/libimageviewer_zh_CN.ts +++ b/libimageviewer/translations/libimageviewer_zh_CN.ts @@ -319,6 +319,14 @@ Photo info 照片信息 + + (Disabled) + (已禁用) + + + (Remaining %1 times) + (剩余 %1 次) + Retry 重 试 diff --git a/libimageviewer/unionimage/baseutils.cpp b/libimageviewer/unionimage/baseutils.cpp index ea6bc8f0..4e07fc0f 100755 --- a/libimageviewer/unionimage/baseutils.cpp +++ b/libimageviewer/unionimage/baseutils.cpp @@ -182,8 +182,11 @@ void showInFileManager(const QString &path) void copyImageToClipboard(const QStringList &paths) { + if (paths.isEmpty()) { + return; + } + // Get clipboard -// QClipboard *cb = QApplication::clipboard(); QClipboard *cb = qApp->clipboard(); // Ownership of the new data is transferred to the clipboard. @@ -210,13 +213,14 @@ void copyImageToClipboard(const QStringList &paths) gnomeFormat.remove(gnomeFormat.length() - 1, 1); newMimeData->setData("x-special/gnome-copied-files", gnomeFormat); - // Copy Image Date -// QImage img(paths.first()); -// Q_ASSERT(!img.isNull()); -// newMimeData->setImageData(img); + // Copy Image Data + QImage img; + QString errMsg; + if (LibUnionImage_NameSpace::loadStaticImageFromFile(paths.first(), img, errMsg)) { + newMimeData->setImageData(img); + } // Set the mimedata -// cb->setMimeData(newMimeData); cb->setMimeData(newMimeData, QClipboard::Clipboard); } diff --git a/libimageviewer/viewpanel/contents/bottomtoolbar.cpp b/libimageviewer/viewpanel/contents/bottomtoolbar.cpp index 852a14ff..80d9181b 100644 --- a/libimageviewer/viewpanel/contents/bottomtoolbar.cpp +++ b/libimageviewer/viewpanel/contents/bottomtoolbar.cpp @@ -25,6 +25,7 @@ #include "unionimage/baseutils.h" #include "unionimage/imageutils.h" #include "unionimage/unionimage.h" +#include "service/permissionconfig.h" #include "service/commonservice.h" #include "imageengine.h" #include "image-viewer_global.h" @@ -173,8 +174,10 @@ void LibBottomToolbar::setRotateBtnClicked(const bool &bRet) void LibBottomToolbar::setPictureDoBtnClicked(const bool &bRet) { + bool enableCopy = PermissionConfig::instance()->checkAuthFlag(PermissionConfig::EnableCopy); + if (m_ocrIsExists && m_ocrBtn) { - m_ocrBtn->setEnabled(bRet); + m_ocrBtn->setEnabled(bRet && enableCopy); } if (m_adaptImageBtn) { m_adaptImageBtn->setEnabled(bRet); @@ -314,11 +317,12 @@ void LibBottomToolbar::deleteImage() if (m_imgListWidget->getCurrentCount() == 0) { m_preButton->setEnabled(false); } -// qDebug() << m_imgListWidget->getCurrentPath(); -// qDebug() << m_imgListWidget->getCurrentCount(); + emit removed(); //删除数据库图片 -// setIsConnectDel(true); + m_imgListWidget->moveCenterWidget(); + + PermissionConfig::instance()->triggerAction(PermissionConfig::TidDelete, path); } } @@ -477,23 +481,39 @@ void LibBottomToolbar::slotOpenImage(int index, QString path) //BUG#93143 QFileInfo info(path); + PermissionConfig::instance()->setCurrentImagePath(info.absoluteFilePath()); + m_trashBtn->setVisible(!PermissionConfig::instance()->isCurrentIsTargetImage()); + + bool isCopyable = PermissionConfig::instance()->checkAuthFlag(PermissionConfig::EnableCopy); + if (m_ocrIsExists) { + if (isCopyable) { + m_ocrBtn->setToolTip(QObject::tr("Extract text")); + } else { + m_ocrBtn->setToolTip(QObject::tr("Extract text") + QObject::tr("(Disabled)")); + } + } + //左转右转的控制不在这里 if (!info.isFile() || !info.exists()) { m_adaptImageBtn->setEnabled(false); m_adaptImageBtn->setChecked(false); m_adaptScreenBtn->setEnabled(false); - m_trashBtn->setEnabled(false); + m_trashBtn->setEnabled(false); if (m_ocrIsExists) { m_ocrBtn->setEnabled(false); } } else { m_adaptImageBtn->setEnabled(true); m_adaptScreenBtn->setEnabled(true); - m_trashBtn->setEnabled(true); + if (!PermissionConfig::instance()->checkAuthFlag(PermissionConfig::EnableEdit)) { + return; + } + + m_trashBtn->setEnabled(true); if (m_ocrIsExists) { - m_ocrBtn->setEnabled(true); + m_ocrBtn->setEnabled(isCopyable); } } } @@ -791,6 +811,17 @@ void LibBottomToolbar::initUI() m_trashBtn->setIconSize(QSize(36, 36)); m_trashBtn->setToolTip(QObject::tr("Delete")); hb->addWidget(m_trashBtn); + + if (PermissionConfig::instance()->isValid() && m_imgListWidget) { + auto authIns = PermissionConfig::instance(); + + if (m_ocrBtn) { + m_ocrBtn->setEnabled(authIns->checkAuthFlag(PermissionConfig::EnableEdit)); + } + bool isDeletable = authIns->checkAuthFlag(PermissionConfig::EnableDelete); + m_trashBtn->setVisible(isDeletable); + m_trashBtn->setEnabled(isDeletable); + } } void LibBottomToolbar::initConnection() diff --git a/libimageviewer/viewpanel/scen/imagegraphicsview.cpp b/libimageviewer/viewpanel/scen/imagegraphicsview.cpp index c12cfc8c..5a2fd6ac 100644 --- a/libimageviewer/viewpanel/scen/imagegraphicsview.cpp +++ b/libimageviewer/viewpanel/scen/imagegraphicsview.cpp @@ -38,6 +38,7 @@ #include "imageengine.h" #include "service/mtpfileproxy.h" #include "service/aimodelservice.h" +#include "service/permissionconfig.h" #include #include @@ -656,10 +657,7 @@ qreal LibImageGraphicsView::imageRelativeScale() const qreal LibImageGraphicsView::windowRelativeScale() const { QRectF bf = sceneRect(); - qDebug()< 1.0 * bf.width() / (bf.height())) { // if (1.0 * width() / (height() - TITLEBAR_HEIGHT * 2) > 1.0 * bf.width() / (bf.height() - TITLEBAR_HEIGHT * 2)) { @@ -965,6 +963,12 @@ void LibImageGraphicsView::slotRotatePixCurrent() pathType != imageViewerSpace::PathTypeSAFEBOX && //保险箱 pathType != imageViewerSpace::PathTypeRECYCLEBIN) { //回收站 + // 图片信息不一定同步,需做二次判断,根据权限配置是否回写文件 + if (!PermissionConfig::instance()->checkAuthFlag(PermissionConfig::EnableEdit, m_path)) { + m_rotateAngel = 0; + return; + } + disconnect(m_imgFileWatcher, &QFileSystemWatcher::fileChanged, this, &LibImageGraphicsView::onImgFileChanged); Libutils::image::rotate(m_path, m_rotateAngel); @@ -980,6 +984,9 @@ void LibImageGraphicsView::slotRotatePixCurrent() connect(m_imgFileWatcher, &QFileSystemWatcher::fileChanged, this, &LibImageGraphicsView::onImgFileChanged); }); m_rotateAngel = 0; + + // 通知授权控制图片编辑完成 + PermissionConfig::instance()->triggerAction(PermissionConfig::TidEdit, m_path); } } } diff --git a/libimageviewer/viewpanel/viewpanel.cpp b/libimageviewer/viewpanel/viewpanel.cpp index 9dc90904..35d986b7 100644 --- a/libimageviewer/viewpanel/viewpanel.cpp +++ b/libimageviewer/viewpanel/viewpanel.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2020 - 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2020 - 2024 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -35,6 +35,7 @@ #include "widgets/renamedialog.h" #include "service/ocrinterface.h" #include "slideshow/slideshowpanel.h" +#include "service/permissionconfig.h" #include "service/configsetter.h" #include "service/imagedataservice.h" #include "service/mtpfileproxy.h" @@ -151,6 +152,13 @@ LibViewPanel::LibViewPanel(AbstractTopToolbar *customToolbar, QWidget *parent) LibViewPanel::~LibViewPanel() { + // 关闭前保存旋转状态 + if (m_view) { + m_view->slotRotatePixCurrent(); + } + // 析构时通知图片窗口关闭 + PermissionConfig::instance()->triggerAction(PermissionConfig::TidClose, m_currentPath); + // 清空图像缓存目录 Libutils::image::clearCacheImageFolder(); @@ -189,6 +197,20 @@ void LibViewPanel::loadImage(const QString &path, QStringList paths) m_dirWatcher->removePaths(m_dirWatcher->directories()); m_dirWatcher->addPath(QFileInfo(path).dir().path()); + + // 判断是否授权状态更新 + QFileInfo targetImageInfo(PermissionConfig::instance()->targetImage()); + if (info.absoluteDir() != targetImageInfo.absoluteDir()) { + if (!paths.contains(targetImageInfo.absoluteFilePath())) { + // 关闭前保存旋转状态 + if (m_view) { + m_view->slotRotatePixCurrent(); + } + // 不含授权文件,提示文件关闭 + PermissionConfig::instance()->triggerAction(PermissionConfig::TidClose, + PermissionConfig::instance()->targetImage()); + } + } } void LibViewPanel::initConnect() @@ -468,8 +490,8 @@ void LibViewPanel::updateMenuContent(const QString &path) QFileInfo info(currentPath); - bool isReadable = info.isReadable() ; //是否可读 - // 判断文件是否可写和文件目录是否可写 + bool isReadable = info.isReadable(); //是否可读 + //判断文件是否可写和文件目录是否可写 bool isWritable = info.isWritable() && QFileInfo(info.dir(), info.dir().path()).isWritable(); //是否可写 bool isRotatable = ImageEngine::instance()->isRotatable(currentPath);//是否可旋转 imageViewerSpace::PathType pathType; // 路径类型 @@ -482,6 +504,9 @@ void LibViewPanel::updateMenuContent(const QString &path) imageViewerSpace::ImageType imageType = LibUnionImage_NameSpace::getImageType(currentPath);//图片类型 + auto authIns = PermissionConfig::instance(); + bool isCopyable = authIns->checkAuthFlag(PermissionConfig::EnableCopy, currentPath); + //判断是否是损坏图片 setCurrentWidget(currentPath); @@ -513,15 +538,23 @@ void LibViewPanel::updateMenuContent(const QString &path) appendAction(IdFullScreen, QObject::tr("Fullscreen"), ss("Fullscreen", "F11")); } - appendAction(IdPrint, QObject::tr("Print"), ss("Print", "Ctrl+P")); + if (authIns->isCurrentIsTargetImage() && !authIns->isUnlimitPrint()) { + appendAction(IdPrint, QObject::tr("Print") + QObject::tr("(Remaining %1 times)").arg(authIns->printCount()), + ss("Print", "Ctrl+P"), authIns->isPrintable()); + } else { + appendAction(IdPrint, QObject::tr("Print"), ss("Print", "Ctrl+P")); + } //ocr按钮,是否是动态图,todo DIconButton *OcrButton = m_bottomToolbar->getBottomtoolbarButton(imageViewerSpace::ButtonTypeOcr); if (OcrButton != nullptr) { - if (imageViewerSpace::ImageTypeDynamic != imageType && isPic && isReadable) { + if (imageViewerSpace::ImageTypeDynamic != imageType && isPic && isReadable && isCopyable) { appendAction(IdOcr, QObject::tr("Extract text"), ss("Extract text", "Alt+O")); OcrButton->setEnabled(true); } else { + if (!isCopyable) { + appendAction(IdOcr, QObject::tr("Extract text") + QObject::tr("(Disabled)"), ss("Extract text", "Alt+O"), false); + } OcrButton->setEnabled(false); } } @@ -587,11 +620,15 @@ void LibViewPanel::updateMenuContent(const QString &path) // 添加AI模型选项,仅处理静态图 addAIMenu(); - if (isAlbum && isReadable && !enhanceImage) { + if (isAlbum && isReadable && !enhanceImage && isCopyable) { appendAction(IdExport, tr("Export"), ss("Export", "Ctrl+E")); //导出 } if (isReadable) { - appendAction(IdCopy, QObject::tr("Copy"), ss("Copy", "Ctrl+C")); + if (isCopyable) { + appendAction(IdCopy, QObject::tr("Copy"), ss("Copy", "Ctrl+C")); + } else { + appendAction(IdCopy, QObject::tr("Copy") + QObject::tr("(Disabled)"), ss("Copy", "Ctrl+C"), false); + } } //如果程序有可读可写的权限,才能重命名,todo @@ -599,7 +636,7 @@ void LibViewPanel::updateMenuContent(const QString &path) if (isReadable && isWritable && imageViewerSpace::PathTypeMTP != pathType && imageViewerSpace::PathTypePTP != pathType && - imageViewerSpace::PathTypeAPPLE != pathType && !isAlbum) { + imageViewerSpace::PathTypeAPPLE != pathType && !isAlbum && authIns->checkAuthFlag(PermissionConfig::EnableRename)) { appendAction(IdRename, QObject::tr("Rename"), ss("Rename", "F2")); } @@ -611,7 +648,7 @@ void LibViewPanel::updateMenuContent(const QString &path) imageViewerSpace::PathTypeRECYCLEBIN != pathType && imageViewerSpace::PathTypeMTP != pathType && imageViewerSpace::PathTypePTP != pathType && - isWritable && isReadable) || (isAlbum && isWritable)) { + isWritable && isReadable && authIns->checkAuthFlag(PermissionConfig::EnableDelete)) || (isAlbum && isWritable)) { if (isAlbum) { appendAction(IdMoveToTrash, QObject::tr("Delete"), ss("Throw to trash", "")); } else { @@ -660,12 +697,15 @@ void LibViewPanel::updateMenuContent(const QString &path) } - //需要判断图片是否支持设置壁纸,若不支持则置灰设置壁纸菜单项 - if (isPic) { + // 需要判断图片是否支持设置壁纸,若不支持则置灰设置壁纸菜单项 + // 当配置权限控制时,屏蔽设置壁纸接口 + bool enableWallpaper = authIns->checkAuthFlag(PermissionConfig::EnableWallpaper, currentPath); + if (isPic && enableWallpaper) { QAction *ac = appendAction(IdSetAsWallpaper, QObject::tr("Set as wallpaper"), ss("Set as wallpaper", "Ctrl+F9")); if (ac) ac->setEnabled(Libutils::image::imageSupportWallPaper(ItemInfo.path)); } + if (isReadable) { appendAction(IdDisplayInFileManager, QObject::tr("Display in file manager"), ss("Display in file manager", "Alt+D")); @@ -761,7 +801,7 @@ void LibViewPanel::showNormal() }); } -QAction *LibViewPanel::appendAction(int id, const QString &text, const QString &shortcut) +QAction *LibViewPanel::appendAction(int id, const QString &text, const QString &shortcut, bool enable) { if (m_menu && m_menuItemDisplaySwitch.test(static_cast(id))) { QAction *ac = new QAction(m_menu); @@ -769,6 +809,7 @@ QAction *LibViewPanel::appendAction(int id, const QString &text, const QString & ac->setText(text); ac->setProperty("MenuID", id); ac->setShortcut(QKeySequence(shortcut)); + ac->setEnabled(enable); m_menu->addAction(ac); return ac; @@ -887,6 +928,9 @@ void LibViewPanel::setWallpaper(const QString &imgPath) } QDBusMessage reply = interface.call(QStringLiteral("SetMonitorBackground"), screenname, path); qDebug() << "SettingWallpaper: replay" << reply.errorMessage(); + + // 通知触发设置壁纸动作(属于拷贝动作) + PermissionConfig::instance()->triggerAction(PermissionConfig::TidCopy, path); } else { qWarning() << "SettingWallpaper failed" << interface.lastError(); } @@ -910,9 +954,26 @@ bool LibViewPanel::startdragImage(const QStringList &paths, const QString &first if (image_list.isEmpty()) return false; + // 判断是否允许切换图片 (首次进入同样判断) + bool enableSwitch = PermissionConfig::instance()->checkAuthFlag(PermissionConfig::EnableSwitch, image_list.first()); + if (!enableSwitch) { + if (image_list.first() != PermissionConfig::instance()->targetImage()) { + return false; + } + } + bool permissionValid = PermissionConfig::instance()->isValid(); + if (LibCommonService::instance()->getImgViewerType() == imageViewerSpace::ImgViewerTypeLocal) { QString path = image_list.first(); - if ((path.indexOf("smb-share:server=") != -1 || path.indexOf("mtp:host=") != -1 || path.indexOf("gphoto2:host=") != -1)) { + + // 判断是否允许切换图片,不切换图片时,将仅使用首张图片 + if (permissionValid && !enableSwitch) { + image_list.clear(); + if (ImageEngine::instance()->isImage(path)) { + image_list << path; + } + + } else if ((path.indexOf("smb-share:server=") != -1 || path.indexOf("mtp:host=") != -1 || path.indexOf("gphoto2:host=") != -1)) { image_list.clear(); //判断是否图片格式 if (ImageEngine::instance()->isImage(path)) { @@ -939,11 +1000,13 @@ bool LibViewPanel::startdragImage(const QStringList &paths, const QString &first } } } + if (image_list.count() > 0) { bRet = true; } else { bRet = false; } + //解决拖入非图片文件会出现崩溃 QString loadingPath = ""; if (image_list.contains(path)) { @@ -1223,6 +1286,12 @@ void LibViewPanel::setBottomToolBarButtonAlawysNotVisible(imageViewerSpace::Butt bool LibViewPanel::startChooseFileDialog() { + // 授权控制状态下,判断是否允许切换图片 + bool enableSwitch = PermissionConfig::instance()->checkAuthFlag(PermissionConfig::EnableSwitch); + if (!enableSwitch) { + return false; + } + bool bRet = false; if (m_stack->currentWidget() != m_sliderPanel) { QString filter = tr("All images"); @@ -1454,6 +1523,9 @@ bool LibViewPanel::slotOcrPicture() QString path = m_bottomToolbar->getCurrentItemInfo().path; //图片过大,会导致崩溃,超过4K,智能裁剪 if (m_ocrInterface != nullptr && m_view != nullptr) { + // 提示授权控制拷贝图片 + PermissionConfig::instance()->triggerAction(PermissionConfig::TidCopy, path); + QImage image = m_view->image(); if (image.width() > 2500) { image = image.scaledToWidth(2500, Qt::SmoothTransformation); @@ -1746,6 +1818,8 @@ void LibViewPanel::onMenuItemClicked(QAction *action) m_bottomToolbar->setCurrentPath(filepath); openImg(0, filepath); } + + PermissionConfig::instance()->triggerAction(PermissionConfig::TidRename, oldPath); } } if (m_dirWatcher) { @@ -1769,6 +1843,7 @@ void LibViewPanel::onMenuItemClicked(QAction *action) Libutils::base::copyImageToClipboard(QStringList(m_bottomToolbar->getCurrentItemInfo().path)); } + PermissionConfig::instance()->triggerAction(PermissionConfig::TidCopy, m_bottomToolbar->getCurrentItemInfo().path); break; } case IdMoveToTrash: { @@ -2141,6 +2216,11 @@ void LibViewPanel::dragEnterEvent(QDragEnterEvent *event) return; } + // 授权控制时判断是否允许拖拽图片进入 + if (!PermissionConfig::instance()->checkAuthFlag(PermissionConfig::EnableSwitch)) { + return; + } + const QMimeData *mimeData = event->mimeData(); if (!pluginUtils::base::checkMimeData(mimeData)) { return; diff --git a/libimageviewer/viewpanel/viewpanel.h b/libimageviewer/viewpanel/viewpanel.h index da0a88ec..a8f3c9e4 100644 --- a/libimageviewer/viewpanel/viewpanel.h +++ b/libimageviewer/viewpanel/viewpanel.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2020 - 2023 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2020 - 2024 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -81,7 +81,7 @@ class LibViewPanel : public QFrame * @param text 按钮名称 * @param shortcut 按钮快捷键 */ - QAction *appendAction(int id, const QString &text, const QString &shortcut = ""); + QAction *appendAction(int id, const QString &text, const QString &shortcut = "", bool enable = true); //设置壁纸 void setWallpaper(const QImage &img); diff --git a/libimageviewer/widgets/printhelper.cpp b/libimageviewer/widgets/printhelper.cpp index 54f105fe..8438d5bf 100755 --- a/libimageviewer/widgets/printhelper.cpp +++ b/libimageviewer/widgets/printhelper.cpp @@ -1,8 +1,9 @@ -// SPDX-FileCopyrightText: 2020 - 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2020 - 2024 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later #include "printhelper.h" +#include "service/permissionconfig.h" #include #include @@ -15,13 +16,13 @@ #include #include +#include #ifdef USE_UNIONIMAGE #include "printhelper.h" #include "unionimage/unionimage.h" #endif - PrintHelper *PrintHelper::m_Printer = nullptr; PrintHelper *PrintHelper::getIntance() @@ -43,66 +44,17 @@ PrintHelper::~PrintHelper() m_re->deleteLater(); } -//暂时没有使用配置文件的快捷键,现在是根据代码中的快捷键 -/* -static QAction *hookToolBarActionIcons(QToolBar *bar, QAction **pageSetupAction = nullptr) +void PrintHelper::showPrintDialog(const QStringList &paths, QWidget *parent) { - QAction *last_action = nullptr; - - for (QAction *action : bar->actions()) { - const QString &text = action->text(); - - if (text.isEmpty()) - continue; - - // 防止被lupdate扫描出来 - const char *context = "QPrintPreviewDialog"; - const char *print = "Print"; - - const QMap map { - {QCoreApplication::translate(context, "Next page"), QStringLiteral("go-next")}, - {QCoreApplication::translate(context, "Previous page"), QStringLiteral("go-previous")}, - {QCoreApplication::translate(context, "First page"), QStringLiteral("go-first")}, - {QCoreApplication::translate(context, "Last page"), QStringLiteral("go-last")}, - {QCoreApplication::translate(context, "Fit width"), QStringLiteral("fit-width")}, - {QCoreApplication::translate(context, "Fit page"), QStringLiteral("fit-page")}, - {QCoreApplication::translate(context, "Zoom in"), QStringLiteral("zoom-in")}, - {QCoreApplication::translate(context, "Zoom out"), QStringLiteral("zoom-out")}, - {QCoreApplication::translate(context, "Portrait"), QStringLiteral("layout-portrait")}, - {QCoreApplication::translate(context, "Landscape"), QStringLiteral("layout-landscape")}, - {QCoreApplication::translate(context, "Show single page"), QStringLiteral("view-page-one")}, - {QCoreApplication::translate(context, "Show facing pages"), QStringLiteral("view-page-sided")}, - {QCoreApplication::translate(context, "Show overview of all pages"), QStringLiteral("view-page-multi")}, - {QCoreApplication::translate(context, print), QStringLiteral("print")}, - {QCoreApplication::translate(context, "Page setup"), QStringLiteral("page-setup")} - }; - - - const QString &icon_name = map.value(action->text()); - - if (icon_name.isEmpty()) - continue; - - if (pageSetupAction && icon_name == "page-setup") { - *pageSetupAction = action; - } - - QIcon icon(QStringLiteral(":/qt-project.org/dialogs/assets/images/qprintpreviewdialog/images/%1-24.svg").arg(icon_name)); -// action->setIcon(icon); - last_action = action; + if (!PermissionConfig::instance()->isPrintable()) { + return; } - return last_action; -} -*/ -void PrintHelper::showPrintDialog(const QStringList &paths, QWidget *parent) -{ m_re->m_paths.clear(); m_re->m_imgs.clear(); m_re->m_paths = paths; - QStringList tempExsitPaths;//保存存在的图片路径 - QImage imgTemp; + QStringList tempExsitPaths; // 保存存在的图片路径 for (const QString &path : paths) { QString errMsg; QImageReader imgReadreder(path); @@ -112,7 +64,7 @@ void PrintHelper::showPrintDialog(const QStringList &paths, QWidget *parent) m_re->m_imgs << imgReadreder.read(); } } else { - //QImage不应该多次赋值,所以换到这里来,修复style问题 + // QImage不应该多次赋值,所以换到这里来,修复style问题 QImage img; LibUnionImage_NameSpace::loadStaticImageFromFile(path, img, errMsg); if (!img.isNull()) { @@ -120,29 +72,48 @@ void PrintHelper::showPrintDialog(const QStringList &paths, QWidget *parent) } } tempExsitPaths << paths; - } - //看图采用同步,因为只有一张图片,传入父指针 + + // 看图采用同步,因为只有一张图片,传入父指针 DPrintPreviewDialog printDialog2(parent); -#if (DTK_VERSION_MAJOR > 5 \ - || (DTK_VERSION_MAJOR >=5 && DTK_VERSION_MINOR > 4) \ - || (DTK_VERSION_MAJOR >= 5 && DTK_VERSION_MINOR >= 4 && DTK_VERSION_PATCH >= 10))//5.4.4暂时没有合入 - //增加运行时版本判断 +#if (DTK_VERSION_MAJOR > 5 || (DTK_VERSION_MAJOR >= 5 && DTK_VERSION_MINOR > 4) || \ + (DTK_VERSION_MAJOR >= 5 && DTK_VERSION_MINOR >= 4 && DTK_VERSION_PATCH >= 10)) // 5.4.4暂时没有合入 + // 增加运行时版本判断 if (DApplication::runtimeDtkVersion() >= DTK_VERSION_CHECK(5, 4, 10, 0)) { if (!tempExsitPaths.isEmpty()) { - //直接传递为路径,不会有问题 + // 直接传递为路径,不会有问题 printDialog2.setDocName(QFileInfo(tempExsitPaths.at(0)).absoluteFilePath()); } } #endif - connect(&printDialog2, SIGNAL(paintRequested(DPrinter *)), - m_re, SLOT(paintRequestSync(DPrinter *))); + +#ifdef DTKWIDGET_CLASS_DWaterMarkHelper + // 检查是否过滤 DTK 部分设置 + if (PermissionConfig::instance()->installFilterPrintDialog(&printDialog2)) { + qInfo() << qPrintable("Enable breakPrintSpacingLimit, filter print spacing config."); + } + + // 定制分支,水印功能不依赖DTK版本,更新打印水印设置 + if (PermissionConfig::instance()->hasPrintWaterMark()) { + PermissionConfig::instance()->setDialogPrintWatermark(&printDialog2); + } +#endif + + connect(&printDialog2, SIGNAL(paintRequested(DPrinter *)), m_re, SLOT(paintRequestSync(DPrinter *))); #ifndef USE_TEST - printDialog2.exec(); + int ret = printDialog2.exec(); #else printDialog2.show(); + int ret = QDialog::Accepted; #endif + + if (QDialog::Accepted == ret) { + if (!tempExsitPaths.isEmpty()) { + PermissionConfig::instance()->triggerPrint(tempExsitPaths.first()); + } + } + m_re->m_paths.clear(); m_re->m_imgs.clear(); } @@ -152,42 +123,35 @@ RequestedSlot::RequestedSlot(QObject *parent) Q_UNUSED(parent) } -RequestedSlot::~RequestedSlot() -{ - -} +RequestedSlot::~RequestedSlot() {} void RequestedSlot::paintRequestSync(DPrinter *_printer) { - //由于之前再度修改了打印的逻辑,导致了相同图片不在被显示,多余多页tiff来说不合理 + // 由于之前再度修改了打印的逻辑,导致了相同图片不在被显示,多余多页tiff来说不合理 QPainter painter(_printer); int indexNum = 0; for (QImage img : m_imgs) { if (!img.isNull()) { painter.setRenderHint(QPainter::Antialiasing); painter.setRenderHint(QPainter::SmoothPixmapTransform); - QRect wRect = _printer->pageRect(); - //修复bug98129,打印不完全问题,ratio应该是适应宽或者高,不应该直接适应宽 + QRect wRect = _printer->pageRect(); + // 修复bug98129,打印不完全问题,ratio应该是适应宽或者高,不应该直接适应宽 qreal ratio = 0.0; qDebug() << wRect; ratio = wRect.width() * 1.0 / img.width(); if (qreal(wRect.height() - img.height() * ratio) > 0) { - painter.drawImage(QRectF(0, abs(qreal(wRect.height() - img.height() * ratio)) / 2, - wRect.width(), img.height() * ratio), img); + painter.drawImage( + QRectF(0, abs(qreal(wRect.height() - img.height() * ratio)) / 2, wRect.width(), img.height() * ratio), img); } else { ratio = wRect.height() * 1.0 / img.height(); - painter.drawImage(QRectF(qreal(wRect.width() - img.width() * ratio) / 2, 0, - img.width() * ratio, wRect.height()), img); + painter.drawImage(QRectF(qreal(wRect.width() - img.width() * ratio) / 2, 0, img.width() * ratio, wRect.height()), + img); } - - } indexNum++; if (indexNum != m_imgs.size()) { _printer->newPage(); - } } painter.end(); } - diff --git a/libimageviewer/widgets/printhelper.h b/libimageviewer/widgets/printhelper.h index e27a238c..caa56ec8 100755 --- a/libimageviewer/widgets/printhelper.h +++ b/libimageviewer/widgets/printhelper.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2020 - 2022 UnionTech Software Technology Co., Ltd. +// SPDX-FileCopyrightText: 2020 - 2024 UnionTech Software Technology Co., Ltd. // // SPDX-License-Identifier: GPL-3.0-or-later @@ -10,14 +10,17 @@ #include #include DWIDGET_USE_NAMESPACE + //重构printhelper,因为dtk更新 //绘制图片处理类 class RequestedSlot : public QObject { Q_OBJECT + public: explicit RequestedSlot(QObject *parent = nullptr); ~RequestedSlot(); + private slots: void paintRequestSync(DPrinter *_printer); @@ -25,6 +28,7 @@ private slots: QStringList m_paths; QList m_imgs; }; + class PrintHelper : public QObject { Q_OBJECT @@ -33,14 +37,13 @@ class PrintHelper : public QObject static PrintHelper *getIntance(); ~PrintHelper(); - // static QSize adjustSize(PrintOptionsPage* optionsPage, QImage img, int resolution, const QSize & viewportSize); - // static QPoint adjustPosition(PrintOptionsPage* optionsPage, const QSize& imageSize, const QSize & viewportSize); void showPrintDialog(const QStringList &paths, QWidget *parent = nullptr); RequestedSlot *m_re = nullptr; + private: explicit PrintHelper(QObject *parent = nullptr); static PrintHelper *m_Printer; }; -#endif // PRINTHELPER_H +#endif // PRINTHELPER_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9310f57d..b5ec06f9 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -13,6 +13,8 @@ add_definitions( -DUSE_UNIONIMAGE ) option (USE_TEST "Use TESTS" ON) add_definitions( -DUSE_TEST ) +ADD_COMPILE_OPTIONS(-fno-access-control) + set(TARGET_NAME image-editor-test) set(CMD_NAME image-editor-test) set(CMAKE_AUTOMOC ON) diff --git a/tests/test_permissionconfig.cpp b/tests/test_permissionconfig.cpp new file mode 100644 index 00000000..45e30a14 --- /dev/null +++ b/tests/test_permissionconfig.cpp @@ -0,0 +1,493 @@ +// SPDX-FileCopyrightText: 2023 - 2024 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include + +#include + +#include + +#include "service/permissionconfig.h" +#include "imageengine.h" + +#ifdef DTKWIDGET_CLASS_DWaterMarkHelper + +class UT_PermissionConfig : public ::testing::Test +{ +public: + void SetUp() override; + void TearDown() override; +}; + +void UT_PermissionConfig::SetUp() +{ + PermissionConfig::instance()->valid = true; + PermissionConfig::instance()->useWaterMarkPlugin = false; + PermissionConfig::instance()->targetImagePath.clear(); + PermissionConfig::instance()->currentImagePath.clear(); +} + +void UT_PermissionConfig::TearDown() +{ + PermissionConfig::instance()->valid = false; + PermissionConfig::instance()->useWaterMarkPlugin = false; +} + +static QString permission_config() +{ + QByteArray permission("{ \"permission\": { \"edit\": true, \"copy\": true, \"setWallpaper\": true, " + "\"pictureSwitch\": true, \"printCount\": 2, " + "\"ignoreDevicePixelRatio\": true, \"breakPrintSpacingLimit\": true } }"); + return permission.toBase64(); +} + +TEST_F(UT_PermissionConfig, parseConfigOption_NormalParam_Pass) +{ + QString configParam; + QStringList imageList; + QString tempPath = QApplication::applicationDirPath() + "/svg.svg"; + QStringList tmpList{tempPath}; + QStringList arguments{qApp->arguments().first(), "--config=test", tempPath}; + + EXPECT_TRUE(PermissionConfig::instance()->parseConfigOption(arguments, configParam, imageList)); + EXPECT_EQ(configParam, QString("test")); + EXPECT_EQ(imageList, tmpList); + + QStringList arguments2{qApp->arguments().first(), "--config", "test", tempPath}; + EXPECT_TRUE(PermissionConfig::instance()->parseConfigOption(arguments2, configParam, imageList)); + EXPECT_EQ(configParam, QString("test")); +} + +TEST_F(UT_PermissionConfig, parseConfigOption_NormalParam_Fail) +{ + QString configParam; + QStringList imageList; + QString tempPath = QApplication::applicationDirPath() + "/svg.svg"; + QStringList arguments{qApp->arguments().first(), "config", "test", tempPath}; + + EXPECT_FALSE(PermissionConfig::instance()->parseConfigOption({}, configParam, imageList)); + EXPECT_TRUE(configParam.isEmpty()); + EXPECT_TRUE(imageList.isEmpty()); + + QStringList tmpList{"config", "test", tempPath}; + EXPECT_FALSE(PermissionConfig::instance()->parseConfigOption(arguments, configParam, imageList)); + EXPECT_TRUE(configParam.isEmpty()); + EXPECT_EQ(imageList, tmpList); +} + +TEST_F(UT_PermissionConfig, initFromArguments_NormalParam_Pass) +{ + PermissionConfig::instance()->valid = false; + PermissionConfig::instance()->authFlags = PermissionConfig::NoAuth; + + QString tempPath = QApplication::applicationDirPath() + "/svg.svg"; + QStringList args; + args << qApp->arguments().first() << QString("--config=%1").arg(permission_config()) << tempPath; + + PermissionConfig::instance()->initFromArguments(args); + EXPECT_TRUE(PermissionConfig::instance()->isValid()); + + PermissionConfig::Authorises auth = + PermissionConfig::Authorises(PermissionConfig::EnableCopy | PermissionConfig::EnableEdit | + PermissionConfig::EnableSwitch | PermissionConfig::EnableWallpaper); + EXPECT_EQ(PermissionConfig::instance()->authFlags, auth); + EXPECT_EQ(PermissionConfig::instance()->targetImagePath, tempPath); + + EXPECT_EQ(PermissionConfig::instance()->ignoreDevicePixelRatio, true); + EXPECT_EQ(PermissionConfig::instance()->breakPrintSpacingLimit, true); +} + +TEST_F(UT_PermissionConfig, initFromArguments_NormalParam_Fail) +{ + PermissionConfig::instance()->valid = false; + PermissionConfig::instance()->authFlags = PermissionConfig::NoAuth; + + PermissionConfig::instance()->initFromArguments({}); + EXPECT_FALSE(PermissionConfig::instance()->isValid()); + EXPECT_EQ(PermissionConfig::instance()->authFlags, PermissionConfig::NoAuth); + EXPECT_TRUE(PermissionConfig::instance()->targetImagePath.isEmpty()); +} + +TEST_F(UT_PermissionConfig, checkAuthFlag_NotInit_Pass) +{ + PermissionConfig::instance()->authFlags = PermissionConfig::Authorises(0b11000000111111); + EXPECT_TRUE(PermissionConfig::instance()->checkAuthFlag(PermissionConfig::EnableEdit)); + EXPECT_TRUE(PermissionConfig::instance()->checkAuthFlag(PermissionConfig::EnableCopy)); + EXPECT_TRUE(PermissionConfig::instance()->checkAuthFlag(PermissionConfig::EnableDelete)); + EXPECT_TRUE(PermissionConfig::instance()->checkAuthFlag(PermissionConfig::EnableRename)); + EXPECT_TRUE(PermissionConfig::instance()->checkAuthFlag(PermissionConfig::EnableSwitch)); + EXPECT_TRUE(PermissionConfig::instance()->checkAuthFlag(PermissionConfig::EnableWallpaper)); + EXPECT_TRUE(PermissionConfig::instance()->checkAuthFlag(PermissionConfig::EnableReadWaterMark)); + EXPECT_TRUE(PermissionConfig::instance()->checkAuthFlag(PermissionConfig::EnablePrintWaterMark)); + EXPECT_TRUE(PermissionConfig::instance()->hasReadWaterMark()); + EXPECT_TRUE(PermissionConfig::instance()->hasPrintWaterMark()); + + PermissionConfig::instance()->currentImagePath = "1.png"; + PermissionConfig::instance()->targetImagePath = "1.png"; + EXPECT_FALSE(PermissionConfig::instance()->checkAuthInvalid()); + EXPECT_TRUE(PermissionConfig::instance()->checkAuthInvalid("2.png")); +} + +TEST_F(UT_PermissionConfig, checkAuthFlag_NotInit_Fail) +{ + EXPECT_TRUE(PermissionConfig::instance()->valid); + PermissionConfig::instance()->authFlags = PermissionConfig::NoAuth; + EXPECT_FALSE(PermissionConfig::instance()->checkAuthFlag(PermissionConfig::EnableEdit)); + EXPECT_FALSE(PermissionConfig::instance()->checkAuthFlag(PermissionConfig::EnableCopy)); + EXPECT_FALSE(PermissionConfig::instance()->checkAuthFlag(PermissionConfig::EnableDelete)); + EXPECT_FALSE(PermissionConfig::instance()->checkAuthFlag(PermissionConfig::EnableRename)); + EXPECT_FALSE(PermissionConfig::instance()->checkAuthFlag(PermissionConfig::EnableSwitch)); + EXPECT_FALSE(PermissionConfig::instance()->checkAuthFlag(PermissionConfig::EnableWallpaper)); + EXPECT_FALSE(PermissionConfig::instance()->checkAuthFlag(PermissionConfig::EnableReadWaterMark)); + EXPECT_FALSE(PermissionConfig::instance()->checkAuthFlag(PermissionConfig::EnablePrintWaterMark)); + EXPECT_FALSE(PermissionConfig::instance()->hasReadWaterMark()); + EXPECT_FALSE(PermissionConfig::instance()->hasPrintWaterMark()); +} + +TEST_F(UT_PermissionConfig, print_Count_Pass) +{ + QString tmpPath("/tmp/tmp.png"); + PermissionConfig::instance()->targetImagePath = tmpPath; + + PermissionConfig::instance()->printLimitCount = 1; + EXPECT_EQ(PermissionConfig::instance()->printLimitCount, 1); + EXPECT_TRUE(PermissionConfig::instance()->isPrintable(tmpPath)); + + PermissionConfig::instance()->triggerPrint(tmpPath); + EXPECT_EQ(PermissionConfig::instance()->printLimitCount, 0); + EXPECT_FALSE(PermissionConfig::instance()->isPrintable(tmpPath)); + + PermissionConfig::instance()->printLimitCount = -1; + EXPECT_TRUE(PermissionConfig::instance()->isPrintable(tmpPath)); + PermissionConfig::instance()->triggerPrint(tmpPath); + EXPECT_EQ(PermissionConfig::instance()->printLimitCount, -1); + EXPECT_TRUE(PermissionConfig::instance()->isUnlimitPrint()); +} + +// DTKWidget 主线和定制线的水印接口不同,通过版本进行区分 +// 主线水印接口在 5.6.9 之后引入. +// 因此,判断定制线:存在水印接口,版本不低于 5.4.42.7 且低于 5.5 +#if DTK_VERSION_CHECK(5, 4, 42, 7) <= DTK_VERSION && DTK_VERSION < DTK_VERSION_CHECK(5, 5, 0, 0) + +TEST_F(UT_PermissionConfig, watarMark_JsonData_Pass_5_4_42) +{ + // Default value + auto markData = PermissionConfig::instance()->readWaterMarkData(); + EXPECT_EQ(markData.type, WaterMarkType::None); + EXPECT_EQ(markData.opacity, 1); + EXPECT_EQ(markData.layout, WaterMarkLayout::Center); + EXPECT_TRUE(markData.text.isEmpty()); + + QJsonObject param{{"text", "test"}, {"opacity", 0}, {"layout", 1}}; + + PermissionConfig::instance()->initReadWaterMark(param); + markData = PermissionConfig::instance()->readWaterMarkData(); + EXPECT_EQ(markData.type, WaterMarkType::Text); + EXPECT_EQ(markData.opacity, 0); + EXPECT_EQ(markData.layout, WaterMarkLayout::Tiled); + EXPECT_EQ(markData.text, QString("test")); + + PermissionConfig::instance()->initPrintWaterMark(param); + markData = PermissionConfig::instance()->printWaterMarkData(); + EXPECT_EQ(markData.type, WaterMarkType::Text); +} + +#else + +TEST_F(UT_PermissionConfig, watarMark_JsonData_Pass) +{ + // Default value + auto markData = PermissionConfig::instance()->readWaterMarkData(); + EXPECT_EQ(markData.type(), WaterMarkData::WaterMarkType::None); + EXPECT_EQ(markData.opacity(), 1); + EXPECT_EQ(markData.layout(), WaterMarkData::WaterMarkLayout::Center); + EXPECT_TRUE(markData.text().isEmpty()); + + QJsonObject param{{"text", "test"}, {"opacity", 0}, {"layout", 1}}; + + PermissionConfig::instance()->initReadWaterMark(param); + markData = PermissionConfig::instance()->readWaterMarkData(); + EXPECT_EQ(markData.type(), WaterMarkData::WaterMarkType::Text); + EXPECT_EQ(markData.opacity(), 0); + EXPECT_EQ(markData.layout(), WaterMarkData::WaterMarkLayout::Tiled); + EXPECT_EQ(markData.text(), QString("test")); + + PermissionConfig::instance()->initPrintWaterMark(param); + markData = PermissionConfig::instance()->printWaterMarkData(); + EXPECT_EQ(markData.type(), WaterMarkData::WaterMarkType::Text); +} +#endif // VERSION CHECK + +TEST_F(UT_PermissionConfig, triggerAction_SignalNotify_Pass) +{ + QJsonObject notifyData; + QObject::connect(PermissionConfig::instance(), &PermissionConfig::authoriseNotify, [&](const QJsonObject ¬ify) { + notifyData = notify; + }); + + auto TriggerFunction = [&](PermissionConfig::TidType tid, const QString &operate) { + // PermissionConfig::targetImagePath is empty. + PermissionConfig::instance()->triggerAction(tid, ""); + // ReportMode::ReportAndBroadcast = 0b11 + auto policyData = notifyData.value("policy").toObject(); + EXPECT_EQ(policyData.value("reportMode").toInt(), 3); + + QJsonObject data = notifyData.value("info").toObject(); + EXPECT_EQ(data.value("tid").toInt(), tid); + EXPECT_EQ(data.value("operate").toString(), operate); + }; + + PermissionConfig::instance()->status = PermissionConfig::NotOpen; + + TriggerFunction(PermissionConfig::TidOpen, "open"); + TriggerFunction(PermissionConfig::TidEdit, "edit"); + TriggerFunction(PermissionConfig::TidCopy, "copy"); + // Close will set PermissionConfig::valid to false. + TriggerFunction(PermissionConfig::TidClose, "close"); + EXPECT_FALSE(PermissionConfig::instance()->isValid()); + EXPECT_EQ(PermissionConfig::instance()->status, PermissionConfig::Close); +} + +TEST_F(UT_PermissionConfig, hasPrintWaterMark_usingWaterMark_Pass) +{ + PermissionConfig::instance()->authFlags = PermissionConfig::EnablePrintWaterMark; + EXPECT_TRUE(PermissionConfig::instance()->hasPrintWaterMark()); + + PermissionConfig::instance()->useWaterMarkPlugin = true; + EXPECT_FALSE(PermissionConfig::instance()->hasPrintWaterMark()); +} + +TEST_F(UT_PermissionConfig, detectWaterMarkPluginExists_Pass) +{ + PermissionConfig::instance()->detectWaterMarkPluginExists(); + + QStringList plugins = DPrintPreviewDialog::availablePlugins(); + if (plugins.contains("WaterMarkFilter")) { + EXPECT_TRUE(PermissionConfig::instance()->useWaterMarkPlugin); + } else { + EXPECT_FALSE(PermissionConfig::instance()->useWaterMarkPlugin); + } +} + +TEST_F(UT_PermissionConfig, initWaterMarkPluginEnvironment_CheckPlugin_Pass) +{ + DWIDGET_USE_NAMESPACE + QStringList plugins = DPrintPreviewDialog::availablePlugins(); + if (plugins.contains("WaterMarkFilter")) { + EXPECT_TRUE(PermissionConfig::instance()->initWaterMarkPluginEnvironment()); + } else { + EXPECT_FALSE(PermissionConfig::instance()->initWaterMarkPluginEnvironment()); + } + + QByteArray envData = qgetenv("DEEPIN_WATERMARK"); + EXPECT_FALSE(envData.isEmpty()); +} + +TEST_F(UT_PermissionConfig, initWaterMarkPluginEnvironment_CheckValue) +{ + static const qreal sc_defaultFontSize = 65.0; + // 验证打印水印设置值 +#if DTK_VERSION_CHECK(5, 4, 42, 7) <= DTK_VERSION && DTK_VERSION < DTK_VERSION_CHECK(5, 5, 0, 0) + WaterMarkData &watermark = PermissionConfig::instance()->printWaterMark; + watermark.layout = WaterMarkLayout::Center; +#else + PermissionConfig::AdapterWaterMarkData &watermark = PermissionConfig::instance()->printAdapterWaterMark; + watermark.layout = PermissionConfig::AdapterWaterMarkData::Center; +#endif // VERSION CHECK + watermark.rotation = 45; + watermark.opacity = 0.3; + watermark.font.setPointSize(30); + watermark.text = "123"; + watermark.lineSpacing = 0; + watermark.spacing = 0; + watermark.color = "#FF00FF"; + + // 强制设置环境变量 + PermissionConfig::instance()->initWaterMarkPluginEnvironment(); + + QByteArray envData = qgetenv("DEEPIN_WATERMARK"); + QJsonDocument doc = QJsonDocument::fromJson(envData); + QJsonObject root = doc.object(); + + EXPECT_EQ(watermark.rotation, root.value("angle").toDouble()); + EXPECT_EQ(watermark.opacity, root.value("transparency").toDouble() / 100); + EXPECT_EQ(watermark.text, root.value("customText").toString()); + EXPECT_EQ(watermark.color, root.value("textColor").toString()); + EXPECT_EQ(DPrintPreviewWatermarkInfo::TextWatermark, root.value("watermarkType").toInt()); + EXPECT_EQ(DPrintPreviewWatermarkInfo::Custom, root.value("textType").toInt()); + EXPECT_EQ(DPrintPreviewWatermarkInfo::Center, root.value("layout").toInt()); + + qreal realFontSize = (root.value("size").toDouble() * sc_defaultFontSize) / 100.0; + EXPECT_EQ(watermark.font.pointSize(), qFloor(realFontSize + 0.5)); + EXPECT_TRUE(qFuzzyCompare(0, root.value("rowSpacing").toDouble())); + EXPECT_TRUE(qFuzzyCompare(0, root.value("columnSpacing").toDouble())); + + // 计算间距 + watermark.lineSpacing = 100; + watermark.spacing = 60; + PermissionConfig::instance()->initWaterMarkPluginEnvironment(); + envData = qgetenv("DEEPIN_WATERMARK"); + doc = QJsonDocument::fromJson(envData); + root = doc.object(); + + QFontMetrics fm(watermark.font); + QSize textSize = fm.size(Qt::TextSingleLine, watermark.text); + qreal realRowSpacing = (qreal(watermark.lineSpacing + textSize.height()) / textSize.height()) - 1.0; + qreal realColumnSpacing = (qreal(watermark.spacing + textSize.width()) / textSize.width()) - 1.0; + EXPECT_TRUE(qFuzzyCompare(realRowSpacing, root.value("rowSpacing").toDouble())); + EXPECT_TRUE(qFuzzyCompare(realColumnSpacing, root.value("columnSpacing").toDouble())); +} + +TEST_F(UT_PermissionConfig, installFilterPrintDialog_Filter_Disable) +{ + auto ins = PermissionConfig::instance(); + DPrintPreviewDialog dialog; + + ins->breakPrintSpacingLimit = false; + ins->printRowSpacing = 10.0; + ins->printColumnSpacing = 2.0; + EXPECT_FALSE(ins->installFilterPrintDialog(&dialog)); + + ins->breakPrintSpacingLimit = true; + EXPECT_FALSE(ins->installFilterPrintDialog(&dialog)); +} + +TEST_F(UT_PermissionConfig, installFilterPrintDialog_Filter_Enable) +{ + auto ins = PermissionConfig::instance(); + DPrintPreviewDialog dialog; + + ins->breakPrintSpacingLimit = true; + ins->printRowSpacing = 11.0; + ins->printColumnSpacing = 2.0; + EXPECT_TRUE(ins->installFilterPrintDialog(&dialog)); + + ins->printRowSpacing = 0; + ins->printColumnSpacing = 2.1; + EXPECT_TRUE(ins->installFilterPrintDialog(&dialog)); +} + +TEST_F(UT_PermissionConfig, installFilterPrintDialog_FilterData_Pass) +{ + auto ins = PermissionConfig::instance(); + DPrintPreviewDialog dialog; + + ins->breakPrintSpacingLimit = true; + ins->printRowSpacing = 11.0; + ins->printColumnSpacing = 2.1; + EXPECT_TRUE(ins->installFilterPrintDialog(&dialog)); + + DPrintPreviewWidget *widget = dialog.findChild(); + ASSERT_NE(nullptr, widget); + + widget->setProperty("_d_print_waterMarkRowSpacing", 2.0); + EXPECT_NE(widget->property("_d_print_waterMarkRowSpacing").toReal(), 2.0); + EXPECT_TRUE(qFuzzyCompare(widget->property("_d_print_waterMarkRowSpacing").toReal(), 11.0)); + widget->setProperty("_d_print_waterMarkColumnSpacing", 1.0); + EXPECT_NE(widget->property("_d_print_waterMarkColumnSpacing").toReal(), 2.0); + EXPECT_TRUE(qFuzzyCompare(widget->property("_d_print_waterMarkColumnSpacing").toReal(), 2.1)); + + ins->printRowSpacing = 15.0; + ins->printColumnSpacing = 12.0; + widget->setProperty("_d_print_waterMarkRowSpacing", 2.0); + EXPECT_TRUE(qFuzzyCompare(widget->property("_d_print_waterMarkRowSpacing").toReal(), 15.0)); + widget->setProperty("_d_print_waterMarkColumnSpacing", 1.0); + EXPECT_TRUE(qFuzzyCompare(widget->property("_d_print_waterMarkColumnSpacing").toReal(), 12.0)); +} + +TEST_F(UT_PermissionConfig, installFilterPrintDialog_FilterData_Ignore) +{ + auto ins = PermissionConfig::instance(); + DPrintPreviewDialog dialog; + + ins->breakPrintSpacingLimit = true; + ins->printRowSpacing = 11.0; + ins->printColumnSpacing = 2.1; + EXPECT_TRUE(ins->installFilterPrintDialog(&dialog)); + + DPrintPreviewWidget *widget = dialog.findChild(); + ASSERT_NE(nullptr, widget); + + ins->printRowSpacing = 5.0; + ins->printColumnSpacing = 1.0; + widget->setProperty("_d_print_waterMarkRowSpacing", 2.0); + EXPECT_TRUE(qFuzzyCompare(widget->property("_d_print_waterMarkRowSpacing").toReal(), 2.0)); + widget->setProperty("_d_print_waterMarkColumnSpacing", 1.0); + EXPECT_TRUE(qFuzzyCompare(widget->property("_d_print_waterMarkColumnSpacing").toReal(), 1.0)); +} + +#if DTK_VERSION_CHECK(5, 4, 42, 7) <= DTK_VERSION && DTK_VERSION < DTK_VERSION_CHECK(5, 5, 0, 0) + +TEST_F(UT_PermissionConfig, convertAdapterWaterMarkData_Equal_Pass) +{ + WaterMarkData data; + data.type = WaterMarkData::Text; + data.layout = WaterMarkData::Tiled; + data.text = "123Test"; + QFont f; + f.setPointSize(15); + data.font = f; + QColor color(Qt::red); + data.color = color; + data.lineSpacing = 10; + data.rotation = 10; + + PermissionConfig::AdapterWaterMarkData adptData; + adptData.type = PermissionConfig::AdapterWaterMarkData::Text; + adptData.layout = PermissionConfig::AdapterWaterMarkData::Tiled; + adptData.text = "123Test"; + adptData.font = f; + adptData.color = color; + adptData.lineSpacing = 10; + adptData.rotation = 10; + + auto cvtData = PermissionConfig::instance()->convertAdapterWaterMarkData(adptData); + EXPECT_EQ(data.type, cvtData.type); + EXPECT_EQ(data.layout, cvtData.layout); + EXPECT_EQ(data.text, cvtData.text); + EXPECT_EQ(data.font, cvtData.font); + EXPECT_EQ(data.color, cvtData.color); + EXPECT_EQ(data.lineSpacing, cvtData.lineSpacing); + EXPECT_EQ(data.rotation, cvtData.rotation); +} + +#else + +TEST_F(UT_PermissionConfig, convertAdapterWaterMarkData_Equal_Pass) +{ + WaterMarkData data; + data.setType(WaterMarkData::Text); + data.setLayout(WaterMarkData::Tiled); + data.setText("123Test"); + QFont f; + f.setPointSize(15); + data.setFont(f); + QColor color(Qt::red); + data.setColor(color); + data.setLineSpacing(10); + data.setRotation(10); + + PermissionConfig::AdapterWaterMarkData adptData; + adptData.type = PermissionConfig::AdapterWaterMarkData::Text; + adptData.layout = PermissionConfig::AdapterWaterMarkData::Tiled; + adptData.text = "123Test"; + adptData.font = f; + adptData.color = color; + adptData.lineSpacing = 10; + adptData.rotation = 10; + + auto cvtData = PermissionConfig::instance()->convertAdapterWaterMarkData(adptData); + EXPECT_EQ(data.type(), cvtData.type()); + EXPECT_EQ(data.layout(), cvtData.layout()); + EXPECT_EQ(data.text(), cvtData.text()); + EXPECT_EQ(data.font(), cvtData.font()); + EXPECT_EQ(data.color(), cvtData.color()); + EXPECT_EQ(data.lineSpacing(), cvtData.lineSpacing()); + EXPECT_EQ(data.rotation(), cvtData.rotation()); +} + +#endif // VERSION CHECK + +#endif