view plugins/gestures/stroke-draw.c @ 9584:fe35f55ee984

[gaim-migrate @ 10427] " When joining a jabber conference many jabber servers send a recap of the last 20 or so messages. If you have sounds enabled, this will result in either 20 sounds in row, or worse if mixing is available, a horrible mix of 20 overlapping sounds. These recap messages can be identifed be the presence of the "jabber:x:delay". This patch identifies delayed messages, passes that information through flags from the prpl to the core, and then on to the gui. Detailed changes: Add GAIM_MESSAGE_DELAYED to GaimMessageFlags to indicate a delayed message. Change gtkconv.c to not play sounds when either GAIM_MESSAGE_DELAYED or GAIM_MESSAGE_SYSTEM are set. Add GaimConvChatFlags, parallel to GaimConvImFlags, to pass flags from protocols to core. Currently contains two flags: GAIM_CONV_CHAT_WHISPER GAIM_CONV_CHAT_DELAYED Change fourth arg of serv_got_chat_in() from "int whisper" to "GaimConvChatFlags chatflags". Change jabber prpl to set delayed flag when the "jabber:x:delay" element is present. Change toc protocol since it uses the whisper flag." --Nathan Fredrickson Date: 2004-07-24 00:49 Sender: marv_sfAccepting Donations Logged In: YES user_id=790708 I'm not sure I like naming the flags "DELAYED". I mean that's okay inside jabber since that's what the jabber protocol refers to it as, but for the the GAIM_*_DELAYED flags, I think they should be named something else. I thought about NOSOUND, but I decided that was wrong, because the flag should say what kind of message it is, not what to do with it, that's up to the UI to decide. What's up with not playing sounds on GAIM_MESSAGE_SYSTEM? This sounds unrelated to this. Are there times when we want to play sounds on system messages? Date: 2004-07-24 09:13 Sender: noif Logged In: YES user_id=365548 I purposely did not use a name that implied what the UI should do with the flag. The only characteristic that makes these messages unique is that they've been stored in the server for some period of time and are not current. I'm open to a better flag name than "DELAYED"... I thought about "RECAP", but that seemed less generalized than "DELAYED". As for not playing sounds on GAIM_MESSAGE_SYSTEM, that can be removed if it's controversial. I think I slipped that in since the setting of the topic was still playing a sound every time you joined a jabber conference. I think we can change the flag name ourselves if something else is better. committer: Tailor Script <tailor@pidgin.im>
author Luke Schierer <lschiere@pidgin.im>
date Sat, 24 Jul 2004 15:18:32 +0000
parents e0535ba0d667
children 19fd43d52d18
line wrap: on
line source

/*
  GNOME stroke implementation
  Copyright (c) 2000, 2001 Dan Nicolaescu
  See the file COPYING for distribution information.
*/

#include "config.h"

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <glib.h>
#include <gtk/gtk.h>

#include <gdk/gdkx.h>
#include "gstroke.h"
#include "gstroke-internal.h"

#include <X11/Xlib.h>
#include <X11/Xutil.h>


static void gstroke_invisible_window_init (GtkWidget *widget);
/*FIXME: Maybe these should be put in a structure, and not static...*/
static Display * gstroke_disp = NULL;
static Window gstroke_window;
static GC gstroke_gc;
static int mouse_button = 2;
static gboolean draw_strokes = FALSE;

#define GSTROKE_TIMEOUT_DURATION 10

#define GSTROKE_SIGNALS "gstroke_signals"

struct gstroke_func_and_data {
  void (*func)(GtkWidget *, void *);
  gpointer data;
};


/*FIXME: maybe it's better to just make 2 static variables, not a
  structure */
struct mouse_position {
  struct s_point last_point;
  gboolean invalid;
};


static struct mouse_position last_mouse_position;
static guint timer_id;

static void gstroke_execute (GtkWidget *widget, const gchar *name);

static void
record_stroke_segment (GtkWidget *widget)
{
  gint x, y;
  struct gstroke_metrics *metrics;

  gtk_widget_get_pointer (widget, &x, &y);

  if (last_mouse_position.invalid)
    last_mouse_position.invalid = FALSE;
  else if (gstroke_draw_strokes())
    {
#if 1
      XDrawLine (gstroke_disp, gstroke_window, gstroke_gc,
                 last_mouse_position.last_point.x,
                 last_mouse_position.last_point.y,
                 x, y);
      /* XFlush (gstroke_disp); */
#else
      /* FIXME: this does not work. It will only work if we create a
         corresponding GDK window for stroke_window and draw on
         that... */
      gdk_draw_line (widget->window, widget->style->fg_gc[GTK_STATE_NORMAL],
                     last_mouse_position.last_point.x,
                     last_mouse_position.last_point.y,
                     x,
                     y);
#endif
    }

  if (last_mouse_position.last_point.x != x
      || last_mouse_position.last_point.y != y)
    {
      last_mouse_position.last_point.x = x;
      last_mouse_position.last_point.y = y;
      metrics = (struct gstroke_metrics *)g_object_get_data(G_OBJECT(widget),
															GSTROKE_METRICS);
      _gstroke_record (x, y, metrics);
    }
}

