view src/gtkwhiteboard.c @ 11869:3ba50c385299

[gaim-migrate @ 14160] This should make joining big channels a bit quicker/more efficient. It was really bad before, now it's just bad. My crude testing showed it shaved a few hundred thousand library calls of joining #gaim, so it can't be too bad. committer: Tailor Script <tailor@pidgin.im>
author Stu Tomlinson <stu@nosnilmot.com>
date Fri, 28 Oct 2005 01:46:12 +0000
parents 4669e7461968
children 2219f4bf4a57
line wrap: on
line source

/*
 * gaim
 *
 * Gaim is the legal property of its developers, whose names are too numerous
 * to list here.  Please refer to the COPYRIGHT file distributed with this
 * source distribution.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 */

#include <stdlib.h>

#include "blist.h"

#include "gtkwhiteboard.h"

/******************************************************************************
 * Prototypes
 *****************************************************************************/
static void			gaim_gtk_whiteboard_create( GaimWhiteboard *wb );

static void			gaim_gtk_whiteboard_destroy( GaimWhiteboard *wb );
static void			gaim_gtk_whiteboard_exit( GtkWidget *widget, gpointer data );

/*static void			gaim_gtkwhiteboard_button_start_press( GtkButton *button, gpointer data ); */

static gboolean		gaim_gtk_whiteboard_configure_event( GtkWidget *widget, GdkEventConfigure *event, gpointer data );
static gboolean		gaim_gtk_whiteboard_expose_event( GtkWidget *widget, GdkEventExpose *event, gpointer data );

static gboolean		gaim_gtk_whiteboard_brush_down( GtkWidget *widget, GdkEventButton *event, gpointer data );
static gboolean		gaim_gtk_whiteboard_brush_motion( GtkWidget *widget, GdkEventMotion *event, gpointer data );
static gboolean		gaim_gtk_whiteboard_brush_up( GtkWidget *widget, GdkEventButton *event, gpointer data );

static void			gaim_gtk_whiteboard_draw_brush_point( GaimWhiteboard *wb,
							      int x, int y, int color, int size );
static void			gaim_gtk_whiteboard_draw_brush_line( GaimWhiteboard *wb,
							     int x0, int y0, int x1, int y1, int color, int size );

static void			gaim_gtk_whiteboard_set_dimensions( GaimWhiteboard *wb, int width, int height );
static void			gaim_gtk_whiteboard_clear( GaimWhiteboard *wb );

static void			gaim_gtk_whiteboard_button_clear_press( GtkWidget *widget, gpointer data );
static void			gaim_gtk_whiteboard_button_save_press( GtkWidget *widget, gpointer data );

static void			gaim_gtk_whiteboard_set_canvas_as_icon( GaimGtkWhiteboard *gtkwb );

static void			gaim_gtk_whiteboard_rgb24_to_rgb48( int color_rgb, GdkColor *color );

/******************************************************************************
 * Globals
 *****************************************************************************/
/*
GList				*buttonList		= NULL;
GdkColor			DefaultColor[PALETTE_NUM_COLORS];
*/

static gboolean			LocalShutdownRequest;

static int			LastX;			/* Tracks last position of the mouse when drawing */
static int			LastY;
static int			MotionCount;		/* Tracks how many brush motions made */
static int			BrushState		= BRUSH_STATE_UP;

static GaimWhiteboardUiOps 	ui_ops			=
{
	gaim_gtk_whiteboard_create,
	gaim_gtk_whiteboard_destroy,
	gaim_gtk_whiteboard_set_dimensions,
	gaim_gtk_whiteboard_draw_brush_point,
	gaim_gtk_whiteboard_draw_brush_line,
	gaim_gtk_whiteboard_clear
};

/******************************************************************************
 * API
 *****************************************************************************/
GaimWhiteboardUiOps *gaim_gtk_whiteboard_get_ui_ops( void )
{
	return( &ui_ops );
}

