Skip to content

Commit c411724

Browse files
committed
[win] Mimic on Windows the macOS's Chrome drag and drop behavior of images and offer the possibility of configuring the decoding functions to be used
This is achieved by looking at the clipboard for a file that contains data in some of the supported image formats, because in Windows, the Chrome implementation puts the image's file content in the clipboard. Then after determining that the file contents corresponds to one of our supported image formats, the configured decoding function is used (which might be the default one)
1 parent a8b8da0 commit c411724

File tree

7 files changed

+185
-7
lines changed

7 files changed

+185
-7
lines changed

examples/drag_and_drop.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,10 @@ class DragTarget : public os::DragTarget {
7777
if (ev.acceptDrop()) {
7878
if (ev.dataProvider()->contains(os::DragDataItemType::Paths))
7979
windowData.paths = ev.dataProvider()->getPaths();
80+
#if CLIP_ENABLE_IMAGE
8081
if (ev.dataProvider()->contains(os::DragDataItemType::Image))
8182
windowData.image = ev.dataProvider()->getImage();
83+
#endif
8284
if (ev.dataProvider()->contains(os::DragDataItemType::Url))
8385
windowData.url = ev.dataProvider()->getUrl();
8486
}

os/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
set(LAF_OS_SOURCES
99
common/event_queue.cpp
1010
common/main.cpp
11+
dnd.cpp
1112
system.cpp
1213
window.cpp)
1314
if(WIN32)

os/dnd.cpp

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// LAF OS Library
2+
// Copyright (C) 2024 Igara Studio S.A.
3+
//
4+
// This file is released under the terms of the MIT license.
5+
// Read LICENSE.txt for more information.
6+
7+
#if CLIP_ENABLE_IMAGE
8+
9+
#ifdef HAVE_CONFIG_H
10+
#include "config.h"
11+
#endif
12+
13+
#include "clip/clip.h"
14+
#include "clip/clip_win.h"
15+
#include "os/dnd.h"
16+
#include "os/system.h"
17+
18+
namespace os {
19+
20+
SurfaceRef default_decode_png(const uint8_t* buf, const uint32_t len)
21+
{
22+
clip::image img;
23+
if (!clip::win::read_png(buf, len, &img, nullptr))
24+
return nullptr;
25+
26+
return os::instance()->makeSurface(img);
27+
}
28+
29+
SurfaceRef default_decode_jpg(const uint8_t* buf, const uint32_t len)
30+
{
31+
clip::image img;
32+
if (!clip::win::read_jpg(buf, len, &img, nullptr))
33+
return nullptr;
34+
35+
return os::instance()->makeSurface(img);
36+
}
37+
38+
SurfaceRef default_decode_bmp(const uint8_t* buf, const uint32_t len)
39+
{
40+
clip::image img;
41+
if (!clip::win::read_bmp(buf, len, &img, nullptr))
42+
return nullptr;
43+
44+
return os::instance()->makeSurface(img);
45+
}
46+
47+
SurfaceRef default_decode_gif(const uint8_t* buf, const uint32_t len)
48+
{
49+
clip::image img;
50+
if (!clip::win::read_gif(buf, len, &img, nullptr))
51+
return nullptr;
52+
53+
return os::instance()->makeSurface(img);
54+
}
55+
56+
static DecoderFunc g_decode_png = default_decode_png;
57+
static DecoderFunc g_decode_jpg = default_decode_jpg;
58+
static DecoderFunc g_decode_bmp = default_decode_bmp;
59+
static DecoderFunc g_decode_gif = default_decode_gif;
60+
61+
void set_decode_png(DecoderFunc func)
62+
{
63+
g_decode_png = func;
64+
}
65+
66+
void set_decode_jpg(DecoderFunc func)
67+
{
68+
g_decode_jpg = func;
69+
}
70+
71+
void set_decode_bmp(DecoderFunc func)
72+
{
73+
g_decode_bmp = func;
74+
}
75+
76+
void set_decode_gif(DecoderFunc func)
77+
{
78+
g_decode_gif = func;
79+
}
80+
81+
SurfaceRef decode_png(const uint8_t* buf, const uint32_t len)
82+
{
83+
return g_decode_png(buf, len);
84+
}
85+
86+
SurfaceRef decode_jpg(const uint8_t* buf, const uint32_t len)
87+
{
88+
return g_decode_jpg(buf, len);
89+
}
90+
91+
SurfaceRef decode_bmp(const uint8_t* buf, const uint32_t len)
92+
{
93+
return g_decode_bmp(buf, len);
94+
}
95+
96+
SurfaceRef decode_gif(const uint8_t* buf, const uint32_t len)
97+
{
98+
return g_decode_gif(buf, len);
99+
}
100+
101+
} // namespace os
102+
103+
#endif

os/dnd.h

+18
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "gfx/point.h"
1414
#include "os/surface.h"
1515

16+
#include <functional>
1617
#include <memory>
1718

1819
#pragma push_macro("None")
@@ -22,6 +23,21 @@ namespace os {
2223

2324
class Window;
2425

26+
#if CLIP_ENABLE_IMAGE
27+
using DecoderFunc = std::function<SurfaceRef(const uint8_t* buf, const uint32_t len)>;
28+
29+
// Methods used to configure custom decoder functions by replacing the default implementations.
30+
31+
void set_decode_png(DecoderFunc func);
32+
void set_decode_jpg(DecoderFunc func);
33+
void set_decode_bmp(DecoderFunc func);
34+
void set_decode_gif(DecoderFunc func);
35+
36+
SurfaceRef decode_png(const uint8_t* buf, const uint32_t len);
37+
SurfaceRef decode_jpg(const uint8_t* buf, const uint32_t len);
38+
SurfaceRef decode_bmp(const uint8_t* buf, const uint32_t len);
39+
SurfaceRef decode_gif(const uint8_t* buf, const uint32_t len);
40+
#endif
2541
// Operations that can be supported by source and target windows in a drag
2642
// and drop operation.
2743
enum class DropOperation {
@@ -44,7 +60,9 @@ namespace os {
4460
public:
4561
virtual ~DragDataProvider() {}
4662
virtual base::paths getPaths() = 0;
63+
#if CLIP_ENABLE_IMAGE
4764
virtual SurfaceRef getImage() = 0;
65+
#endif
4866
virtual std::string getUrl() = 0;
4967
virtual bool contains(DragDataItemType type) { return false; }
5068
};

os/win/dnd.cpp

+58-6
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
// This file is released under the terms of the MIT license.
55
// Read LICENSE.txt for more information.
66

7+
#include "base/fs.h"
78
#include "os/window.h"
89
#include "os/win/dnd.h"
910
#include "os/system.h"
@@ -68,6 +69,8 @@ class GLock {
6869

6970
operator T() { return m_data; }
7071

72+
T operator ->() { return m_data; }
73+
7174
bool operator==(std::nullptr_t) const { return m_data == nullptr; }
7275
bool operator!=(std::nullptr_t) const { return m_data != nullptr; }
7376

@@ -108,13 +111,14 @@ class DataWrapper {
108111
DataWrapper(IDataObject* data) : m_data(data) {}
109112

110113
template<typename T>
111-
Medium<T> get(CLIPFORMAT cfmt) {
114+
Medium<T> get(CLIPFORMAT cfmt, LONG lindex = -1)
115+
{
112116
STGMEDIUM medium;
113117
FORMATETC fmt;
114118
fmt.cfFormat = cfmt;
115119
fmt.ptd = nullptr;
116120
fmt.dwAspect = DVASPECT_CONTENT;
117-
fmt.lindex = -1;
121+
fmt.lindex = lindex;
118122
fmt.tymed = TYMED::TYMED_HGLOBAL;
119123
if (m_data->GetData(&fmt, &medium) != S_OK)
120124
return nullptr;
@@ -151,20 +155,20 @@ base::paths DragDataProviderWin::getPaths()
151155
return files;
152156
}
153157

158+
#if CLIP_ENABLE_IMAGE
154159
SurfaceRef DragDataProviderWin::getImage()
155160
{
156161
SurfaceRef surface = nullptr;
157-
clip::image img;
158162

159163
DataWrapper data(m_data);
160164
UINT png_format = RegisterClipboardFormatA("PNG");
161165
if (png_format) {
162166
Medium<uint8_t*> png_handle = data.get<uint8_t*>(png_format);
163-
if (png_handle != nullptr &&
164-
clip::win::read_png(png_handle, png_handle.size(), &img, nullptr))
165-
return os::instance()->makeSurface(img);
167+
if (png_handle != nullptr)
168+
return os::decode_png(png_handle, png_handle.size());
166169
}
167170

171+
clip::image img;
168172
Medium<BITMAPV5HEADER*> b5 = data.get<BITMAPV5HEADER*>(CF_DIBV5);
169173
if (b5 != nullptr) {
170174
clip::win::BitmapInfo bi(b5);
@@ -179,9 +183,40 @@ SurfaceRef DragDataProviderWin::getImage()
179183
return os::instance()->makeSurface(img);
180184
}
181185

186+
// If there is a file descriptor available, then we inspect the first
187+
// file of the group to see if its filename has any of the supported
188+
// filename extensions for image formats.
189+
UINT fileDescriptorFormat = RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR);
190+
UINT fileContentsFormat = RegisterClipboardFormat(CFSTR_FILECONTENTS);
191+
if (fileDescriptorFormat) {
192+
Medium<FILEGROUPDESCRIPTOR*> fgd = data.get<FILEGROUPDESCRIPTOR*>(fileDescriptorFormat);
193+
if (fgd != nullptr && fgd->cItems > 0) {
194+
// Get content of the first file on the group.
195+
Medium<uint8_t*> content = data.get<uint8_t*>(fileContentsFormat, 0);
196+
if (content != nullptr) {
197+
std::string filename(base::to_utf8(fgd->fgd->cFileName));
198+
std::string ext = base::get_file_extension(filename);
199+
std::transform(ext.begin(), ext.end(), ext.begin(), ::toupper);
200+
201+
if (ext == "PNG")
202+
return os::decode_png(content, content.size());
203+
204+
if (ext == "JPG" || ext == "JPEG" || ext == "JPE")
205+
return os::decode_jpg(content, content.size());
206+
207+
if (ext == "GIF")
208+
return os::decode_gif(content, content.size());
209+
210+
if (ext == "BMP")
211+
return os::decode_bmp(content, content.size());
212+
}
213+
}
214+
}
215+
182216
// No suitable image format found.
183217
return nullptr;
184218
}
219+
#endif
185220

186221
std::string DragDataProviderWin::getUrl()
187222
{
@@ -203,6 +238,7 @@ bool DragDataProviderWin::contains(DragDataItemType type)
203238

204239
UINT urlFormat = RegisterClipboardFormat(CFSTR_INETURL);
205240
UINT pngFormat = RegisterClipboardFormat(L"PNG");
241+
UINT fileDescriptorFormat = RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR);
206242
char name[101];
207243
FORMATETC fmt;
208244
while (formats->Next(1, &fmt, nullptr) == S_OK) {
@@ -211,6 +247,7 @@ bool DragDataProviderWin::contains(DragDataItemType type)
211247
if (type == DragDataItemType::Paths)
212248
return true;
213249
break;
250+
#if CLIP_ENABLE_IMAGE
214251
case CF_DIBV5:
215252
if (type == DragDataItemType::Image)
216253
return true;
@@ -219,13 +256,28 @@ bool DragDataProviderWin::contains(DragDataItemType type)
219256
if (type == DragDataItemType::Image)
220257
return true;
221258
break;
259+
#endif
222260
default: {
223261
switch (type) {
262+
#if CLIP_ENABLE_IMAGE
224263
case DragDataItemType::Image:
225264
if (fmt.cfFormat == pngFormat)
226265
return true;
227266

267+
if (fmt.cfFormat == fileDescriptorFormat) {
268+
DataWrapper data(m_data);
269+
Medium<FILEGROUPDESCRIPTOR*> fgd = data.get<FILEGROUPDESCRIPTOR*>(fileDescriptorFormat);
270+
if (fgd != nullptr && fgd->cItems > 0) {
271+
std::string filename(base::to_utf8(fgd->fgd->cFileName));
272+
std::string ext = base::get_file_extension(filename);
273+
std::transform(ext.begin(), ext.end(), ext.begin(), ::toupper);
274+
if (ext == "PNG" || ext == "JPG" || ext == "JPEG" ||
275+
ext == "JPE" || ext == "GIF" || ext == "BMP")
276+
return true;
277+
}
278+
}
228279
break;
280+
#endif
229281
case DragDataItemType::Url:
230282
if (fmt.cfFormat == urlFormat)
231283
return true;

os/win/dnd.h

+2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,9 @@ namespace os {
2323
IDataObject* m_data;
2424

2525
base::paths getPaths() override;
26+
#if CLIP_ENABLE_IMAGE
2627
SurfaceRef getImage() override;
28+
#endif
2729
std::string getUrl() override;
2830
bool contains(DragDataItemType type) override;
2931
};

0 commit comments

Comments
 (0)