pidgin/pidgin

Add some new methods to purple tags
default tip
8 hours ago, Gary Kramlich
b45add2a840c
Add some new methods to purple tags

* purple_tags_exists is a simplier version of purple_tags_lookup.
* purple_tags_contains makes it easier to find multiple matching tags.

Testing Done:
Ran the unit tests under valgrind and had the turtles check in on things too.

Reviewed at https://reviews.imfreedom.org/r/3143/
/*
* pidgin
*
* Pidgin 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., 51 Franklin Street, Fifth Floor, Boston, MA 02111-1301 USA
*
*/
#include <glib/gi18n-lib.h>
#include <gtk/gtk.h>
#include <adwaita.h>
#include <math.h>
#ifndef M_PI
#define M_PI (3.14159265358979323846264338327950288)
#endif
#include <purple.h>
#include "gtkwhiteboard.h"
#include "gtkutils.h"
#define UI_DATA "pidgin-ui-data"
#define PIDGIN_TYPE_WHITEBOARD (pidgin_whiteboard_get_type())
G_DECLARE_FINAL_TYPE(PidginWhiteboard, pidgin_whiteboard, PIDGIN, WHITEBOARD,
GtkWindow)
/**
* PidginWhiteboard:
* @cr: Cairo context for drawing
* @surface: Cairo surface for drawing
* @wb: Backend data for this whiteboard
* @drawing_area: Drawing area
* @color_button: A color chooser widget
* @width: Canvas width
* @height: Canvas height
* @brush_color: Foreground color
* @brush_size: Brush size
* @brush_state: The @PidginWhiteboardBrushState state of the brush
*
* A PidginWhiteboard
*/
struct _PidginWhiteboard
{
GtkWindow parent;
cairo_t *cr;
cairo_surface_t *surface;
PurpleWhiteboard *wb;
GtkWidget *drawing_area;
GtkBox *toolbar;
GtkWidget *color_button;
int width;
int height;
int brush_color;
int brush_size;
/* Tracks last position of the mouse when drawing */
gdouble start_x;
gdouble start_y;
gdouble last_x;
gdouble last_y;
};
G_DEFINE_FINAL_TYPE(PidginWhiteboard, pidgin_whiteboard, GTK_TYPE_WINDOW)
/******************************************************************************
* Helpers
*****************************************************************************/
static void
pidgin_whiteboard_rgb24_to_rgba(int color_rgb, GdkRGBA *color)
{
color->red = ((color_rgb >> 16) & 0xFF) / 255.0f;
color->green = ((color_rgb >> 8) & 0xFF) / 255.0f;
color->blue = (color_rgb & 0xFF) / 255.0f;
color->alpha = 1.0f;
}
static void
whiteboard_destroy_cb(GtkWidget *widget, G_GNUC_UNUSED gpointer data)
{
PidginWhiteboard *gtkwb = PIDGIN_WHITEBOARD(widget);
g_clear_object(&gtkwb->wb);
}
static void
pidgin_whiteboard_resize(G_GNUC_UNUSED GtkDrawingArea *self, gint width,
gint height, gpointer data)
{
PidginWhiteboard *gtkwb = (PidginWhiteboard*)data;
GdkRGBA white = {1.0f, 1.0f, 1.0f, 1.0f};
cairo_t *cr;
g_clear_pointer(&gtkwb->cr, cairo_destroy);
g_clear_pointer(&gtkwb->surface, cairo_surface_destroy);
gtkwb->surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width,
height);
gtkwb->cr = cr = cairo_create(gtkwb->surface);
gdk_cairo_set_source_rgba(cr, &white);
cairo_rectangle(cr, 0, 0, width, height);
cairo_fill(cr);
}
static gboolean
pidgin_whiteboard_draw_event(G_GNUC_UNUSED GtkWidget *widget, cairo_t *cr,
gpointer _gtkwb)
{
PidginWhiteboard *gtkwb = _gtkwb;
cairo_set_source_surface(cr, gtkwb->surface, 0, 0);
cairo_paint(cr);
return FALSE;
}
static void
pidgin_whiteboard_draw_brush_point(PurpleWhiteboard *wb, int x, int y,
int color, int size)
{
PidginWhiteboard *gtkwb = g_object_get_data(G_OBJECT(wb), UI_DATA);
GtkWidget *widget = gtkwb->drawing_area;
cairo_t *gfx_con = gtkwb->cr;
GdkRGBA rgba;
/* Interpret and convert color */
pidgin_whiteboard_rgb24_to_rgba(color, &rgba);
gdk_cairo_set_source_rgba(gfx_con, &rgba);
/* Draw a circle */
cairo_arc(gfx_con, x, y, size / 2.0, 0.0, 2.0 * M_PI);
cairo_fill(gfx_con);
gtk_widget_queue_draw(widget);
}
/* Uses Bresenham's algorithm (as provided by Wikipedia) */
static void
pidgin_whiteboard_draw_brush_line(PurpleWhiteboard *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) {
pidgin_whiteboard_draw_brush_point(wb, y, x, color, size);
} else {
pidgin_whiteboard_draw_brush_point(wb, x, y, color, size);
}
while (x != x1) {
x += xstep;
error += derror;
if ((error * 2) >= dx) {
y += ystep;
error -= dx;
}
if (steep) {
pidgin_whiteboard_draw_brush_point(wb, y, x, color,
size);
} else {
pidgin_whiteboard_draw_brush_point(wb, x, y, color,
size);
}
}
}
static void
pidgin_whiteboard_brush_down(G_GNUC_UNUSED GtkGestureDrag* self, gdouble x,
gdouble y, gpointer data)
{
PidginWhiteboard *gtkwb = (PidginWhiteboard*)data;
PurpleWhiteboard *wb = gtkwb->wb;
GList *draw_list = purple_whiteboard_get_draw_list(wb);
if(gtkwb->cr != NULL) {
/* Check if draw_list has contents; if so, clear it */
if(draw_list) {
purple_whiteboard_draw_list_destroy(draw_list);
draw_list = NULL;
}
/* Set tracking variables */
gtkwb->start_x = x;
gtkwb->start_y = y;
gtkwb->last_x = 0;
gtkwb->last_y = 0;
draw_list = g_list_append(draw_list, GINT_TO_POINTER(gtkwb->start_x));
draw_list = g_list_append(draw_list, GINT_TO_POINTER(gtkwb->start_y));
pidgin_whiteboard_draw_brush_point(gtkwb->wb, gtkwb->start_x,
gtkwb->start_y, gtkwb->brush_color,
gtkwb->brush_size);
}
purple_whiteboard_set_draw_list(wb, draw_list);
}
static void
pidgin_whiteboard_brush_motion(G_GNUC_UNUSED GtkGestureDrag* self, gdouble x,
gdouble y, gpointer data)
{
PidginWhiteboard *gtkwb = (PidginWhiteboard*)data;
PurpleWhiteboard *wb = gtkwb->wb;
GList *draw_list = purple_whiteboard_get_draw_list(wb);
if (gtkwb->cr != NULL) {
gdouble dx, dy;
/* x and y are relative to the starting post, but we need to know where
* there are according to the last point, so we have to do the algebra.
*/
dx = (x + gtkwb->start_x - gtkwb->last_x);
dy = (y + gtkwb->start_y - gtkwb->last_y);
draw_list = g_list_append(draw_list, GINT_TO_POINTER(dx));
draw_list = g_list_append(draw_list, GINT_TO_POINTER(dy));
pidgin_whiteboard_draw_brush_line(gtkwb->wb,
gtkwb->start_x + gtkwb->last_x,
gtkwb->start_y + gtkwb->last_y,
gtkwb->start_x + x,
gtkwb->start_y + y,
gtkwb->brush_color,
gtkwb->brush_size);
gtkwb->last_x = x;
gtkwb->last_y = y;
}
purple_whiteboard_set_draw_list(wb, draw_list);
}
static void
pidgin_whiteboard_brush_up(G_GNUC_UNUSED GtkGestureDrag *self,
G_GNUC_UNUSED gdouble x, G_GNUC_UNUSED gdouble y,
gpointer data)
{
PidginWhiteboard *gtkwb = (PidginWhiteboard*)data;
PurpleWhiteboard *wb = gtkwb->wb;
GList *draw_list = purple_whiteboard_get_draw_list(wb);
if(gtkwb->cr != NULL) {
/* Send draw list to protocol draw_list handler */
purple_whiteboard_send_draw_list(gtkwb->wb, draw_list);
/* The brush stroke is finished, clear the list for another one
*/
if (draw_list) {
purple_whiteboard_draw_list_destroy(draw_list);
}
purple_whiteboard_set_draw_list(wb, NULL);
}
}
static void pidgin_whiteboard_set_dimensions(PurpleWhiteboard *wb, int width, int height)
{
PidginWhiteboard *gtkwb = g_object_get_data(G_OBJECT(wb), UI_DATA);
gtkwb->width = width;
gtkwb->height = height;
}
static void pidgin_whiteboard_set_brush(PurpleWhiteboard *wb, int size, int color)
{
PidginWhiteboard *gtkwb = g_object_get_data(G_OBJECT(wb), UI_DATA);
gtkwb->brush_size = size;
gtkwb->brush_color = color;
}
static void pidgin_whiteboard_clear(PurpleWhiteboard *wb)
{
PidginWhiteboard *gtkwb = g_object_get_data(G_OBJECT(wb), UI_DATA);
GtkWidget *drawing_area = gtkwb->drawing_area;
cairo_t *cr = gtkwb->cr;
GtkAllocation allocation;
GdkRGBA white = {1.0f, 1.0f, 1.0f, 1.0f};
gtk_widget_get_allocation(drawing_area, &allocation);
gdk_cairo_set_source_rgba(cr, &white);
cairo_rectangle(cr, 0, 0, allocation.width, allocation.height);
cairo_fill(cr);
gtk_widget_queue_draw(drawing_area);
}
static void
pidgin_whiteboard_clear_response(G_GNUC_UNUSED AdwMessageDialog *self,
char *response, gpointer data)
{
PidginWhiteboard *gtkwb = (PidginWhiteboard *)data;
if(!purple_strequal(response, "yes")) {
return;
}
pidgin_whiteboard_clear(gtkwb->wb);
/* Do protocol specific clearing procedures */
purple_whiteboard_send_clear(gtkwb->wb);
}
static void
pidgin_whiteboard_button_clear_press(G_GNUC_UNUSED GtkWidget *widget,
gpointer data)
{
PidginWhiteboard *gtkwb = (PidginWhiteboard*)(data);
AdwMessageDialog *dialog = NULL;
/* Confirm whether the user really wants to clear */
dialog = ADW_MESSAGE_DIALOG(adw_message_dialog_new(
GTK_WINDOW(gtkwb),
_("Clear whiteboard?"),
_("Do you want to clear this whiteboard?")));
adw_message_dialog_add_responses(dialog, "no", _("_No"), "yes", _("_Yes"),
NULL);
adw_message_dialog_set_response_appearance(dialog, "yes",
ADW_RESPONSE_DESTRUCTIVE);
adw_message_dialog_set_default_response(dialog, "yes");
adw_message_dialog_set_close_response(dialog, "no");
g_signal_connect(dialog, "response",
G_CALLBACK(pidgin_whiteboard_clear_response), gtkwb);
gtk_window_set_modal(GTK_WINDOW(dialog), TRUE);
gtk_window_present_with_time(GTK_WINDOW(dialog), GDK_CURRENT_TIME);
}
static void
pidgin_whiteboard_save_response(GObject *obj, GAsyncResult *result,
gpointer data)
{
PidginWhiteboard *gtkwb = (PidginWhiteboard *)data;
GFile *file = NULL;
char *filename = NULL;
GdkPixbuf *pixbuf;
gboolean success;
file = gtk_file_dialog_save_finish(GTK_FILE_DIALOG(obj), result, NULL);
if(file == NULL) {
return;
}
filename = g_file_get_path(file);
pixbuf = gdk_pixbuf_get_from_surface(gtkwb->surface, 0, 0,
gtkwb->width, gtkwb->height);
success = gdk_pixbuf_save(pixbuf, filename, "png", NULL,
"compression", "9", NULL);
g_object_unref(pixbuf);
if (success) {
purple_debug_info("gtkwhiteboard", "whiteboard saved to \"%s\"",
filename);
} else {
purple_notify_error(NULL, _("Whiteboard"),
_("Unable to save the file"), NULL, NULL);
purple_debug_error("gtkwhiteboard",
"whiteboard couldn't be saved to \"%s\"", filename);
}
g_free(filename);
g_object_unref(file);
}
static void
pidgin_whiteboard_button_save_press(G_GNUC_UNUSED GtkWidget *widget,
gpointer _gtkwb)
{
PidginWhiteboard *gtkwb = _gtkwb;
GtkFileDialog *dialog;
dialog = gtk_file_dialog_new();
gtk_file_dialog_set_title(dialog, _("Save File"));
gtk_file_dialog_set_modal(dialog, TRUE);
gtk_file_dialog_set_initial_name(dialog, "whiteboard.png");
gtk_file_dialog_save(dialog, GTK_WINDOW(gtkwb), NULL,
pidgin_whiteboard_save_response, gtkwb);
g_object_unref(dialog);
}
static void
notify_color_cb(GObject *obj, G_GNUC_UNUSED GParamSpec *pspec, gpointer data) {
PidginWhiteboard *gtkwb = data;
PurpleWhiteboard *wb = gtkwb->wb;
const GdkRGBA *color;
int old_size, old_color;
int new_color;
color = gtk_color_dialog_button_get_rgba(GTK_COLOR_DIALOG_BUTTON(obj));
new_color = (unsigned int)(color->red * 255) << 16;
new_color |= (unsigned int)(color->green * 255) << 8;
new_color |= (unsigned int)(color->blue * 255);
purple_whiteboard_get_brush(wb, &old_size, &old_color);
purple_whiteboard_send_brush(wb, old_size, new_color);
}
static void
pidgin_whiteboard_create(PurpleWhiteboard *wb)
{
PidginWhiteboard *gtkwb;
GdkRGBA color;
gtkwb = g_object_new(PIDGIN_TYPE_WHITEBOARD, NULL);
gtkwb->wb = wb;
g_object_set_data_full(G_OBJECT(wb), UI_DATA, gtkwb, g_object_unref);
/* Get dimensions (default?) for the whiteboard canvas */
if (!purple_whiteboard_get_dimensions(wb, &gtkwb->width,
&gtkwb->height)) {
/* Give some initial board-size */
gtkwb->width = 300;
gtkwb->height = 250;
}
if (!purple_whiteboard_get_brush(wb, &gtkwb->brush_size,
&gtkwb->brush_color)) {
/* Give some initial brush-info */
gtkwb->brush_size = 2;
gtkwb->brush_color = 0xff0000;
}
/* Set the window title to the id of the whiteboard as we can't test
* anything right now.
*/
gtk_window_set_title(GTK_WINDOW(gtkwb), purple_whiteboard_get_id(wb));
gtk_widget_set_name(GTK_WIDGET(gtkwb), purple_whiteboard_get_id(wb));
gtk_widget_set_size_request(GTK_WIDGET(gtkwb->drawing_area),
gtkwb->width, gtkwb->height);
pidgin_whiteboard_rgb24_to_rgba(gtkwb->brush_color, &color);
gtk_color_dialog_button_set_rgba(GTK_COLOR_DIALOG_BUTTON(gtkwb->color_button),
&color);
/* Make all this (window) visible */
gtk_widget_set_visible(GTK_WIDGET(gtkwb), TRUE);
/* 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;
*/
}
static void
pidgin_whiteboard_destroy(PurpleWhiteboard *wb)
{
PidginWhiteboard *gtkwb;
g_return_if_fail(wb != NULL);
gtkwb = g_object_get_data(G_OBJECT(wb), UI_DATA);
g_return_if_fail(gtkwb != NULL);
/* TODO Ask if user wants to save picture before the session is closed
*/
gtkwb->wb = NULL;
}
/******************************************************************************
* GObject implementation
*****************************************************************************/
static void
pidgin_whiteboard_init(PidginWhiteboard *self) {
gtk_widget_init_template(GTK_WIDGET(self));
self->color_button = gtk_color_dialog_button_new(gtk_color_dialog_new());
g_signal_connect(self->color_button, "notify::rgba",
G_CALLBACK(notify_color_cb), self);
gtk_box_append(self->toolbar, GTK_WIDGET(self->color_button));
}
static void
pidgin_whiteboard_finalize(GObject *obj) {
PidginWhiteboard *gtkwb = PIDGIN_WHITEBOARD(obj);
/* Clear graphical memory */
g_clear_pointer(&gtkwb->cr, cairo_destroy);
g_clear_pointer(&gtkwb->surface, cairo_surface_destroy);
G_OBJECT_CLASS(pidgin_whiteboard_parent_class)->finalize(obj);
}
static void
pidgin_whiteboard_class_init(PidginWhiteboardClass *klass)
{
GObjectClass *obj_class = G_OBJECT_CLASS(klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
obj_class->finalize = pidgin_whiteboard_finalize;
gtk_widget_class_set_template_from_resource(
widget_class, "/im/pidgin/Pidgin3/Whiteboard/whiteboard.ui");
gtk_widget_class_bind_template_child(widget_class, PidginWhiteboard,
drawing_area);
gtk_widget_class_bind_template_child(widget_class, PidginWhiteboard,
toolbar);
gtk_widget_class_bind_template_callback(
widget_class, whiteboard_destroy_cb);
gtk_widget_class_bind_template_callback(
widget_class, pidgin_whiteboard_draw_event);
gtk_widget_class_bind_template_callback(
widget_class, pidgin_whiteboard_resize);
gtk_widget_class_bind_template_callback(
widget_class, pidgin_whiteboard_brush_down);
gtk_widget_class_bind_template_callback(
widget_class, pidgin_whiteboard_brush_motion);
gtk_widget_class_bind_template_callback(
widget_class, pidgin_whiteboard_brush_up);
gtk_widget_class_bind_template_callback(
widget_class, pidgin_whiteboard_button_clear_press);
gtk_widget_class_bind_template_callback(
widget_class, pidgin_whiteboard_button_save_press);
}
/******************************************************************************
* API
*****************************************************************************/
static PurpleWhiteboardUiOps ui_ops = {
.create = pidgin_whiteboard_create,
.destroy = pidgin_whiteboard_destroy,
.set_dimensions = pidgin_whiteboard_set_dimensions,
.set_brush = pidgin_whiteboard_set_brush,
.draw_point = pidgin_whiteboard_draw_brush_point,
.draw_line = pidgin_whiteboard_draw_brush_line,
.clear = pidgin_whiteboard_clear,
};
PurpleWhiteboardUiOps *
pidgin_whiteboard_get_ui_ops(void)
{
return &ui_ops;
}