void gaim_gtk_whiteboard_create( GaimWhiteboard *wb )
{
	int i;

	GtkWidget *window;
	GtkWidget *drawing_area;

	GtkWidget *hbox_palette;
	GtkWidget *vbox_palette_above_canvas_and_controls;
	GtkWidget *hbox_canvas_and_controls;
	GtkWidget *vbox_controls;

	/*
		--------------------------
		|[][][][palette[][][][][]|
		|------------------------|
		|       canvas     | con |
		|                  | trol|
		|                  | s   |
		|                  |     |
		|                  |     |
		--------------------------
	*/
	GtkWidget *clear_button;
	GtkWidget *save_button;

	GtkWidget *palette_color_box[PALETTE_NUM_COLORS];
	/* GdkPixbuf *palette_color_area[PALETTE_NUM_COLORS]; */

	GaimGtkWhiteboard *gtkwb = g_new0( GaimGtkWhiteboard, 1 );

	const char *window_title;

	gtkwb->wb = wb;
	wb->ui_data = gtkwb;

	/* Get dimensions (default?) for the whiteboard canvas */
	if( wb->prpl_ops && wb->prpl_ops->get_dimensions )
		wb->prpl_ops->get_dimensions( wb, &gtkwb->width, &gtkwb->height );

	window = gtk_window_new( GTK_WINDOW_TOPLEVEL );
	gtkwb->window = window;
	gtk_widget_set_name( window, wb->who );

	/* Try and set window title as the name of the buddy, else just use their
	 * username
	 */
	window_title = gaim_contact_get_alias( gaim_buddy_get_contact( gaim_find_buddy( wb->account, wb->who ) ) );
	if( window_title )
		gtk_window_set_title( ( GtkWindow* )( window ), window_title );
	else
		gtk_window_set_title( ( GtkWindow* )( window ), wb->who );

	gtk_window_set_resizable( ( GtkWindow* )( window ), FALSE );

	g_signal_connect( G_OBJECT( window ), "destroy",
			  G_CALLBACK( gaim_gtk_whiteboard_exit ), ( gpointer )( gtkwb ) );

	/* Create vertical box to place palette above the canvas and controls */
	vbox_palette_above_canvas_and_controls = gtk_vbox_new( FALSE, 0 );
	gtk_container_add( GTK_CONTAINER( window ), vbox_palette_above_canvas_and_controls );
	gtk_widget_show( vbox_palette_above_canvas_and_controls );

	/* Create horizontal box for the palette and all its entries */
	hbox_palette = gtk_hbox_new( FALSE, 0 );
	gtk_container_add( GTK_CONTAINER( vbox_palette_above_canvas_and_controls ), hbox_palette );
	gtk_widget_show( hbox_palette );

	/* Create horizontal box to seperate the canvas from the controls */
	hbox_canvas_and_controls = gtk_hbox_new( FALSE, 0 );
	gtk_container_add( GTK_CONTAINER( vbox_palette_above_canvas_and_controls ), hbox_canvas_and_controls );
	gtk_widget_show( hbox_canvas_and_controls );

	for( i = 0; i < PALETTE_NUM_COLORS; i++ )
	{
		/* palette_color_area[i] = */

		palette_color_box[i] = gtk_image_new_from_pixbuf( NULL );
		gtk_widget_set_size_request( palette_color_box[i], gtkwb->width / PALETTE_NUM_COLORS ,32 );
		gtk_container_add( GTK_CONTAINER( hbox_palette ), palette_color_box[i] );

		gtk_widget_show( palette_color_box[i] );
	}

	/* Create the drawing area */
	drawing_area = gtk_drawing_area_new();
	gtkwb->drawing_area = drawing_area;
	gtk_widget_set_size_request( GTK_WIDGET( drawing_area ), gtkwb->width, gtkwb->height );
	gtk_box_pack_start( GTK_BOX( hbox_canvas_and_controls ), drawing_area, TRUE, TRUE, 8 );

	gtk_widget_show( drawing_area );

	/* Signals used to handle backing pixmap */
	g_signal_connect( G_OBJECT( drawing_area ), "expose_event",
			  G_CALLBACK( gaim_gtk_whiteboard_expose_event ), ( gpointer )( gtkwb ) );

	g_signal_connect( G_OBJECT( drawing_area ), "configure_event",
			  G_CALLBACK( gaim_gtk_whiteboard_configure_event ), ( gpointer )( gtkwb ) );

	/* Event signals */
	g_signal_connect( G_OBJECT( drawing_area ), "button_press_event",
			  G_CALLBACK( gaim_gtk_whiteboard_brush_down ), ( gpointer )( gtkwb ) );

	g_signal_connect( G_OBJECT( drawing_area ), "motion_notify_event",
			  G_CALLBACK( gaim_gtk_whiteboard_brush_motion ), ( gpointer )( gtkwb ) );

	g_signal_connect( G_OBJECT( drawing_area ), "button_release_event",
			  G_CALLBACK( gaim_gtk_whiteboard_brush_up ), ( gpointer )( gtkwb ) );

	gtk_widget_set_events( drawing_area, GDK_EXPOSURE_MASK		|
					     GDK_LEAVE_NOTIFY_MASK	|
					     GDK_BUTTON_PRESS_MASK	|
					     GDK_POINTER_MOTION_MASK	|
					     GDK_BUTTON_RELEASE_MASK	|
					     GDK_POINTER_MOTION_HINT_MASK );

	/* Create vertical box to contain the controls */
	vbox_controls = gtk_vbox_new( FALSE, 0 );
	gtk_container_add( GTK_CONTAINER( hbox_canvas_and_controls ), vbox_controls );
	gtk_widget_show( vbox_controls );

	/* Add a clear button */
	clear_button = gtk_button_new_with_label( "Clear" );
	gtk_widget_set_size_request( clear_button, 96 ,32 );
	gtk_box_pack_start( GTK_BOX( vbox_controls ), clear_button, FALSE, FALSE, 0 );
	gtk_widget_show( clear_button );

	g_signal_connect( G_OBJECT( clear_button ), "clicked",
			  G_CALLBACK( gaim_gtk_whiteboard_button_clear_press ), ( gpointer )( gtkwb ) );

	/* Add a save button */
	save_button = gtk_button_new_with_label( "Save" );
	gtk_widget_set_size_request( save_button, 96 ,32 );
	gtk_box_pack_start( GTK_BOX( vbox_controls ), save_button, FALSE, FALSE, 8 );
	gtk_widget_show( save_button );

	g_signal_connect( G_OBJECT( save_button ), "clicked",
			  G_CALLBACK( gaim_gtk_whiteboard_button_save_press ), ( gpointer )( gtkwb ) );

	/* Make all this (window) visible */
	gtk_widget_show( window );

	gaim_gtk_whiteboard_set_canvas_as_icon( gtkwb );

	/* TODO Specific protocol/whiteboard assignment here? Needs a UI Op? */
	/* Set default brush size and color */
	/*
	ds->brush_size	= DOODLE_BRUSH_MEDIUM;
	ds->brush_color	= 0;
	*/
}

