1 /*
2  * pidgin
3  *
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.
7  *
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.
12  *
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.
17  *
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
21  *
22  */
23 
24 #include "internal.h"
25 #include "blist.h"
26 #include "debug.h"
27 
28 #include "gtkwhiteboard.h"
29 #include "gtkutils.h"
30 
31 /******************************************************************************
32  * Prototypes
33  *****************************************************************************/
34 static void pidgin_whiteboard_create(PurpleWhiteboard *wb);
35 
36 static void pidgin_whiteboard_destroy(PurpleWhiteboard *wb);
37 static gboolean whiteboard_close_cb(GtkWidget *widget, GdkEvent *event, PidginWhiteboard *gtkwb);
38 
39 /*static void pidginwhiteboard_button_start_press(GtkButton *button, gpointer data); */
40 
41 static gboolean pidgin_whiteboard_configure_event(GtkWidget *widget, GdkEventConfigure *event, gpointer data);
42 static gboolean pidgin_whiteboard_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data);
43 
44 static gboolean pidgin_whiteboard_brush_down(GtkWidget *widget, GdkEventButton *event, gpointer data);
45 static gboolean pidgin_whiteboard_brush_motion(GtkWidget *widget, GdkEventMotion *event, gpointer data);
46 static gboolean pidgin_whiteboard_brush_up(GtkWidget *widget, GdkEventButton *event, gpointer data);
47 
48 static void pidgin_whiteboard_draw_brush_point(PurpleWhiteboard *wb,
49 												  int x, int y, int color, int size);
50 static void pidgin_whiteboard_draw_brush_line(PurpleWhiteboard *wb, int x0, int y0,
51 												int x1, int y1, int color, int size);
52 
53 static void pidgin_whiteboard_set_dimensions(PurpleWhiteboard *wb, int width, int height);
54 static void pidgin_whiteboard_set_brush(PurpleWhiteboard *wb, int size, int color);
55 static void pidgin_whiteboard_clear(PurpleWhiteboard *wb);
56 
57 static void pidgin_whiteboard_button_clear_press(GtkWidget *widget, gpointer data);
58 static void pidgin_whiteboard_button_save_press(GtkWidget *widget, gpointer data);
59 
60 static void pidgin_whiteboard_set_canvas_as_icon(PidginWhiteboard *gtkwb);
61 
62 static void pidgin_whiteboard_rgb24_to_rgb48(int color_rgb, GdkColor *color);
63 
64 static void color_select_dialog(GtkWidget *widget, PidginWhiteboard *gtkwb);
65 
66 /******************************************************************************
67  * Globals
68  *****************************************************************************/
69 /*
70 GList *buttonList = NULL;
71 GdkColor DefaultColor[PALETTE_NUM_COLORS];
72 */
73 
74 static int LastX;       /* Tracks last position of the mouse when drawing */
75 static int LastY;
76 static int MotionCount; /* Tracks how many brush motions made */
77 static int BrushState = BRUSH_STATE_UP;
78 
79 static PurpleWhiteboardUiOps ui_ops =
80 {
81 	pidgin_whiteboard_create,
82 	pidgin_whiteboard_destroy,
83 	pidgin_whiteboard_set_dimensions,
84 	pidgin_whiteboard_set_brush,
85 	pidgin_whiteboard_draw_brush_point,
86 	pidgin_whiteboard_draw_brush_line,
87 	pidgin_whiteboard_clear,
88 	NULL,
89 	NULL,
90 	NULL,
91 	NULL
92 };
93 
94 /******************************************************************************
95  * API
96  *****************************************************************************/
pidgin_whiteboard_get_ui_ops(void)97 PurpleWhiteboardUiOps *pidgin_whiteboard_get_ui_ops(void)
98 {
99 	return &ui_ops;
100 }
101 
pidgin_whiteboard_create(PurpleWhiteboard * wb)102 static void pidgin_whiteboard_create(PurpleWhiteboard *wb)
103 {
104 	PurpleBuddy *buddy;
105 	GtkWidget *window;
106 	GtkWidget *drawing_area;
107 	GtkWidget *vbox_controls;
108 	GtkWidget *hbox_canvas_and_controls;
109 
110 	/*
111 		--------------------------
112 		|[][][][palette[][][][][]|
113 		|------------------------|
114 		|       canvas     | con |
115 		|                  | trol|
116 		|                  | s   |
117 		|                  |     |
118 		|                  |     |
119 		--------------------------
120 	*/
121 	GtkWidget *clear_button;
122 	GtkWidget *save_button;
123 	GtkWidget *color_button;
124 
125 	PidginWhiteboard *gtkwb = g_new0(PidginWhiteboard, 1);
126 
127 	gtkwb->wb = wb;
128 	wb->ui_data = gtkwb;
129 
130 	/* Get dimensions (default?) for the whiteboard canvas */
131 	if (!purple_whiteboard_get_dimensions(wb, &gtkwb->width, &gtkwb->height))
132 	{
133 		/* Give some initial board-size */
134 		gtkwb->width = 300;
135 		gtkwb->height = 250;
136 	}
137 
138 	if (!purple_whiteboard_get_brush(wb, &gtkwb->brush_size, &gtkwb->brush_color))
139 	{
140 		/* Give some initial brush-info */
141 		gtkwb->brush_size = 2;
142 		gtkwb->brush_color = 0xff0000;
143 	}
144 
145 	/* Try and set window title as the name of the buddy, else just use their
146 	 * username
147 	 */
148 	buddy = purple_find_buddy(wb->account, wb->who);
149 
150 	window = pidgin_create_window(buddy != NULL ? purple_buddy_get_contact_alias(buddy) : wb->who, 0, NULL, FALSE);
151 	gtkwb->window = window;
152 	gtk_widget_set_name(window, wb->who);
153 
154 	g_signal_connect(G_OBJECT(window), "delete_event",
155 					 G_CALLBACK(whiteboard_close_cb), gtkwb);
156 
157 #if 0
158 	int i;
159 
160 	GtkWidget *hbox_palette;
161 	GtkWidget *vbox_palette_above_canvas_and_controls;
162 	GtkWidget *palette_color_box[PALETTE_NUM_COLORS];
163 
164 	/* Create vertical box to place palette above the canvas and controls */
165 	vbox_palette_above_canvas_and_controls = gtk_vbox_new(FALSE, 0);
166 	gtk_container_add(GTK_CONTAINER(window), vbox_palette_above_canvas_and_controls);
167 	gtk_widget_show(vbox_palette_above_canvas_and_controls);
168 
169 	/* Create horizontal box for the palette and all its entries */
170 	hbox_palette = gtk_hbox_new(FALSE, 0);
171 	gtk_box_pack_start(GTK_BOX(vbox_palette_above_canvas_and_controls),
172 			hbox_palette, FALSE, FALSE, PIDGIN_HIG_BORDER);
173 	gtk_widget_show(hbox_palette);
174 
175 	/* Create horizontal box to seperate the canvas from the controls */
176 	hbox_canvas_and_controls = gtk_hbox_new(FALSE, 0);
177 	gtk_box_pack_start(GTK_BOX(vbox_palette_above_canvas_and_controls),
178 			hbox_canvas_and_controls, FALSE, FALSE, PIDGIN_HIG_BORDER);
179 	gtk_widget_show(hbox_canvas_and_controls);
180 
181 	for(i = 0; i < PALETTE_NUM_COLORS; i++)
182 	{
183 		palette_color_box[i] = gtk_image_new_from_pixbuf(NULL);
184 		gtk_widget_set_size_request(palette_color_box[i], gtkwb->width / PALETTE_NUM_COLORS ,32);
185 		gtk_container_add(GTK_CONTAINER(hbox_palette), palette_color_box[i]);
186 
187 		gtk_widget_show(palette_color_box[i]);
188 	}
189 #endif
190 
191 	hbox_canvas_and_controls = gtk_hbox_new(FALSE, 0);
192 	gtk_widget_show(hbox_canvas_and_controls);
193 
194 	gtk_container_add(GTK_CONTAINER(window), hbox_canvas_and_controls);
195 	gtk_container_set_border_width(GTK_CONTAINER(window), PIDGIN_HIG_BORDER);
196 
197 	/* Create the drawing area */
198 	drawing_area = gtk_drawing_area_new();
199 	gtkwb->drawing_area = drawing_area;
200 	gtk_widget_set_size_request(GTK_WIDGET(drawing_area), gtkwb->width, gtkwb->height);
201 	gtk_box_pack_start(GTK_BOX(hbox_canvas_and_controls), drawing_area, TRUE, TRUE, PIDGIN_HIG_BOX_SPACE);
202 
203 	gtk_widget_show(drawing_area);
204 
205 	/* Signals used to handle backing pixmap */
206 	g_signal_connect(G_OBJECT(drawing_area), "expose_event",
207 					 G_CALLBACK(pidgin_whiteboard_expose_event), gtkwb);
208 
209 	g_signal_connect(G_OBJECT(drawing_area), "configure_event",
210 					 G_CALLBACK(pidgin_whiteboard_configure_event), gtkwb);
211 
212 	/* Event signals */
213 	g_signal_connect(G_OBJECT(drawing_area), "button_press_event",
214 					 G_CALLBACK(pidgin_whiteboard_brush_down), gtkwb);
215 
216 	g_signal_connect(G_OBJECT(drawing_area), "motion_notify_event",
217 					 G_CALLBACK(pidgin_whiteboard_brush_motion), gtkwb);
218 
219 	g_signal_connect(G_OBJECT(drawing_area), "button_release_event",
220 					 G_CALLBACK(pidgin_whiteboard_brush_up), gtkwb);
221 
222 	gtk_widget_set_events(drawing_area,
223 						  GDK_EXPOSURE_MASK |
224 						  GDK_LEAVE_NOTIFY_MASK |
225 						  GDK_BUTTON_PRESS_MASK |
226 						  GDK_POINTER_MOTION_MASK |
227 						  GDK_BUTTON_RELEASE_MASK |
228 						  GDK_POINTER_MOTION_HINT_MASK);
229 
230 	/* Create vertical box to contain the controls */
231 	vbox_controls = gtk_vbox_new(FALSE, 0);
232 	gtk_box_pack_start(GTK_BOX(hbox_canvas_and_controls),
233 					vbox_controls, FALSE, FALSE, PIDGIN_HIG_BOX_SPACE);
234 	gtk_widget_show(vbox_controls);
235 
236 	/* Add a clear button */
237 	clear_button = gtk_button_new_from_stock(GTK_STOCK_CLEAR);
238 	gtk_box_pack_start(GTK_BOX(vbox_controls), clear_button, FALSE, FALSE, PIDGIN_HIG_BOX_SPACE);
239 	gtk_widget_show(clear_button);
240 	g_signal_connect(G_OBJECT(clear_button), "clicked",
241 					 G_CALLBACK(pidgin_whiteboard_button_clear_press), gtkwb);
242 
243 	/* Add a save button */
244 	save_button = gtk_button_new_from_stock(GTK_STOCK_SAVE);
245 	gtk_box_pack_start(GTK_BOX(vbox_controls), save_button, FALSE, FALSE, PIDGIN_HIG_BOX_SPACE);
246 	gtk_widget_show(save_button);
247 
248 	g_signal_connect(G_OBJECT(save_button), "clicked",
249 					 G_CALLBACK(pidgin_whiteboard_button_save_press), gtkwb);
250 
251 	/* Add a color selector */
252 	color_button = gtk_button_new_from_stock(GTK_STOCK_SELECT_COLOR);
253 	gtk_box_pack_start(GTK_BOX(vbox_controls), color_button, FALSE, FALSE, PIDGIN_HIG_BOX_SPACE);
254 	gtk_widget_show(color_button);
255 	g_signal_connect(G_OBJECT(color_button), "clicked",
256 					 G_CALLBACK(color_select_dialog), gtkwb);
257 
258 	/* Make all this (window) visible */
259 	gtk_widget_show(window);
260 
261 	pidgin_whiteboard_set_canvas_as_icon(gtkwb);
262 
263 	/* TODO Specific protocol/whiteboard assignment here? Needs a UI Op? */
264 	/* Set default brush size and color */
265 	/*
266 	ds->brush_size = DOODLE_BRUSH_MEDIUM;
267 	ds->brush_color = 0;
268 	*/
269 }
270 
pidgin_whiteboard_destroy(PurpleWhiteboard * wb)271 static void pidgin_whiteboard_destroy(PurpleWhiteboard *wb)
272 {
273 	PidginWhiteboard *gtkwb;
274 	GtkWidget *colour_dialog;
275 
276 	g_return_if_fail(wb != NULL);
277 	gtkwb = wb->ui_data;
278 	g_return_if_fail(gtkwb != NULL);
279 
280 	/* TODO Ask if user wants to save picture before the session is closed */
281 
282 	/* Clear graphical memory */
283 	if(gtkwb->pixmap)
284 	{
285 		cairo_t *cr = g_object_get_data(G_OBJECT(gtkwb->pixmap), "cairo-context");
286 		if (cr)
287 			cairo_destroy(cr);
288 		g_object_unref(gtkwb->pixmap);
289 		gtkwb->pixmap = NULL;
290 	}
291 
292 	colour_dialog = g_object_get_data(G_OBJECT(gtkwb->window), "colour-dialog");
293 	if (colour_dialog) {
294 		gtk_widget_destroy(colour_dialog);
295 		g_object_set_data(G_OBJECT(gtkwb->window), "colour-dialog", NULL);
296 	}
297 
298 	if(gtkwb->window)
299 	{
300 		gtk_widget_destroy(gtkwb->window);
301 		gtkwb->window = NULL;
302 	}
303 	g_free(gtkwb);
304 	wb->ui_data = NULL;
305 }
306 
whiteboard_close_cb(GtkWidget * widget,GdkEvent * event,PidginWhiteboard * gtkwb)307 static gboolean whiteboard_close_cb(GtkWidget *widget, GdkEvent *event, PidginWhiteboard *gtkwb)
308 {
309 	PurpleWhiteboard *wb;
310 
311 	g_return_val_if_fail(gtkwb != NULL, FALSE);
312 	wb = gtkwb->wb;
313 	g_return_val_if_fail(wb != NULL, FALSE);
314 
315 	purple_whiteboard_destroy(wb);
316 
317 	return FALSE;
318 }
319 
320 /*
321  * Whiteboard start button on conversation window (move this code to gtkconv?
322  * and use new prpl_info member?)
323  */
324 #if 0
325 static void pidginwhiteboard_button_start_press(GtkButton *button, gpointer data)
326 {
327 	PurpleConversation *conv = data;
328 	PurpleAccount *account = purple_conversation_get_account(conv);
329 	PurpleConnection *gc = purple_account_get_connection(account);
330 	char *to = (char*)(purple_conversation_get_name(conv));
331 
332 	/* Only handle this if local client requested Doodle session (else local
333 	 * client would have sent one)
334 	 */
335 	PurpleWhiteboard *wb = purple_whiteboard_get(account, to);
336 
337 	/* Write a local message to this conversation showing that a request for a
338 	 * Doodle session has been made
339 	 */
340 	/* XXXX because otherwise gettext will see this string, even though it's
341 	 * in an #if 0 block. Remove the XXXX if you want to use this code.
342 	 * But, it really shouldn't be a Yahoo-specific string. ;) */
343 	purple_conv_im_write(PURPLE_CONV_IM(conv), "", XXXX_("Sent Doodle request."),
344 					   PURPLE_MESSAGE_NICK | PURPLE_MESSAGE_RECV, time(NULL));
345 
346 	yahoo_doodle_command_send_request(gc, to);
347 	yahoo_doodle_command_send_ready(gc, to);
348 
349 	/* Insert this 'session' in the list.  At this point, it's only a requested
350 	 * session.
351 	 */
352 	wb = purple_whiteboard_create(account, to, DOODLE_STATE_REQUESTING);
353 }
354 #endif
355 
pidgin_whiteboard_configure_event(GtkWidget * widget,GdkEventConfigure * event,gpointer data)356 static gboolean pidgin_whiteboard_configure_event(GtkWidget *widget, GdkEventConfigure *event, gpointer data)
357 {
358 	PidginWhiteboard *gtkwb = (PidginWhiteboard*)data;
359 	GdkPixmap *pixmap = gtkwb->pixmap;
360 	cairo_t *cr;
361 
362 	if (pixmap) {
363 		cr = g_object_get_data(G_OBJECT(pixmap), "cairo-context");
364 		if (cr)
365 			cairo_destroy(cr);
366 		g_object_unref(pixmap);
367 	}
368 
369 	pixmap = gdk_pixmap_new(widget->window,
370 							widget->allocation.width,
371 							widget->allocation.height,
372 							-1);
373 	gtkwb->pixmap = pixmap;
374 
375 	cr = gdk_cairo_create(GDK_DRAWABLE(pixmap));
376 	g_object_set_data(G_OBJECT(pixmap), "cairo-context", cr);
377 	gdk_cairo_set_source_color(cr, &widget->style->white);
378 	cairo_rectangle(cr,
379 	                0, 0,
380 	                widget->allocation.width, widget->allocation.height);
381 	cairo_fill(cr);
382 
383 	return TRUE;
384 }
385 
pidgin_whiteboard_expose_event(GtkWidget * widget,GdkEventExpose * event,gpointer data)386 static gboolean pidgin_whiteboard_expose_event(GtkWidget *widget, GdkEventExpose *event, gpointer data)
387 {
388 	PidginWhiteboard *gtkwb = (PidginWhiteboard*)(data);
389 	GdkPixmap *pixmap = gtkwb->pixmap;
390 	cairo_t *cr;
391 
392 	cr = gdk_cairo_create(GDK_DRAWABLE(widget->window));
393 	gdk_cairo_set_source_pixmap(cr, pixmap, 0, 0);
394 	cairo_rectangle(cr,
395 	                event->area.x, event->area.y,
396 	                event->area.width, event->area.height);
397 	cairo_fill(cr);
398 	cairo_destroy(cr);
399 
400 	return FALSE;
401 }
402 
pidgin_whiteboard_brush_down(GtkWidget * widget,GdkEventButton * event,gpointer data)403 static gboolean pidgin_whiteboard_brush_down(GtkWidget *widget, GdkEventButton *event, gpointer data)
404 {
405 	PidginWhiteboard *gtkwb = (PidginWhiteboard*)data;
406 	GdkPixmap *pixmap = gtkwb->pixmap;
407 
408 	PurpleWhiteboard *wb = gtkwb->wb;
409 	GList *draw_list = wb->draw_list;
410 
411 	if(BrushState != BRUSH_STATE_UP)
412 	{
413 		/* Potential double-click DOWN to DOWN? */
414 		BrushState = BRUSH_STATE_DOWN;
415 
416 		/* return FALSE; */
417 	}
418 
419 	BrushState = BRUSH_STATE_DOWN;
420 
421 	if(event->button == 1 && pixmap != NULL)
422 	{
423 		/* Check if draw_list has contents; if so, clear it */
424 		if(draw_list)
425 		{
426 			purple_whiteboard_draw_list_destroy(draw_list);
427 			draw_list = NULL;
428 		}
429 
430 		/* Set tracking variables */
431 		LastX = event->x;
432 		LastY = event->y;
433 
434 		MotionCount = 0;
435 
436 		draw_list = g_list_append(draw_list, GINT_TO_POINTER(LastX));
437 		draw_list = g_list_append(draw_list, GINT_TO_POINTER(LastY));
438 
439 		pidgin_whiteboard_draw_brush_point(gtkwb->wb,
440 											 event->x, event->y,
441 											 gtkwb->brush_color, gtkwb->brush_size);
442 	}
443 
444 	wb->draw_list = draw_list;
445 
446 	return TRUE;
447 }
448 
pidgin_whiteboard_brush_motion(GtkWidget * widget,GdkEventMotion * event,gpointer data)449 static gboolean pidgin_whiteboard_brush_motion(GtkWidget *widget, GdkEventMotion *event, gpointer data)
450 {
451 	int x;
452 	int y;
453 	int dx;
454 	int dy;
455 
456 	GdkModifierType state;
457 
458 	PidginWhiteboard *gtkwb = (PidginWhiteboard*)data;
459 	GdkPixmap *pixmap = gtkwb->pixmap;
460 
461 	PurpleWhiteboard *wb = gtkwb->wb;
462 	GList *draw_list = wb->draw_list;
463 
464 	if(event->is_hint)
465 		gdk_window_get_pointer(event->window, &x, &y, &state);
466 	else
467 	{
468 		x = event->x;
469 		y = event->y;
470 		state = event->state;
471 	}
472 
473 	if(state & GDK_BUTTON1_MASK && pixmap != NULL)
474 	{
475 		if((BrushState != BRUSH_STATE_DOWN) && (BrushState != BRUSH_STATE_MOTION))
476 		{
477 			purple_debug_error("gtkwhiteboard", "***Bad brush state transition %d to MOTION\n", BrushState);
478 
479 			BrushState = BRUSH_STATE_MOTION;
480 
481 			return FALSE;
482 		}
483 		BrushState = BRUSH_STATE_MOTION;
484 
485 		dx = x - LastX;
486 		dy = y - LastY;
487 
488 		MotionCount++;
489 
490 		/* NOTE 100 is a temporary constant for how many deltas/motions in a
491 		 * stroke (needs UI Ops?)
492 		 */
493 		if(MotionCount == 100)
494 		{
495 			draw_list = g_list_append(draw_list, GINT_TO_POINTER(dx));
496 			draw_list = g_list_append(draw_list, GINT_TO_POINTER(dy));
497 
498 			/* Send draw list to the draw_list handler */
499 			purple_whiteboard_send_draw_list(gtkwb->wb, draw_list);
500 
501 			/* The brush stroke is finished, clear the list for another one */
502 			if(draw_list)
503 			{
504 				purple_whiteboard_draw_list_destroy(draw_list);
505 				draw_list = NULL;
506 			}
507 
508 			/* Reset motion tracking */
509 			MotionCount = 0;
510 
511 			draw_list = g_list_append(draw_list, GINT_TO_POINTER(LastX));
512 			draw_list = g_list_append(draw_list, GINT_TO_POINTER(LastY));
513 
514 			dx = x - LastX;
515 			dy = y - LastY;
516 		}
517 
518 		draw_list = g_list_append(draw_list, GINT_TO_POINTER(dx));
519 		draw_list = g_list_append(draw_list, GINT_TO_POINTER(dy));
520 
521 		pidgin_whiteboard_draw_brush_line(gtkwb->wb,
522 											LastX, LastY,
523 											x, y,
524 											gtkwb->brush_color, gtkwb->brush_size);
525 
526 		/* Set tracking variables */
527 		LastX = x;
528 		LastY = y;
529 	}
530 
531 	wb->draw_list = draw_list;
532 
533 	return TRUE;
534 }
535 
pidgin_whiteboard_brush_up(GtkWidget * widget,GdkEventButton * event,gpointer data)536 static gboolean pidgin_whiteboard_brush_up(GtkWidget *widget, GdkEventButton *event, gpointer data)
537 {
538 	PidginWhiteboard *gtkwb = (PidginWhiteboard*)data;
539 	GdkPixmap *pixmap = gtkwb->pixmap;
540 
541 	PurpleWhiteboard *wb = gtkwb->wb;
542 	GList *draw_list = wb->draw_list;
543 
544 	if((BrushState != BRUSH_STATE_DOWN) && (BrushState != BRUSH_STATE_MOTION))
545 	{
546 		purple_debug_error("gtkwhiteboard", "***Bad brush state transition %d to UP\n", BrushState);
547 
548 		BrushState = BRUSH_STATE_UP;
549 
550 		return FALSE;
551 	}
552 	BrushState = BRUSH_STATE_UP;
553 
554 	if(event->button == 1 && pixmap != NULL)
555 	{
556 		/* If the brush was never moved, express two sets of two deltas That's a
557 		 * 'point,' but not for Yahoo!
558 		 */
559 		/* if((event->x == LastX) && (event->y == LastY)) */
560 		if(MotionCount == 0)
561 		{
562 			int index;
563 
564 			/* For Yahoo!, a (0 0) indicates the end of drawing */
565 			/* FIXME: Yahoo Doodle specific! */
566 			for(index = 0; index < 2; index++)
567 			{
568 				draw_list = g_list_append(draw_list, 0);
569 				draw_list = g_list_append(draw_list, 0);
570 			}
571 		}
572 		/*
573 		else
574 			MotionCount = 0;
575 		*/
576 
577 		/* Send draw list to prpl draw_list handler */
578 		purple_whiteboard_send_draw_list(gtkwb->wb, draw_list);
579 
580 		pidgin_whiteboard_set_canvas_as_icon(gtkwb);
581 
582 		/* The brush stroke is finished, clear the list for another one */
583 		if(draw_list)
584 			purple_whiteboard_draw_list_destroy(draw_list);
585 
586 		wb->draw_list = NULL;
587 	}
588 
589 	return TRUE;
590 }
591 
pidgin_whiteboard_draw_brush_point(PurpleWhiteboard * wb,int x,int y,int color,int size)592 static void pidgin_whiteboard_draw_brush_point(PurpleWhiteboard *wb, int x, int y, int color, int size)
593 {
594 	PidginWhiteboard *gtkwb = wb->ui_data;
595 	GtkWidget *widget = gtkwb->drawing_area;
596 	GdkPixmap *pixmap = gtkwb->pixmap;
597 
598 	cairo_t *gfx_con = g_object_get_data(G_OBJECT(pixmap), "cairo-context");
599 	GdkColor col;
600 
601 	/* Interpret and convert color */
602 	pidgin_whiteboard_rgb24_to_rgb48(color, &col);
603 
604 	gdk_cairo_set_source_color(gfx_con, &col);
605 
606 	/* Draw a circle */
607 	cairo_arc(gfx_con,
608 	          x, y,
609 	          size / 2.0,
610 	          0.0, 2.0 * M_PI);
611 	cairo_fill(gfx_con);
612 
613 	gtk_widget_queue_draw_area(widget,
614 							   x - size / 2, y - size / 2,
615 							   size, size);
616 }
617 
618 /* Uses Bresenham's algorithm (as provided by Wikipedia) */
pidgin_whiteboard_draw_brush_line(PurpleWhiteboard * wb,int x0,int y0,int x1,int y1,int color,int size)619 static void pidgin_whiteboard_draw_brush_line(PurpleWhiteboard *wb, int x0, int y0, int x1, int y1, int color, int size)
620 {
621 	int temp;
622 
623 	int xstep;
624 	int ystep;
625 
626 	int dx;
627 	int dy;
628 
629 	int error;
630 	int derror;
631 
632 	int x;
633 	int y;
634 
635 	gboolean steep = abs(y1 - y0) > abs(x1 - x0);
636 
637 	if(steep)
638 	{
639 		temp = x0; x0 = y0; y0 = temp;
640 		temp = x1; x1 = y1; y1 = temp;
641 	}
642 
643 	dx = abs(x1 - x0);
644 	dy = abs(y1 - y0);
645 
646 	error = 0;
647 	derror = dy;
648 
649 	x = x0;
650 	y = y0;
651 
652 	if(x0 < x1)
653 		xstep = 1;
654 	else
655 		xstep = -1;
656 
657 	if(y0 < y1)
658 		ystep = 1;
659 	else
660 		ystep = -1;
661 
662 	if(steep)
663 		pidgin_whiteboard_draw_brush_point(wb, y, x, color, size);
664 	else
665 		pidgin_whiteboard_draw_brush_point(wb, x, y, color, size);
666 
667 	while(x != x1)
668 	{
669 		x += xstep;
670 		error += derror;
671 
672 		if((error * 2) >= dx)
673 		{
674 			y += ystep;
675 			error -= dx;
676 		}
677 
678 		if(steep)
679 			pidgin_whiteboard_draw_brush_point(wb, y, x, color, size);
680 		else
681 			pidgin_whiteboard_draw_brush_point(wb, x, y, color, size);
682 	}
683 }
684 
pidgin_whiteboard_set_dimensions(PurpleWhiteboard * wb,int width,int height)685 static void pidgin_whiteboard_set_dimensions(PurpleWhiteboard *wb, int width, int height)
686 {
687 	PidginWhiteboard *gtkwb = wb->ui_data;
688 
689 	gtkwb->width = width;
690 	gtkwb->height = height;
691 }
692 
pidgin_whiteboard_set_brush(PurpleWhiteboard * wb,int size,int color)693 static void pidgin_whiteboard_set_brush(PurpleWhiteboard *wb, int size, int color)
694 {
695 	PidginWhiteboard *gtkwb = wb->ui_data;
696 
697 	gtkwb->brush_size = size;
698 	gtkwb->brush_color = color;
699 }
700 
pidgin_whiteboard_clear(PurpleWhiteboard * wb)701 static void pidgin_whiteboard_clear(PurpleWhiteboard *wb)
702 {
703 	PidginWhiteboard *gtkwb = wb->ui_data;
704 	GdkPixmap *pixmap = gtkwb->pixmap;
705 	GtkWidget *drawing_area = gtkwb->drawing_area;
706 	cairo_t *cr = g_object_get_data(G_OBJECT(pixmap), "cairo-context");
707 
708 	gdk_cairo_set_source_color(cr, &drawing_area->style->white);
709 	cairo_rectangle(cr,
710 	                0, 0,
711 	                drawing_area->allocation.width,
712 	                drawing_area->allocation.height);
713 	cairo_fill(cr);
714 
715 	gtk_widget_queue_draw_area(drawing_area,
716 							   0, 0,
717 							   drawing_area->allocation.width,
718 							   drawing_area->allocation.height);
719 }
720 
pidgin_whiteboard_button_clear_press(GtkWidget * widget,gpointer data)721 static void pidgin_whiteboard_button_clear_press(GtkWidget *widget, gpointer data)
722 {
723 	PidginWhiteboard *gtkwb = (PidginWhiteboard*)(data);
724 
725 	/* Confirm whether the user really wants to clear */
726 	GtkWidget *dialog = gtk_message_dialog_new(GTK_WINDOW(gtkwb->window),
727 											   GTK_DIALOG_DESTROY_WITH_PARENT,
728 											   GTK_MESSAGE_QUESTION,
729 											   GTK_BUTTONS_YES_NO,
730 											   _("Do you really want to clear?"));
731 	gint response = gtk_dialog_run(GTK_DIALOG(dialog));
732 	gtk_widget_destroy(dialog);
733 
734 	if (response == GTK_RESPONSE_YES)
735 	{
736 		pidgin_whiteboard_clear(gtkwb->wb);
737 
738 		pidgin_whiteboard_set_canvas_as_icon(gtkwb);
739 
740 		/* Do protocol specific clearing procedures */
741 		purple_whiteboard_send_clear(gtkwb->wb);
742 	}
743 }
744 
pidgin_whiteboard_button_save_press(GtkWidget * widget,gpointer data)745 static void pidgin_whiteboard_button_save_press(GtkWidget *widget, gpointer data)
746 {
747 	PidginWhiteboard *gtkwb = (PidginWhiteboard*)(data);
748 	GdkPixbuf *pixbuf;
749 
750 	GtkWidget *dialog;
751 
752 	int result;
753 
754 	dialog = gtk_file_chooser_dialog_new (_("Save File"),
755 										  GTK_WINDOW(gtkwb->window),
756 										  GTK_FILE_CHOOSER_ACTION_SAVE,
757 										  GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
758 										  GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
759 										  NULL);
760 
761 	/* gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), (gboolean)(TRUE)); */
762 
763 	/* if(user_edited_a_new_document) */
764 	{
765 	/* gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dialog), default_folder_for_saving); */
766 		gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "whiteboard.jpg");
767 	}
768 	/*
769 	else
770 		gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (dialog), filename_for_existing_document);
771 	*/
772 
773 	result = gtk_dialog_run(GTK_DIALOG(dialog));
774 
775 	if(result == GTK_RESPONSE_ACCEPT)
776 	{
777 		char *filename;
778 
779 		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
780 
781 		gtk_widget_destroy(dialog);
782 
783 		/* Makes an icon from the whiteboard's canvas 'image' */
784 		pixbuf = gdk_pixbuf_get_from_drawable(NULL,
785 											  (GdkDrawable*)(gtkwb->pixmap),
786 											  gdk_drawable_get_colormap(gtkwb->pixmap),
787 											  0, 0,
788 											  0, 0,
789 											  gtkwb->width, gtkwb->height);
790 
791 		if(gdk_pixbuf_save(pixbuf, filename, "jpeg", NULL, "quality", "100", NULL))
792 			purple_debug_info("gtkwhiteboard", "File Saved...\n");
793 		else
794 			purple_debug_info("gtkwhiteboard", "File not Saved... Error\n");
795 		g_free(filename);
796 	}
797 	else if(result == GTK_RESPONSE_CANCEL)
798 	{
799 		gtk_widget_destroy(dialog);
800 
801 		purple_debug_info("gtkwhiteboard", "File not Saved... Cancelled\n");
802 	}
803 }
804 
pidgin_whiteboard_set_canvas_as_icon(PidginWhiteboard * gtkwb)805 static void pidgin_whiteboard_set_canvas_as_icon(PidginWhiteboard *gtkwb)
806 {
807 	GdkPixbuf *pixbuf;
808 
809 	/* Makes an icon from the whiteboard's canvas 'image' */
810 	pixbuf = gdk_pixbuf_get_from_drawable(NULL,
811 										  (GdkDrawable*)(gtkwb->pixmap),
812 										  gdk_drawable_get_colormap(gtkwb->pixmap),
813 										  0, 0,
814 										  0, 0,
815 										  gtkwb->width, gtkwb->height);
816 
817 	gtk_window_set_icon((GtkWindow*)(gtkwb->window), pixbuf);
818 }
819 
pidgin_whiteboard_rgb24_to_rgb48(int color_rgb,GdkColor * color)820 static void pidgin_whiteboard_rgb24_to_rgb48(int color_rgb, GdkColor *color)
821 {
822 	color->red   = (color_rgb >> 8) | 0xFF;
823 	color->green = (color_rgb & 0xFF00) | 0xFF;
824 	color->blue  = ((color_rgb & 0xFF) << 8) | 0xFF;
825 }
826 
827 static void
change_color_cb(GtkColorSelection * selection,PidginWhiteboard * gtkwb)828 change_color_cb(GtkColorSelection *selection, PidginWhiteboard *gtkwb)
829 {
830 	GdkColor color;
831 	int old_size = 5;
832 	int old_color = 0;
833 	int new_color;
834 	PurpleWhiteboard *wb = gtkwb->wb;
835 
836 	gtk_color_selection_get_current_color(selection, &color);
837 	new_color = (color.red & 0xFF00) << 8;
838 	new_color |= (color.green & 0xFF00);
839 	new_color |= (color.blue & 0xFF00) >> 8;
840 
841 	purple_whiteboard_get_brush(wb, &old_size, &old_color);
842 	purple_whiteboard_send_brush(wb, old_size, new_color);
843 }
844 
color_selection_dialog_destroy(GtkWidget * w,PidginWhiteboard * gtkwb)845 static void color_selection_dialog_destroy(GtkWidget *w, PidginWhiteboard *gtkwb)
846 {
847 	GtkWidget *dialog = g_object_get_data(G_OBJECT(gtkwb->window), "colour-dialog");
848 	gtk_widget_destroy(dialog);
849 	g_object_set_data(G_OBJECT(gtkwb->window), "colour-dialog", NULL);
850 }
851 
color_select_dialog(GtkWidget * widget,PidginWhiteboard * gtkwb)852 static void color_select_dialog(GtkWidget *widget, PidginWhiteboard *gtkwb)
853 {
854 	GdkColor color;
855 	GtkColorSelectionDialog *dialog;
856 
857 	dialog = (GtkColorSelectionDialog *)gtk_color_selection_dialog_new(_("Select color"));
858 	g_object_set_data(G_OBJECT(gtkwb->window), "colour-dialog", dialog);
859 
860 	g_signal_connect(G_OBJECT(dialog->colorsel), "color-changed",
861 					G_CALLBACK(change_color_cb), gtkwb);
862 
863 	gtk_widget_destroy(dialog->cancel_button);
864 	gtk_widget_destroy(dialog->help_button);
865 
866 	g_signal_connect(G_OBJECT(dialog->ok_button), "clicked",
867 					G_CALLBACK(color_selection_dialog_destroy), gtkwb);
868 
869 	gtk_color_selection_set_has_palette(GTK_COLOR_SELECTION(dialog->colorsel), TRUE);
870 
871 	pidgin_whiteboard_rgb24_to_rgb48(gtkwb->brush_color, &color);
872 	gtk_color_selection_set_current_color(GTK_COLOR_SELECTION(dialog->colorsel), &color);
873 
874 	gtk_widget_show_all(GTK_WIDGET(dialog));
875 }
876 
877