gtk2/gdk/win32/gdkselection-win32.c
Tor Lillqvist 3f4c73d0d5 Bug 559408 - Transparency lost when images are copied between GTK+ and the
2009-01-29  Tor Lillqvist  <tml@novell.com>

	Bug 559408 - Transparency lost when images are copied between GTK+
	and the clipboard on Windows

	The code in gdkselection-win32.c is simplified quite a lot. When
	an image is stored on the clipboard from GTK+, only the "PNG" and
	CF_DIB formats are advertised. PNG is our preferred format because
	it can losslessly represent any image that gdk-pixbuf can handle,
	even with alpha, unambiguously. CF_DIB is also advertised because
	of its ubiquitous support in Windows software.

	Delayed rendering is used as before, so for other data than text
	nothing is actually stored on the clipboard until some app asks
	for it.

	It's pointless to advertise images formats like "image/png" or
	"image/x-MS-bmp" that no other Windows application is going to
	look for anyway. Likewise it's pointless to store text under
	formats like "text/plain" or "UTF8_STRING". Just store
	CF_UNICODETEXT. CF_TEXT will be available as a synthesized format
	anyways.

	Office 2007 offers and asks for images in PNG format using the
	registered clipboard format name "PNG", so we do likewise. If the
	"PNG" format is available that is preferred when copying to GTK+
	from the clipboard.

	Unfortunately Word 2007 still uses CF_DIB without handling
	alpha. But PowerPoint 2007 uses PNG if available and handles alpha
	correctly. Both Word and Powerpoint offer PNG with alpha nicely.

	Firefox and IE7 offer images that contain alpha as 32-bit version
	3 BI_RGB DIBs with the undocumented "extra" bytes actually being
	alpha. Also, alpha is premultiplied into the RGB bytes, presumably
	because that is how AlphaBlend() wants such DIBs. That is also
	taken care of. At least for Firefox it easy to be sure that a
	CF_DIB on the clipboard is from Firefox.

	Also some general stylistic cleanup, comment improvements, and
	improvements of debugging printout especially in the clipboard
	handling. Those are not detailled below.

	* gdk/win32/gdkprivate-win32.h
	* gdk/win32/gdkglobals-win32.c
	* gdk/win32/gdkgmain-win32.c: Move some globals that were used
	only in gdkselection-win32.c to be local to that file.

	* gdk/win32/gdkproperty-win32.c (gdk_property_change): Don't
	bother checking if text to be placed on the clipboard consists of
	only ASCII.

	* gdk/win32/gdkselection-win32.c: Add static variables for a list
	of GdkPixbuf-supported formats, well-known registered clipboard
	formats for images, and for GdkAtoms for well-known image and text
	formats.

	(_gdk_win32_selection_init): Initialize above static variables.

	(selection_property_store) (gdk_selection_property_get)
	(_gdk_selection_property_delete): Don't use a FIFO of GdkSelProps
	for a window after all, it breaks testtext. See bug #163844.

	(gdk_selection_convert): When converting to the TARGETS format,
	i.e. when the caller wants to know what clipboard formats are
	available, if PNG is available we report just that and skip
	CF_DIB, JPEG and GIF even if advertised.

	If CF_UNICODETEXT is available, report only UTF8_STRING.

	When converting to the UTF8_STRING format, i.e. when the caller
	wants text from the clipboard, try just CF_UNICODETEXT. There is
	no point in trying CF_TEXT as Windows will synthesize
	CF_UNICODETEXT from CF_TEXT anyway, if some app has stored just
	CF_TEXT.

	When converting to the image/bmp format, i.e. when the caller
	wants an CF_DIB image from the clipboard, we check if the DIB is a
	version 3 32-bit BI_RGB one that is likely to actually contain
	alpha in the "extra" bytes. Such a DIB is likely to have
	premultiplied alpha even, at least in the case of Firefox 3 and
	IE7. We then edit the DIB in-place into a version 5 one in
	BI_BITFIELDS format and undo the alpha premultiplication.

	In any case, prepend a BMP file header before letting go of the
	data which will be fed to the gdk-pixbuf bmp loader by upper
	levels.

	(gdk_win32_selection_add_targets): If some kind of pixmap image
	format is being added, actually advertise just PNG and
	CF_DIB. Note that alpha won't be stored on the clipboard through
	CF_DIB. This is because gdk-pixbuf's bmp loader doesn't save
	alpha. Furthermore, few if any non-GTK+ Windows apps seem to
	understand a version 5 DIB with proper alpha anyway.

	(_gdk_win32_selection_convert_to_dib): Simplified muchly.



svn path=/trunk/; revision=22255
2009-01-30 01:16:47 +00:00

1321 lines
34 KiB
C