void gaim_gtk_whiteboard_destroy( GaimWhiteboard *wb )
{
	GaimGtkWhiteboard *gtkwb = wb->ui_data;

	/* TODO Ask if user wants to save picture before the session is closed */

	/* Clear graphical memory */
	if( gtkwb->pixmap )
	{
		g_object_unref( gtkwb->pixmap );
		gtkwb->pixmap = NULL;
	}

	if( gtkwb->window )
	{
		gtk_widget_destroy( gtkwb->window );
		gtkwb->window = NULL;
	}
}

void gaim_gtk_whiteboard_exit( GtkWidget *widget, gpointer data )
{
	GaimGtkWhiteboard	*gtkwb	= ( GaimGtkWhiteboard* )( data );
	GaimWhiteboard		*wb	= gtkwb->wb;

	if( gtkwb->window && gtkwb->pixmap )
	{
		LocalShutdownRequest = TRUE;

		gaim_gtk_whiteboard_destroy( wb );
	}
	else
		LocalShutdownRequest = FALSE;

	if( gtkwb )
	{
		g_free( gtkwb );

		gtkwb = NULL;
		wb->ui_data = NULL;
	}

	/* Destroy whiteboard core, if the local user exited the whiteboard window */
	if( wb && LocalShutdownRequest )
	{
		gaim_whiteboard_destroy( wb );
		wb = NULL;
	}
}

/*
 * Whiteboard start button on conversation window (move this code to gtkconv?
 * and use new prpl_info member?)
 */
#if 0
void gaim_gtkwhiteboard_button_start_press( GtkButton *button, gpointer data )
{
	GaimConversation	*conv		= data;
	GaimAccount		*account	= gaim_conversation_get_account( conv );
	GaimConnection		*gc		= gaim_account_get_connection( account );
	char			*to		= ( char* )( gaim_conversation_get_name( conv ) );

	/* Only handle this if local client requested Doodle session (else local
	 * client would have sent one)
	 */
	GaimWhiteboard	*wb			= gaim_whiteboard_get( account, to );

	/* Write a local message to this conversation showing that a request for a
	 * Doodle session has been made
	 */
	gaim_conv_im_write( GAIM_CONV_IM( conv ), "", _("Sent Doodle request."),
			    GAIM_MESSAGE_NICK | GAIM_MESSAGE_RECV, time( NULL ) );

	yahoo_doodle_command_send_request( gc, to );
	yahoo_doodle_command_send_ready( gc, to );

	/* Insert this 'session' in the list.  At this point, it's only a requested
	 * session.
	 */
	wb = gaim_whiteboard_create( account, to, DOODLE_STATE_REQUESTING );
}
#endif

