Skip to content

Commit c3df657

Browse files
committed
feat: implement clipboard image paste functionality
Added support for pasting images from clipboard as files through the kTouchFile event system. The implementation centralizes image handling logic in FileOperationsEventReceiver with new doTouchFileFromClipboard method, replacing scattered implementations across different plugins. All clipboard image operations now flow through the standard file operations pipeline for consistency. Key changes: 1. Added clipboard image detection in doTouchFilePremature 2. Created new doTouchFileFromClipboard method for centralized image handling 3. Modified FileOperatorProxy and FileOperatorHelper to use new kTouchFile mechanism 4. Added proper callbacks for post-operation file selection 5. Included undo/redo support for image operations 6. Added proper error handling and logging Log: Added support for pasting clipboard images as files Influence: 1. Test clipboard image paste in various directories 2. Verify filename generation with different formats 3. Test undo/redo functionality for image operations 4. Verify error handling when clipboard has no image data 5. Check performance with large clipboard images 6. Test paste operations with different image formats 7. Verify file selection after paste operation feat: 实现剪贴板图片粘贴功能 新增通过kTouchFile事件系统将剪贴板中的图像作为文件粘贴的支持。 该实现将图像处理逻辑集中在FileOperationsEventReceiver新增的 doTouchFileFromClipboard方法中,取代了原先分散在不同插件中的实现。现在所 有剪贴板图像操作都通过标准的文件操作流程执行以提高一致性。 主要变更: 1. 在doTouchFilePremature中添加剪贴板图像检测 2. 创建新的doTouchFileFromClipboard方法进行集中式图像处理 3. 修改FileOperatorProxy和FileOperatorHelper使用新的kTouchFile机制 4. 添加适当的回调函数实现操作后的文件选择 5. 包含图像操作的撤销/重做支持 6. 添加完善的错误处理和日志记录 Log: 新增剪贴板图片粘贴文件的支持 Influence: 1. 测试在不同目录中粘贴剪贴板图片 2. 验证不同格式的文件名生成 3. 测试图像操作的撤销/重做功能 4. 验证剪贴板无图像数据时的错误处理 5. 检查处理大型剪贴板图像的性能表现 6. 测试不同图像格式的粘贴操作 7. 验证粘贴操作后的文件选择功能
1 parent e1e009e commit c3df657

File tree

6 files changed

+237
-6
lines changed

6 files changed

+237
-6
lines changed

src/plugins/common/dfmplugin-fileoperations/fileoperationsevent/fileoperationseventreceiver.cpp

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,11 @@
3030

3131
#include <QFileDialog>
3232
#include <QDir>
33+
#include <QApplication>
34+
#include <QClipboard>
35+
#include <QMimeData>
36+
#include <QImage>
37+
#include <QDateTime>
3338