static gint
gstroke_timeout (gpointer data)
{
  GtkWidget *widget = GTK_WIDGET (data);
  record_stroke_segment (widget);

  return TRUE;
}

static gint
process_event (GtkWidget *widget, GdkEvent *event, gpointer data G_GNUC_UNUSED)
{
  static GtkWidget *original_widget = NULL;
  static GdkCursor *cursor = NULL;

  switch (event->type) {
    case GDK_BUTTON_PRESS:
      if (event->button.button != gstroke_get_mouse_button())
        break;

      original_widget = widget; /* remeber the widget where
                                   the stroke started */

      gstroke_invisible_window_init (widget);

      record_stroke_segment (widget);

	  if (cursor == NULL)
		  cursor = gdk_cursor_new(GDK_PENCIL);

      gdk_pointer_grab (widget->window, FALSE,
			GDK_BUTTON_RELEASE_MASK, NULL, cursor,
			event->button.time);
      timer_id = g_timeout_add (GSTROKE_TIMEOUT_DURATION,
				  gstroke_timeout, widget);
      return TRUE;

    case GDK_BUTTON_RELEASE:
      if ((event->button.button != gstroke_get_mouse_button())
	  || (original_widget == NULL)) {

		  /* Nice bug when you hold down one button and press another. */
		  /* We'll just cancel the gesture instead. */
		  last_mouse_position.invalid = TRUE;
		  original_widget = NULL;

		  if (timer_id > 0)
			  g_source_remove (timer_id);

		  gdk_pointer_ungrab (event->button.time);
		  timer_id = 0;

		  if (gstroke_draw_strokes() && gstroke_disp != NULL) {
			  /* get rid of the invisible stroke window */
			  XUnmapWindow (gstroke_disp, gstroke_window);
			  XFlush (gstroke_disp);
		  }

		  break;
	  
	  }

      last_mouse_position.invalid = TRUE;
      original_widget = NULL;
      g_source_remove (timer_id);
      gdk_pointer_ungrab (event->button.time);
      timer_id = 0;

      {
	char result[GSTROKE_MAX_SEQUENCE];
	struct gstroke_metrics *metrics;

	metrics = (struct gstroke_metrics *)g_object_get_data(G_OBJECT (widget),
														  GSTROKE_METRICS);
		if (gstroke_draw_strokes()) {
			/* get rid of the invisible stroke window */
			XUnmapWindow (gstroke_disp, gstroke_window);
			XFlush (gstroke_disp);
		}

	_gstroke_canonical (result, metrics);
	gstroke_execute (widget, result);
	return FALSE;
      }
      return TRUE;
    default:
      break;
  }

  return FALSE;
}

void
gstroke_set_draw_strokes(gboolean draw)
{
	draw_strokes = draw;
}

gboolean
gstroke_draw_strokes(void)
{
	return draw_strokes;
}

void
gstroke_set_mouse_button(gint button)
{
	mouse_button = button;
}

guint
gstroke_get_mouse_button(void)
{
	return mouse_button;
}

void
gstroke_enable (GtkWidget *widget)
{
  struct gstroke_metrics*
    metrics = (struct gstroke_metrics *)g_object_get_data(G_OBJECT(widget),
														  GSTROKE_METRICS);
  if (metrics == NULL)
    {
      metrics = (struct gstroke_metrics *)g_malloc (sizeof
                                                    (struct gstroke_metrics));
      metrics->pointList = NULL;
      metrics->min_x = 10000;
      metrics->min_y = 10000;
      metrics->max_x = 0;
      metrics->max_y = 0;
      metrics->point_count = 0;

      g_object_set_data(G_OBJECT(widget), GSTROKE_METRICS, metrics);

      g_signal_connect(G_OBJECT(widget), "event",
					   G_CALLBACK(process_event), NULL);
    }
  else
    _gstroke_init (metrics);

  last_mouse_position.invalid = TRUE;
}