gboolean gaim_gtk_whiteboard_configure_event( GtkWidget *widget, GdkEventConfigure *event, gpointer data )
{
	GaimGtkWhiteboard	*gtkwb	= ( GaimGtkWhiteboard* )( data );

	GdkPixmap		*pixmap = gtkwb->pixmap;

	if( pixmap )
		g_object_unref( pixmap );

	pixmap = gdk_pixmap_new( widget->window,
				 widget->allocation.width,
				 widget->allocation.height,
				 -1 );

	gtkwb->pixmap = pixmap;

	gdk_draw_rectangle( pixmap,
			    widget->style->white_gc,
			    TRUE,
			    0, 0,
			    widget->allocation.width,
			    widget->allocation.height );

	return( TRUE );
}

gboolean gaim_gtk_whiteboard_expose_event( GtkWidget *widget, GdkEventExpose *event, gpointer data )
{
	GaimGtkWhiteboard	*gtkwb	= ( GaimGtkWhiteboard* )( data );
	GdkPixmap		*pixmap	= gtkwb->pixmap;

	gdk_draw_drawable( widget->window,
			   widget->style->fg_gc[GTK_WIDGET_STATE( widget )],
			   pixmap,
			   event->area.x, event->area.y,
			   event->area.x, event->area.y,
			   event->area.width, event->area.height );

	return( FALSE );
}

gboolean gaim_gtk_whiteboard_brush_down( GtkWidget *widget, GdkEventButton *event, gpointer data )
{
	GaimGtkWhiteboard	*gtkwb		= ( GaimGtkWhiteboard* )( data );
	GdkPixmap		*pixmap		= gtkwb->pixmap;

	GaimWhiteboard		*wb		= gtkwb->wb;
	GList			*draw_list	= wb->draw_list;

	int			*x0		= NULL;
	int			*y0		= NULL;

	if( BrushState != BRUSH_STATE_UP )
	{
		/* Potential double-click DOWN to DOWN? */
		BrushState = BRUSH_STATE_DOWN;

		/* return( FALSE ); */
	}

	BrushState = BRUSH_STATE_DOWN;

	if( event->button == 1 && pixmap != NULL )
	{
		/* Check if draw_list has contents; if so, clear it */
		if( draw_list )
			draw_list = gaim_whiteboard_draw_list_destroy( draw_list );

		x0		= g_new0( int, 1 );
		y0		= g_new0( int, 1 );

		*x0		= event->x;
		*y0		= event->y;

		/* Set tracking variables */
		LastX		= *x0;
		LastY		= *y0;

		MotionCount	= 0;

		draw_list	= g_list_append( draw_list, ( gpointer )( x0 ) );
		draw_list	= g_list_append( draw_list, ( gpointer )( y0 ) );

		gaim_gtk_whiteboard_draw_brush_point( gtkwb->wb,
						      event->x, event->y,
						      0,5 );
		/* gtkwb->brush_color, gtkwb->brush_size ); NOTE temp const prot uiop */
	}

	wb->draw_list = draw_list;

	return( TRUE );
}

