Mercurial > pidgin
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) |