3439
Q_DECLARE_METATYPE(QList<QUrl> *)
3540
Q_DECLARE_METATYPE(bool *)
@@ -667,6 +672,15 @@ bool FileOperationsEventReceiver::doMkdir(const quint64 windowId, const QUrl &ur
667672
QString FileOperationsEventReceiver::doTouchFilePremature(const quint64 windowId, const QUrl &url, const CreateFileType fileType, const QString &suffix,
668673
const QVariant &custom, AbstractJobHandler::OperatorCallback callbackImmediately)
669674
{
675+
// Check if this is a clipboard image creation request
676+
if (custom.isValid() && custom.canConvert<QVariantMap>()) {
677+
QVariantMap customData = custom.toMap();
678+
if (customData.contains("clipboardImage") && customData.value("clipboardImage").toBool()) {
679+
fmInfo() << "Detected clipboard image creation request";
680+
return doTouchFileFromClipboard(windowId, url, suffix, custom, callbackImmediately);
681+
}
682+
}
683+
670684
const QString newPath = newDocmentName(url, suffix, fileType);
671685
if (newPath.isEmpty())
672686
return newPath;
@@ -736,6 +750,106 @@ QString FileOperationsEventReceiver::doTouchFilePremature(const quint64 windowId
736750
}
737751
}
738752

753+
QString FileOperationsEventReceiver::doTouchFileFromClipboard(
754+
const quint64 windowId,
755+
const QUrl &url,
756+
const QString &suffix,
757+
const QVariant &custom,
758+
AbstractJobHandler::OperatorCallback callbackImmediately)
759+
{
760+
fmInfo() << "Creating file from clipboard image at:" << url;
761+
762+
// 1. Get clipboard image
763+
const QMimeData *mimeData = QApplication::clipboard()->mimeData();
764+
if (!mimeData || !mimeData->hasImage()) {
765+
fmWarning() << "Clipboard does not contain image data";
766+
if (callbackImmediately) {
767+
AbstractJobHandler::CallbackArgus args(new QMap<AbstractJobHandler::CallbackKey, QVariant>);
768+
args->insert(AbstractJobHandler::CallbackKey::kWindowId, QVariant::fromValue(windowId));
769+
args->insert(AbstractJobHandler::CallbackKey::kSuccessed, false);
770+
args->insert(AbstractJobHandler::CallbackKey::kCustom, custom);
771+
callbackImmediately(args);
772+
}
773+
return QString();
774+
}
775+
776+
QImage image = qvariant_cast<QImage>(mimeData->imageData());
777+
if (image.isNull()) {
778+
fmWarning() << "Failed to get valid image from clipboard";
779+
if (callbackImmediately) {
780+
AbstractJobHandler::CallbackArgus args(new QMap<AbstractJobHandler::CallbackKey, QVariant>);
781+
args->insert(AbstractJobHandler::CallbackKey::kWindowId, QVariant::fromValue(windowId));
782+
args->insert(AbstractJobHandler::CallbackKey::kSuccessed, false);
783+
args->insert(AbstractJobHandler::CallbackKey::kCustom, custom);
784+
callbackImmediately(args);
785+
}
786+
return QString();
787+
}
788+
789+
// 2. Generate unique filename (reuse existing naming logic)
790+
QDateTime now = QDateTime::currentDateTime();
791+
QString timeStr = now.toString("yyyyMMdd_HHmmss_zzz");
792+
QString baseName = QString("image_%1").arg(timeStr);
793+
QString effectiveSuffix = suffix.isEmpty() ? "png" : suffix;
794+
795+
const QString newPath = newDocmentName(url, baseName, effectiveSuffix);
796+
if (newPath.isEmpty()) {
797+
fmWarning() << "Failed to generate file path for clipboard image";
798+
if (callbackImmediately) {
799+
AbstractJobHandler::CallbackArgus args(new QMap<AbstractJobHandler::CallbackKey, QVariant>);
800+
args->insert(AbstractJobHandler::CallbackKey::kSuccessed, false);
801+
callbackImmediately(args);
802+
}
803+
return QString();
804+
}
805+
806+
QUrl targetUrl = QUrl::fromLocalFile(newPath);
807+
808+
// 3. Synchronously save image (typically fast, no need for async)
809+
bool success = image.save(newPath, effectiveSuffix.toUpper().toUtf8().constData());
810+
811+
if (success) {
812+
fmInfo() << "Clipboard image saved successfully:" << newPath;
813+
814+
// 4. Save operation record for undo/redo
815+
saveFileOperation(
816+
{ targetUrl }, {},
817+
GlobalEventType::kDeleteFiles,
818+
{ targetUrl }, {},
819+
GlobalEventType::kTouchFile);
820+
821+
// 5. Publish result event
822+
dpfSignalDispatcher->publish(
823+
DFMBASE_NAMESPACE::GlobalEventType::kTouchFileResult,
824+
windowId,
825+
QList<QUrl>() << url,
826+
true,
827+
QString());
828+
} else {
829+
fmWarning() << "Failed to save clipboard image:" << newPath;
830+
831+
dpfSignalDispatcher->publish(
832+
DFMBASE_NAMESPACE::GlobalEventType::kTouchFileResult,
833+
windowId,
834+
QList<QUrl>() << url,
835+
false,
836+
"Failed to save image");
837+
}
838+
839+
// 6. Invoke callback after saving (with success status)
840+
if (callbackImmediately) {
841+
AbstractJobHandler::CallbackArgus args(new QMap<AbstractJobHandler::CallbackKey, QVariant>);
842+
args->insert(AbstractJobHandler::CallbackKey::kWindowId, QVariant::fromValue(windowId));
843+
args->insert(AbstractJobHandler::CallbackKey::kSourceUrls, QVariant::fromValue(QList<QUrl>() << url));
844+
args->insert(AbstractJobHandler::CallbackKey::kTargets, QVariant::fromValue(QList<QUrl>() << targetUrl));
845+
args->insert(AbstractJobHandler::CallbackKey::kSuccessed, success);
846+
args->insert(AbstractJobHandler::CallbackKey::kCustom, custom);
847+
callbackImmediately(args);
848+
}
849+
850+
return success ? newPath : QString();
851+
}
852+
739853
void FileOperationsEventReceiver::saveFileOperation(const QList<QUrl> &sourcesUrls,
740854
const QList<QUrl> &targetUrls,
741855
GlobalEventType type,

src/plugins/common/dfmplugin-fileoperations/fileoperationsevent/fileoperationseventreceiver.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,9 @@ public slots:
275275
const QVariant &custom,
276276
DFMBASE_NAMESPACE::AbstractJobHandler::OperatorCallback callbackImmediately);
277277
bool doTouchFilePractically(const quint64 windowId, const QUrl &url, const QUrl &tempUrl = QUrl());
278+
QString doTouchFileFromClipboard(const quint64 windowId, const QUrl &url,
279+
const QString &suffix, const QVariant &custom,
280+
DFMBASE_NAMESPACE::AbstractJobHandler::OperatorCallback callbackImmediately);
278281
void saveFileOperation(const QList<QUrl> &sourcesUrls, const QList<QUrl> &targetUrls,
279282
DFMBASE_NAMESPACE::GlobalEventType type,
280283
const QList<QUrl> &redoSourcesUrls, const QList<QUrl> &redoTargetUrls,

src/plugins/common/dfmplugin-menu/menuscene/clipboardmenuscene.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717

1818
#include <QMenu>
1919
#include <QVariant>
20+
#include <QClipboard>
21+
#include <QMimeData>
22+
#include <QApplication>
2023

2124
Q_DECLARE_METATYPE(QList<QUrl> *)
2225

@@ -121,7 +124,13 @@ void ClipBoardMenuScene::updateState(QMenu *parent)
121124
return;
122125

123126
curDirInfo->refresh();
124-
bool disabled = (ClipBoard::instance()->clipboardAction() == ClipBoard::kUnknownAction)
127+
128+
// 检查是否有传统的剪贴板action或者是否有图像数据
129+
const QMimeData *mimeData = QApplication::clipboard()->mimeData();
130+
bool hasValidClipboardData = (ClipBoard::instance()->clipboardAction() != ClipBoard::kUnknownAction)
131+
|| (mimeData && mimeData->hasImage());
132+
133+
bool disabled = !hasValidClipboardData
125134
|| !curDirInfo->isAttributes(OptInfoType::kIsWritable);
126135
paste->setDisabled(disabled);
127136
}

src/plugins/desktop/ddplugin-canvas/view/operator/fileoperatorproxy.cpp

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313
#include <dfm-base/dfm_event_defines.h>
1414
#include <dfm-base/utils/clipboard.h>
1515
#include <dfm-base/utils/fileutils.h>
16+
#include <QClipboard>
17+
#include <QMimeData>
18+
#include <QImage>
19+
#include <QDateTime>
20+
#include <QApplication>
1621

1722
#include <dfm-framework/dpf.h>
1823

@@ -261,7 +266,43 @@ void FileOperatorProxy::pasteFiles(const CanvasView *view, const QPoint pos)
261266
}
262267

