Skip to content

Commit d6eacf7

Browse files
committed
feat: initial implementation of paste from clipboard
1 parent 7a28649 commit d6eacf7

4 files changed

Lines changed: 132 additions & 41 deletions

File tree

‎src/meme-application.c‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* myapp-application.c
1+
/* meme-application.c
22
*
33
* Copyright 2025 Giovanni
44
*
@@ -366,5 +366,11 @@ gboolean on_window_key_pressed (GtkEventControllerKey *controller,
366366
return TRUE;
367367
}
368368

369+
if (!editing_text && is_exact_mods (state, GDK_CONTROL_MASK) &&
370+
(keyval == GDK_KEY_v || keyval == GDK_KEY_V)) {
371+
meme_window_paste_from_clipboard (self);
372+
return TRUE;
373+
}
374+
369375
return FALSE;
370376
}

‎src/meme-fileio.c‎

Lines changed: 84 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -25,68 +25,113 @@ static void gif_export_data_free(gpointer data) {
2525
g_free(ctx);
2626
}
2727

28+
static void
29+
meme_window_load_pixbuf (MemeWindow *self, GdkPixbuf *pixbuf,
30+
gboolean is_gif, const char *gif_path)
31+
{
32+
g_clear_pointer (&self->template_gif_path, g_free);
33+
self->template_is_gif = is_gif;
34+
if (is_gif)
35+
self->template_gif_path = g_strdup (gif_path);
36+
37+
g_clear_object (&self->template_image);
38+
self->template_image = pixbuf;
39+
40+
if (self->layers) {
41+
meme_layer_list_free (self->layers);
42+
self->layers = NULL;
43+
self->selected_layer = NULL;
44+
}
45+
free_history_stack (&self->undo_stack);
46+
free_history_stack (&self->redo_stack);
47+
48+
gtk_stack_set_visible_child_name (self->content_stack, "content");
49+
gtk_widget_set_sensitive (GTK_WIDGET (self->add_text_button), TRUE);
50+
gtk_widget_set_sensitive (GTK_WIDGET (self->export_button), TRUE);
51+
gtk_widget_set_sensitive (GTK_WIDGET (self->clear_button), TRUE);
52+
gtk_widget_set_sensitive (GTK_WIDGET (self->add_image_button), TRUE);
53+
gtk_widget_set_sensitive (GTK_WIDGET (self->deep_fry_button), TRUE);
54+
gtk_widget_set_sensitive (GTK_WIDGET (self->cinematic_button), TRUE);
55+
gtk_widget_set_sensitive (GTK_WIDGET (self->bw_button), TRUE);
56+
gtk_widget_set_sensitive (GTK_WIDGET (self->crop_mode_button), TRUE);
57+
gtk_widget_set_sensitive (GTK_WIDGET (self->draw_mode_button), TRUE);
58+
gtk_widget_set_sensitive (GTK_WIDGET (self->save_project_button), TRUE);
59+
gtk_widget_set_sensitive (GTK_WIDGET (self->global_filters_button), TRUE);
60+
gtk_widget_set_sensitive (GTK_WIDGET (self->zoom_in), TRUE);
61+
gtk_widget_set_sensitive (GTK_WIDGET (self->zoom_out), TRUE);
62+
gtk_widget_set_sensitive (GTK_WIDGET (self->copy_clipboard_button), TRUE);
63+
self->zoom_level = 1.0;
64+
apply_zoom (self);
65+
render_meme (self);
66+
meme_window_start_gif_animation (self);
67+
}
68+
2869
void
2970
meme_window_open_file (MemeWindow *self, GFile *file)
3071
{
3172
GError *error = NULL;
73+
gboolean is_gif;
74+
GdkPixbuf *pixbuf;
3275
char *path = g_file_get_path (file);
33-
if (!path)
76+
if (!path) {
77+
adw_toast_overlay_add_toast (self->copy_clip_feedback,
78+
adw_toast_new ("Couldn't open that item, try saving the image locally first"));
3479
return;
35-
36-
g_clear_pointer (&self->template_gif_path, g_free);
37-
if (g_str_has_suffix (path, ".gif")) {
38-
self->template_is_gif = TRUE;
39-
self->template_gif_path = g_strdup (path);
40-
} else {
41-
self->template_is_gif = FALSE;
4280
}
4381

44-
self->template_image = gdk_pixbuf_new_from_file (path, &error);
82+
is_gif = g_str_has_suffix (path, ".gif");
83+
pixbuf = gdk_pixbuf_new_from_file (path, &error);
4584

46-
if (!self->template_image) {
85+
if (!pixbuf) {
4786
char *err_msg = g_strdup_printf ("Couldn't open image: %s",
4887
error ? error->message : "unknown error");
4988
AdwToast *toast = adw_toast_new (err_msg);
5089
adw_toast_overlay_add_toast (self->copy_clip_feedback, toast);
5190
g_free (err_msg);
5291
g_clear_error (&error);
53-
g_clear_pointer (&self->template_gif_path, g_free);
54-
self->template_is_gif = FALSE;
5592
g_free (path);
5693
return;
5794
}
5895

59-
if (self->layers) {
60-
meme_layer_list_free (self->layers);
61-
self->layers = NULL;
62-
self->selected_layer = NULL;
96+
meme_window_load_pixbuf (self, pixbuf, is_gif, is_gif ? path : NULL);
97+
g_free (path);
98+
}
99+
100+
void
101+
meme_window_open_texture (MemeWindow *self, GdkTexture *texture)
102+
{
103+
GdkPixbuf *pixbuf = gdk_pixbuf_get_from_texture (texture);
104+
if (!pixbuf) {
105+
AdwToast *toast = adw_toast_new ("Couldn't read image");
106+
adw_toast_overlay_add_toast (self->copy_clip_feedback, toast);
107+
return;
63108
}
64-
free_history_stack (&self->undo_stack);
65-
free_history_stack (&self->redo_stack);
109+
meme_window_load_pixbuf (self, pixbuf, FALSE, NULL);
110+
}
66111

67-
if (self->template_image) {
68-
gtk_stack_set_visible_child_name (self->content_stack, "content");
69-
gtk_widget_set_sensitive (GTK_WIDGET (self->add_text_button), TRUE);
70-
gtk_widget_set_sensitive (GTK_WIDGET (self->export_button), TRUE);
71-
gtk_widget_set_sensitive (GTK_WIDGET (self->clear_button), TRUE);
72-
gtk_widget_set_sensitive (GTK_WIDGET (self->add_image_button), TRUE);
73-
gtk_widget_set_sensitive (GTK_WIDGET (self->deep_fry_button), TRUE);
74-
gtk_widget_set_sensitive (GTK_WIDGET (self->cinematic_button), TRUE);
75-
gtk_widget_set_sensitive (GTK_WIDGET (self->bw_button), TRUE);
76-
gtk_widget_set_sensitive (GTK_WIDGET (self->crop_mode_button), TRUE);
77-
gtk_widget_set_sensitive (GTK_WIDGET (self->draw_mode_button), TRUE);
78-
gtk_widget_set_sensitive (GTK_WIDGET (self->save_project_button), TRUE);
79-
gtk_widget_set_sensitive (GTK_WIDGET (self->global_filters_button), TRUE);
80-
gtk_widget_set_sensitive (GTK_WIDGET (self->zoom_in), TRUE);
81-
gtk_widget_set_sensitive (GTK_WIDGET (self->zoom_out), TRUE);
82-
gtk_widget_set_sensitive (GTK_WIDGET (self->copy_clipboard_button), TRUE);
83-
self->zoom_level = 1.0;
84-
apply_zoom (self);
85-
render_meme (self);
86-
meme_window_start_gif_animation (self);
112+
static void
113+
on_paste_texture_ready (GObject *source, GAsyncResult *res, gpointer user_data)
114+
{
115+
MemeWindow *self = MEME_WINDOW (user_data);
116+
GError *error = NULL;
117+
GdkTexture *texture = gdk_clipboard_read_texture_finish (GDK_CLIPBOARD (source), res, &error);
118+
119+
if (!texture) {
120+
adw_toast_overlay_add_toast (self->copy_clip_feedback,
121+
adw_toast_new ("Clipboard doesn't contain an image"));
122+
g_clear_error (&error);
123+
return;
87124
}
88125

89-
g_free (path);
126+
meme_window_open_texture (self, texture);
127+
g_object_unref (texture);
128+
}
129+
130+
void
131+
meme_window_paste_from_clipboard (MemeWindow *self)
132+
{
133+
GdkClipboard *clipboard = gtk_widget_get_clipboard (GTK_WIDGET (self));
134+
gdk_clipboard_read_texture_async (clipboard, NULL, on_paste_texture_ready, self);
90135
}
91136

92137
static GdkPixbuf *

‎src/meme-window.c‎

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1113,6 +1113,36 @@ static void meme_window_class_init (MemeWindowClass *klass) {
11131113
"win.toggle-sidebar", NULL);
11141114
}
11151115

1116+
static gboolean
1117+
on_window_drop (GtkDropTarget *target, const GValue *value,
1118+
double x, double y, MemeWindow *self)
1119+
{
1120+
if (G_VALUE_HOLDS (value, GDK_TYPE_FILE_LIST)) {
1121+
GdkFileList *file_list = g_value_get_boxed (value);
1122+
GSList *files = gdk_file_list_get_files (file_list);
1123+
gboolean handled = FALSE;
1124+
1125+
if (files) {
1126+
meme_window_open_file (self, G_FILE (files->data));
1127+
handled = TRUE;
1128+
} else {
1129+
adw_toast_overlay_add_toast (self->copy_clip_feedback,
1130+
adw_toast_new ("Couldn't read the dropped item"));
1131+
}
1132+
g_slist_free (files);
1133+
return handled;
1134+
}
1135+
1136+
if (G_VALUE_HOLDS (value, GDK_TYPE_TEXTURE)) {
1137+
meme_window_open_texture (self, GDK_TEXTURE (g_value_get_object (value)));
1138+
return TRUE;
1139+
}
1140+
1141+
adw_toast_overlay_add_toast (self->copy_clip_feedback,
1142+
adw_toast_new ("That doesn't look like an image"));
1143+
return FALSE;
1144+
}
1145+
11161146
static void meme_window_init (MemeWindow *self) {
11171147
GtkEventController *scroll;
11181148
GtkEventController *key_controller;
@@ -1202,6 +1232,14 @@ static void meme_window_init (MemeWindow *self) {
12021232
g_signal_connect (self->drag_gesture, "drag-begin", G_CALLBACK (on_drag_begin), self);
12031233
g_signal_connect (self->drag_gesture, "drag-update", G_CALLBACK (on_drag_update), self);
12041234
g_signal_connect (self->drag_gesture, "drag-end", G_CALLBACK (on_drag_end), self);
1235+
1236+
{
1237+
GtkDropTarget *drop_target = gtk_drop_target_new (G_TYPE_INVALID, GDK_ACTION_COPY);
1238+
GType drop_types[] = { GDK_TYPE_FILE_LIST, GDK_TYPE_TEXTURE };
1239+
gtk_drop_target_set_gtypes (drop_target, drop_types, G_N_ELEMENTS (drop_types));
1240+
g_signal_connect (drop_target, "drop", G_CALLBACK (on_window_drop), self);
1241+
gtk_widget_add_controller (GTK_WIDGET (self), GTK_EVENT_CONTROLLER (drop_target));
1242+
}
12051243

12061244
self->zoom_level = 1.0;
12071245
g_signal_connect_swapped (self->zoom_in, "clicked", G_CALLBACK (on_zoom_in_clicked), self);

‎src/meme-window.h‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* myapp-window.h
1+
/* meme-window.h
22
*
33
* Copyright 2025 Giovanni
44
*
@@ -35,3 +35,5 @@ void myapp_window_perform_redo (MemeWindow *self);
3535
void myapp_window_save_project (MemeWindow *self);
3636
void on_copy_clipboard_clicked (MemeWindow *self);
3737
void meme_window_open_file (MemeWindow *self, GFile *file);
38+
void meme_window_open_texture (MemeWindow *self, GdkTexture *texture);
39+
void meme_window_paste_from_clipboard (MemeWindow *self);

0 commit comments

Comments
 (0)