gboolean gaim_gtk_whiteboard_brush_motion( GtkWidget *widget, GdkEventMotion *event, gpointer data )
{
	int			x;
	int			y;
	int			*dx;
	int			*dy;

	GdkModifierType		state;

	GaimGtkWhiteboard	*gtkwb		= ( GaimGtkWhiteboard* )( data );
	GdkPixmap		*pixmap		= gtkwb->pixmap;

	GaimWhiteboard		*wb		= gtkwb->wb;
	GList			*draw_list	= wb->draw_list;

	if( event->is_hint )
		gdk_window_get_pointer( event->window, &x, &y, &state );
	else
	{
		x	= event->x;
		y	= event->y;
		state	= event->state;
	}

	if( state & GDK_BUTTON1_MASK && pixmap != NULL )
	{
		if( ( BrushState != BRUSH_STATE_DOWN ) && ( BrushState != BRUSH_STATE_MOTION ) )
		{
			g_print( "***Bad brush state transition %d to MOTION\n", BrushState );

			BrushState = BRUSH_STATE_MOTION;

			return( FALSE );
		}
		BrushState = BRUSH_STATE_MOTION;

		dx	= g_new0( int, 1 );
		dy	= g_new0( int, 1 );

		*dx	= x - LastX;
		*dy	= y - LastY;

		MotionCount++;

		/* NOTE 100 is a temporary constant for how many deltas/motions in a
		 * stroke (needs UI Ops?)
		 */
		if( MotionCount == 100 )
		{
			int *x0			= g_new0( int, 1 );
			int *y0			= g_new0( int, 1 );

			draw_list = g_list_append( draw_list, ( gpointer )( dx ) );
			draw_list = g_list_append( draw_list, ( gpointer )( dy ) );

			/* Send draw list to prpl draw_list handler */
			if( gtkwb->wb->prpl_ops && gtkwb->wb->prpl_ops->send_draw_list )
				gtkwb->wb->prpl_ops->send_draw_list( gtkwb->wb, draw_list );

			/* The brush stroke is finished, clear the list for another one */
			if( draw_list )
				draw_list = gaim_whiteboard_draw_list_destroy( draw_list );

			*x0			= LastX;
			*y0			= LastY;

			/* Reset motion tracking */
			MotionCount		= 0;

			draw_list		= g_list_append( draw_list, ( gpointer )( x0 ) );
			draw_list		= g_list_append( draw_list, ( gpointer )( y0 ) );

			dx	= g_new0( int, 1 );
			dy	= g_new0( int, 1 );

			*dx	= x - LastX;
			*dy	= y - LastY;
		}

		draw_list = g_list_append( draw_list, ( gpointer )( dx ) );
		draw_list = g_list_append( draw_list, ( gpointer )( dy ) );

		gaim_gtk_whiteboard_draw_brush_line( gtkwb->wb,
						     LastX, LastY,
						     x, y,
						     0, 5 );
		/*gtkwb->brush_color, gtkwb->brush_size ); temp const proto ui ops? */

		/* Set tracking variables */
		LastX = x;
		LastY = y;
	}

	wb->draw_list = draw_list;

	return( TRUE );
}

gboolean gaim_gtk_whiteboard_brush_up( GtkWidget *widget, GdkEventButton *event, gpointer data )
{
	GaimGtkWhiteboard	*gtkwb		= ( GaimGtkWhiteboard* )( data );
	GdkPixmap		*pixmap		= gtkwb->pixmap;

	GaimWhiteboard		*wb		= gtkwb->wb;
	GList			*draw_list	= wb->draw_list;

	if( ( BrushState != BRUSH_STATE_DOWN ) && ( BrushState != BRUSH_STATE_MOTION ) )
	{
		g_print( "***Bad brush state transition %d to UP\n", BrushState );

		BrushState = BRUSH_STATE_UP;

		return( FALSE );
	}
	BrushState = BRUSH_STATE_UP;

	if( event->button == 1 && pixmap != NULL )
	{
		/* If the brush was never moved, express two sets of two deltas That's a
		 * 'point,' but not for Yahoo!
		 */
		/* if( ( event->x == LastX ) && ( event->y == LastY ) ) */
		if( MotionCount == 0 )
		{
			int index;

			for( index = 0; index < 2; index++ )/* NOTE Yahoo Doodle specific! */
			{
				int *x0		= NULL;
				int *y0		= NULL;

				x0		= g_new0( int, 1 );
				y0		= g_new0( int, 1 );

				draw_list	= g_list_append( draw_list, ( gpointer )( x0 ) );
				draw_list	= g_list_append( draw_list, ( gpointer )( y0 ) );
			}
		}
		/*
		else
			MotionCount = 0;
		*/

		/* Send draw list to prpl draw_list handler */
		if( gtkwb->wb->prpl_ops && gtkwb->wb->prpl_ops->send_draw_list )
			gtkwb->wb->prpl_ops->send_draw_list( gtkwb->wb, draw_list );

		gaim_gtk_whiteboard_set_canvas_as_icon( gtkwb );

		/* The brush stroke is finished, clear the list for another one */
		if( draw_list )
			draw_list = gaim_whiteboard_draw_list_destroy( draw_list );

		wb->draw_list = draw_list;
	}

	return( TRUE );
}