263268
if (urls.isEmpty()) {
264-
fmDebug() << "No URLs in clipboard to paste";
269+
// Check for clipboard image
270+
const QMimeData *mimeData = QApplication::clipboard()->mimeData();
271+
if (mimeData && mimeData->hasImage()) {
272+
fmInfo() << "Detected clipboard image, creating file via kTouchFile event";
273+
274+
// Prepare custom data to identify this is a clipboard image
275+
QVariantMap clipboardData;
276+
clipboardData.insert("clipboardImage", true);
277+
278+
// Define callback to select newly created file
279+
AbstractJobHandler::OperatorCallback callback = [this](const AbstractJobHandler::CallbackArgus args) {
280+
if (args->value(AbstractJobHandler::CallbackKey::kSuccessed).toBool() != false) {
281+
auto targets = args->value(AbstractJobHandler::CallbackKey::kTargets)
282+
.value<QList<QUrl>>();
283+
if (!targets.isEmpty()) {
284+
fmDebug() << "Canvas: Requesting selection for created image file:" << targets;
285+
// Add to pasteFileData to trigger selection via existing mechanism
286+
d->pasteFileData.insert(targets.first());
287+
emit filePastedCallback();
288+
}
289+
}
290+
};
291+
292+
// Publish kTouchFile event with png suffix
293+
dpfSignalDispatcher->publish(
294+
GlobalEventType::kTouchFile,
295+
view->winId(),
296+
view->model()->rootUrl(),
297+
CreateFileType::kCreateFileTypeDefault,
298+
QString("png"), // Image suffix
299+
QVariant::fromValue(clipboardData),
300+
callback);
301+
302+
return;
303+
}
304+
305+
fmDebug() << "No URLs or image data in clipboard to paste";
265306
return;
266307
}
267308

src/plugins/filemanager/dfmplugin-workspace/utils/fileoperatorhelper.cpp

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@
1919

2020
#include <dfm-framework/dpf.h>
2121

22+
#include <QClipboard>
23+
#include <QMimeData>
24+
#include <QImage>
25+
#include <QDateTime>
26+
#include <QApplication>
27+
2228
Q_DECLARE_METATYPE(QList<QUrl> *)
2329