guint
gstroke_signal_connect (GtkWidget *widget,
                        const gchar *name,
                        void (*func)(GtkWidget *widget, void *data),
                        gpointer data)
{
  struct gstroke_func_and_data *func_and_data;
  GHashTable *hash_table =
    (GHashTable*)g_object_get_data(G_OBJECT(widget), GSTROKE_SIGNALS);

  if (!hash_table)
    {
      hash_table = g_hash_table_new (g_str_hash, g_str_equal);
      g_object_set_data(G_OBJECT(widget), GSTROKE_SIGNALS,
						(gpointer)hash_table);
    }
  func_and_data = g_new (struct gstroke_func_and_data, 1);
  func_and_data->func = func;
  func_and_data->data = data;
  g_hash_table_insert (hash_table, (gpointer)name, (gpointer)func_and_data);
  return TRUE;
}

static void
gstroke_execute (GtkWidget *widget, const gchar *name)
{

  GHashTable *hash_table =
    (GHashTable*)g_object_get_data(G_OBJECT(widget), GSTROKE_SIGNALS);

#if 0
  gaim_debug(GAIM_DEBUG_MISC, "gestures", "gstroke %s\n", name);
#endif
  
  if (hash_table)
    {
      struct gstroke_func_and_data *fd =
	(struct gstroke_func_and_data*)g_hash_table_lookup (hash_table, name);
      if (fd)
	(*fd->func)(widget, fd->data);
    }
}

void
gstroke_cleanup (GtkWidget *widget)
{
  struct gstroke_metrics *metrics;
  GHashTable *hash_table =
    (GHashTable*)g_object_get_data(G_OBJECT(widget), GSTROKE_SIGNALS);
  if (hash_table)
    /*  FIXME: does this delete the elements too?  */
    g_hash_table_destroy (hash_table);

  g_object_steal_data(G_OBJECT(widget), GSTROKE_SIGNALS);

  metrics = (struct gstroke_metrics*)g_object_get_data(G_OBJECT(widget),
													   GSTROKE_METRICS);
  if (metrics)
    g_free (metrics);
  g_object_steal_data(G_OBJECT(widget), GSTROKE_METRICS);
}


/* This function should be written using Gtk+ primitives*/
static void
gstroke_invisible_window_init (GtkWidget *widget)
{
  XSetWindowAttributes w_attr;
  XWindowAttributes orig_w_attr;
  unsigned long mask, col_border, col_background;
  unsigned int border_width;
  XSizeHints hints;
  Display *disp = GDK_WINDOW_XDISPLAY(widget->window);
  Window wind = GDK_WINDOW_XWINDOW (widget->window);
  int screen = DefaultScreen (disp);

	if (!gstroke_draw_strokes())
		return;

  gstroke_disp = disp;

  /* X server should save what's underneath */
  XGetWindowAttributes (gstroke_disp, wind, &orig_w_attr);
  hints.x = orig_w_attr.x;
  hints.y = orig_w_attr.y;
  hints.width = orig_w_attr.width;
  hints.height = orig_w_attr.height;
  mask = CWSaveUnder;
  w_attr.save_under = True;

  /* inhibit all the decorations */
  mask |= CWOverrideRedirect;
  w_attr.override_redirect = True;

  /* Don't set a background, transparent window */
  mask |= CWBackPixmap;
  w_attr.background_pixmap = None;

  /* Default input window look */
  col_background = WhitePixel (gstroke_disp, screen);

  /* no border for the window */
#if 0
  border_width = 5;
#endif
  border_width = 0;

  col_border = BlackPixel (gstroke_disp, screen);

  gstroke_window = XCreateSimpleWindow (gstroke_disp, wind,
                                        0, 0,
                                        hints.width - 2 * border_width,
                                        hints.height - 2 * border_width,
                                        border_width,
                                        col_border, col_background);

  gstroke_gc = XCreateGC (gstroke_disp, gstroke_window, 0, NULL);

  XSetFunction (gstroke_disp, gstroke_gc, GXinvert);

  XChangeWindowAttributes (gstroke_disp, gstroke_window, mask, &w_attr);

  XSetLineAttributes (gstroke_disp, gstroke_gc, 2, LineSolid,
                      CapButt, JoinMiter);
  XMapRaised (gstroke_disp, gstroke_window);

#if 0
  /*FIXME: is this call really needed? If yes, does it need the real
    argc and argv? */
  hints.flags = PPosition | PSize;
  XSetStandardProperties (gstroke_disp, gstroke_window, "gstroke_test", NULL,
                          (Pixmap)NULL, NULL, 0, &hints);


  /* Receive the close window client message */
  {
    /* FIXME: is this really needed? If yes, something should be done
       with wmdelete...*/
    Atom wmdelete = XInternAtom (gstroke_disp, "WM_DELETE_WINDOW",
                                 False);
    XSetWMProtocols (gstroke_disp, gstroke_window, &wmdelete, True);
  }
#endif
}