/* void gaim_gtk_whiteboard_draw_brush_point( GtkWidget *widget, GaimGtkWhiteboard *gtkwb,
					  int x, int y, int color, int size )
*/
void gaim_gtk_whiteboard_draw_brush_point( GaimWhiteboard *wb, int x, int y, int color, int size )
{
	GaimGtkWhiteboard	*gtkwb	= wb->ui_data;
	GtkWidget		*widget	= gtkwb->drawing_area;
	GdkPixmap 		*pixmap	= gtkwb->pixmap;

	GdkRectangle		update_rect;

	GdkGC			*gfx_con = gdk_gc_new( pixmap );
	GdkColor		col;

	update_rect.x		= x - size / 2;
	update_rect.y		= y - size / 2;
	update_rect.width	= size;
	update_rect.height	= size;

	/* Interpret and convert color */
	gaim_gtk_whiteboard_rgb24_to_rgb48( color, &col );

	gdk_gc_set_rgb_fg_color( gfx_con, &col );
	/* gdk_gc_set_rgb_bg_color( gfx_con, &col ); */

	/* NOTE 5 is a size constant for now... this is because of how poorly the
	 * gdk_draw_arc draws small circles
	 */
	if( size < 5 )
	{
		/* Draw a rectangle/square */
		gdk_draw_rectangle( pixmap,
				    gfx_con,
				    TRUE,
				    update_rect.x, update_rect.y,
				    update_rect.width, update_rect.height );
	}
	else
	{
		/* Draw a circle */
		gdk_draw_arc( pixmap,
			      gfx_con,
			      TRUE,
			      update_rect.x, update_rect.y,
			      update_rect.width, update_rect.height,
			      0, FULL_CIRCLE_DEGREES );
	}

	gtk_widget_queue_draw_area( widget, 
				    update_rect.x, update_rect.y,
				    update_rect.width, update_rect.height );

	gdk_gc_unref( gfx_con );
}

/* Uses Bresenham's algorithm (as provided by Wikipedia) */
/* void gaim_gtk_whiteboard_draw_brush_line( GtkWidget *widget, GaimGtkWhiteboard *gtkwb,
 					 int x0, int y0, int x1, int y1, int color, int size )
*/
void gaim_gtk_whiteboard_draw_brush_line( GaimWhiteboard *wb, int x0, int y0, int x1, int y1, int color, int size )
{
	int temp;

	int xstep;
	int ystep;

	int dx;
	int dy;

	int error;
	int derror;

	int x;
	int y;

	gboolean steep = abs( y1 - y0 ) > abs( x1 - x0 );

	if( steep )
	{
		temp = x0; x0 = y0; y0 = temp;
		temp = x1; x1 = y1; y1 = temp;
	}

	dx		= abs( x1 - x0 );
	dy		= abs( y1 - y0 );

	error	= 0;
	derror	= dy;

	x		= x0;
	y		= y0;

	if( x0 < x1 )
		xstep = 1;
	else
		xstep = -1;

	if( y0 < y1 )
		ystep = 1;
	else
		ystep = -1;

	if( steep )
		gaim_gtk_whiteboard_draw_brush_point( wb, y, x, color, size );
	else
		gaim_gtk_whiteboard_draw_brush_point( wb, x, y, color, size );

	while( x != x1 )
	{
		x	= x + xstep;
		error	= error + derror;

		if( ( error * 2 ) >= dx )
		{
			y	= y + ystep;
			error	= error - dx;
		}

		if( steep )
			gaim_gtk_whiteboard_draw_brush_point( wb, y, x, color, size );
		else
			gaim_gtk_whiteboard_draw_brush_point( wb, x, y, color, size );
	}
}

void gaim_gtk_whiteboard_set_dimensions( GaimWhiteboard *wb, int width, int height )
{
	GaimGtkWhiteboard *gtkwb = wb->ui_data;

	gtkwb->width	= width;
	gtkwb->height	= height;
}

