Merge branch 'wip/otte/widgetfactory-dnd' into 'master'

Improve DND

See merge request GNOME/gtk!3785
This commit is contained in:
Matthias Clasen 2021-07-27 04:12:58 +00:00
commit 910f23ea19
15 changed files with 443 additions and 23 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 MiB

View File

@ -696,6 +696,37 @@ on_range_to_changed (GtkSpinButton *to)
gtk_spin_button_set_value (from, v2);
}
static GdkContentProvider *
on_picture_drag_prepare (GtkDragSource *source,
double x,
double y,
gpointer unused)
{
GtkWidget *picture;
picture = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (source));
return gdk_content_provider_new_typed (GDK_TYPE_TEXTURE, gtk_picture_get_paintable (GTK_PICTURE (picture)));
}
static gboolean
on_picture_drop (GtkDropTarget *dest,
const GValue *value,
double x,
double y,
gpointer unused)
{
GtkWidget *picture;
GdkPaintable *paintable;
picture = gtk_event_controller_get_widget (GTK_EVENT_CONTROLLER (dest));
paintable = g_value_get_object (value);
gtk_picture_set_paintable (GTK_PICTURE (picture), paintable);
return TRUE;
}
static void
info_bar_response (GtkWidget *infobar, int response_id)
{
@ -1182,7 +1213,7 @@ populate_flowbox (GtkWidget *flowbox)
GtkWidget *child;
int i;
const char *resources[] = {
"sunset.jpg", "snowy.jpg", "portland-rose.jpg"
"sunset.jpg", "portland-rose.jpg", "beach.jpg", "nyc.jpg"
};
if (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (flowbox), "populated")))
@ -2040,6 +2071,8 @@ activate (GApplication *app)
"on_page_combo_changed", (GCallback)on_page_combo_changed,
"on_range_from_changed", (GCallback)on_range_from_changed,
"on_range_to_changed", (GCallback)on_range_to_changed,
"on_picture_drag_prepare", (GCallback)on_picture_drag_prepare,
"on_picture_drop", (GCallback)on_picture_drop,
"tab_close_cb", (GCallback)tab_close_cb,
"increase_icon_size", (GCallback)increase_icon_size,
"decrease_icon_size", (GCallback)decrease_icon_size,

View File

@ -115,7 +115,8 @@
<gresource prefix="/org/gtk/WidgetFactory4">
<file>gtk-logo.webm</file>
<file>sunset.jpg</file>
<file>snowy.jpg</file>
<file>portland-rose.jpg</file>
<file>nyc.jpg</file>
<file>beach.jpg</file>
</gresource>
</gresources>

View File

@ -1251,8 +1251,21 @@ Suspendisse feugiat quam quis dolor accumsan cursus.</property>
<child>
<object class="GtkNotebookPage">
<property name="child">
<object class="GtkBox" id="box6">
<property name="orientation">vertical</property>
<object class="GtkPicture">
<property name="file">resource:///org/gtk/WidgetFactory4/sunset.jpg</property>
<child>
<object class="GtkDragSource">
<property name="actions">copy</property>
<signal name="prepare" handler="on_picture_drag_prepare" swapped="no"/>
</object>
</child>
<child>
<object class="GtkDropTarget">
<property name="actions">copy</property>
<property name="formats">GdkTexture</property>
<signal name="drop" handler="on_picture_drop" swapped="no"/>
</object>
</child>
</object>
</property>
<property name="tab">
@ -1265,8 +1278,21 @@ Suspendisse feugiat quam quis dolor accumsan cursus.</property>
<child>
<object class="GtkNotebookPage">
<property name="child">
<object class="GtkBox" id="box7">
<property name="orientation">vertical</property>
<object class="GtkPicture">
<property name="file">resource:///org/gtk/WidgetFactory4/nyc.jpg</property>
<child>
<object class="GtkDragSource">
<property name="actions">copy</property>
<signal name="prepare" handler="on_picture_drag_prepare" swapped="no"/>
</object>
</child>
<child>
<object class="GtkDropTarget">
<property name="actions">copy</property>
<property name="formats">GdkTexture</property>
<signal name="drop" handler="on_picture_drop" swapped="no"/>
</object>
</child>
</object>
</property>
<property name="tab">
@ -1278,10 +1304,22 @@ Suspendisse feugiat quam quis dolor accumsan cursus.</property>
</child>
<child>
<object class="GtkNotebookPage">
<property name="position">2</property>
<property name="child">
<object class="GtkBox" id="box8">
<property name="orientation">vertical</property>
<object class="GtkPicture">
<property name="file">resource:///org/gtk/WidgetFactory4/beach.jpg</property>
<child>
<object class="GtkDragSource">
<property name="actions">copy</property>
<signal name="prepare" handler="on_picture_drag_prepare" swapped="no"/>
</object>
</child>
<child>
<object class="GtkDropTarget">
<property name="actions">copy</property>
<property name="formats">GdkTexture</property>
<signal name="drop" handler="on_picture_drop" swapped="no"/>
</object>
</child>
</object>
</property>
<property name="tab">
@ -1299,7 +1337,7 @@ Suspendisse feugiat quam quis dolor accumsan cursus.</property>
<child>
<object class="GtkNotebookPage">
<property name="child">
<object class="GtkBox" id="box9">
<object class="GtkBox" id="box8">
<property name="orientation">vertical</property>
</object>
</property>