/* GDK - The GIMP Drawing Kit
* Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
* Copyright (C) 1998-2002 Tor Lillqvist
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
/*
* Modified by the GTK+ Team and others 1997-2000. See the AUTHORS
* file for a list of people on the GTK+ Team. See the ChangeLog
* files for a list of changes. These files are distributed with
* GTK+ at ftp://ftp.gtk.org/pub/gtk/.
*/
#include "config.h"
#include <string.h>
#include <stdlib.h>
#include "gdkproperty.h"
#include "gdkselection.h"
#include "gdkdisplay.h"
#include "gdkprivate-win32.h"
/* We emulate the GDK_SELECTION window properties of windows (as used
* in the X11 backend) by using a hash table from window handles to
* GdkSelProp structs.
*/
typedef struct {
guchar *data;
gsize length;
gint format;
GdkAtom type;
} GdkSelProp;
static GHashTable *sel_prop_table = NULL;
static GdkSelProp *dropfiles_prop = NULL;
/* We store the owner of each selection in this table. Obviously, this only
* is valid intra-app, and in fact it is necessary for the intra-app DND to work.
*/
static GHashTable *sel_owner_table = NULL;
/* Well-known registered clipboard image formats */
static UINT cf_png;
static UINT cf_jfif;
static UINT cf_gif;
/* GdkAtoms for well-known image formats */
static GdkAtom *known_pixbuf_formats;
static int n_known_pixbuf_formats;
static GdkAtom image_png;
static GdkAtom image_jpeg;
static GdkAtom image_bmp;
static GdkAtom image_gif;
/* GdkAtoms for well-known text formats */
static GdkAtom text_plain;
static GdkAtom text_plain_charset_utf_8;
static GdkAtom text_plain_charset_CP1252;
void
_gdk_win32_selection_init (void)
{
GSList *pixbuf_formats;
GSList *rover;
sel_prop_table = g_hash_table_new (NULL, NULL);
sel_owner_table = g_hash_table_new (NULL, NULL);
_format_atom_table = g_hash_table_new (NULL, NULL);
/* MS Office 2007, at least, offers images in common file formats
* using clipboard format names like "PNG" and "JFIF". So we follow
* the lead and map the GDK target name "image/png" to the clipboard
* format name "PNG" etc.
*/
cf_png = RegisterClipboardFormat ("PNG");
cf_jfif = RegisterClipboardFormat ("JFIF");
cf_gif = RegisterClipboardFormat ("GIF");
pixbuf_formats = gdk_pixbuf_get_formats ();
n_known_pixbuf_formats = 0;
for (rover = pixbuf_formats; rover != NULL; rover = rover->next)
{
gchar **mime_types =
gdk_pixbuf_format_get_mime_types ((GdkPixbufFormat *) rover->data);
gchar **mime_type;
for (mime_type = mime_types; *mime_type != NULL; mime_type++)
n_known_pixbuf_formats++;
}
known_pixbuf_formats = g_new (GdkAtom, n_known_pixbuf_formats);
n_known_pixbuf_formats = 0;
for (rover = pixbuf_formats; rover != NULL; rover = rover->next)
{
gchar **mime_types =
gdk_pixbuf_format_get_mime_types ((GdkPixbufFormat *) rover->data);
gchar **mime_type;
for (mime_type = mime_types; *mime_type != NULL; mime_type++)
known_pixbuf_formats[n_known_pixbuf_formats++] = gdk_atom_intern (*mime_type, FALSE);
}
g_slist_free (pixbuf_formats);
image_png = gdk_atom_intern ("image/png", FALSE);
image_jpeg = gdk_atom_intern ("image/jpeg", FALSE);
image_bmp = gdk_atom_intern ("image/bmp", FALSE);
image_gif = gdk_atom_intern ("image/gif", FALSE);
text_plain = gdk_atom_intern ("text/plain", FALSE);
text_plain_charset_utf_8= gdk_atom_intern ("text/plain;charset=utf-8", FALSE);
text_plain_charset_CP1252 = gdk_atom_intern ("text/plain;charset=CP1252", FALSE);
g_hash_table_replace (_format_atom_table,
GINT_TO_POINTER (cf_png),
image_png);
g_hash_table_replace (_format_atom_table,
GINT_TO_POINTER (CF_DIB),
image_bmp);
}
/* The specifications for COMPOUND_TEXT and STRING specify that C0 and
* C1 are not allowed except for \n and \t, however the X conversions
* routines for COMPOUND_TEXT only enforce this in one direction,
* causing cut-and-paste of \r and \r\n separated text to fail.
* This routine strips out all non-allowed C0 and C1 characters
* from the input string and also canonicalizes \r, and \r\n to \n
*/
static gchar *
sanitize_utf8 (const gchar *src,
gint length)
{
GString *result = g_string_sized_new (length + 1);
const gchar *p = src;
const gchar *endp = src + length;
while (p < endp)
{
if (*p == '\r')
{
p++;
if (*p == '\n')
p++;
g_string_append_c (result, '\n');
}
else
{
gunichar ch = g_utf8_get_char (p);
char buf[7];
gint buflen;
if (!((ch < 0x20 && ch != '\t' && ch != '\n') || (ch >= 0x7f && ch < 0xa0)))
{
buflen = g_unichar_to_utf8 (ch, buf);
g_string_append_len (result, buf, buflen);
}
p = g_utf8_next_char (p);
}
}
g_string_append_c (result, '\0');
return g_string_free (result, FALSE);
}
static gchar *
_gdk_utf8_to_string_target_internal (const gchar *str,
gint length)
{
GError *error = NULL;
gchar *tmp_str = sanitize_utf8 (str, length);
gchar *result = g_convert_with_fallback (tmp_str, -1,
"ISO-8859-1", "UTF-8",
NULL, NULL, NULL, &error);
if (!result)
{
g_warning ("Error converting from UTF-8 to STRING: %s",
error->message);
g_error_free (error);
}
g_free (tmp_str);
return result;
}
static void
selection_property_store (GdkWindow *owner,
GdkAtom type,
gint format,
guchar *data,
gint length)
{
GdkSelProp *prop;
g_return_if_fail (type != GDK_TARGET_STRING);
prop = g_hash_table_lookup (sel_prop_table, GDK_WINDOW_HWND (owner));
if (prop != NULL)
{
g_free (prop->data);
g_free (prop);
g_hash_table_remove (sel_prop_table, GDK_WINDOW_HWND (owner));
}
prop = g_new (GdkSelProp, 1);
prop->data = data;
prop->length = length;
prop->format = format;
prop->type = type;
g_hash_table_insert (sel_prop_table, GDK_WINDOW_HWND (owner), prop);
}
void
_gdk_dropfiles_store (gchar *data)
{
if (data != NULL)
{
g_assert (dropfiles_prop == NULL);
dropfiles_prop = g_new (GdkSelProp, 1);
dropfiles_prop->data = (guchar *) data;
dropfiles_prop->length = strlen (data) + 1;
dropfiles_prop->format = 8;
dropfiles_prop->type = _text_uri_list;
}
else
{
if (dropfiles_prop != NULL)
{
g_free (dropfiles_prop->data);
g_free (dropfiles_prop);
}
dropfiles_prop = NULL;
}
}
static gchar *
get_mapped_gdk_atom_name (GdkAtom gdk_target)
{
if (gdk_target == image_png)
return g_strdup ("PNG");
if (gdk_target == image_jpeg)
return g_strdup ("JFIF");
if (gdk_target == image_gif)
return g_strdup ("GIF");
return gdk_atom_name (gdk_target);
}
gboolean
gdk_selection_owner_set_for_display (GdkDisplay *display,
GdkWindow *owner,
GdkAtom selection,
guint32 time,
gboolean send_event)
{
HWND hwnd;
GdkEvent tmp_event;
g_return_val_if_fail (display == _gdk_display, FALSE);
g_return_val_if_fail (selection != GDK_NONE, FALSE);
GDK_NOTE (DND, {
gchar *sel_name = gdk_atom_name (selection);
g_print ("gdk_selection_owner_set_for_display: %p %s\n",
(owner ? GDK_WINDOW_HWND (owner) : NULL),
sel_name);
g_free (sel_name);
});
if (selection != GDK_SELECTION_CLIPBOARD)
{
if (owner != NULL)
g_hash_table_insert (sel_owner_table, selection, GDK_WINDOW_HWND (owner));
else
g_hash_table_remove (sel_owner_table, selection);
return TRUE;
}
/* Rest of this function handles the CLIPBOARD selection */
if (owner != NULL)
{
if (GDK_WINDOW_DESTROYED (owner))
return FALSE;
hwnd = GDK_WINDOW_HWND (owner);
}
else
hwnd = NULL;
if (!API_CALL (OpenClipboard, (hwnd)))
return FALSE;
_ignore_destroy_clipboard = TRUE;
GDK_NOTE (DND, g_print ("... EmptyClipboard()\n"));
if (!API_CALL (EmptyClipboard, ()))
{
_ignore_destroy_clipboard = FALSE;
API_CALL (CloseClipboard, ());
return FALSE;
}
_ignore_destroy_clipboard = FALSE;
if (!API_CALL (CloseClipboard, ()))
return FALSE;
if (owner != NULL)
{
/* Send ourselves a selection request message so that
* gdk_property_change will be called to store the clipboard
* data.
*/
GDK_NOTE (DND, g_print ("... sending GDK_SELECTION_REQUEST to ourselves\n"));
tmp_event.selection.type = GDK_SELECTION_REQUEST;
tmp_event.selection.window = owner;
tmp_event.selection.send_event = FALSE;
tmp_event.selection.selection = selection;
tmp_event.selection.target = _utf8_string;
tmp_event.selection.property = _gdk_selection;
tmp_event.selection.requestor = hwnd;
tmp_event.selection.time = time;
gdk_event_put (&tmp_event);
}
return TRUE;
}
GdkWindow*
gdk_selection_owner_get_for_display (GdkDisplay *display,
GdkAtom selection)
{
GdkWindow *window;
g_return_val_if_fail (display == _gdk_display, NULL);
g_return_val_if_fail (selection != GDK_NONE, NULL);
if (selection == GDK_SELECTION_CLIPBOARD)
{
HWND owner = GetClipboardOwner ();
if (owner == NULL)
return NULL;
return gdk_win32_handle_table_lookup ((GdkNativeWindow) owner);
}
window = gdk_window_lookup ((GdkNativeWindow) g_hash_table_lookup (sel_owner_table, selection));
GDK_NOTE (DND, {
gchar *sel_name = gdk_atom_name (selection);
g_print ("gdk_selection_owner_get: %s = %p\n",
sel_name,
(window ? GDK_WINDOW_HWND (window) : NULL));
g_free (sel_name);
});
return window;
}
static void
generate_selection_notify (GdkWindow *requestor,
GdkAtom selection,
GdkAtom target,
GdkAtom property,
guint32 time)
{
GdkEvent tmp_event;
tmp_event.selection.type = GDK_SELECTION_NOTIFY;
tmp_event.selection.window = requestor;
tmp_event.selection.send_event = FALSE;
tmp_event.selection.selection = selection;
tmp_event.selection.target = target;
tmp_event.selection.property = property;
tmp_event.selection.requestor = 0;
tmp_event.selection.time = time;
gdk_event_put (&tmp_event);
}
void
gdk_selection_convert (GdkWindow *requestor,
GdkAtom selection,
GdkAtom target,
guint32 time)
{
HGLOBAL hdata;
GdkAtom property = _gdk_selection;
g_return_if_fail (selection != GDK_NONE);
g_return_if_fail (requestor != NULL);
if (GDK_WINDOW_DESTROYED (requestor))
return;
GDK_NOTE (DND, {
gchar *sel_name = gdk_atom_name (selection);
gchar *tgt_name = gdk_atom_name (target);
g_print ("gdk_selection_convert: %p %s %s\n",
GDK_WINDOW_HWND (requestor),
sel_name, tgt_name);
g_free (sel_name);
g_free (tgt_name);
});
if (selection == GDK_SELECTION_CLIPBOARD && target == _targets)
{
gint ntargets, fmt;
GdkAtom *targets;
gboolean has_text = FALSE;
gboolean has_png = FALSE;
gboolean has_bmp = FALSE;
if (!API_CALL (OpenClipboard, (GDK_WINDOW_HWND (requestor))))
return;
targets = g_new (GdkAtom, CountClipboardFormats ());
ntargets = 0;
for (fmt = 0; 0 != (fmt = EnumClipboardFormats (fmt)); )
{
if (fmt == cf_png)
{
targets[ntargets++] = image_png;
has_png = TRUE;
}
}
for (fmt = 0; 0 != (fmt = EnumClipboardFormats (fmt)); )
{
gchar sFormat[80];
if (fmt == CF_UNICODETEXT || fmt == CF_TEXT)
{
/* Advertise text to GDK always as UTF8_STRING */
if (!has_text)
targets[ntargets++] = _utf8_string;
has_text = TRUE;
}
else if (fmt == cf_png)
{
/* Already handled above */
}
else if (fmt == CF_DIB ||
fmt == CF_DIBV5)
{
/* Don't bother telling that a bitmap is present if there is
* also PNG, which is much more reliable in transferring
* transparency.
*/
if (!has_bmp && !has_png)
targets[ntargets++] = image_bmp;
has_bmp = TRUE;
}
else if (fmt == cf_jfif)
{
/* Ditto for JPEG */
if (!has_png)
targets[ntargets++] = image_jpeg;
}
else if (fmt == cf_gif)
{
/* Ditto for GIF.
*/
if (!has_png)
targets[ntargets++] = image_gif;
}
else if (GetClipboardFormatName (fmt, sFormat, 80) > 0)
{
if (strcmp (sFormat, "image/bmp") == 0 ||
strcmp (sFormat, "image/x-bmp") == 0 ||
strcmp (sFormat, "image/x-MS-bmp") == 0 ||
strcmp (sFormat, "image/x-icon") == 0 ||
strcmp (sFormat, "image/x-ico") == 0 ||
strcmp (sFormat, "image/x-win-bitmap") == 0)
{
/* Ignore these (from older GTK+ versions
* presumably), as the same image in the CF_DIB
* format will also be on the clipboard anyway.
*/
}
else
targets[ntargets++] = gdk_atom_intern (sFormat, FALSE);
}
}
GDK_NOTE (DND, {
int i;
g_print ("... ");
for (i = 0; i < ntargets; i++)
{
gchar *atom_name = gdk_atom_name (targets[i]);
g_print ("%s", atom_name);
g_free (atom_name);
if (i < ntargets - 1)
g_print (", ");
}
g_print ("\n");
});
if (ntargets > 0)
selection_property_store (requestor, GDK_SELECTION_TYPE_ATOM,
32, (guchar *) targets,
ntargets * sizeof (GdkAtom));
else
property = GDK_NONE;
API_CALL (CloseClipboard, ());
}
else if (selection == GDK_SELECTION_CLIPBOARD && target == _utf8_string)
{
/* Converting the CLIPBOARD selection means he wants the
* contents of the clipboard. Get the clipboard data, and store
* it for later.
*/
if (!API_CALL (OpenClipboard, (GDK_WINDOW_HWND (requestor))))
return;
if ((hdata = GetClipboardData (CF_UNICODETEXT)) != NULL)
{
wchar_t *ptr, *wcs, *p, *q;
guchar *data;
glong length, wclen;
if ((ptr = GlobalLock (hdata)) != NULL)
{
length = GlobalSize (hdata);
GDK_NOTE (DND, g_print ("... CF_UNICODETEXT: %ld bytes\n",
length));
/* Strip out \r */
wcs = g_new (wchar_t, length / 2 + 1);
p = ptr;
q = wcs;
wclen = 0;
while (p < ptr + length / 2)
{
if (*p != '\r')
{
*q++ = *p;
wclen++;
}
p++;
}
data = g_utf16_to_utf8 (wcs, wclen, NULL, NULL, NULL);
g_free (wcs);
if (data)
selection_property_store (requestor, _utf8_string, 8,
data, strlen (data) + 1);
GlobalUnlock (hdata);
}
}
else
property = GDK_NONE;
API_CALL (CloseClipboard, ());
}
else if (selection == GDK_SELECTION_CLIPBOARD && target == image_bmp)
{
if (!API_CALL (OpenClipboard, (GDK_WINDOW_HWND (requestor))))
return;
if ((hdata = GetClipboardData (CF_DIB)) != NULL)
{
BITMAPINFOHEADER *bi;
if ((bi = GlobalLock (hdata)) != NULL)
{
/* Need to add a BMP file header so gdk-pixbuf can load
* it.
*
* If the data is from Mozilla Firefox or IE7, and
* starts with an "oldfashioned" BITMAPINFOHEADER,
* i.e. with biSize==40, and biCompression == BI_RGB and
* biBitCount==32, we assume that the "extra" byte in
* each pixel in fact is alpha.
*
* The gdk-pixbuf bmp loader doesn't trust 32-bit BI_RGB
* nitmaps to in fact have alpha, so we have to convince
* it by changint the bitmap header to a version 5
* BI_BITFIELDS one with explicit alpha mask indicated.
*
* The RGB bytes that are in bitmaps on the clipboard
* originating from Firefox or IE7 seem to be
* premultiplied with alpha. The gdk-pixbuf bmp loader
* of course doesn't expect that, so we have to undo the
* premultiplication before feeding the bitmap to the
* bmp loader.
*
* Note that for some reason the bmp loader used to want
* the alpha bytes in its input to actually be
* 255-alpha, but here we assume that this has been
* fixed before this is committed.
*/
BITMAPFILEHEADER *bf;
gpointer data;
gint data_length = GlobalSize (hdata);
gint new_length;
gboolean make_dibv5 = FALSE;
GDK_NOTE (DND, g_print ("... CF_DIB: %d bytes\n", data_length));
if (bi->biSize == sizeof (BITMAPINFOHEADER) &&
bi->biPlanes == 1 &&
bi->biBitCount == 32 &&
bi->biCompression == BI_RGB &&
#if 0
/* Maybe check explicitly for Mozilla or IE7?
*
* If the clipboard format
* application/x-moz-nativeimage is present, that is
* a reliable indicator that the data is offered by
* Mozilla one would think. For IE7,
* UniformResourceLocatorW is presumably not that
* uniqie, so probably need to do some
* GetClipboardOwner(), GetWindowThreadProcessId(),
* OpenProcess(), GetModuleFileNameEx() dance to
* check?
*/
(IsClipboardFormatAvailable
(RegisterClipboardFormat ("application/x-moz-nativeimage")) ||
IsClipboardFormatAvailable
(RegisterClipboardFormat ("UniformResourceLocatorW"))) &&
#endif
TRUE)
{
/* We turn the BITMAPINFOHEADER into a
* BITMAPV5HEADER before feeding it to gdk-pixbuf.
*/
new_length = (data_length +
sizeof (BITMAPFILEHEADER) +
(sizeof (BITMAPV5HEADER) - sizeof (BITMAPINFOHEADER)));
make_dibv5 = TRUE;
}
else
{
new_length = data_length + sizeof (BITMAPFILEHEADER);
}
data = g_try_malloc (new_length);
if (data)
{
bf = (BITMAPFILEHEADER *)data;
bf->bfType = 0x4d42; /* "BM" */
bf->bfSize = new_length;
bf->bfReserved1 = 0;
bf->bfReserved2 = 0;
if (make_dibv5)
{
BITMAPV5HEADER *bV5 = data + sizeof (BITMAPFILEHEADER);
guchar *p;
int i;
bV5->bV5Size = sizeof (BITMAPV5HEADER);
bV5->bV5Width = bi->biWidth;
bV5->bV5Height = bi->biHeight;
bV5->bV5Planes = 1;
bV5->bV5BitCount = 32;
bV5->bV5Compression = BI_BITFIELDS;
bV5->bV5SizeImage = 4 * bV5->bV5Width * ABS (bV5->bV5Height);
bV5->bV5XPelsPerMeter = bi->biXPelsPerMeter;
bV5->bV5YPelsPerMeter = bi->biYPelsPerMeter;
bV5->bV5ClrUsed = 0;
bV5->bV5ClrImportant = 0;
/* Now the added mask fields */
bV5->bV5RedMask = 0x00ff0000;
bV5->bV5GreenMask = 0x0000ff00;
bV5->bV5BlueMask = 0x000000ff;
bV5->bV5AlphaMask = 0xff000000;
((char *) &bV5->bV5CSType)[3] = 's';
((char *) &bV5->bV5CSType)[2] = 'R';
((char *) &bV5->bV5CSType)[1] = 'G';
((char *) &bV5->bV5CSType)[0] = 'B';
/* Ignore colorspace and profile fields */
bV5->bV5Intent = LCS_GM_GRAPHICS;
bV5->bV5Reserved = 0;
bf->bfOffBits = (sizeof (BITMAPFILEHEADER) +
bV5->bV5Size);
p = ((guchar *) data) + sizeof (BITMAPFILEHEADER) + sizeof (BITMAPV5HEADER);
memcpy (p, ((char *) bi) + bi->biSize,
data_length - sizeof (BITMAPINFOHEADER));
for (i = 0; i < bV5->bV5SizeImage/4; i++)
{
if (p[3] != 0)
{
gdouble inverse_alpha = 255./p[3];
p[0] = p[0] * inverse_alpha + 0.5;
p[1] = p[1] * inverse_alpha + 0.5;
p[2] = p[2] * inverse_alpha + 0.5;
}
p += 4;
}
}
else
{
bf->bfOffBits = (sizeof (BITMAPFILEHEADER) +
bi->biSize +
bi->biClrUsed * sizeof (RGBQUAD));
memcpy (data + sizeof (BITMAPFILEHEADER),
bi,
data_length);
}
selection_property_store (requestor, image_bmp, 8,
data, new_length);
}
GlobalUnlock (hdata);
}
}
API_CALL (CloseClipboard, ());
}
else if (selection == GDK_SELECTION_CLIPBOARD)
{
gchar *mapped_target_name;
UINT fmt = 0;
if (!API_CALL (OpenClipboard, (GDK_WINDOW_HWND (requestor))))
return;
mapped_target_name = get_mapped_gdk_atom_name (target);
/* Check if it's available. We could simply call
* GetClipboardData (RegisterClipboardFormat (targetname)), but
* the global custom format ID space is limited,
* (0xC000~0xFFFF), and we better not waste an format ID if we
* are just a requestor.
*/
for ( ; 0 != (fmt = EnumClipboardFormats (fmt)); )
{
char sFormat[80];
if (GetClipboardFormatName (fmt, sFormat, 80) > 0 &&
strcmp (sFormat, mapped_target_name) == 0)
{
if ((hdata = GetClipboardData (fmt)) != NULL)
{
/* Simply get it without conversion */
guchar *ptr;
gint length;
if ((ptr = GlobalLock (hdata)) != NULL)
{
length = GlobalSize (hdata);
GDK_NOTE (DND, g_print ("... %s: %d bytes\n", mapped_target_name, length));
selection_property_store (requestor, target, 8,
g_memdup (ptr, length), length);
GlobalUnlock (hdata);
break;
}
}
}
}
g_free (mapped_target_name);
API_CALL (CloseClipboard, ());
}
else if (selection == _gdk_win32_dropfiles)
{
/* This means he wants the names of the dropped files.
* gdk_dropfiles_filter already has stored the text/uri-list
* data temporarily in dropfiles_prop.
*/
if (dropfiles_prop != NULL)
{
selection_property_store
(requestor, selection, dropfiles_prop->format,
dropfiles_prop->data, dropfiles_prop->length);
g_free (dropfiles_prop);
dropfiles_prop = NULL;
}
}
else
property = GDK_NONE;
/* Generate a selection notify message so that we actually fetch the
* data (if property == _gdk_selection) or indicating failure (if
* property == GDK_NONE).
*/
generate_selection_notify (requestor, selection, target, property, time);
}
gint
gdk_selection_property_get (GdkWindow *requestor,
guchar **data,
GdkAtom *ret_type,
gint *ret_format)
{
GdkSelProp *prop;
g_return_val_if_fail (requestor != NULL, 0);
g_return_val_if_fail (GDK_IS_WINDOW (requestor), 0);
if (GDK_WINDOW_DESTROYED (requestor))
return 0;
GDK_NOTE (DND, g_print ("gdk_selection_property_get: %p",
GDK_WINDOW_HWND (requestor)));
prop = g_hash_table_lookup (sel_prop_table, GDK_WINDOW_HWND (requestor));
if (prop == NULL)
{
GDK_NOTE (DND, g_print (" (nothing)\n"));
*data = NULL;
return 0;
}
*data = g_malloc (prop->length + 1);
(*data)[prop->length] = '\0';
if (prop->length > 0)
memmove (*data, prop->data, prop->length);
GDK_NOTE (DND, {
gchar *type_name = gdk_atom_name (prop->type);
g_print (" %s format:%d length:%d\n", type_name, prop->format, prop->length);
g_free (type_name);
});
if (ret_type)
*ret_type = prop->type;
if (ret_format)
*ret_format = prop->format;
return prop->length;
}
void
_gdk_selection_property_delete (GdkWindow *window)
{
GdkSelProp *prop;
GDK_NOTE (DND, g_print ("_gdk_selection_property_delete: %p (no-op)\n",
GDK_WINDOW_HWND (window)));
#if 0
prop = g_hash_table_lookup (sel_prop_table, GDK_WINDOW_HWND (window));
if (prop != NULL)
{
g_free (prop->data);
g_free (prop);
g_hash_table_remove (sel_prop_table, GDK_WINDOW_HWND (window));
}
#endif
}
void
gdk_selection_send_notify_for_display (GdkDisplay *display,
GdkNativeWindow requestor,
GdkAtom selection,
GdkAtom target,
GdkAtom property,
guint32 time)
{
g_return_if_fail (display == _gdk_display);
GDK_NOTE (DND, {
gchar *sel_name = gdk_atom_name (selection);
gchar *tgt_name = gdk_atom_name (target);
gchar *prop_name = gdk_atom_name (property);
g_print ("gdk_selection_send_notify_for_display: %p %s %s %s (no-op)\n",
requestor, sel_name, tgt_name, prop_name);
g_free (sel_name);
g_free (tgt_name);
g_free (prop_name);
});
}
/* It's hard to say whether implementing this actually is of any use
* on the Win32 platform? gtk calls only
* gdk_text_property_to_utf8_list_for_display().
*/
gint
gdk_text_property_to_text_list_for_display (GdkDisplay *display,
GdkAtom encoding,
gint format,
const guchar *text,
gint length,
gchar ***list)
{
gchar *result;
const gchar *charset;
gchar *source_charset;
g_return_val_if_fail (display == _gdk_display, 0);
GDK_NOTE (DND, {
gchar *enc_name = gdk_atom_name (encoding);
g_print ("gdk_text_property_to_text_list_for_display: %s %d %.20s %d\n",
enc_name, format, text, length);
g_free (enc_name);
});
if (!list)
return 0;
if (encoding == GDK_TARGET_STRING)
source_charset = g_strdup ("ISO-8859-1");
else if (encoding == _utf8_string)
source_charset = g_strdup ("UTF-8");
else
source_charset = gdk_atom_name (encoding);
g_get_charset (&charset);
result = g_convert (text, length, charset, source_charset,
NULL, NULL, NULL);
g_free (source_charset);
if (!result)
return 0;
*list = g_new (gchar *, 1);
**list = result;
return 1;
}
void
gdk_free_text_list (gchar **list)
{
g_return_if_fail (list != NULL);
g_free (*list);
g_free (list);
}
static gint
make_list (const gchar *text,
gint length,
gboolean latin1,
gchar ***list)
{
GSList *strings = NULL;
gint n_strings = 0;
gint i;
const gchar *p = text;
const gchar *q;
GSList *tmp_list;
GError *error = NULL;
while (p < text + length)
{
gchar *str;
q = p;
while (*q && q < text + length)
q++;
if (latin1)
{
str = g_convert (p, q - p,
"UTF-8", "ISO-8859-1",
NULL, NULL, &error);
if (!str)
{
g_warning ("Error converting selection from STRING: %s",
error->message);
g_error_free (error);
}
}
else
str = g_strndup (p, q - p);
if (str)
{
strings = g_slist_prepend (strings, str);
n_strings++;
}
p = q + 1;
}
if (list)
*list = g_new (gchar *, n_strings + 1);
(*list)[n_strings] = NULL;
i = n_strings;
tmp_list = strings;
while (tmp_list)
{
if (list)
(*list)[--i] = tmp_list->data;
else
g_free (tmp_list->data);
tmp_list = tmp_list->next;
}
g_slist_free (strings);
return n_strings;
}
gint
gdk_text_property_to_utf8_list_for_display (GdkDisplay *display,
GdkAtom encoding,
gint format,
const guchar *text,
gint length,
gchar ***list)
{
g_return_val_if_fail (text != NULL, 0);
g_return_val_if_fail (length >= 0, 0);
g_return_val_if_fail (display == _gdk_display, 0);
if (encoding == GDK_TARGET_STRING)
{
return make_list ((gchar *)text, length, TRUE, list);
}
else if (encoding == _utf8_string)
{
return make_list ((gchar *)text, length, FALSE, list);
}
else
{
gchar *enc_name = gdk_atom_name (encoding);
g_warning ("gdk_text_property_to_utf8_list_for_display: encoding %s not handled\n", enc_name);
g_free (enc_name);
if (list)
*list = NULL;
return 0;
}
}
gint
gdk_string_to_compound_text_for_display (GdkDisplay *display,
const gchar *str,
GdkAtom *encoding,
gint *format,
guchar **ctext,
gint *length)
{
g_return_val_if_fail (str != NULL, 0);
g_return_val_if_fail (length >= 0, 0);
g_return_val_if_fail (display == _gdk_display, 0);
GDK_NOTE (DND, g_print ("gdk_string_to_compound_text_for_display: %.20s\n", str));
/* Always fail on Win32. No COMPOUND_TEXT support. */
if (encoding)
*encoding = GDK_NONE;
if (format)
*format = 0;
if (ctext)
*ctext = NULL;
if (length)
*length = 0;
return -1;
}
gchar *
gdk_utf8_to_string_target (const gchar *str)
{
return _gdk_utf8_to_string_target_internal (str, strlen (str));
}
gboolean
gdk_utf8_to_compound_text_for_display (GdkDisplay *display,
const gchar *str,
GdkAtom *encoding,
gint *format,
guchar **ctext,
gint *length)
{
g_return_val_if_fail (str != NULL, FALSE);
g_return_val_if_fail (display == _gdk_display, FALSE);
GDK_NOTE (DND, g_print ("gdk_utf8_to_compound_text_for_display: %.20s\n", str));
/* Always fail on Win32. No COMPOUND_TEXT support. */
if (encoding)
*encoding = GDK_NONE;
if (format)
*format = 0;
if (ctext)
*ctext = NULL;
if (length)
*length = 0;
return FALSE;
}
void
gdk_free_compound_text (guchar *ctext)
{
/* As we never generate anything claimed to be COMPOUND_TEXT, this
* should never be called. Or if it is called, ctext should be the
* NULL returned for conversions to COMPOUND_TEXT above.
*/
g_return_if_fail (ctext == NULL);
}
/* This function is called from gtk_selection_add_target() and
* gtk_selection_add_targets() in gtkselection.c. It is this function
* that takes care of setting those clipboard formats for which we use
* delayed rendering. Formats copied directly to the clipboard are
* handled in gdk_property_change() in gdkproperty-win32.c.
*/
void
gdk_win32_selection_add_targets (GdkWindow *owner,
GdkAtom selection,
gint n_targets,
GdkAtom *targets)
{
HWND hwnd = NULL;
gboolean has_image = FALSE;
gint i;
GDK_NOTE (DND, {
gchar *sel_name = gdk_atom_name (selection);
g_print ("gdk_win32_selection_add_targets: %p: %s: ",
owner ? GDK_WINDOW_HWND (owner) : NULL,
sel_name);
g_free (sel_name);
for (i = 0; i < n_targets; i++)
{
gchar *tgt_name = gdk_atom_name (targets[i]);
g_print ("%s", tgt_name);
g_free (tgt_name);
if (i < n_targets - 1)
g_print (", ");
}
g_print ("\n");
});
if (selection != GDK_SELECTION_CLIPBOARD)
return;
if (owner != NULL)
{
if (GDK_WINDOW_DESTROYED (owner))
return;
hwnd = GDK_WINDOW_HWND (owner);
}
if (!API_CALL (OpenClipboard, (hwnd)))
return;
/* We have a very simple strategy: If some kind of pixmap image
* format is being added, actually advertise just PNG and DIB. PNG
* is our preferred format because it can losslessly represent any
* image that gdk-pixbuf formats in general can, even with alpha,
* unambiguously. CF_DIB is also advertised because of the general
* support for it in Windows software, but note that alpha won't be
* handled.
*/
for (i = 0; !has_image && i < n_targets; ++i)
{
UINT cf;
gchar *target_name;
int j;
for (j = 0; j < n_known_pixbuf_formats; j++)
if (targets[i] == known_pixbuf_formats[j])
{
if (!has_image)
{
GDK_NOTE (DND, g_print ("... SetClipboardData(PNG,NULL)\n"));
SetClipboardData (cf_png, NULL);
GDK_NOTE (DND, g_print ("... SetClipboardData(CF_DIB,NULL)\n"));
SetClipboardData (CF_DIB, NULL);
has_image = TRUE;
}
break;
}
/* If it is one of the pixmap formats, already handled or not
* needed.
*/
if (j < n_known_pixbuf_formats)
continue;
/* We don't bother registering and advertising clipboard formats
* that are X11 specific or no non-GTK+ apps will have ever
* heard of, and when there are equivalent clipboard formats
* that are commonly used.
*/
if (targets[i] == _save_targets ||
targets[i] == _utf8_string ||
targets[i] == GDK_TARGET_STRING ||
targets[i] == _compound_text ||
targets[i] == _text ||
targets[i] == text_plain_charset_utf_8 ||
targets[i] == text_plain_charset_CP1252 ||
targets[i] == text_plain)
continue;
target_name = gdk_atom_name (targets[i]);
if (g_str_has_prefix (target_name, "text/plain;charset="))
{
g_free (target_name);
continue;
}
cf = RegisterClipboardFormat (target_name);
g_hash_table_replace (_format_atom_table,
GINT_TO_POINTER (cf),
targets[i]);
GDK_NOTE (DND, g_print ("... SetClipboardData(%s,NULL)\n",
_gdk_win32_cf_to_string (cf)));
SetClipboardData (cf, NULL);
g_free (target_name);
}
API_CALL (CloseClipboard, ());
}
/* Convert from types such as "image/jpg" or "image/png" to DIB using
* gdk-pixbuf so that image copied from GTK+ apps can be pasted in
* native apps like mspaint.exe
*/
HGLOBAL
_gdk_win32_selection_convert_to_dib (HGLOBAL hdata,
GdkAtom target)
{
GDK_NOTE (DND, {
gchar *target_name = gdk_atom_name (target);
g_print ("_gdk_win32_selection_convert_to_dib: %p %s\n",
hdata, target_name);
g_free (target_name);
});
if (target == image_bmp)
{
/* No conversion is needed, just strip the BITMAPFILEHEADER */
HGLOBAL hdatanew;
SIZE_T size = GlobalSize (hdata) - sizeof (BITMAPFILEHEADER);
guchar *ptr = GlobalLock (hdata);
memmove (ptr, ptr + sizeof (BITMAPFILEHEADER), size);
GlobalUnlock (hdata);
if ((hdatanew = GlobalReAlloc (hdata, size, GMEM_MOVEABLE)) == NULL)
{
WIN32_API_FAILED ("GlobalReAlloc");
GlobalFree (hdata); /* The old hdata is not freed if error */
}
return hdatanew;
}
g_warning ("Should not happen: We provide some image format but not CF_DIB and CF_DIB is requested.");
return NULL;
}