comparison plugins/docklet/docklet.c @ 11709:cae2fb7e8594

[gaim-migrate @ 14000] This is a patch from Casey Harkins to significantly overhaul the docklet plugin. I'm pretty happy about this because it enables us to remove a win32 GTK+ dependency on the core and all the prpls. committer: Tailor Script <tailor@pidgin.im>
author Daniel Atallah <daniel.atallah@gmail.com>
date Sat, 22 Oct 2005 01:18:08 +0000
parents bb0d7b719af2
children 2aa3211ef3e2
comparison
equal deleted inserted replaced
11708:69602de55fe9 11709:cae2fb7e8594
19 * You should have received a copy of the GNU General Public License 19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software 20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
22 * 02111-1307, USA. 22 * 02111-1307, USA.
23 */ 23 */
24
25 /* TODO (in order of importance):
26 * - unify the queue so we can have a global away without the dialog
27 * - handle and update tooltips to show your current accounts/queued messages?
28 * - show a count of queued messages in the unified queue
29 * - dernyi's account status menu in the right click
30 * - optional pop up notices when GNOME2's system-tray-applet supports it
31 */
32
33 #include "internal.h" 24 #include "internal.h"
34 #include "gtkgaim.h" 25 #include "gtkgaim.h"
35 26
36 #include "core.h" 27 #include "core.h"
28 #include "conversation.h"
37 #include "debug.h" 29 #include "debug.h"
38 #include "prefs.h" 30 #include "prefs.h"
39 #include "signals.h" 31 #include "signals.h"
40 #include "sound.h" 32 #include "sound.h"
41 #include "version.h" 33 #include "version.h"
42 34
43 #include "gtkaccount.h" 35 #include "gtkaccount.h"
44 #include "gtkblist.h" 36 #include "gtkblist.h"
37 #include "gtkconv.h"
45 #include "gtkft.h" 38 #include "gtkft.h"
46 #include "gtkplugin.h" 39 #include "gtkplugin.h"
47 #include "gtkprefs.h" 40 #include "gtkprefs.h"
48 #include "gtksound.h" 41 #include "gtksound.h"
49 #include "gtkutils.h" 42 #include "gtkutils.h"
51 #include "docklet.h" 44 #include "docklet.h"
52 45
53 #include "gaim.h" 46 #include "gaim.h"
54 #include "gtkdialogs.h" 47 #include "gtkdialogs.h"
55 48
49 #define DOCKLET_PLUGIN_ID "gtk-docklet"
50
56 /* globals */ 51 /* globals */
57
58 GaimPlugin *handle = NULL; 52 GaimPlugin *handle = NULL;
59 static struct docklet_ui_ops *ui_ops = NULL; 53 static struct docklet_ui_ops *ui_ops = NULL;
60 static enum docklet_status status = offline; 54 static DockletStatus status = DOCKLET_STATUS_OFFLINE;
61 gboolean online_account_supports_chat = FALSE; 55 static gulong gtkblist_delete_cb_id = 0;
62 #if 0 /* XXX CUI */ 56 static gboolean enable_join_chat = FALSE;
63 #ifdef _WIN32 57
64 __declspec(dllimport) GSList *unread_message_queue; 58 /**************************************************************************
65 __declspec(dllimport) GSList *away_messages; 59 * docklet status and utility functions
66 __declspec(dllimport) struct away_message *awaymessage; 60 **************************************************************************/
67 __declspec(dllimport) GSList *message_queue;
68 #endif
69 #endif
70
71 /* private functions */
72
73 static void
74 docklet_toggle_mute(GtkWidget *toggle, void *data)
75 {
76 gaim_prefs_set_bool("/gaim/gtk/sound/mute", GTK_CHECK_MENU_ITEM(toggle)->active);
77 }
78
79 static void
80 docklet_set_bool(GtkWidget *widget, const char *key)
81 {
82 gaim_prefs_set_bool(key, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget)));
83 }
84
85 #ifdef _WIN32
86 /* This is a workaround for a bug in windows GTK+. Clicking outside of the
87 menu does not get rid of it, so instead we get rid of it as soon as the
88 pointer leaves the menu. */
89 static gboolean hide_docklet_menu(gpointer data)
90 {
91 if (data != NULL) {
92 gtk_menu_popdown(GTK_MENU(data));
93 }
94 return FALSE;
95 }
96 static gboolean
97 docklet_menu_leave_enter(GtkWidget *menu, GdkEventCrossing *event, void *data)
98 {
99 static guint hide_docklet_timer = 0;
100 if (event->type == GDK_LEAVE_NOTIFY && event->detail == GDK_NOTIFY_ANCESTOR) {
101 gaim_debug(GAIM_DEBUG_INFO, "tray icon", "menu leave-notify-event\n");
102 /* Add some slop so that the menu doesn't annoyingly disappear when mousing around */
103 if (hide_docklet_timer == 0) {
104 hide_docklet_timer = gaim_timeout_add(500,
105 hide_docklet_menu, menu);
106 }
107 } else if (event->type == GDK_ENTER_NOTIFY && event->detail == GDK_NOTIFY_ANCESTOR) {
108 gaim_debug(GAIM_DEBUG_INFO, "tray icon", "menu enter-notify-event\n");
109 if (hide_docklet_timer != 0) {
110 /* Cancel the hiding if we reenter */
111 gaim_timeout_remove(hide_docklet_timer);
112 hide_docklet_timer = 0;
113 }
114 }
115 return FALSE;
116 }
117 #endif
118
119 static void docklet_menu() {
120 static GtkWidget *menu = NULL;
121 GtkWidget *entry;
122 GtkWidget *menuitem;
123
124 if (menu) {
125 gtk_widget_destroy(menu);
126 }
127
128 menu = gtk_menu_new();
129
130 switch (status) {
131 case offline:
132 case offline_connecting:
133 /* No special menu items right now */
134 break;
135 default:
136 gaim_new_item_from_stock(menu, _("New Message..."), GAIM_STOCK_IM, G_CALLBACK(gaim_gtkdialogs_im), NULL, 0, 0, NULL);
137 menuitem = gaim_new_item_from_stock(menu, _("Join A Chat..."), GAIM_STOCK_CHAT, G_CALLBACK(gaim_gtk_blist_joinchat_show), NULL, 0, 0, NULL);
138 gtk_widget_set_sensitive(menuitem, online_account_supports_chat);
139
140 gaim_separator(menu);
141 break;
142 }
143
144 switch (status) {
145 case offline:
146 case offline_connecting:
147 break;
148 case online:
149 case online_connecting:
150 case online_pending: {
151 #if 0 /* XXX NEW STATUS */
152 GtkWidget *docklet_awaymenu;
153 GSList *awy = NULL;
154 struct away_message *a = NULL;
155
156 docklet_awaymenu = gtk_menu_new();
157 awy = away_messages;
158
159 while (awy) {
160 a = (struct away_message *)awy->data;
161
162 entry = gtk_menu_item_new_with_label(a->name);
163 g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(do_away_message), a);
164 gtk_menu_shell_append(GTK_MENU_SHELL(docklet_awaymenu), entry);
165
166 awy = g_slist_next(awy);
167 }
168
169 if (away_messages)
170 gaim_separator(docklet_awaymenu);
171
172 entry = gtk_menu_item_new_with_label(_("New..."));
173 g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(create_away_mess), NULL);
174 gtk_menu_shell_append(GTK_MENU_SHELL(docklet_awaymenu), entry);
175
176 entry = gtk_menu_item_new_with_label(_("Away"));
177 gtk_menu_item_set_submenu(GTK_MENU_ITEM(entry), docklet_awaymenu);
178 gtk_menu_shell_append(GTK_MENU_SHELL(menu), entry);
179 #endif
180 } break;
181 case away:
182 case away_pending:
183 #if 0 /* XXX NEW STATUS */
184 entry = gtk_menu_item_new_with_label(_("Back"));
185 g_signal_connect(G_OBJECT(entry), "activate", G_CALLBACK(do_im_back), NULL);
186 gtk_menu_shell_append(GTK_MENU_SHELL(menu), entry);
187 #endif
188 break;
189 }
190
191 entry = gtk_check_menu_item_new_with_label(_("Mute Sounds"));
192 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry), gaim_prefs_get_bool("/gaim/gtk/sound/mute"));
193 if (!strcmp(gaim_prefs_get_string("/gaim/gtk/sound/method"), "none"))
194 gtk_widget_set_sensitive(GTK_WIDGET(entry), FALSE);
195 g_signal_connect(G_OBJECT(entry), "toggled", G_CALLBACK(docklet_toggle_mute), NULL);
196 gtk_menu_shell_append(GTK_MENU_SHELL(menu), entry);
197
198 gaim_new_item_from_stock(menu, _("File Transfers"), GAIM_STOCK_FILE_TRANSFER, G_CALLBACK(gaim_show_xfer_dialog), NULL, 0, 0, NULL);
199 gaim_new_item_from_stock(menu, _("Accounts"), GAIM_STOCK_ACCOUNTS, G_CALLBACK(gaim_gtk_accounts_window_show), NULL, 0, 0, NULL);
200 gaim_new_item_from_stock(menu, _("Preferences"), GTK_STOCK_PREFERENCES, G_CALLBACK(gaim_gtk_prefs_show), NULL, 0, 0, NULL);
201
202 gaim_separator(menu);
203
204 gaim_new_item_from_stock(menu, _("Quit"), GTK_STOCK_QUIT, G_CALLBACK(gaim_core_quit), NULL, 0, 0, NULL);
205
206 #ifdef _WIN32
207 g_signal_connect(menu, "leave-notify-event", G_CALLBACK(docklet_menu_leave_enter), NULL);
208 g_signal_connect(menu, "enter-notify-event", G_CALLBACK(docklet_menu_leave_enter), NULL);
209 #endif
210 gtk_widget_show_all(menu);
211 gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
212 ui_ops->position_menu,
213 NULL, 0, gtk_get_current_event_time());
214 }
215
216 static gboolean 61 static gboolean
217 docklet_blink_icon() 62 docklet_blink_icon()
218 { 63 {
219 static gboolean blinked = FALSE; 64 static gboolean blinked = FALSE;
220 gboolean ret = FALSE; /* by default, don't keep blinking */ 65 gboolean ret = FALSE; /* by default, don't keep blinking */
221 66
222 blinked = !blinked; 67 blinked = !blinked;
223 68
224 switch (status) { 69 switch (status) {
225 case online_pending: 70 case DOCKLET_STATUS_ONLINE_PENDING:
226 case away_pending: 71 case DOCKLET_STATUS_AWAY_PENDING:
227 if (blinked) { 72 if (blinked) {
228 if (ui_ops && ui_ops->blank_icon) 73 if (ui_ops && ui_ops->blank_icon)
229 ui_ops->blank_icon(); 74 ui_ops->blank_icon();
230 } else { 75 } else {
231 if (ui_ops && ui_ops->update_icon) 76 if (ui_ops && ui_ops->update_icon)
232 ui_ops->update_icon(status); 77 ui_ops->update_icon(status);
233 } 78 }
234 ret = TRUE; /* keep blinking */ 79 ret = TRUE; /* keep blinking */
235 break; 80 break;
236 case offline: 81 default:
237 case offline_connecting:
238 case online:
239 case online_connecting:
240 case away:
241 blinked = FALSE; 82 blinked = FALSE;
242 break; 83 break;
243 } 84 }
244 85
245 return ret; 86 return ret;
246 } 87 }
247 88
248 static gboolean 89 static gboolean
249 docklet_update_status() 90 docklet_update_status()
250 { 91 {
251 GList *c; 92 GList *l;
252 enum docklet_status oldstatus; 93 DockletStatus newstatus = DOCKLET_STATUS_OFFLINE;
253 94 gboolean pending = FALSE;
254 oldstatus = status; 95
255 96 /* determine if any ims have unseen messages */
256 if ((c = gaim_connections_get_all())) { 97 for(l = gaim_get_ims(); l!=NULL; l=l->next) {
257 #if 0 /* XXX NEW STATUS */ 98 GaimConversation *conv = (GaimConversation*)l->data;
258 if (unread_message_queue) { 99 if(GAIM_IS_GTK_CONVERSATION(conv)) {
259 status = online_pending; 100 if(GAIM_GTK_CONVERSATION(conv)->unseen_state!=GAIM_UNSEEN_NONE) {
260 } else if (awaymessage) { 101 pending = TRUE;
261 if (message_queue) {
262 status = away_pending;
263 } else {
264 status = away;
265 }
266 } else if (gaim_connections_get_connecting()) {
267 #else
268 if (gaim_connections_get_connecting()) {
269 #endif
270 status = online_connecting;
271 } else {
272 status = online;
273 }
274 /* Check if any online accounts support chats */
275 while (c != NULL) {
276 GaimConnection *gc = c->data;
277 if (GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info != NULL) {
278 online_account_supports_chat = TRUE;
279 break; 102 break;
280 } 103 }
281 c = c->next; 104 }
282 } 105 }
283 } else { 106
284 if (gaim_connections_get_connecting()) { 107 /* iterate through all accounts and determine which
285 status = offline_connecting; 108 * status to show in the tray icon based on the following
286 } else { 109 * ranks (highest encountered rank will be used):
287 status = offline; 110 *
288 } 111 * 1) OFFLINE
112 * 2) ONLINE
113 * 3) ONLINE_PENDING
114 * 4) AWAY
115 * 5) AWAY_PENDING
116 * 6) CONNECTING
117 */
118 for(l = gaim_accounts_get_all(); l!=NULL; l=l->next) {
119 DockletStatus tmpstatus = DOCKLET_STATUS_OFFLINE;
120
121 GaimAccount *account = (GaimAccount*)l->data;
122 GaimStatus *account_status;
123
124 if (!gaim_account_get_enabled(account, GAIM_GTK_UI))
125 continue;
126
127 if(gaim_account_is_disconnected(account))
128 continue;
129
130 account_status = gaim_account_get_active_status(account);
131
132 if(gaim_account_is_connecting(account)) {
133 tmpstatus = DOCKLET_STATUS_CONNECTING;
134 }
135 else if(gaim_status_is_online(account_status)) {
136 if(!gaim_status_is_available(account_status)) {
137 if(pending)
138 tmpstatus = DOCKLET_STATUS_AWAY_PENDING;
139 else
140 tmpstatus = DOCKLET_STATUS_AWAY;
141 }
142 else {
143 if(pending)
144 tmpstatus = DOCKLET_STATUS_ONLINE_PENDING;
145 else
146 tmpstatus = DOCKLET_STATUS_ONLINE;
147 }
148 }
149
150 if(tmpstatus>newstatus) newstatus=tmpstatus;
289 } 151 }
290 152
291 /* update the icon if we changed status */ 153 /* update the icon if we changed status */
292 if (status != oldstatus) { 154 if (status != newstatus) {
155 status = newstatus;
156
293 if (ui_ops && ui_ops->update_icon) 157 if (ui_ops && ui_ops->update_icon)
294 ui_ops->update_icon(status); 158 ui_ops->update_icon(status);
295 159
296 /* and schedule the blinker function if messages are pending */ 160 /* and schedule the blinker function if messages are pending */
297 if (status == online_pending || status == away_pending) { 161 if (status == DOCKLET_STATUS_ONLINE_PENDING
162 || status == DOCKLET_STATUS_AWAY_PENDING) {
298 g_timeout_add(500, docklet_blink_icon, &handle); 163 g_timeout_add(500, docklet_blink_icon, &handle);
299 } 164 }
300 } 165 }
301 166
302 return FALSE; /* for when we're called by the glib idle handler */ 167 return FALSE; /* for when we're called by the glib idle handler */
303 } 168 }
304 169
305 #if 0 /* XXX CUI */ 170 static gboolean
306 static void 171 online_account_supports_chat()
307 docklet_flush_queue() 172 {
308 { 173 GList *c = NULL;
309 if (unread_message_queue) { 174 c = gaim_connections_get_all();
310 purge_away_queue(&unread_message_queue); 175
311 } 176 while(c!=NULL) {
177 GaimConnection *gc = c->data;
178 if(GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info!=NULL)
179 return TRUE;
180 }
181
182 return FALSE;
183 }
184
185 static gboolean
186 focus_first_unseen_conv()
187 {
188 GList *l;
189 GaimConversation *conv;
190 GaimGtkConversation *gtkconv;
191
192 /* find first im with unseen messages */
193 for(l = gaim_get_ims(); l!=NULL; l=l->next) {
194 conv = (GaimConversation*)l->data;
195 if(GAIM_IS_GTK_CONVERSATION(conv)) {
196 gtkconv=GAIM_GTK_CONVERSATION(conv);
197 if(gtkconv->unseen_state!=GAIM_UNSEEN_NONE) {
198 gtkconv->active_conv=conv;
199 gaim_gtk_conv_window_switch_gtkconv(gtkconv->win, gtkconv);
200 gaim_gtk_conv_window_raise(gtkconv->win);
201 gtk_window_present(GTK_WINDOW(gtkconv->win->window));
202 return TRUE;
203 }
204 }
205 }
206
207 return FALSE;
208 }
209
210 /**************************************************************************
211 * minimize to and unminimize from the tray icon
212 **************************************************************************/
213 static void
214 minimize_to_tray()
215 {
216 GaimGtkBuddyList *blist = gaim_gtk_blist_get_default_gtk_blist();
217
218 if(!blist || !blist->window)
219 return;
220
221 if (ui_ops && ui_ops->minimize)
222 ui_ops->minimize(blist->window);
223
224 gaim_prefs_set_bool("/gaim/gtk/blist/list_visible", FALSE);
225 gtk_widget_hide(blist->window);
226
227 docklet_update_status();
228 }
229
230 static void
231 unminimize_from_tray()
232 {
233 GaimGtkBuddyList *blist = gaim_gtk_blist_get_default_gtk_blist();
234
235 if(!blist || !blist->window)
236 return;
237
238 if (ui_ops && ui_ops->maximize)
239 ui_ops->maximize(blist->window);
240
241 gaim_blist_set_visible(TRUE);
242
243 docklet_update_status();
244 }
245
246 static void
247 docklet_toggle_blist()
248 {
249 if(gaim_prefs_get_bool("/gaim/gtk/blist/list_visible"))
250 minimize_to_tray();
251 else
252 unminimize_from_tray();
253 }
254
255 /**************************************************************************
256 * callbacks and signal handlers
257 **************************************************************************/
258 /* catch delete events on gtkblist and hide it instead */
259 static gboolean
260 gtkblist_delete_cb(GtkWidget *widget) {
261 gaim_debug(GAIM_DEBUG_INFO, "docklet", "hiding buddy list\n");
262 minimize_to_tray(widget);
263 return TRUE;
264 }
265
266 /* connect to delete signal when gtkblist is created */
267 static void
268 gtkblist_created_cb(GaimBuddyList *list)
269 {
270 if(list!=NULL && GAIM_IS_GTK_BLIST(list) &&
271 GAIM_GTK_BLIST(list)->window!=NULL &&
272 gtkblist_delete_cb_id==0) {
273
274 gtkblist_delete_cb_id = g_signal_connect(G_OBJECT(GAIM_GTK_BLIST(list)->window),
275 "delete_event", G_CALLBACK(gtkblist_delete_cb), NULL);
276 }
277 }
278
279 static void
280 gaim_quit_cb()
281 {
282 /* TODO: confirm quit while pending */
283 }
284
285 static void
286 docklet_update_status_cb(void *data, ...)
287 {
288 /* The odd function arguments allow this callback to be used for
289 * any signal which has a pointer as the first callback parameter.
290 * Although ugly, it allows this single callback to be used instead
291 * of multiple functions with different signatures that do the same
292 * thing.
293 */
294 docklet_update_status();
295 }
296
297 static void
298 docklet_conv_updated_cb(GaimConversation *conv, GaimConvUpdateType type)
299 {
300 if(type==GAIM_CONV_UPDATE_UNSEEN)
301 docklet_update_status();
302 }
303
304 static void
305 docklet_signed_on_cb(GaimConnection *gc)
306 {
307 if(!enable_join_chat) {
308 if(GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info!=NULL)
309 enable_join_chat = TRUE;
310 }
311 docklet_update_status();
312 }
313
314 static void
315 docklet_signed_off_cb(GaimConnection *gc)
316 {
317 if(enable_join_chat) {
318 if(GAIM_PLUGIN_PROTOCOL_INFO(gc->prpl)->chat_info!=NULL)
319 enable_join_chat = online_account_supports_chat();
320 }
321 docklet_update_status();
322 }
323
324 /**************************************************************************
325 * docklet pop-up menu
326 **************************************************************************/
327 static void
328 docklet_toggle_mute(GtkWidget *toggle, void *data)
329 {
330 gaim_prefs_set_bool("/gaim/gtk/sound/mute", GTK_CHECK_MENU_ITEM(toggle)->active);
331 }
332
333 #ifdef _WIN32
334 /* This is a workaround for a bug in windows GTK+. Clicking outside of the
335 menu does not get rid of it, so instead we get rid of it as soon as the
336 pointer leaves the menu. */
337 static gboolean
338 hide_docklet_menu(gpointer data)
339 {
340 if (data != NULL) {
341 gtk_menu_popdown(GTK_MENU(data));
342 }
343 return FALSE;
344 }
345
346 static gboolean
347 docklet_menu_leave_enter(GtkWidget *menu, GdkEventCrossing *event, void *data)
348 {
349 static guint hide_docklet_timer = 0;
350 if (event->type == GDK_LEAVE_NOTIFY && event->detail == GDK_NOTIFY_ANCESTOR) {
351 gaim_debug(GAIM_DEBUG_INFO, "docklet", "menu leave-notify-event\n");
352 /* Add some slop so that the menu doesn't annoyingly disappear when mousing around */
353 if (hide_docklet_timer == 0) {
354 hide_docklet_timer = gaim_timeout_add(500,
355 hide_docklet_menu, menu);
356 }
357 } else if (event->type == GDK_ENTER_NOTIFY && event->detail == GDK_NOTIFY_ANCESTOR) {
358 gaim_debug(GAIM_DEBUG_INFO, "docklet", "menu enter-notify-event\n");
359 if (hide_docklet_timer != 0) {
360 /* Cancel the hiding if we reenter */
361
362 gaim_timeout_remove(hide_docklet_timer);
363 hide_docklet_timer = 0;
364 }
365 }
366 return FALSE;
312 } 367 }
313 #endif 368 #endif
314 369
315 static void 370 static void
316 docklet_remove_callbacks() 371 docklet_menu() {
317 { 372 static GtkWidget *menu = NULL;
318 gaim_debug(GAIM_DEBUG_INFO, "tray icon", "removing callbacks"); 373 GtkWidget *entry;
319 374 GtkWidget *menuitem;
320 while (g_source_remove_by_user_data(&handle)) { 375
321 gaim_debug(GAIM_DEBUG_INFO, NULL, "."); 376 if (menu) {
322 } 377 gtk_widget_destroy(menu);
323 378 }
324 gaim_debug(GAIM_DEBUG_INFO, NULL, "\n"); 379
325 } 380 menu = gtk_menu_new();
326 381
327 /* public code */ 382 entry = gtk_check_menu_item_new_with_label(_("Show Buddy List"));
328 383 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry), gaim_prefs_get_bool("/gaim/gtk/blist/list_visible"));
384 g_signal_connect(G_OBJECT(entry), "toggled", G_CALLBACK(docklet_toggle_blist), NULL);
385 gtk_menu_shell_append(GTK_MENU_SHELL(menu), entry);
386
387 gaim_separator(menu);
388
389 menuitem = gaim_new_item_from_stock(menu, _("New Message..."), GAIM_STOCK_IM, G_CALLBACK(gaim_gtkdialogs_im), NULL, 0, 0, NULL);
390 if(status == DOCKLET_STATUS_OFFLINE)
391 gtk_widget_set_sensitive(menuitem, FALSE);
392
393 menuitem = gaim_new_item_from_stock(menu, _("Join A Chat..."), GAIM_STOCK_CHAT, G_CALLBACK(gaim_gtk_blist_joinchat_show), NULL, 0, 0, NULL);
394 gtk_widget_set_sensitive(menuitem, enable_join_chat);
395
396 gaim_separator(menu);
397
398 entry = gtk_check_menu_item_new_with_label(_("Mute Sounds"));
399 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(entry), gaim_prefs_get_bool("/gaim/gtk/sound/mute"));
400 if (!strcmp(gaim_prefs_get_string("/gaim/gtk/sound/method"), "none"))
401 gtk_widget_set_sensitive(GTK_WIDGET(entry), FALSE);
402 g_signal_connect(G_OBJECT(entry), "toggled", G_CALLBACK(docklet_toggle_mute), NULL);
403 gtk_menu_shell_append(GTK_MENU_SHELL(menu), entry);
404
405 gaim_new_item_from_stock(menu, _("File Transfers"), GAIM_STOCK_FILE_TRANSFER, G_CALLBACK(gaim_show_xfer_dialog), NULL, 0, 0, NULL);
406 gaim_new_item_from_stock(menu, _("Accounts"), GAIM_STOCK_ACCOUNTS, G_CALLBACK(gaim_gtk_accounts_window_show), NULL, 0, 0, NULL);
407 gaim_new_item_from_stock(menu, _("Preferences"), GTK_STOCK_PREFERENCES, G_CALLBACK(gaim_gtk_prefs_show), NULL, 0, 0, NULL);
408
409 gaim_separator(menu);
410
411 /* TODO: need a submenu to change status, this needs to "link"
412 * to the status in the buddy list gtkstatusbox
413 */
414
415 gaim_new_item_from_stock(menu, _("Quit"), GTK_STOCK_QUIT, G_CALLBACK(gaim_core_quit), NULL, 0, 0, NULL);
416
417 #ifdef _WIN32
418 g_signal_connect(menu, "leave-notify-event", G_CALLBACK(docklet_menu_leave_enter), NULL);
419 g_signal_connect(menu, "enter-notify-event", G_CALLBACK(docklet_menu_leave_enter), NULL);
420 #endif
421 gtk_widget_show_all(menu);
422 gtk_menu_popup(GTK_MENU(menu), NULL, NULL,
423 ui_ops->position_menu,
424 NULL, 0, gtk_get_current_event_time());
425 }
426
427 /**************************************************************************
428 * public api for ui_ops
429 **************************************************************************/
329 void 430 void
330 docklet_clicked(int button_type) 431 docklet_clicked(int button_type)
331 { 432 {
332 switch (button_type) { 433 switch (button_type) {
333 case 1: 434 case 1:
334 #if 0 /* XXX CUI */ 435 if(status==DOCKLET_STATUS_ONLINE_PENDING
335 if (unread_message_queue) { 436 || status==DOCKLET_STATUS_AWAY_PENDING)
336 docklet_flush_queue(); 437 focus_first_unseen_conv();
337 } else { 438 else
338 #endif 439 docklet_toggle_blist();
339 gaim_gtk_blist_docklet_toggle();
340 #if 0 /* XXX CUI */
341 }
342 #endif
343 break;
344 case 2:
345 /* Don't do anything for middle click */
346 break; 440 break;
347 case 3: 441 case 3:
348 docklet_menu(); 442 docklet_menu();
349 break; 443 break;
350 } 444 }
351 } 445 }
352 446
353 void 447 void
354 docklet_embedded() 448 docklet_embedded()
355 { 449 {
356 gaim_gtk_blist_docklet_add();
357
358 docklet_update_status(); 450 docklet_update_status();
359 if (ui_ops && ui_ops->update_icon) 451 if (ui_ops && ui_ops->update_icon)
360 ui_ops->update_icon(status); 452 ui_ops->update_icon(status);
361 } 453 }
362 454
363 void 455 void
364 docklet_remove(gboolean visible) 456 docklet_remove(gboolean visible)
365 { 457 {
366 if (visible) 458 unminimize_from_tray();
367 gaim_gtk_blist_docklet_remove();
368
369 #if 0 /* XXX CUI */
370 docklet_flush_queue();
371 #endif
372 } 459 }
373 460
374 void 461 void
375 docklet_unload() 462 docklet_unload()
376 { 463 {
381 docklet_set_ui_ops(struct docklet_ui_ops *ops) 468 docklet_set_ui_ops(struct docklet_ui_ops *ops)
382 { 469 {
383 ui_ops = ops; 470 ui_ops = ops;
384 } 471 }
385 472
386 /* callbacks */ 473 /**************************************************************************
387 474 * plugin glue
388 static void 475 **************************************************************************/
389 gaim_login(GaimConnection *gc, void *data)
390 {
391 docklet_update_status();
392 }
393
394 static void
395 gaim_logout(GaimConnection *gc, void *data)
396 {
397 /* do this when idle so that if the prpl was connecting
398 and was cancelled, we register that connecting_count
399 has returned to 0 */
400 /* no longer necessary because Chip decided that us plugins
401 didn't need to know if an account was connecting or not
402 g_idle_add(docklet_update_status, &docklet); */
403 docklet_update_status();
404 }
405
406 static void
407 gaim_connecting(GaimAccount *account, void *data)
408 {
409 docklet_update_status();
410 }
411
412 static void
413 gaim_away(GaimAccount *account, char *state, char *message, void *data)
414 {
415 /* we only support global away. this is the way it is, ok? */
416 docklet_update_status();
417 }
418
419 static gboolean
420 gaim_conv_im_recv(GaimAccount *account, char *sender, char *message,
421 GaimConversation *conv, int flags, void *data)
422 {
423 /* if message queuing while away is enabled, this event could be the first
424 message so we need to see if the status (and hence icon) needs changing.
425 do this when idle so that all message processing is completed, queuing
426 etc, before we run. */
427 g_idle_add(docklet_update_status, &handle);
428
429 return FALSE;
430 }
431
432 static void
433 gaim_new_conversation(GaimConversation *conv, void *data)
434 {
435 /* queue a callback here so if the queue is being
436 flushed, we stop flashing. thanks javabsp. */
437 g_idle_add(docklet_update_status, &handle);
438 }
439
440 /* We need this because the blist purge_away_queue cb won't be called before the
441 plugin is unloaded, when quitting */
442 static void gaim_quit_cb() {
443 gaim_debug(GAIM_DEBUG_INFO, "tray icon", "dealing with queued messages on exit\n");
444 #if 0 /* XXX CUI */
445 docklet_flush_queue();
446 #endif
447 }
448
449
450 /* static void gaim_buddy_signon(GaimConnection *gc, char *who, void *data) {
451 }
452
453 static void gaim_buddy_signoff(GaimConnection *gc, char *who, void *data) {
454 }
455
456 static void gaim_buddy_away(GaimConnection *gc, char *who, void *data) {
457 }
458
459 static void gaim_buddy_back(GaimConnection *gc, char *who, void *data) {
460 } */
461
462 /* plugin glue */
463
464 #define DOCKLET_PLUGIN_ID "gtk-docklet"
465
466 static gboolean 476 static gboolean
467 plugin_load(GaimPlugin *plugin) 477 plugin_load(GaimPlugin *plugin)
468 { 478 {
469 void *conn_handle = gaim_connections_get_handle(); 479 void *conn_handle = gaim_connections_get_handle();
470 void *conv_handle = gaim_conversations_get_handle(); 480 void *conv_handle = gaim_conversations_get_handle();
471 void *accounts_handle = gaim_accounts_get_handle(); 481 void *accounts_handle = gaim_accounts_get_handle();
472 void *core_handle = gaim_get_core(); 482 void *core_handle = gaim_get_core();
473 483
474 gaim_debug(GAIM_DEBUG_INFO, "tray icon", "plugin loaded\n"); 484 gaim_debug(GAIM_DEBUG_INFO, "docklet", "plugin loaded\n");
475 485
476 handle = plugin; 486 handle = plugin;
477 487
478 docklet_ui_init(); 488 docklet_ui_init();
479 if (ui_ops && ui_ops->create) 489 if (ui_ops && ui_ops->create)
480 ui_ops->create(); 490 ui_ops->create();
481 491
482 gaim_signal_connect(conn_handle, "signed-on", 492 gaim_signal_connect(conn_handle, "signed-on",
483 plugin, GAIM_CALLBACK(gaim_login), NULL); 493 plugin, GAIM_CALLBACK(docklet_signed_on_cb), NULL);
484 gaim_signal_connect(conn_handle, "signed-off", 494 gaim_signal_connect(conn_handle, "signed-off",
485 plugin, GAIM_CALLBACK(gaim_logout), NULL); 495 plugin, GAIM_CALLBACK(docklet_signed_off_cb), NULL);
486 gaim_signal_connect(accounts_handle, "account-connecting", 496 gaim_signal_connect(accounts_handle, "account-connecting",
487 plugin, GAIM_CALLBACK(gaim_connecting), NULL); 497 plugin, GAIM_CALLBACK(docklet_update_status_cb), NULL);
488 gaim_signal_connect(accounts_handle, "account-away", 498 gaim_signal_connect(accounts_handle, "account-away",
489 plugin, GAIM_CALLBACK(gaim_away), NULL); 499 plugin, GAIM_CALLBACK(docklet_update_status_cb), NULL);
490 gaim_signal_connect(conv_handle, "received-im-msg", 500 gaim_signal_connect(conv_handle, "received-im-msg",
491 plugin, GAIM_CALLBACK(gaim_conv_im_recv), NULL); 501 plugin, GAIM_CALLBACK(docklet_update_status_cb), NULL);
492 gaim_signal_connect(conv_handle, "conversation-created", 502 gaim_signal_connect(conv_handle, "conversation-created",
493 plugin, GAIM_CALLBACK(gaim_new_conversation), NULL); 503 plugin, GAIM_CALLBACK(docklet_update_status_cb), NULL);
504 gaim_signal_connect(conv_handle, "conversation-updated",
505 plugin, GAIM_CALLBACK(docklet_conv_updated_cb), NULL);
506
507 gaim_signal_connect(gaim_gtk_blist_get_handle(), "gtkblist-created",
508 plugin, GAIM_CALLBACK(gtkblist_created_cb), NULL);
509 gtkblist_created_cb(gaim_get_blist());
494 510
495 gaim_signal_connect(core_handle, "quitting", 511 gaim_signal_connect(core_handle, "quitting",
496 plugin, GAIM_CALLBACK(gaim_quit_cb), NULL); 512 plugin, GAIM_CALLBACK(gaim_quit_cb), NULL);
497 /* gaim_signal_connect(plugin, event_buddy_signon, gaim_buddy_signon, NULL); 513
498 gaim_signal_connect(plugin, event_buddy_signoff, gaim_buddy_signoff, NULL); 514 enable_join_chat = online_account_supports_chat();
499 gaim_signal_connect(plugin, event_buddy_away, gaim_buddy_away, NULL); 515
500 gaim_signal_connect(plugin, event_buddy_back, gaim_buddy_back, NULL); */ 516 if(!gaim_prefs_get_bool("/gaim/gtk/blist/list_visible"))
517 minimize_to_tray();
501 518
502 return TRUE; 519 return TRUE;
503 } 520 }
504 521
505 static gboolean 522 static gboolean
506 plugin_unload(GaimPlugin *plugin) 523 plugin_unload(GaimPlugin *plugin)
507 { 524 {
525 GaimGtkBuddyList *gtkblist = gaim_gtk_blist_get_default_gtk_blist();
526
508 if (ui_ops && ui_ops->destroy) 527 if (ui_ops && ui_ops->destroy)
509 ui_ops->destroy(); 528 ui_ops->destroy();
510 529
511 docklet_remove_callbacks(); 530 /* remove callbacks */
512 531 gaim_signals_disconnect_by_handle(handle);
513 gaim_debug(GAIM_DEBUG_INFO, "tray icon", "plugin unloaded\n"); 532 if(gtkblist_delete_cb_id!=0)
533 g_signal_handler_disconnect(G_OBJECT(gtkblist->window), gtkblist_delete_cb_id);
534
535 unminimize_from_tray();
536
537 gaim_debug(GAIM_DEBUG_INFO, "docklet", "plugin unloaded\n");
514 538
515 return TRUE; 539 return TRUE;
516 } 540 }
517
518 static GtkWidget *
519 plugin_config_frame(GaimPlugin *plugin)
520 {
521 GtkWidget *frame;
522 GtkWidget *vbox, *hbox;
523 GtkWidget *toggle;
524 static const char *qmpref = "/plugins/gtk/docklet/queue_messages";
525
526 frame = gtk_vbox_new(FALSE, 18);
527 gtk_container_set_border_width(GTK_CONTAINER(frame), 12);
528
529 vbox = gaim_gtk_make_frame(frame, _("Tray Icon Configuration"));
530 hbox = gtk_hbox_new(FALSE, 18);
531 gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
532
533 toggle = gtk_check_button_new_with_mnemonic(_("_Hide new messages until tray icon is clicked"));
534 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(toggle), gaim_prefs_get_bool(qmpref));
535 g_signal_connect(G_OBJECT(toggle), "clicked", G_CALLBACK(docklet_set_bool), (void *)qmpref);
536 gtk_box_pack_start(GTK_BOX(vbox), toggle, FALSE, FALSE, 0);
537
538 gtk_widget_show_all(frame);
539 return frame;
540 }
541
542 static GaimGtkPluginUiInfo ui_info =
543 {
544 plugin_config_frame
545 };
546 541
547 static GaimPluginInfo info = 542 static GaimPluginInfo info =
548 { 543 {
549 GAIM_PLUGIN_MAGIC, 544 GAIM_PLUGIN_MAGIC,
550 GAIM_MAJOR_VERSION, 545 GAIM_MAJOR_VERSION,
571 566
572 plugin_load, /**< load */ 567 plugin_load, /**< load */
573 plugin_unload, /**< unload */ 568 plugin_unload, /**< unload */
574 NULL, /**< destroy */ 569 NULL, /**< destroy */
575 570
576 &ui_info, /**< ui_info */ 571 NULL, /**< ui_info */
577 NULL, /**< extra_info */ 572 NULL, /**< extra_info */
578 NULL, 573 NULL,
579 NULL 574 NULL
580 }; 575 };
581 576
582 static void 577 static void
583 plugin_init(GaimPlugin *plugin) 578 plugin_init(GaimPlugin *plugin)
584 { 579 {
580 /* TODO: these will be removed once queuing is working in the ui */
585 gaim_prefs_add_none("/plugins/gtk/docklet"); 581 gaim_prefs_add_none("/plugins/gtk/docklet");
586 gaim_prefs_add_bool("/plugins/gtk/docklet/queue_messages", FALSE); 582 gaim_prefs_add_bool("/plugins/gtk/docklet/queue_messages", FALSE);
587 } 583 }
588 584
589 GAIM_INIT_PLUGIN(docklet, plugin_init, info) 585 GAIM_INIT_PLUGIN(docklet, plugin_init, info)