Simplify handling of GG xfer auth queue.
[pidgin-git.git] / pidgin / gtkwhiteboard.c
blob9aa451bcdac171129db63f75a868a02fc47537a2
1 /*
2 * pidgin
4 * Pidgin is the legal property of its developers, whose names are too numerous
5 * to list here. Please refer to the COPYRIGHT file distributed with this
6 * source distribution.
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
24 #include "internal.h"
25 #include "buddylist.h"
26 #include "debug.h"
27 #include "pidgin.h"
28 #include "whiteboard.h"
30 #include "gtk3compat.h"
31 #include "gtkwhiteboard.h"
32 #include "gtkutils.h"
34 typedef enum {
35 PIDGIN_WHITEBOARD_BRUSH_UP,
36 PIDGIN_WHITEBOARD_BRUSH_DOWN,
37 PIDGIN_WHITEBOARD_BRUSH_MOTION
38 } PidginWhiteboardBrushState;
40 #define PIDGIN_TYPE_WHITEBOARD (pidgin_whiteboard_get_type())
41 G_DECLARE_FINAL_TYPE(PidginWhiteboard, pidgin_whiteboard, PIDGIN, WHITEBOARD,
42 GtkWindow)
44 /**
45 * PidginWhiteboard:
46 * @cr: Cairo context for drawing
47 * @surface: Cairo surface for drawing
48 * @wb: Backend data for this whiteboard
49 * @drawing_area: Drawing area
50 * @color_button: A color chooser widget
51 * @width: Canvas width
52 * @height: Canvas height
53 * @brush_color: Foreground color
54 * @brush_size: Brush size
55 * @brush_state: The @PidginWhiteboardBrushState state of the brush
57 * A PidginWhiteboard
59 struct _PidginWhiteboard
61 GtkWindow parent;
63 cairo_t *cr;
64 cairo_surface_t *surface;
66 PurpleWhiteboard *wb;
68 GtkWidget *drawing_area;
69 GtkWidget *color_button;
71 int width;
72 int height;
73 int brush_color;
74 int brush_size;
75 PidginWhiteboardBrushState brush_state;
77 /* Tracks last position of the mouse when drawing */
78 gint last_x;
79 gint last_y;
80 /* Tracks how many brush motions made */
81 gint motion_count;
84 G_DEFINE_TYPE(PidginWhiteboard, pidgin_whiteboard, GTK_TYPE_WINDOW)
86 /******************************************************************************
87 * Helpers
88 *****************************************************************************/
89 static void
90 pidgin_whiteboard_rgb24_to_rgba(int color_rgb, GdkRGBA *color)
92 color->red = ((color_rgb >> 16) & 0xFF) / 255.0f;
93 color->green = ((color_rgb >> 8) & 0xFF) / 255.0f;
94 color->blue = (color_rgb & 0xFF) / 255.0f;
95 color->alpha = 1.0;
98 static gboolean
99 whiteboard_close_cb(GtkWidget *widget, GdkEvent *event,
100 G_GNUC_UNUSED gpointer data)
102 PidginWhiteboard *gtkwb = PIDGIN_WHITEBOARD(widget);
104 g_clear_object(&gtkwb->wb);
106 return FALSE;
109 static gboolean pidgin_whiteboard_configure_event(GtkWidget *widget, GdkEventConfigure *event, gpointer data)
111 PidginWhiteboard *gtkwb = (PidginWhiteboard*)data;
112 cairo_t *cr;
113 GtkAllocation allocation;
114 GdkRGBA white = {1.0, 1.0, 1.0, 1.0};
116 if (gtkwb->cr) {
117 cairo_destroy(gtkwb->cr);
119 if (gtkwb->surface) {
120 cairo_surface_destroy(gtkwb->surface);
123 gtk_widget_get_allocation(widget, &allocation);
125 gtkwb->surface = cairo_image_surface_create(
126 CAIRO_FORMAT_RGB24, allocation.width, allocation.height);
127 gtkwb->cr = cr = cairo_create(gtkwb->surface);
128 gdk_cairo_set_source_rgba(cr, &white);
129 cairo_rectangle(cr, 0, 0, allocation.width, allocation.height);
130 cairo_fill(cr);
132 return TRUE;
135 static gboolean
136 pidgin_whiteboard_draw_event(GtkWidget *widget, cairo_t *cr,
137 gpointer _gtkwb)
139 PidginWhiteboard *gtkwb = _gtkwb;
141 cairo_set_source_surface(cr, gtkwb->surface, 0, 0);
142 cairo_paint(cr);
144 return FALSE;
147 static void
148 pidgin_whiteboard_set_canvas_as_icon(PidginWhiteboard *gtkwb)
150 GdkPixbuf *pixbuf;
152 /* Makes an icon from the whiteboard's canvas 'image' */
153 pixbuf = gdk_pixbuf_get_from_surface(gtkwb->surface, 0, 0, gtkwb->width,
154 gtkwb->height);
155 gtk_window_set_icon(GTK_WINDOW(gtkwb), pixbuf);
156 g_object_unref(pixbuf);
159 static void
160 pidgin_whiteboard_draw_brush_point(PurpleWhiteboard *wb, int x, int y,
161 int color, int size)
163 PidginWhiteboard *gtkwb = purple_whiteboard_get_ui_data(wb);
164 GtkWidget *widget = gtkwb->drawing_area;
165 cairo_t *gfx_con = gtkwb->cr;
166 GdkRGBA rgba;
168 /* Interpret and convert color */
169 pidgin_whiteboard_rgb24_to_rgba(color, &rgba);
170 gdk_cairo_set_source_rgba(gfx_con, &rgba);
172 /* Draw a circle */
173 cairo_arc(gfx_con, x, y, size / 2.0, 0.0, 2.0 * M_PI);
174 cairo_fill(gfx_con);
176 gtk_widget_queue_draw_area(widget, x - size / 2, y - size / 2, size,
177 size);
179 /* Uses Bresenham's algorithm (as provided by Wikipedia) */
180 static void
181 pidgin_whiteboard_draw_brush_line(PurpleWhiteboard *wb, int x0, int y0, int x1,
182 int y1, int color, int size)
184 int temp;
186 int xstep;
187 int ystep;
189 int dx;
190 int dy;
192 int error;
193 int derror;
195 int x;
196 int y;
198 gboolean steep = abs(y1 - y0) > abs(x1 - x0);
200 if (steep) {
201 temp = x0;
202 x0 = y0;
203 y0 = temp;
204 temp = x1;
205 x1 = y1;
206 y1 = temp;
209 dx = abs(x1 - x0);
210 dy = abs(y1 - y0);
212 error = 0;
213 derror = dy;
215 x = x0;
216 y = y0;
218 if (x0 < x1) {
219 xstep = 1;
220 } else {
221 xstep = -1;
224 if (y0 < y1) {
225 ystep = 1;
226 } else {
227 ystep = -1;
230 if (steep) {
231 pidgin_whiteboard_draw_brush_point(wb, y, x, color, size);
232 } else {
233 pidgin_whiteboard_draw_brush_point(wb, x, y, color, size);
236 while (x != x1) {
237 x += xstep;
238 error += derror;
240 if ((error * 2) >= dx) {
241 y += ystep;
242 error -= dx;
245 if (steep) {
246 pidgin_whiteboard_draw_brush_point(wb, y, x, color,
247 size);
248 } else {
249 pidgin_whiteboard_draw_brush_point(wb, x, y, color,
250 size);
255 static gboolean pidgin_whiteboard_brush_down(GtkWidget *widget, GdkEventButton *event, gpointer data)
257 PidginWhiteboard *gtkwb = (PidginWhiteboard*)data;
259 PurpleWhiteboard *wb = gtkwb->wb;
260 GList *draw_list = purple_whiteboard_get_draw_list(wb);
262 if (gtkwb->brush_state != PIDGIN_WHITEBOARD_BRUSH_UP) {
263 /* Potential double-click DOWN to DOWN? */
264 gtkwb->brush_state = PIDGIN_WHITEBOARD_BRUSH_DOWN;
266 /* return FALSE; */
269 gtkwb->brush_state = PIDGIN_WHITEBOARD_BRUSH_DOWN;
271 if (event->button == GDK_BUTTON_PRIMARY && gtkwb->cr != NULL) {
272 /* Check if draw_list has contents; if so, clear it */
273 if(draw_list)
275 purple_whiteboard_draw_list_destroy(draw_list);
276 draw_list = NULL;
279 /* Set tracking variables */
280 gtkwb->last_x = event->x;
281 gtkwb->last_y = event->y;
283 gtkwb->motion_count = 0;
285 draw_list = g_list_append(draw_list,
286 GINT_TO_POINTER(gtkwb->last_x));
287 draw_list = g_list_append(draw_list,
288 GINT_TO_POINTER(gtkwb->last_y));
290 pidgin_whiteboard_draw_brush_point(gtkwb->wb,
291 event->x, event->y,
292 gtkwb->brush_color, gtkwb->brush_size);
295 purple_whiteboard_set_draw_list(wb, draw_list);
297 return TRUE;
300 static gboolean pidgin_whiteboard_brush_motion(GtkWidget *widget, GdkEventMotion *event, gpointer data)
302 int x;
303 int y;
304 int dx;
305 int dy;
307 GdkModifierType state;
309 PidginWhiteboard *gtkwb = (PidginWhiteboard*)data;
311 PurpleWhiteboard *wb = gtkwb->wb;
312 GList *draw_list = purple_whiteboard_get_draw_list(wb);
314 if(event->is_hint)
315 gdk_window_get_device_position(event->window, event->device, &x, &y,
316 &state);
317 else
319 x = event->x;
320 y = event->y;
321 state = event->state;
324 if (state & GDK_BUTTON1_MASK && gtkwb->cr != NULL) {
325 if ((gtkwb->brush_state != PIDGIN_WHITEBOARD_BRUSH_DOWN) &&
326 (gtkwb->brush_state != PIDGIN_WHITEBOARD_BRUSH_MOTION)) {
327 purple_debug_error(
328 "gtkwhiteboard",
329 "***Bad brush state transition %d to MOTION\n",
330 gtkwb->brush_state);
332 gtkwb->brush_state = PIDGIN_WHITEBOARD_BRUSH_MOTION;
334 return FALSE;
336 gtkwb->brush_state = PIDGIN_WHITEBOARD_BRUSH_MOTION;
338 dx = x - gtkwb->last_x;
339 dy = y - gtkwb->last_y;
341 gtkwb->motion_count++;
343 /* NOTE 100 is a temporary constant for how many deltas/motions in a
344 * stroke (needs UI Ops?)
346 if (gtkwb->motion_count == 100) {
347 draw_list = g_list_append(draw_list, GINT_TO_POINTER(dx));
348 draw_list = g_list_append(draw_list, GINT_TO_POINTER(dy));
350 /* Send draw list to the draw_list handler */
351 purple_whiteboard_send_draw_list(gtkwb->wb, draw_list);
353 /* The brush stroke is finished, clear the list for another one */
354 if(draw_list)
356 purple_whiteboard_draw_list_destroy(draw_list);
357 draw_list = NULL;
360 /* Reset motion tracking */
361 gtkwb->motion_count = 0;
363 draw_list = g_list_append(
364 draw_list, GINT_TO_POINTER(gtkwb->last_x));
365 draw_list = g_list_append(
366 draw_list, GINT_TO_POINTER(gtkwb->last_y));
368 dx = x - gtkwb->last_x;
369 dy = y - gtkwb->last_y;
372 draw_list = g_list_append(draw_list, GINT_TO_POINTER(dx));
373 draw_list = g_list_append(draw_list, GINT_TO_POINTER(dy));
375 pidgin_whiteboard_draw_brush_line(
376 gtkwb->wb, gtkwb->last_x, gtkwb->last_y, x, y,
377 gtkwb->brush_color, gtkwb->brush_size);
379 /* Set tracking variables */
380 gtkwb->last_x = x;
381 gtkwb->last_y = y;
384 purple_whiteboard_set_draw_list(wb, draw_list);
386 return TRUE;
389 static gboolean pidgin_whiteboard_brush_up(GtkWidget *widget, GdkEventButton *event, gpointer data)
391 PidginWhiteboard *gtkwb = (PidginWhiteboard*)data;
393 PurpleWhiteboard *wb = gtkwb->wb;
394 GList *draw_list = purple_whiteboard_get_draw_list(wb);
396 if ((gtkwb->brush_state != PIDGIN_WHITEBOARD_BRUSH_DOWN) &&
397 (gtkwb->brush_state != PIDGIN_WHITEBOARD_BRUSH_MOTION)) {
398 purple_debug_error("gtkwhiteboard",
399 "***Bad brush state transition %d to UP\n",
400 gtkwb->brush_state);
402 gtkwb->brush_state = PIDGIN_WHITEBOARD_BRUSH_UP;
404 return FALSE;
406 gtkwb->brush_state = PIDGIN_WHITEBOARD_BRUSH_UP;
408 if (event->button == GDK_BUTTON_PRIMARY && gtkwb->cr != NULL) {
409 /* If the brush was never moved, express two sets of two deltas That's a
410 * 'point,' but not for Yahoo!
412 if (gtkwb->motion_count == 0) {
413 int index;
415 /* For Yahoo!, a (0 0) indicates the end of drawing */
416 /* FIXME: Yahoo Doodle specific! */
417 for (index = 0; index < 2; index++) {
418 draw_list = g_list_append(draw_list, 0);
419 draw_list = g_list_append(draw_list, 0);
423 /* Send draw list to protocol draw_list handler */
424 purple_whiteboard_send_draw_list(gtkwb->wb, draw_list);
426 pidgin_whiteboard_set_canvas_as_icon(gtkwb);
428 /* The brush stroke is finished, clear the list for another one
430 if (draw_list) {
431 purple_whiteboard_draw_list_destroy(draw_list);
434 purple_whiteboard_set_draw_list(wb, NULL);
437 return TRUE;
440 static void pidgin_whiteboard_set_dimensions(PurpleWhiteboard *wb, int width, int height)
442 PidginWhiteboard *gtkwb = purple_whiteboard_get_ui_data(wb);
444 gtkwb->width = width;
445 gtkwb->height = height;
448 static void pidgin_whiteboard_set_brush(PurpleWhiteboard *wb, int size, int color)
450 PidginWhiteboard *gtkwb = purple_whiteboard_get_ui_data(wb);
452 gtkwb->brush_size = size;
453 gtkwb->brush_color = color;
456 static void pidgin_whiteboard_clear(PurpleWhiteboard *wb)
458 PidginWhiteboard *gtkwb = purple_whiteboard_get_ui_data(wb);
459 GtkWidget *drawing_area = gtkwb->drawing_area;
460 cairo_t *cr = gtkwb->cr;
461 GtkAllocation allocation;
462 GdkRGBA white = {1.0, 1.0, 1.0, 1.0};
464 gtk_widget_get_allocation(drawing_area, &allocation);
466 gdk_cairo_set_source_rgba(cr, &white);
467 cairo_rectangle(cr, 0, 0, allocation.width, allocation.height);
468 cairo_fill(cr);
470 gtk_widget_queue_draw_area(drawing_area, 0, 0,
471 allocation.width, allocation.height);
474 static void pidgin_whiteboard_button_clear_press(GtkWidget *widget, gpointer data)
476 PidginWhiteboard *gtkwb = (PidginWhiteboard*)(data);
478 /* Confirm whether the user really wants to clear */
479 GtkWidget *dialog = gtk_message_dialog_new(
480 GTK_WINDOW(gtkwb), GTK_DIALOG_DESTROY_WITH_PARENT,
481 GTK_MESSAGE_QUESTION, GTK_BUTTONS_YES_NO, "%s",
482 _("Do you really want to clear?"));
483 gint response = gtk_dialog_run(GTK_DIALOG(dialog));
484 gtk_widget_destroy(dialog);
486 if (response == GTK_RESPONSE_YES)
488 pidgin_whiteboard_clear(gtkwb->wb);
490 pidgin_whiteboard_set_canvas_as_icon(gtkwb);
492 /* Do protocol specific clearing procedures */
493 purple_whiteboard_send_clear(gtkwb->wb);
497 static void
498 pidgin_whiteboard_button_save_press(GtkWidget *widget, gpointer _gtkwb)
500 PidginWhiteboard *gtkwb = _gtkwb;
501 GdkPixbuf *pixbuf;
502 GtkFileChooserNative *chooser;
503 int result;
505 chooser = gtk_file_chooser_native_new(_("Save File"), GTK_WINDOW(gtkwb),
506 GTK_FILE_CHOOSER_ACTION_SAVE,
507 _("_Save"), _("_Cancel"));
509 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(chooser),
510 TRUE);
511 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(chooser),
512 "whiteboard.png");
514 result = gtk_native_dialog_run(GTK_NATIVE_DIALOG(chooser));
515 if (result == GTK_RESPONSE_ACCEPT) {
516 gboolean success;
517 gchar *filename =
518 gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(chooser));
520 pixbuf = gdk_pixbuf_get_from_surface(
521 gtkwb->surface, 0, 0, gtkwb->width, gtkwb->height);
523 success = gdk_pixbuf_save(pixbuf, filename, "png", NULL,
524 "compression", "9", NULL);
525 g_object_unref(pixbuf);
526 if (success) {
527 purple_debug_info("gtkwhiteboard",
528 "whiteboard saved to \"%s\"", filename);
529 } else {
530 purple_notify_error(NULL, _("Whiteboard"),
531 _("Unable to save the file"), NULL, NULL);
532 purple_debug_error("gtkwhiteboard", "whiteboard "
533 "couldn't be saved to \"%s\"", filename);
535 g_free(filename);
538 g_object_unref(chooser);
541 static void
542 color_selected(GtkColorButton *button, PidginWhiteboard *gtkwb)
544 GdkRGBA color;
545 PurpleWhiteboard *wb = gtkwb->wb;
546 int old_size, old_color;
547 int new_color;
549 gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(button), &color);
551 new_color = (unsigned int)(color.red * 255) << 16;
552 new_color |= (unsigned int)(color.green * 255) << 8;
553 new_color |= (unsigned int)(color.blue * 255);
555 purple_whiteboard_get_brush(wb, &old_size, &old_color);
556 purple_whiteboard_send_brush(wb, old_size, new_color);
559 static void
560 pidgin_whiteboard_create(PurpleWhiteboard *wb)
562 PidginWhiteboard *gtkwb;
563 PurpleBuddy *buddy;
564 GdkRGBA color;
566 gtkwb = PIDGIN_WHITEBOARD(g_object_new(PIDGIN_TYPE_WHITEBOARD, NULL));
567 gtkwb->wb = wb;
568 purple_whiteboard_set_ui_data(wb, gtkwb);
570 /* Get dimensions (default?) for the whiteboard canvas */
571 if (!purple_whiteboard_get_dimensions(wb, &gtkwb->width,
572 &gtkwb->height)) {
573 /* Give some initial board-size */
574 gtkwb->width = 300;
575 gtkwb->height = 250;
578 if (!purple_whiteboard_get_brush(wb, &gtkwb->brush_size,
579 &gtkwb->brush_color)) {
580 /* Give some initial brush-info */
581 gtkwb->brush_size = 2;
582 gtkwb->brush_color = 0xff0000;
585 /* Try and set window title as the name of the buddy, else just use
586 * their username
588 buddy = purple_blist_find_buddy(purple_whiteboard_get_account(wb),
589 purple_whiteboard_get_who(wb));
591 gtk_window_set_title(GTK_WINDOW(gtkwb),
592 buddy != NULL
593 ? purple_buddy_get_contact_alias(buddy)
594 : purple_whiteboard_get_who(wb));
595 gtk_widget_set_name(GTK_WIDGET(gtkwb), purple_whiteboard_get_who(wb));
597 gtk_widget_set_size_request(GTK_WIDGET(gtkwb->drawing_area),
598 gtkwb->width, gtkwb->height);
600 pidgin_whiteboard_rgb24_to_rgba(gtkwb->brush_color, &color);
601 gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(gtkwb->color_button),
602 &color);
604 /* Make all this (window) visible */
605 gtk_widget_show(GTK_WIDGET(gtkwb));
607 pidgin_whiteboard_set_canvas_as_icon(gtkwb);
609 /* TODO Specific protocol/whiteboard assignment here? Needs a UI Op? */
610 /* Set default brush size and color */
612 ds->brush_size = DOODLE_BRUSH_MEDIUM;
613 ds->brush_color = 0;
617 static void
618 pidgin_whiteboard_destroy(PurpleWhiteboard *wb)
620 PidginWhiteboard *gtkwb;
622 g_return_if_fail(wb != NULL);
623 gtkwb = purple_whiteboard_get_ui_data(wb);
624 g_return_if_fail(gtkwb != NULL);
626 /* TODO Ask if user wants to save picture before the session is closed
629 gtkwb->wb = NULL;
630 g_object_unref(gtkwb);
631 purple_whiteboard_set_ui_data(wb, NULL);
634 /******************************************************************************
635 * GObject implementation
636 *****************************************************************************/
637 static void
638 pidgin_whiteboard_init(PidginWhiteboard *self)
640 gtk_widget_init_template(GTK_WIDGET(self));
642 self->brush_state = PIDGIN_WHITEBOARD_BRUSH_UP;
645 static void
646 pidgin_whiteboard_finalize(GObject *obj)
648 PidginWhiteboard *gtkwb = PIDGIN_WHITEBOARD(obj);
650 /* Clear graphical memory */
651 g_clear_pointer(&gtkwb->cr, cairo_destroy);
652 g_clear_pointer(&gtkwb->surface, cairo_surface_destroy);
654 G_OBJECT_CLASS(pidgin_whiteboard_parent_class)->finalize(obj);
657 static void
658 pidgin_whiteboard_class_init(PidginWhiteboardClass *klass)
660 GObjectClass *obj_class = G_OBJECT_CLASS(klass);
661 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
663 obj_class->finalize = pidgin_whiteboard_finalize;
665 gtk_widget_class_set_template_from_resource(
666 widget_class, "/im/pidgin/Pidgin/Whiteboard/whiteboard.ui");
668 gtk_widget_class_bind_template_child(widget_class, PidginWhiteboard,
669 drawing_area);
670 gtk_widget_class_bind_template_child(widget_class, PidginWhiteboard,
671 color_button);
673 gtk_widget_class_bind_template_callback(
674 widget_class, whiteboard_close_cb);
675 gtk_widget_class_bind_template_callback(
676 widget_class, pidgin_whiteboard_draw_event);
677 gtk_widget_class_bind_template_callback(
678 widget_class, pidgin_whiteboard_configure_event);
679 gtk_widget_class_bind_template_callback(
680 widget_class, pidgin_whiteboard_brush_down);
681 gtk_widget_class_bind_template_callback(
682 widget_class, pidgin_whiteboard_brush_motion);
683 gtk_widget_class_bind_template_callback(
684 widget_class, pidgin_whiteboard_brush_up);
685 gtk_widget_class_bind_template_callback(
686 widget_class, pidgin_whiteboard_button_clear_press);
687 gtk_widget_class_bind_template_callback(
688 widget_class, pidgin_whiteboard_button_save_press);
689 gtk_widget_class_bind_template_callback(
690 widget_class, color_selected);
693 /******************************************************************************
694 * API
695 *****************************************************************************/
696 static PurpleWhiteboardUiOps ui_ops =
698 pidgin_whiteboard_create,
699 pidgin_whiteboard_destroy,
700 pidgin_whiteboard_set_dimensions,
701 pidgin_whiteboard_set_brush,
702 pidgin_whiteboard_draw_brush_point,
703 pidgin_whiteboard_draw_brush_line,
704 pidgin_whiteboard_clear,
705 NULL,
706 NULL,
707 NULL,
708 NULL
711 PurpleWhiteboardUiOps *
712 pidgin_whiteboard_get_ui_ops(void)
714 return &ui_ops;