Mercurial > pidgin
annotate plugins/gestures/stroke-draw.c @ 12645:fc28451f5d96
[gaim-migrate @ 14983]
SF Patch #1314512 from Sadrul (who has a patch for everything)
"This patch introduces a flag for protocol plugins that
support offline messages (like Y!M and ICQ). This was
encouraged by the following conversation:
<sadrul> should offline buddies be listed/enabled in
the send-to menu?
<rekkanoryo> i would think only for protocols that
support offline messaging, if it's indicated that the
buddy is offline
-- <snip> --
<Bleeter> sadrul: personally, I'd like to see a
'supports offline' flag of some description
<Bleeter> one could then redirect (via plugins) through
email or alternative methods
<Bleeter> just a thought
<Paco-Paco> yeah, that sounds like a reasonble thing to have
This patch uses this flag to disable the buddies in the
send-to menu who are offline and the protocol doesn't
support offline messages."
I made this make the label insensitive instead of the whole menuitem. This
should address SimGuy's concerns about inconsistency (i.e. you could create a
conversation with someone via the buddy list that you couldn't create via the
Send To menu). I also hacked up some voodoo to show the label as sensitive when
moused-over, as that looks better (given the label-insensitive thing is itself a
hack). I think this works quite well.
BUG NOTE:
This makes more obvious an existing bug. The Send To menu isn't updated when
buddies sign on or off or change status (at least under some circumstances).
We need to fix that anyway, so I'm not going to let it hold up this commit.
Switching tabs will clear it up. I'm thinking we just might want to build the
contents of that menu when it is selected. That would save us a mess of
inefficient signal callbacks that update the Send To menus in open windows all
the time.
AIM NOTE:
This assumes that AIM can't offline message. That's not strictly true. You can
message invisible users on AIM. However, by design, we can't tell when a user
is invisible without resorting to dirty hackery. In practice, this isn't a
problem, as you can still select the AIM user from the menu. And really, how
often will you be choosing the Invisible contact, rather than the user going
Invisible in the middle of a conversation or IMing you while they're Invisible?
JABBER NOTE:
This assumes that Jabber can always offline message. This isn't strictly true.
Sadrul said:
I have updated Jabber according to this link which seems to
talk about how to determine the existence offline-message
support in a server:
http://www.jabber.org/jeps/jep-0013.html#discover
However, jabber.org doesn't seem to send the required
info. So I am not sure about it.
He later said:
I talked to Nathan and he said offline message support is
mostly assumed for most jabber servers. GTalk doesn't yet
support it, but they are working on it. So I have made
jabber to always return TRUE.
If there is truly no way to detect offline messaging capability, then this is
an acceptable solution. We could special case Google Talk because of its
popularity, and remove that later. It's probably not worth it though.
MSN NOTE:
This assumes that MSN can never offline message. That's effectively true, but
to be technically correct, MSN can offline message if there's already a
switchboard conversation open with a user. We could write an offline_message
function in the MSN prpl to detect that, but it'd be of limited usefulness,
especially given that under most circumstances (where this might matter), the
switchboard connection will be closed almost immediately.
CVS NOTE:
I'm writing to share a tragic little story.
I have a PC that I use for Gaim development. One day, I was writing a commit
message on it, when all of a suddent it went berserk. The screen started
flashing, and the whole commit message just disappeared. All of it. And it was
a good commit message! I had to cram and rewrite it really quickly. Needless to
say, my rushed commit message wasn't nearly as good, and I blame the PC for that.
Seriously, though, what kind of version control system loses your commit
message on a broken connection to the server? Stupid!
committer: Tailor Script <tailor@pidgin.im>
| author | Richard Laager <rlaager@wiktel.com> |
|---|---|
| date | Fri, 23 Dec 2005 19:26:04 +0000 |
| parents | 2c7e79b6d7b2 |
| children | a99a0cc67713 |
| rev | line source |
|---|---|
| 4390 | 1 /* |
| 2 GNOME stroke implementation | |
| 3 Copyright (c) 2000, 2001 Dan Nicolaescu | |
| 4 See the file COPYING for distribution information. | |
| 5 */ | |
| 6 | |
| 7 #include "config.h" | |
| 8 | |
| 9 #include <unistd.h> | |
| 10 #include <stdlib.h> | |
| 11 #include <stdio.h> | |
| 12 #include <glib.h> | |
| 13 #include <gtk/gtk.h> | |
| 14 | |
| 15 #include <gdk/gdkx.h> | |
| 16 #include "gstroke.h" | |
| 17 #include "gstroke-internal.h" | |
| 18 | |
| 19 #include <X11/Xlib.h> | |
| 20 #include <X11/Xutil.h> | |
| 21 | |
| 22 | |
| 23 static void gstroke_invisible_window_init (GtkWidget *widget); | |
| 24 /*FIXME: Maybe these should be put in a structure, and not static...*/ | |
|
4529
f630a793b9d4
[gaim-migrate @ 4807]
Christian Hammond <chipx86@chipx86.com>
parents:
4432
diff
changeset
|
25 static Display * gstroke_disp = NULL; |
| 4390 | 26 static Window gstroke_window; |
| 27 static GC gstroke_gc; | |
| 28 static int mouse_button = 2; | |
| 29 static gboolean draw_strokes = FALSE; | |
| 30 | |
| 31 #define GSTROKE_TIMEOUT_DURATION 10 | |
| 32 | |
| 33 #define GSTROKE_SIGNALS "gstroke_signals" | |
| 34 | |
| 35 struct gstroke_func_and_data { | |
| 9855 | 36 void (*func)(GtkWidget *, void *); |
| 37 gpointer data; | |
| 4390 | 38 }; |
| 39 | |
| 40 | |
| 41 /*FIXME: maybe it's better to just make 2 static variables, not a | |
| 42 structure */ | |
| 43 struct mouse_position { | |
| 9855 | 44 struct s_point last_point; |
| 45 gboolean invalid; | |
| 4390 | 46 }; |
| 47 | |
| 48 | |
| 49 static struct mouse_position last_mouse_position; | |
| 50 static guint timer_id; | |
| 51 | |
| 52 static void gstroke_execute (GtkWidget *widget, const gchar *name); | |
| 53 | |
| 54 static void | |
| 55 record_stroke_segment (GtkWidget *widget) | |
| 56 { | |
| 57 gint x, y; | |
| 58 struct gstroke_metrics *metrics; | |
| 59 | |
| 9843 | 60 g_return_if_fail( widget != NULL ); |
| 61 | |
| 4390 | 62 gtk_widget_get_pointer (widget, &x, &y); |
| 63 | |
| 64 if (last_mouse_position.invalid) | |
| 65 last_mouse_position.invalid = FALSE; | |
| 66 else if (gstroke_draw_strokes()) | |
| 67 { | |
| 68 #if 1 | |
| 69 XDrawLine (gstroke_disp, gstroke_window, gstroke_gc, | |
| 70 last_mouse_position.last_point.x, | |
| 71 last_mouse_position.last_point.y, | |
| 72 x, y); | |
| 73 /* XFlush (gstroke_disp); */ | |
| 74 #else | |
| 75 /* FIXME: this does not work. It will only work if we create a | |
| 76 corresponding GDK window for stroke_window and draw on | |
| 77 that... */ | |
| 78 gdk_draw_line (widget->window, widget->style->fg_gc[GTK_STATE_NORMAL], | |
| 79 last_mouse_position.last_point.x, | |
| 80 last_mouse_position.last_point.y, | |
| 81 x, | |
| 82 y); | |
| 83 #endif | |
| 84 } | |
| 85 | |
| 86 if (last_mouse_position.last_point.x != x | |
| 87 || last_mouse_position.last_point.y != y) | |
| 88 { | |
| 89 last_mouse_position.last_point.x = x; | |
| 90 last_mouse_position.last_point.y = y; | |
| 91 metrics = (struct gstroke_metrics *)g_object_get_data(G_OBJECT(widget), | |
| 92 GSTROKE_METRICS); | |
| 93 _gstroke_record (x, y, metrics); | |
| 94 } | |
| 95 } | |
| 96 | |
| 97 static gint | |
| 98 gstroke_timeout (gpointer data) | |
| 99 { | |
| 9855 | 100 GtkWidget *widget; |
| 101 | |
| 102 g_return_val_if_fail(data != NULL, FALSE); | |
| 4390 | 103 |
| 9855 | 104 widget = GTK_WIDGET (data); |
| 105 record_stroke_segment (widget); | |
| 106 | |
| 107 return TRUE; | |
| 4390 | 108 } |
| 109 | |
| 9843 | 110 static void gstroke_cancel(GdkEvent *event) |
| 111 { | |
| 112 last_mouse_position.invalid = TRUE; | |
| 113 | |
| 114 if (timer_id > 0) | |
| 115 g_source_remove (timer_id); | |
| 116 | |
| 117 timer_id = 0; | |
| 118 | |
| 119 if( event != NULL ) | |
| 120 gdk_pointer_ungrab (event->button.time); | |
| 121 | |
| 122 | |
| 123 if (gstroke_draw_strokes() && gstroke_disp != NULL) { | |
| 124 /* get rid of the invisible stroke window */ | |
| 125 XUnmapWindow (gstroke_disp, gstroke_window); | |
| 126 XFlush (gstroke_disp); | |
| 127 } | |
| 128 | |
| 129 } | |
| 130 | |
| 4390 | 131 static gint |
| 132 process_event (GtkWidget *widget, GdkEvent *event, gpointer data G_GNUC_UNUSED) | |
| 133 { | |
| 134 static GtkWidget *original_widget = NULL; | |
|
5861
711db8ff91dd
[gaim-migrate @ 6292]
Christian Hammond <chipx86@chipx86.com>
parents:
5227
diff
changeset
|
135 static GdkCursor *cursor = NULL; |
|
711db8ff91dd
[gaim-migrate @ 6292]
Christian Hammond <chipx86@chipx86.com>
parents:
5227
diff
changeset
|
136 |
| 4390 | 137 switch (event->type) { |
| 138 case GDK_BUTTON_PRESS: | |
| 9843 | 139 if (event->button.button != gstroke_get_mouse_button()) { |
| 140 /* Similar to the bug below catch when any other button is | |
| 141 * clicked after the middle button is clicked (but possibly | |
| 142 * not released) | |
| 143 */ | |
| 144 gstroke_cancel(event); | |
| 145 original_widget = NULL; | |
| 146 break; | |
| 147 } | |
| 4390 | 148 |
| 149 original_widget = widget; /* remeber the widget where | |
| 150 the stroke started */ | |
| 151 | |
| 152 gstroke_invisible_window_init (widget); | |
| 153 | |
| 154 record_stroke_segment (widget); | |
| 155 | |
|
5861
711db8ff91dd
[gaim-migrate @ 6292]
Christian Hammond <chipx86@chipx86.com>
parents:
5227
diff
changeset
|
156 if (cursor == NULL) |
|
711db8ff91dd
[gaim-migrate @ 6292]
Christian Hammond <chipx86@chipx86.com>
parents:
5227
diff
changeset
|
157 cursor = gdk_cursor_new(GDK_PENCIL); |
|
711db8ff91dd
[gaim-migrate @ 6292]
Christian Hammond <chipx86@chipx86.com>
parents:
5227
diff
changeset
|
158 |
| 4390 | 159 gdk_pointer_grab (widget->window, FALSE, |
|
5861
711db8ff91dd
[gaim-migrate @ 6292]
Christian Hammond <chipx86@chipx86.com>
parents:
5227
diff
changeset
|
160 GDK_BUTTON_RELEASE_MASK, NULL, cursor, |
| 4390 | 161 event->button.time); |
| 8555 | 162 timer_id = g_timeout_add (GSTROKE_TIMEOUT_DURATION, |
| 4390 | 163 gstroke_timeout, widget); |
| 164 return TRUE; | |
| 165 | |
| 166 case GDK_BUTTON_RELEASE: | |
| 167 if ((event->button.button != gstroke_get_mouse_button()) | |
|
4399
ce8d35b435de
[gaim-migrate @ 4668]
Christian Hammond <chipx86@chipx86.com>
parents:
4390
diff
changeset
|
168 || (original_widget == NULL)) { |
|
ce8d35b435de
[gaim-migrate @ 4668]
Christian Hammond <chipx86@chipx86.com>
parents:
4390
diff
changeset
|
169 |
| 9843 | 170 /* Nice bug when you hold down one button and press another. */ |
| 171 /* We'll just cancel the gesture instead. */ | |
| 172 gstroke_cancel(event); | |
| 173 original_widget = NULL; | |
| 174 break; | |
|
4399
ce8d35b435de
[gaim-migrate @ 4668]
Christian Hammond <chipx86@chipx86.com>
parents:
4390
diff
changeset
|
175 } |
| 4390 | 176 |
| 177 last_mouse_position.invalid = TRUE; | |
| 178 original_widget = NULL; | |
| 8555 | 179 g_source_remove (timer_id); |
| 4390 | 180 gdk_pointer_ungrab (event->button.time); |
| 181 timer_id = 0; | |
| 182 | |
| 183 { | |
| 184 char result[GSTROKE_MAX_SEQUENCE]; | |
| 185 struct gstroke_metrics *metrics; | |
| 186 | |
| 187 metrics = (struct gstroke_metrics *)g_object_get_data(G_OBJECT (widget), | |
| 188 GSTROKE_METRICS); | |
| 189 if (gstroke_draw_strokes()) { | |
| 190 /* get rid of the invisible stroke window */ | |
| 191 XUnmapWindow (gstroke_disp, gstroke_window); | |
| 192 XFlush (gstroke_disp); | |
| 193 } | |
| 194 | |
| 195 _gstroke_canonical (result, metrics); | |
| 196 gstroke_execute (widget, result); | |
| 197 } | |
|
12204
2c7e79b6d7b2
[gaim-migrate @ 14506]
Richard Laager <rlaager@wiktel.com>
parents:
10814
diff
changeset
|
198 return FALSE; |
|
2c7e79b6d7b2
[gaim-migrate @ 14506]
Richard Laager <rlaager@wiktel.com>
parents:
10814
diff
changeset
|
199 |
| 4390 | 200 default: |
| 201 break; | |
| 202 } | |
| 203 | |
| 204 return FALSE; | |
| 205 } | |
| 206 | |
| 207 void | |
| 208 gstroke_set_draw_strokes(gboolean draw) | |
| 209 { | |
| 210 draw_strokes = draw; | |
| 211 } | |
| 212 | |
| 213 gboolean | |
| 214 gstroke_draw_strokes(void) | |
| 215 { | |
| 216 return draw_strokes; | |
| 217 } | |
| 218 | |
| 219 void | |
| 220 gstroke_set_mouse_button(gint button) | |
| 221 { | |
| 222 mouse_button = button; | |
| 223 } | |
| 224 | |
| 7631 | 225 guint |
| 4390 | 226 gstroke_get_mouse_button(void) |
| 227 { | |
| 228 return mouse_button; | |
| 229 } | |
| 230 | |
| 231 void | |
| 232 gstroke_enable (GtkWidget *widget) | |
| 233 { | |
| 234 struct gstroke_metrics* | |
| 235 metrics = (struct gstroke_metrics *)g_object_get_data(G_OBJECT(widget), | |
| 236 GSTROKE_METRICS); | |
| 237 if (metrics == NULL) | |
| 238 { | |
| 239 metrics = (struct gstroke_metrics *)g_malloc (sizeof | |
| 240 (struct gstroke_metrics)); | |
| 241 metrics->pointList = NULL; | |
| 242 metrics->min_x = 10000; | |
| 243 metrics->min_y = 10000; | |
| 244 metrics->max_x = 0; | |
| 245 metrics->max_y = 0; | |
| 246 metrics->point_count = 0; | |
| 247 | |
| 248 g_object_set_data(G_OBJECT(widget), GSTROKE_METRICS, metrics); | |
| 249 | |
| 250 g_signal_connect(G_OBJECT(widget), "event", | |
| 251 G_CALLBACK(process_event), NULL); | |
| 252 } | |
| 253 else | |
| 254 _gstroke_init (metrics); | |
| 255 | |
| 256 last_mouse_position.invalid = TRUE; | |
| 257 } | |
| 258 | |
| 259 guint | |
| 260 gstroke_signal_connect (GtkWidget *widget, | |
| 261 const gchar *name, | |
| 262 void (*func)(GtkWidget *widget, void *data), | |
| 263 gpointer data) | |
| 264 { | |
| 265 struct gstroke_func_and_data *func_and_data; | |
| 266 GHashTable *hash_table = | |
| 267 (GHashTable*)g_object_get_data(G_OBJECT(widget), GSTROKE_SIGNALS); | |
| 268 | |
| 269 if (!hash_table) | |
| 270 { | |
| 271 hash_table = g_hash_table_new (g_str_hash, g_str_equal); | |
| 272 g_object_set_data(G_OBJECT(widget), GSTROKE_SIGNALS, | |
| 273 (gpointer)hash_table); | |
| 274 } | |
| 275 func_and_data = g_new (struct gstroke_func_and_data, 1); | |
| 276 func_and_data->func = func; | |
| 277 func_and_data->data = data; | |
| 278 g_hash_table_insert (hash_table, (gpointer)name, (gpointer)func_and_data); | |
| 279 return TRUE; | |
| 280 } | |
| 281 | |
| 282 static void | |
| 283 gstroke_execute (GtkWidget *widget, const gchar *name) | |
| 284 { | |
| 285 | |
| 286 GHashTable *hash_table = | |
| 287 (GHashTable*)g_object_get_data(G_OBJECT(widget), GSTROKE_SIGNALS); | |
| 288 | |
| 289 #if 0 | |
|
5227
6d1707dc8c3d
[gaim-migrate @ 5597]
Christian Hammond <chipx86@chipx86.com>
parents:
4529
diff
changeset
|
290 gaim_debug(GAIM_DEBUG_MISC, "gestures", "gstroke %s\n", name); |
| 4390 | 291 #endif |
| 292 | |
| 293 if (hash_table) | |
| 294 { | |
| 295 struct gstroke_func_and_data *fd = | |
| 296 (struct gstroke_func_and_data*)g_hash_table_lookup (hash_table, name); | |
| 297 if (fd) | |
| 298 (*fd->func)(widget, fd->data); | |
| 299 } | |
| 300 } | |
| 301 | |
| 302 void | |
| 303 gstroke_cleanup (GtkWidget *widget) | |
| 304 { | |
| 305 struct gstroke_metrics *metrics; | |
| 306 GHashTable *hash_table = | |
| 307 (GHashTable*)g_object_get_data(G_OBJECT(widget), GSTROKE_SIGNALS); | |
| 308 if (hash_table) | |
| 309 /* FIXME: does this delete the elements too? */ | |
| 310 g_hash_table_destroy (hash_table); | |
| 311 | |
| 312 g_object_steal_data(G_OBJECT(widget), GSTROKE_SIGNALS); | |
| 313 | |
| 314 metrics = (struct gstroke_metrics*)g_object_get_data(G_OBJECT(widget), | |
| 315 GSTROKE_METRICS); | |
| 316 if (metrics) | |
| 317 g_free (metrics); | |
| 318 g_object_steal_data(G_OBJECT(widget), GSTROKE_METRICS); | |
| 319 } | |
| 320 | |
| 321 | |
| 10814 | 322 /* This function should be written using GTK+ primitives*/ |
| 4390 | 323 static void |
| 324 gstroke_invisible_window_init (GtkWidget *widget) | |
| 325 { | |
| 326 XSetWindowAttributes w_attr; | |
| 327 XWindowAttributes orig_w_attr; | |
| 328 unsigned long mask, col_border, col_background; | |
| 329 unsigned int border_width; | |
| 330 XSizeHints hints; | |
| 331 Display *disp = GDK_WINDOW_XDISPLAY(widget->window); | |
| 332 Window wind = GDK_WINDOW_XWINDOW (widget->window); | |
| 333 int screen = DefaultScreen (disp); | |
| 334 | |
| 335 if (!gstroke_draw_strokes()) | |
| 336 return; | |
| 337 | |
| 338 gstroke_disp = disp; | |
| 339 | |
| 340 /* X server should save what's underneath */ | |
| 341 XGetWindowAttributes (gstroke_disp, wind, &orig_w_attr); | |
| 342 hints.x = orig_w_attr.x; | |
| 343 hints.y = orig_w_attr.y; | |
| 344 hints.width = orig_w_attr.width; | |
| 345 hints.height = orig_w_attr.height; | |
| 346 mask = CWSaveUnder; | |
| 347 w_attr.save_under = True; | |
| 348 | |
| 349 /* inhibit all the decorations */ | |
| 350 mask |= CWOverrideRedirect; | |
| 351 w_attr.override_redirect = True; | |
| 352 | |
| 353 /* Don't set a background, transparent window */ | |
| 354 mask |= CWBackPixmap; | |
| 355 w_attr.background_pixmap = None; | |
| 356 | |
| 357 /* Default input window look */ | |
| 358 col_background = WhitePixel (gstroke_disp, screen); | |
| 359 | |
| 360 /* no border for the window */ | |
|
4399
ce8d35b435de
[gaim-migrate @ 4668]
Christian Hammond <chipx86@chipx86.com>
parents:
4390
diff
changeset
|
361 #if 0 |
| 4390 | 362 border_width = 5; |
|
4399
ce8d35b435de
[gaim-migrate @ 4668]
Christian Hammond <chipx86@chipx86.com>
parents:
4390
diff
changeset
|
363 #endif |
| 4390 | 364 border_width = 0; |
|
4399
ce8d35b435de
[gaim-migrate @ 4668]
Christian Hammond <chipx86@chipx86.com>
parents:
4390
diff
changeset
|
365 |
| 4390 | 366 col_border = BlackPixel (gstroke_disp, screen); |
| 367 | |
| 368 gstroke_window = XCreateSimpleWindow (gstroke_disp, wind, | |
| 369 0, 0, | |
| 370 hints.width - 2 * border_width, | |
| 371 hints.height - 2 * border_width, | |
| 372 border_width, | |
| 373 col_border, col_background); | |
| 374 | |
| 375 gstroke_gc = XCreateGC (gstroke_disp, gstroke_window, 0, NULL); | |
| 376 | |
| 377 XSetFunction (gstroke_disp, gstroke_gc, GXinvert); | |
| 378 | |
| 379 XChangeWindowAttributes (gstroke_disp, gstroke_window, mask, &w_attr); | |
| 380 | |
| 381 XSetLineAttributes (gstroke_disp, gstroke_gc, 2, LineSolid, | |
| 382 CapButt, JoinMiter); | |
| 383 XMapRaised (gstroke_disp, gstroke_window); | |
| 384 | |
| 385 #if 0 | |
| 386 /*FIXME: is this call really needed? If yes, does it need the real | |
| 387 argc and argv? */ | |
| 388 hints.flags = PPosition | PSize; | |
| 389 XSetStandardProperties (gstroke_disp, gstroke_window, "gstroke_test", NULL, | |
| 390 (Pixmap)NULL, NULL, 0, &hints); | |
| 391 | |
| 392 | |
| 393 /* Receive the close window client message */ | |
| 394 { | |
| 395 /* FIXME: is this really needed? If yes, something should be done | |
| 396 with wmdelete...*/ | |
| 397 Atom wmdelete = XInternAtom (gstroke_disp, "WM_DELETE_WINDOW", | |
| 398 False); | |
| 399 XSetWMProtocols (gstroke_disp, gstroke_window, &wmdelete, True); | |
| 400 } | |
| 401 #endif | |
| 402 } |