2430
DFMGLOBAL_USE_NAMESPACE
@@ -252,16 +258,32 @@ void FileOperatorHelper::cutFiles(const FileView *view)
252258
void FileOperatorHelper::pasteFiles(const FileView *view)
253259
{
254260
fmInfo() << "Paste file by clipboard and current dir: " << view->rootUrl();
255-
auto action = ClipBoard::instance()->clipboardAction();
256-
// trash dir can't paste files for copy
261+
262+
// Check if target directory is trash
257263
if (FileUtils::isTrashFile(view->rootUrl())) {
258264
fmDebug() << "Paste operation blocked - target is trash directory";
259265
return;
260266
}
261267

268+
// Try traditional file paste
269+
if (pasteTraditionalFiles(view)) {
270+
return;
271+
}
272+
273+
// Try clipboard image paste
274+
pasteClipboardImage(view);
275+
}
276+
277+
bool FileOperatorHelper::pasteTraditionalFiles(const FileView *view)
278+
{
279+
auto action = ClipBoard::instance()->clipboardAction();
262280
auto sourceUrls = ClipBoard::instance()->clipboardFileUrlList();
263281
auto windowId = WorkspaceHelper::instance()->windowId(view);
264282

283+
if (action == ClipBoard::kUnknownAction || sourceUrls.isEmpty()) {
284+
return false;
285+
}
286+
265287
if (ClipBoard::kCopyAction == action) {
266288
fmDebug() << "Executing copy action";
267289
dpfSignalDispatcher->publish(GlobalEventType::kCopy,
@@ -270,7 +292,6 @@ void FileOperatorHelper::pasteFiles(const FileView *view)
270292
view->rootUrl(),
271293
AbstractJobHandler::JobFlag::kNoHint, nullptr);
272294
} else if (ClipBoard::kCutAction == action) {
273-
274295
if (ClipBoard::supportCut()) {
275296
fmDebug() << "Executing cut action and clearing clipboard";
276297
dpfSignalDispatcher->publish(GlobalEventType::kCutFile,
@@ -294,8 +315,49 @@ void FileOperatorHelper::pasteFiles(const FileView *view)
294315
AbstractJobHandler::JobFlag::kCopyRemote,
295316
nullptr);
296317
} else {
297-
fmWarning() << "Unknown clipboard past action:" << action << " urls:" << sourceUrls;
318+
return false;
298319
}
320+
321+
return true;
322+
}
323+
324+
void FileOperatorHelper::pasteClipboardImage(const FileView *view)
325+
{
326+
const QMimeData *mimeData = QApplication::clipboard()->mimeData();
327+
if (!mimeData || !mimeData->hasImage()) {
328+
fmDebug() << "No image data in clipboard to paste";
329+
return;
330+
}
331+
332+
fmInfo() << "Detected clipboard image, creating file via kTouchFile event";
333+
334+
// Prepare custom data
335+
QVariantMap clipboardData;
336+
clipboardData.insert("clipboardImage", true);
337+
338+
// Define callback to select newly created file
339+
AbstractJobHandler::OperatorCallback callback = [](const AbstractJobHandler::CallbackArgus args) {
340+
if (args->value(AbstractJobHandler::CallbackKey::kSuccessed).toBool() != false) {
341+
auto targets = args->value(AbstractJobHandler::CallbackKey::kTargets)
342+
.value<QList<QUrl>>();
343+
if (!targets.isEmpty()) {
344+
fmDebug() << "Requesting selection for created image file:" << targets;
345+
WorkspaceHelper::instance()->laterRequestSelectFiles(targets);
346+
}
347+
}
348+
};
349+
350+
auto windowId = WorkspaceHelper::instance()->windowId(view);
351+
352+
// Publish kTouchFile event
353+
dpfSignalDispatcher->publish(
354+
GlobalEventType::kTouchFile,
355+
windowId,
356+
view->rootUrl(),
357+
CreateFileType::kCreateFileTypeDefault,
358+
QString("png"),
359+
QVariant::fromValue(clipboardData),
360+
callback);
299361
}
300362

301363
void FileOperatorHelper::undoFiles(const FileView *view)

src/plugins/filemanager/dfmplugin-workspace/utils/fileoperatorhelper.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ class FileOperatorHelper : public QObject
4848
private:
4949
explicit FileOperatorHelper(QObject *parent = nullptr);
5050
void callBackFunction(const DFMBASE_NAMESPACE::AbstractJobHandler::CallbackArgus args);
51+
bool pasteTraditionalFiles(const FileView *view);
52+
void pasteClipboardImage(const FileView *view);
5153
void undoCallBackFunction(QSharedPointer<DFMBASE_NAMESPACE::AbstractJobHandler> handler);
5254

5355
DFMBASE_NAMESPACE::AbstractJobHandler::OperatorCallback callBack;

0 commit comments

Comments
 (0)