View File

@ -179,6 +179,76 @@ gdk_content_formats_new_for_gtype (GType type)
return gdk_content_formats_new_take (data, 1, NULL, 0);
}
/**
* gdk_content_formats_parse:
* @string: the string to parse
*
* Parses the given @string into `GdkContentFormats` and
* returns the formats.
*
* Strings printed via [method@Gdk.ContentFormats.to_string]
* can be read in again successfully using this function.
*
* If @string does not describe valid content formats, %NULL
* is returned.
*
* Returns: (nullable): the content formats if @string is valid
*
* Since: 4.4
*/
GdkContentFormats *
gdk_content_formats_parse (const char *string)
{
GdkContentFormatsBuilder *builder;
char **split;
gsize i;
g_return_val_if_fail (string != NULL, NULL);
split = g_strsplit_set (string, "\t\n\f\r ", -1); /* same as g_ascii_isspace() */
builder = gdk_content_formats_builder_new ();
/* first the GTypes */
for (i = 0; split[i] != NULL; i++)
{
GType type;
if (split[i][0] == 0)
continue;
type = g_type_from_name (split[i]);
if (type != 0)
gdk_content_formats_builder_add_gtype (builder, type);
else
break;
}
/* then the mime types */
for (; split[i] != NULL; i++)
{
const char *mime_type;
if (split[i][0] == 0)
continue;
mime_type = gdk_intern_mime_type (split[i]);
if (mime_type)
gdk_content_formats_builder_add_mime_type (builder, mime_type);
else
break;
}
if (split[i] != NULL)
{
g_strfreev (split);
gdk_content_formats_builder_unref (builder);
return NULL;
}
g_strfreev (split);
return gdk_content_formats_builder_free_to_formats (builder);
}
/**
* gdk_content_formats_ref:
* @formats: a `GdkContentFormats`
@ -227,10 +297,8 @@ gdk_content_formats_unref (GdkContentFormats *formats)
*
* Prints the given @formats into a string for human consumption.
*
* This is meant for debugging and logging.
*
* The form of the representation may change at any time and is
* not guaranteed to stay identical.
* The result of this function can later be parsed with
* [func@Gdk.ContentFormats.parse].
*/
void
gdk_content_formats_print (GdkContentFormats *formats,
@ -241,20 +309,18 @@ gdk_content_formats_print (GdkContentFormats *formats,
g_return_if_fail (formats != NULL);
g_return_if_fail (string != NULL);
g_string_append (string, "{ ");
for (i = 0; i < formats->n_gtypes; i++)
{
if (i > 0)
g_string_append (string, ", ");
g_string_append (string, " ");
g_string_append (string, g_type_name (formats->gtypes[i]));
}
for (i = 0; i < formats->n_mime_types; i++)
{
if (i > 0 || formats->n_gtypes > 0)
g_string_append (string, ", ");
g_string_append (string, " ");
g_string_append (string, formats->mime_types[i]);
}
g_string_append (string, " }");
}
/**
@ -263,6 +329,8 @@ gdk_content_formats_print (GdkContentFormats *formats,
*
* Prints the given @formats into a human-readable string.
*
* The resulting string can be parsed with [func@Gdk.ContentFormats.parse].
*
* This is a small wrapper around [method@Gdk.ContentFormats.print]
* to help when debugging.
*

View File

@ -40,6 +40,8 @@ GdkContentFormats * gdk_content_formats_new (const char
guint n_mime_types);
GDK_AVAILABLE_IN_ALL
GdkContentFormats * gdk_content_formats_new_for_gtype (GType type);
GDK_AVAILABLE_IN_4_4
GdkContentFormats * gdk_content_formats_parse (const char *string);
GDK_AVAILABLE_IN_ALL
GdkContentFormats * gdk_content_formats_ref (GdkContentFormats *formats);
GDK_AVAILABLE_IN_ALL

View File

@ -242,9 +242,11 @@ gdk_macos_drag_drop_performed (GdkDrag *drag,
g_assert (GDK_IS_MACOS_DRAG (self));
g_object_ref (self);
drag_ungrab (self);
g_signal_emit_by_name (drag, "dnd-finished");
gdk_drag_drop_done (drag, TRUE);
g_object_unref (self);
}
static void

View File

@ -290,8 +290,10 @@ data_source_dnd_finished (void *data,
{
GdkDrag *drag = data;
g_object_ref (drag);
g_signal_emit_by_name (drag, "dnd-finished");
gdk_drag_drop_done (drag, TRUE);
g_object_unref (drag);
}
static void

View File

@ -884,8 +884,10 @@ gdk_x11_drag_handle_finished (GdkDisplay *display,
if (drag_x11->version == 5)
drag_x11->drop_failed = xevent->xclient.data.l[1] == 0;
g_object_ref (drag);
g_signal_emit_by_name (drag, "dnd-finished");
gdk_drag_drop_done (drag, !drag_x11->drop_failed);
g_object_unref (drag);
}
}

View File

@ -2242,6 +2242,23 @@ gtk_builder_value_from_string_type (GtkBuilder *builder,
ret = FALSE;
}
}
else if (G_VALUE_HOLDS (value, GDK_TYPE_CONTENT_FORMATS))
{
GdkContentFormats *formats;
formats = gdk_content_formats_parse (string);
if (formats)
g_value_take_boxed (value, formats);
else
{
g_set_error (error,
GTK_BUILDER_ERROR,
GTK_BUILDER_ERROR_INVALID_VALUE,
"Could not parse GdkContentFormats '%s'",
string);
ret = FALSE;
}
}
else if (G_VALUE_HOLDS (value, GSK_TYPE_TRANSFORM))
{
GskTransform *transform;

View File

@ -344,7 +344,7 @@ gtk_drop_target_accept (GtkDropTarget *self,
if (self->formats == NULL)
return TRUE;
return gdk_content_formats_match (self->formats, gdk_drop_get_formats (drop));
return gdk_content_formats_match_gtype (self->formats, gdk_drop_get_formats (drop)) != G_TYPE_INVALID;
}
static GdkDragAction
@ -544,6 +544,12 @@ gtk_drop_target_set_property (GObject *object,
gtk_drop_target_set_actions (self, g_value_get_flags (value));
break;
case PROP_FORMATS:
self->formats = g_value_dup_boxed (value);
if (self->formats == NULL)
self->formats = gdk_content_formats_new (NULL, 0);
break;
case PROP_PRELOAD:
gtk_drop_target_set_preload (self, g_value_get_boolean (value));
break;
@ -661,7 +667,7 @@ gtk_drop_target_class_init (GtkDropTargetClass *class)
P_("Formats"),
P_("The supported formats"),
GDK_TYPE_CONTENT_FORMATS,
GTK_PARAM_READABLE);
GTK_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
/**
* GtkDropTarget:preload: (attributes org.gtk.Property.get=gtk_drop_target_get_preload org.gtk.Property.set=gtk_drop_target_set_preload)
@ -843,7 +849,6 @@ gtk_drop_target_class_init (GtkDropTargetClass *class)
static void
gtk_drop_target_init (GtkDropTarget *self)
{
self->formats = gdk_content_formats_new (NULL, 0);
}
/**
@ -864,13 +869,19 @@ gtk_drop_target_new (GType type,
GdkDragAction actions)
{
GtkDropTarget *result;
GdkContentFormats *formats;
if (type != G_TYPE_INVALID)
formats = gdk_content_formats_new_for_gtype (type);
else
formats = NULL;
result = g_object_new (GTK_TYPE_DROP_TARGET,
"formats", formats,
"actions", actions,
NULL);
if (type != G_TYPE_INVALID)
gtk_drop_target_set_gtypes (result, &type, 1);
g_clear_pointer (&formats, gdk_content_formats_unref);
return result;
}

View File

@ -0,0 +1,243 @@
#include <stdlib.h>
#include <gtk/gtk.h>
static GType
string_type (void)
{
return G_TYPE_STRING;
}
static struct {
GType (* type_func) (void);
const char *mime_type;
} possible_types[] = {
/* GTypes go here */
{ string_type, NULL },
{ gdk_file_list_get_type, NULL },
{ gdk_rgba_get_type, NULL },
{ gdk_texture_get_type, NULL },
/* mime types go here */
{ NULL, "text/plain" },
{ NULL, "text/plain;charset=utf-8" },
{ NULL, "image/png" },
{ NULL, "image/jpeg" },
{ NULL, "application/x-color" },
};
#define assert_printf(...) G_STMT_START{ \
char *_s = g_strdup_printf (__VA_ARGS__); \
g_assertion_message (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, _s); \
g_free (_s); \
}G_STMT_END
#define assert_formats_subset(a, b) G_STMT_START{ \
const GType *_gtypes; \
const char * const *_mime_types; \
gsize _i, _n; \
\
_gtypes = gdk_content_formats_get_gtypes (a, &_n); \
for (_i = 0; _i < _n; _i++) \
{ \
if (!gdk_content_formats_contain_gtype (b, _gtypes[_i])) \
assert_printf (#a "" #b ": does not contain GType %s", g_type_name (_gtypes[_i])); \
} \
\
_mime_types = gdk_content_formats_get_mime_types (a, &_n); \
for (_i = 0; _i < _n; _i++) \
{ \
if (!gdk_content_formats_contain_mime_type (b, _mime_types[_i])) \
assert_printf (#a "" #b ": does not contain mime type %s", _mime_types[_i]); \
} \
}G_STMT_END
#define assert_formats_equal(a, b) G_STMT_START{\
assert_formats_subset(a, b); \
assert_formats_subset(b, a); \
}G_STMT_END
static GdkContentFormats *
create_random_content_formats (void)
{
GdkContentFormatsBuilder *builder;
gsize i, n;
n = g_test_rand_int_range (0, G_N_ELEMENTS (possible_types));
builder = gdk_content_formats_builder_new ();
for (i = 0; i < n; i++)
{
gsize j = g_random_int_range (0, G_N_ELEMENTS (possible_types));
if (possible_types[j].type_func)
gdk_content_formats_builder_add_gtype (builder, possible_types[j].type_func ());
else if (possible_types[j].mime_type)
gdk_content_formats_builder_add_mime_type (builder, possible_types[j].mime_type);
else
g_assert_not_reached ();
}
return gdk_content_formats_builder_free_to_formats (builder);
}
static void
test_print_and_parse (void)
{
GdkContentFormats *before, *parsed;
char *string_before, *string_parsed;
gsize i;
for (i = 0; i < 100; i++)
{
before = create_random_content_formats ();
string_before = gdk_content_formats_to_string (before);
parsed = gdk_content_formats_parse (string_before);
g_assert (parsed);
assert_formats_equal (before, parsed);
string_parsed = gdk_content_formats_to_string (parsed);
g_assert_cmpstr (string_before, ==, string_parsed);
g_free (string_parsed);
g_free (string_before);
gdk_content_formats_unref (parsed);
gdk_content_formats_unref (before);
}
}
static void
test_union (void)
{
GdkContentFormatsBuilder *builder;
GdkContentFormats *a, *b, *ab, *ab2;
gsize i;
for (i = 0; i < 100; i++)
{
a = create_random_content_formats ();
b = create_random_content_formats ();
ab = gdk_content_formats_union (gdk_content_formats_ref (a), b);
assert_formats_subset (a, ab);
assert_formats_subset (b, ab);
ab2 = gdk_content_formats_union (gdk_content_formats_ref (a), ab);
assert_formats_equal (ab, ab2);
gdk_content_formats_unref (ab2);
builder = gdk_content_formats_builder_new ();
gdk_content_formats_builder_add_formats (builder, a);
gdk_content_formats_builder_add_formats (builder, b);
ab2 = gdk_content_formats_builder_free_to_formats (builder);
assert_formats_equal (ab, ab2);
gdk_content_formats_unref (ab2);
gdk_content_formats_unref (ab);
gdk_content_formats_unref (a);
gdk_content_formats_unref (b);
}
}
static void
append_separator (GString *string)
{
static const char *separators = "\t\n\f\r ";
do {
g_string_append_c (string, separators[g_test_rand_int_range (0, strlen (separators))]);
} while (g_test_rand_bit ());
}
static char *
fuzzy_print (GdkContentFormats *formats)
{
GString *string;
const GType *types;
const char * const *mime_types;
gsize i, n;
string = g_string_new ("");
types = gdk_content_formats_get_gtypes (formats, &n);
for (i = 0; i < n; i++)
{
if (string->len || g_test_rand_bit ())
append_separator (string);
g_string_append (string, g_type_name (types[i]));
}
mime_types = gdk_content_formats_get_mime_types (formats, &n);
for (i = 0; i < n; i++)
{
if (string->len || g_test_rand_bit ())
append_separator (string);
g_string_append (string, mime_types[i]);
}
if (g_test_rand_bit ())
append_separator (string);
return g_string_free (string, FALSE);
}
static void
test_parse (void)
{
gsize i;
for (i = 0; i < 100; i++)
{
GdkContentFormats *formats, *parsed;
char *fuzzy;
formats = create_random_content_formats ();
fuzzy = fuzzy_print (formats);
parsed = gdk_content_formats_parse (fuzzy);
assert_formats_equal (formats, parsed);
g_free (fuzzy);
gdk_content_formats_unref (parsed);
gdk_content_formats_unref (formats);
}
}
static void
test_parse_fail (void)
{
static const char *failures[] = {
"GtkNonexistingType",
"text/plain TypeAfterMime",
"notamimetype",
"image/png stillnotamimetype",
};
gsize i;
for (i = 0; i < G_N_ELEMENTS (failures); i++)
{
g_assert_null (gdk_content_formats_parse (failures[i]));
}
}
int
main (int argc, char *argv[])
{
gsize i;
(g_test_init) (&argc, &argv, NULL);
gtk_init ();
/* Ensure all the types we care about to exist */
for (i = 0; i < G_N_ELEMENTS(possible_types); i++)
{
if (possible_types[i].type_func)
g_type_ensure (possible_types[i].type_func ());
}
g_test_add_func ("/contentformats/parse", test_parse);
g_test_add_func ("/contentformats/parse_fail", test_parse_fail);
g_test_add_func ("/contentformats/print_and_parse", test_print_and_parse);
g_test_add_func ("/contentformats/union", test_union);
return g_test_run ();
}

View File

@ -13,6 +13,7 @@ tests = [
'array',
'cairo',
'clipboard',
'contentformats',
'contentserializer',
'cursor',
'display',