void gaim_gtk_whiteboard_clear( GaimWhiteboard *wb )
{
	GaimGtkWhiteboard	*gtkwb		= wb->ui_data;
	GdkPixmap		*pixmap		= gtkwb->pixmap;
	GtkWidget		*drawing_area	= gtkwb->drawing_area;

	gdk_draw_rectangle( pixmap,
			    drawing_area->style->white_gc,
			    TRUE,
			    0, 0,
			    drawing_area->allocation.width, drawing_area->allocation.height );

	gtk_widget_queue_draw_area( drawing_area,
				    0, 0,
				    drawing_area->allocation.width, drawing_area->allocation.height );
}

void gaim_gtk_whiteboard_button_clear_press( GtkWidget *widget, gpointer data )
{
	GaimGtkWhiteboard	*gtkwb	= ( GaimGtkWhiteboard* )( data );

	gaim_gtk_whiteboard_clear( gtkwb->wb );

	gaim_gtk_whiteboard_set_canvas_as_icon( gtkwb );

	/* Do protocol specific clearing procedures */
	if( gtkwb->wb->prpl_ops && gtkwb->wb->prpl_ops->clear )
		gtkwb->wb->prpl_ops->clear( gtkwb->wb );
}

void gaim_gtk_whiteboard_button_save_press( GtkWidget *widget, gpointer data )
{
	GaimGtkWhiteboard	*gtkwb	= ( GaimGtkWhiteboard* )( data );
	GdkPixbuf		*pixbuf;

	GtkWidget *dialog;

	int result;

#if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
	dialog = gtk_file_chooser_dialog_new ("Save File",
					      GTK_WINDOW(gtkwb->window),
					      GTK_FILE_CHOOSER_ACTION_SAVE,
					      GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
					      GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
					      NULL );

	/* gtk_file_chooser_set_do_overwrite_confirmation( GTK_FILE_CHOOSER( dialog ), (gboolean)(TRUE) ); */

/*	if( user_edited_a_new_document ) */
	{
/*		gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER( dialog ), default_folder_for_saving ); */
		gtk_file_chooser_set_current_name( GTK_FILE_CHOOSER( dialog ), "whiteboard.jpg" );
	}
/*
	else
		gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (dialog), filename_for_existing_document);
*/
#else
	dialog = gtk_file_selection_new("Save File");
	gtk_file_selection_set_filename(GTK_FILE_SELECTION(dialog), "whiteboard.jpg");
#endif
	result = gtk_dialog_run( GTK_DIALOG( dialog ) );

	if( result == GTK_RESPONSE_ACCEPT )
	{
		char *filename;

#if GTK_CHECK_VERSION(2,4,0) /* FILECHOOSER */
		filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER( dialog ) );
#else
		filename = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION(dialog)));
#endif
		gtk_widget_destroy( dialog );

		/* Makes an icon from the whiteboard's canvas 'image' */
		pixbuf = gdk_pixbuf_get_from_drawable( NULL,
						( GdkDrawable* )( gtkwb->pixmap ),
						gdk_drawable_get_colormap( gtkwb->pixmap ),
						0, 0,
						0, 0,
						gtkwb->width, gtkwb->height );

		if( gdk_pixbuf_save( pixbuf,
		    		     filename,
				     "jpeg",
				     NULL,
				     "quality",
				     "100",
				     NULL ) )
			g_print( "File Saved...\n" );
		else
			g_print( "File not Saved... Error\n" );
		g_free(filename);
	}
	else
	if( result == GTK_RESPONSE_CANCEL )
	{
		gtk_widget_destroy( dialog );

		g_print( "File not Saved... Canceled\n" );
	}
}

void gaim_gtk_whiteboard_set_canvas_as_icon( GaimGtkWhiteboard *gtkwb )
{
	GdkPixbuf *pixbuf;

	/* Makes an icon from the whiteboard's canvas 'image' */
	pixbuf = gdk_pixbuf_get_from_drawable( NULL,
					       ( GdkDrawable* )( gtkwb->pixmap ),
					       gdk_drawable_get_colormap( gtkwb->pixmap ),
					       0, 0,
					       0, 0,
					       gtkwb->width, gtkwb->height );

	gtk_window_set_icon( ( GtkWindow* )( gtkwb->window ), pixbuf );
}

void gaim_gtk_whiteboard_rgb24_to_rgb48( int color_rgb, GdkColor *color )
{
	color->red	= ( color_rgb >> 8 ) | 0xFF;
	color->green	= ( color_rgb & 0xFF00 ) | 0xFF;
	color->blue	= ( ( color_rgb & 0xFF ) << 8 ) | 0xFF;
}