summary refs log blame commit diff stats
path: root/src/fe-gtk/userlistgui.c
blob: 5a18d8b87d9852558fef762303209063dffd478b (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15














                                                                       
                                                                            







                   

                           
                              


                               
                           
                             
                               
                         





                        
                  
 





















                                                



                                                  













                                                            


                                                                                                       
                                 
                                                          



























                                                                                                      
                                                                           































































































































































                                                                                                                                
                                                                                                   





































                                                                                     
                           





                                                                                     
                                               


                                                       


                                                                                   
                                                                                                                








                                                                          
                   
                           
 
                                                  


                                                          

                             
                                       














                                                                                                    
                                                                                                                              

                                                                      
                                       
         
                            
         























































                                                                                                                               
                                                                                                          










































                                                                                                                
                                  






                                                                                                                                                   
                                  





                                                                                                                                                   
                                           


                                                         
                                          


















                                                                                                                                                           
                                           
                                                                                       



                                                             
                                                                                                    































































                                                                                    
                                                                        


















                                                                                 
                                                              


                                                      
                                                              





                                                                                                                               
                                                                                                                          






                                                                                                                      
                                                           























































































                                                                                                              
/* X-Chat
 * Copyright (C) 1998 Peter Zelezny.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "fe-gtk.h"

#include <gdk/gdkkeysyms.h>

#include "../common/hexchat.h"
#include "../common/util.h"
#include "../common/userlist.h"
#include "../common/modes.h"
#include "../common/text.h"
#include "../common/notify.h"
#include "../common/hexchatc.h"
#include "../common/fe.h"
#include "gtkutil.h"
#include "palette.h"
#include "maingui.h"
#include "menu.h"
#include "pixmaps.h"
#include "userlistgui.h"
#include "fkeys.h"

enum
{
	COL_PIX=0,		// GdkPixbuf *
	COL_NICK=1,		// char *
	COL_HOST=2,		// char *
	COL_USER=3,		// struct User *
	COL_GDKCOLOR=4	// GdkColor *
};


GdkPixbuf *
get_user_icon (server *serv, struct User *user)
{
	char *pre;
	int level;

	if (!user)
		return NULL;

	/* these ones are hardcoded */
	switch (user->prefix[0])
	{
		case 0: return NULL;
		case '+': return pix_ulist_voice;
		case '%': return pix_ulist_halfop;
		case '@': return pix_ulist_op;
	}

	/* find out how many levels above Op this user is */
	pre = strchr (serv->nick_prefixes, '@');
	if (pre && pre != serv->nick_prefixes)
	{
		pre--;
		level = 0;
		while (1)
		{
			if (pre[0] == user->prefix[0])
			{
				switch (level)
				{
					case 0: return pix_ulist_owner;		/* 1 level above op */
					case 1: return pix_ulist_founder;	/* 2 levels above op */
					case 2: return pix_ulist_netop;		/* 3 levels above op */
				}
				break;	/* 4+, no icons */
			}
			level++;
			if (pre == serv->nick_prefixes)
				break;
			pre--;
		}
	}

	return NULL;
}

void
fe_userlist_numbers (session *sess)
{
	char tbuf[256];

	if (sess == current_tab || !sess->gui->is_tab)
	{
		if (sess->total)
		{
			snprintf (tbuf, sizeof (tbuf), _("%d ops, %d total"), sess->ops, sess->total);
			tbuf[sizeof (tbuf) - 1] = 0;
			gtk_label_set_text (GTK_LABEL (sess->gui->namelistinfo), tbuf);
		} else
		{
			gtk_label_set_text (GTK_LABEL (sess->gui->namelistinfo), NULL);
		}

		if (sess->type == SESS_CHANNEL && prefs.hex_gui_win_ucount)
			fe_set_title (sess);
	}
}

static void
scroll_to_iter (GtkTreeIter *iter, GtkTreeView *treeview, GtkTreeModel *model)
{
	GtkTreePath *path = gtk_tree_model_get_path (model, iter);
	if (path)
	{
		gtk_tree_view_scroll_to_cell (treeview, path, NULL, TRUE, 0.5, 0.5);
		gtk_tree_path_free (path);
	}
}

/* select a row in the userlist by nick-name */

void
userlist_select (session *sess, char *name)
{
	GtkTreeIter iter;
	GtkTreeView *treeview = GTK_TREE_VIEW (sess->gui->user_tree);
	GtkTreeModel *model = gtk_tree_view_get_model (treeview);
	GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
	struct User *row_user;

	if (gtk_tree_model_get_iter_first (model, &iter))
	{
		do
		{
			gtk_tree_model_get (model, &iter, COL_USER, &row_user, -1);
			if (sess->server->p_cmp (row_user->nick, name) == 0)
			{
				if (gtk_tree_selection_iter_is_selected (selection, &iter))
					gtk_tree_selection_unselect_iter (selection, &iter);
				else
					gtk_tree_selection_select_iter (selection, &iter);

				/* and make sure it's visible */
				scroll_to_iter (&iter, treeview, model);
				return;
			}
		}
		while (gtk_tree_model_iter_next (model, &iter));
	}
}

char **
userlist_selection_list (GtkWidget *widget, int *num_ret)
{
	GtkTreeIter iter;
	GtkTreeView *treeview = (GtkTreeView *) widget;
	GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
	GtkTreeModel *model = gtk_tree_view_get_model (treeview);
	struct User *user;
	int i, num_sel;
	char **nicks;

	*num_ret = 0;
	/* first, count the number of selections */
	num_sel = 0;
	if (gtk_tree_model_get_iter_first (model, &iter))
	{
		do
		{
			if (gtk_tree_selection_iter_is_selected (selection, &iter))
				num_sel++;
		}
		while (gtk_tree_model_iter_next (model, &iter));
	}

	if (num_sel < 1)
		return NULL;

	nicks = malloc (sizeof (char *) * (num_sel + 1));

	i = 0;
	gtk_tree_model_get_iter_first (model, &iter);
	do
	{
		if (gtk_tree_selection_iter_is_selected (selection, &iter))
		{
			gtk_tree_model_get (model, &iter, COL_USER, &user, -1);
			nicks[i] = g_strdup (user->nick);
			i++;
			nicks[i] = NULL;
		}
	}
	while (gtk_tree_model_iter_next (model, &iter));

	*num_ret = i;
	return nicks;
}

void
fe_userlist_set_selected (struct session *sess)
{
	GtkListStore *store = sess->res->user_model;
	GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (sess->gui->user_tree));
	GtkTreeIter iter;
	struct User *user;

	/* if it's not front-most tab it doesn't own the GtkTreeView! */
	if (store != (GtkListStore*) gtk_tree_view_get_model (GTK_TREE_VIEW (sess->gui->user_tree)))
		return;

	if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter))
	{
		do
		{
			gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, COL_USER, &user, -1);

			if (gtk_tree_selection_iter_is_selected (selection, &iter))
				user->selected = 1;
			else
				user->selected = 0;
				
		} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter));
	}
}

static GtkTreeIter *
find_row (GtkTreeView *treeview, GtkTreeModel *model, struct User *user,
			 int *selected)
{
	static GtkTreeIter iter;
	struct User *row_user;

	*selected = FALSE;
	if (gtk_tree_model_get_iter_first (model, &iter))
	{
		do
		{
			gtk_tree_model_get (model, &iter, COL_USER, &row_user, -1);
			if (row_user == user)
			{
				if (gtk_tree_view_get_model (treeview) == model)
				{
					if (gtk_tree_selection_iter_is_selected (gtk_tree_view_get_selection (treeview), &iter))
						*selected = TRUE;
				}
				return &iter;
			}
		}
		while (gtk_tree_model_iter_next (model, &iter));
	}

	return NULL;
}

void
userlist_set_value (GtkWidget *treeview, gfloat val)
{
	gtk_adjustment_set_value (
			gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (treeview)), val);
}

gfloat
userlist_get_value (GtkWidget *treeview)
{
	return gtk_adjustment_get_value (gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (treeview)));
}

int
fe_userlist_remove (session *sess, struct User *user)
{
	GtkTreeIter *iter;
/*	GtkAdjustment *adj;
	gfloat val, end;*/
	int sel;

	iter = find_row (GTK_TREE_VIEW (sess->gui->user_tree),
						  sess->res->user_model, user, &sel);
	if (!iter)
		return 0;

/*	adj = gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (sess->gui->user_tree));
	val = adj->value;*/

	gtk_list_store_remove (sess->res->user_model, iter);

	/* is it the front-most tab? */
/*	if (gtk_tree_view_get_model (GTK_TREE_VIEW (sess->gui->user_tree))
		 == sess->res->user_model)
	{
		end = adj->upper - adj->lower - adj->page_size;
		if (val > end)
			val = end;
		gtk_adjustment_set_value (adj, val);
	}*/

	return sel;
}

void
fe_userlist_rehash (session *sess, struct User *user)
{
	GtkTreeIter *iter;
	int sel;
	int nick_color = 0;

	iter = find_row (GTK_TREE_VIEW (sess->gui->user_tree),
						  sess->res->user_model, user, &sel);
	if (!iter)
		return;

	if (prefs.hex_away_track && user->away)
		nick_color = COL_AWAY;
	else if (prefs.hex_gui_ulist_color)
		nick_color = text_color_of(user->nick);

	gtk_list_store_set (GTK_LIST_STORE (sess->res->user_model), iter,
							  COL_HOST, user->hostname,
							  COL_GDKCOLOR, nick_color ? &colors[nick_color] : NULL,
							  -1);
}

void
fe_userlist_insert (session *sess, struct User *newuser, int row, int sel)
{
	GtkTreeModel *model = sess->res->user_model;
	GdkPixbuf *pix = get_user_icon (sess->server, newuser);
	GtkTreeIter iter;
	char *nick;
	int nick_color = 0;

	if (prefs.hex_away_track && newuser->away)
		nick_color = COL_AWAY;
	else if (prefs.hex_gui_ulist_color)
		nick_color = text_color_of(newuser->nick);

	nick = newuser->nick;
	if (!prefs.hex_gui_ulist_icons)
	{
		nick = malloc (strlen (newuser->nick) + 2);
		nick[0] = newuser->prefix[0];
		if (!nick[0] || nick[0] == ' ')
			strcpy (nick, newuser->nick);
		else
			strcpy (nick + 1, newuser->nick);
		pix = NULL;
	}

	gtk_list_store_insert_with_values (GTK_LIST_STORE (model), &iter, row,
									COL_PIX, pix,
									COL_NICK, nick,
									COL_HOST, newuser->hostname,
									COL_USER, newuser,
									COL_GDKCOLOR, nick_color ? &colors[nick_color] : NULL,
								  -1);

	if (!prefs.hex_gui_ulist_icons)
	{
		free (nick);
	}

	/* is it me? */
	if (newuser->me && sess->gui->nick_box)
	{
		if (!sess->gui->is_tab || sess == current_tab)
			mg_set_access_icon (sess->gui, pix, sess->server->is_away);
	}

#if 0
	if (prefs.hilitenotify && notify_isnotify (sess, newuser->nick))
	{
		gtk_clist_set_foreground ((GtkCList *) sess->gui->user_clist, row,
										  &colors[prefs.nu_color]);
	}
#endif

	/* is it the front-most tab? */
	if (gtk_tree_view_get_model (GTK_TREE_VIEW (sess->gui->user_tree))
		 == model)
	{
		if (sel)
			gtk_tree_selection_select_iter (gtk_tree_view_get_selection
										(GTK_TREE_VIEW (sess->gui->user_tree)), &iter);
	}
}

void
fe_userlist_move (session *sess, struct User *user, int new_row)
{
	fe_userlist_insert (sess, user, new_row, fe_userlist_remove (sess, user));
}

void
fe_userlist_clear (session *sess)
{
	gtk_list_store_clear (sess->res->user_model);
}

static void
userlist_dnd_drop (GtkTreeView *widget, GdkDragContext *context,
						 gint x, gint y, GtkSelectionData *selection_data,
						 guint info, guint ttime, gpointer userdata)
{
	struct User *user;
	GtkTreePath *path;
	GtkTreeModel *model;
	GtkTreeIter iter;

	if (!gtk_tree_view_get_path_at_pos (widget, x, y, &path, NULL, NULL, NULL))
		return;

	model = gtk_tree_view_get_model (widget);
	if (!gtk_tree_model_get_iter (model, &iter, path))
		return;
	gtk_tree_model_get (model, &iter, COL_USER, &user, -1);

	mg_dnd_drop_file (current_sess, user->nick, (char *)gtk_selection_data_get_data (selection_data));
}

static gboolean
userlist_dnd_motion (GtkTreeView *widget, GdkDragContext *context, gint x,
							gint y, guint ttime, gpointer tree)
{
	GtkTreePath *path;
	GtkTreeSelection *sel;

	if (!tree)
		return FALSE;

	if (gtk_tree_view_get_path_at_pos (widget, x, y, &path, NULL, NULL, NULL))
	{
		sel = gtk_tree_view_get_selection (widget);
		gtk_tree_selection_unselect_all (sel);
		gtk_tree_selection_select_path (sel, path);
	}

	return FALSE;
}

static gboolean
userlist_dnd_leave (GtkTreeView *widget, GdkDragContext *context, guint ttime)
{
	gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (widget));
	return TRUE;
}

void *
userlist_create_model (void)
{
	return gtk_list_store_new (5, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING,
										G_TYPE_POINTER, GDK_TYPE_COLOR);
}

static void
userlist_add_columns (GtkTreeView * treeview)
{
	GtkCellRenderer *renderer;

	/* icon column */
	renderer = gtk_cell_renderer_pixbuf_new ();
	if (prefs.hex_gui_compact)
		g_object_set (G_OBJECT (renderer), "ypad", 0, NULL);
	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
																-1, NULL, renderer,
																"pixbuf", 0, NULL);

	/* nick column */
	renderer = gtk_cell_renderer_text_new ();
	if (prefs.hex_gui_compact)
		g_object_set (G_OBJECT (renderer), "ypad", 0, NULL);
	gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer), 1);
	gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
																-1, NULL, renderer,
													"text", 1, "foreground-gdk", 4, NULL);

	if (prefs.hex_gui_ulist_show_hosts)
	{
		/* hostname column */
		renderer = gtk_cell_renderer_text_new ();
		if (prefs.hex_gui_compact)
			g_object_set (G_OBJECT (renderer), "ypad", 0, NULL);
		gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer), 1);
		gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview),
																	-1, NULL, renderer,
																	"text", 2, NULL);
	}
}

static gint
userlist_click_cb (GtkWidget *widget, GdkEventButton *event, gpointer userdata)
{
	char **nicks;
	int i;
	GtkTreeSelection *sel;
	GtkTreePath *path;

	if (!event)
		return FALSE;

	if (!(event->state & STATE_CTRL) &&
		event->type == GDK_2BUTTON_PRESS && prefs.hex_gui_ulist_doubleclick[0])
	{
		nicks = userlist_selection_list (widget, &i);
		if (nicks)
		{
			nick_command_parse (current_sess, prefs.hex_gui_ulist_doubleclick, nicks[0],
									  nicks[0]);
			while (i)
			{
				i--;
				g_free (nicks[i]);
			}
			free (nicks);
		}
		return TRUE;
	}

	if (event->button == 3)
	{
		/* do we have a multi-selection? */
		nicks = userlist_selection_list (widget, &i);
		if (nicks && i > 1)
		{
			menu_nickmenu (current_sess, event, nicks[0], i);
			while (i)
			{
				i--;
				g_free (nicks[i]);
			}
			free (nicks);
			return TRUE;
		}
		if (nicks)
		{
			g_free (nicks[0]);
			free (nicks);
		}

		sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget));
		if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget),
			 event->x, event->y, &path, 0, 0, 0))
		{
			gtk_tree_selection_unselect_all (sel);
			gtk_tree_selection_select_path (sel, path);
			gtk_tree_path_free (path);
			nicks = userlist_selection_list (widget, &i);
			if (nicks)
			{
				menu_nickmenu (current_sess, event, nicks[0], i);
				while (i)
				{
					i--;
					g_free (nicks[i]);
				}
				free (nicks);
			}
		} else
		{
			gtk_tree_selection_unselect_all (sel);
		}

		return TRUE;
	}

	return FALSE;
}

static gboolean
userlist_key_cb (GtkWidget *wid, GdkEventKey *evt, gpointer userdata)
{
	if (evt->keyval >= GDK_KEY_asterisk && evt->keyval <= GDK_KEY_z)
	{
		/* dirty trick to avoid auto-selection */
		SPELL_ENTRY_SET_EDITABLE (current_sess->gui->input_box, FALSE);
		gtk_widget_grab_focus (current_sess->gui->input_box);
		SPELL_ENTRY_SET_EDITABLE (current_sess->gui->input_box, TRUE);
		gtk_widget_event (current_sess->gui->input_box, (GdkEvent *)evt);
		return TRUE;
	}

	return FALSE;
}

GtkWidget *
userlist_create (GtkWidget *box)
{
	GtkWidget *sw, *treeview;
	static const GtkTargetEntry dnd_dest_targets[] =
	{
		{"text/uri-list", 0, 1},
		{"HEXCHAT_CHANVIEW", GTK_TARGET_SAME_APP, 75 }
	};
	static const GtkTargetEntry dnd_src_target[] =
	{
		{"HEXCHAT_USERLIST", GTK_TARGET_SAME_APP, 75 }
	};

	sw = gtk_scrolled_window_new (NULL, NULL);
	gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw),
													 GTK_SHADOW_ETCHED_IN);
	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw),
											  prefs.hex_gui_ulist_show_hosts ?
												GTK_POLICY_AUTOMATIC :
												GTK_POLICY_NEVER,
											  GTK_POLICY_AUTOMATIC);
	gtk_box_pack_start (GTK_BOX (box), sw, TRUE, TRUE, 0);
	gtk_widget_show (sw);

	treeview = gtk_tree_view_new ();
	gtk_widget_set_name (treeview, "hexchat-userlist");
	gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE);
	gtk_tree_selection_set_mode (gtk_tree_view_get_selection
										  (GTK_TREE_VIEW (treeview)),
										  GTK_SELECTION_MULTIPLE);

	/* set up drops */
	gtk_drag_dest_set (treeview, GTK_DEST_DEFAULT_ALL, dnd_dest_targets, 2,
							 GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK);
	gtk_drag_source_set (treeview, GDK_BUTTON1_MASK, dnd_src_target, 1, GDK_ACTION_MOVE);

	/* file DND (for DCC) */
	g_signal_connect (G_OBJECT (treeview), "drag_motion",
							G_CALLBACK (userlist_dnd_motion), treeview);
	g_signal_connect (G_OBJECT (treeview), "drag_leave",
							G_CALLBACK (userlist_dnd_leave), 0);
	g_signal_connect (G_OBJECT (treeview), "drag_data_received",
							G_CALLBACK (userlist_dnd_drop), treeview);

	g_signal_connect (G_OBJECT (treeview), "button_press_event",
							G_CALLBACK (userlist_click_cb), 0);
	g_signal_connect (G_OBJECT (treeview), "key_press_event",
							G_CALLBACK (userlist_key_cb), 0);

	/* tree/chanview DND */
#ifndef WIN32	/* leaks GDI pool memory, don't enable */
	g_signal_connect (G_OBJECT (treeview), "drag_begin",
							G_CALLBACK (mg_drag_begin_cb), NULL);
	g_signal_connect (G_OBJECT (treeview), "drag_drop",
							G_CALLBACK (mg_drag_drop_cb), NULL);
	g_signal_connect (G_OBJECT (treeview), "drag_motion",
							G_CALLBACK (mg_drag_motion_cb), NULL);
	g_signal_connect (G_OBJECT (treeview), "drag_end",
							G_CALLBACK (mg_drag_end_cb), NULL);
#endif

	userlist_add_columns (GTK_TREE_VIEW (treeview));

	gtk_container_add (GTK_CONTAINER (sw), treeview);
	gtk_widget_show (treeview);

	return treeview;
}

void
userlist_show (session *sess)
{
	gtk_tree_view_set_model (GTK_TREE_VIEW (sess->gui->user_tree),
									 sess->res->user_model);
}

void
fe_uselect (session *sess, char *word[], int do_clear, int scroll_to)
{
	int thisname;
	char *name;
	GtkTreeIter iter;
	GtkTreeView *treeview = GTK_TREE_VIEW (sess->gui->user_tree);
	GtkTreeModel *model = gtk_tree_view_get_model (treeview);
	GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
	struct User *row_user;

	if (gtk_tree_model_get_iter_first (model, &iter))
	{
		if (do_clear)
			gtk_tree_selection_unselect_all (selection);

		do
		{
			if (*word[0])
			{
				gtk_tree_model_get (model, &iter, COL_USER, &row_user, -1);
				thisname = 0;
				while ( *(name = word[thisname++]) )
				{
					if (sess->server->p_cmp (row_user->nick, name) == 0)
					{
						gtk_tree_selection_select_iter (selection, &iter);
						if (scroll_to)
							scroll_to_iter (&iter, treeview, model);
						break;
					}
				}
			}

		}
		while (gtk_tree_model_iter_next (model, &iter));
	}
}
" #include "maingui.h" #include "menu.h" #ifndef WIN32 #include <unistd.h> #endif #ifdef USE_LIBNOTIFY #include <libnotify/notify.h> #ifndef NOTIFY_CHECK_VERSION #define NOTIFY_CHECK_VERSION(x,y,z) 0 #endif #if NOTIFY_CHECK_VERSION(0,7,0) #define XC_NOTIFY_NEW(a,b,c,d) notify_notification_new(a,b,c) #else #define XC_NOTIFY_NEW(a,b,c,d) notify_notification_new(a,b,c,d) #endif #endif typedef enum /* current icon status */ { TS_NONE, TS_MESSAGE, TS_HIGHLIGHT, TS_FILEOFFER, TS_CUSTOM /* plugin */ } TrayStatus; typedef enum { WS_FOCUSED, WS_NORMAL, WS_HIDDEN } WinStatus; typedef GdkPixbuf* TrayIcon; #define tray_icon_from_file(f) gdk_pixbuf_new_from_file(f,NULL) #define tray_icon_free(i) g_object_unref(i) #define ICON_NORMAL pix_hexchat #define ICON_MSG pix_tray_message #define ICON_HILIGHT pix_tray_highlight #define ICON_FILE pix_tray_fileoffer #define TIMEOUT 500 static GtkStatusIcon *sticon; static gint flash_tag; static TrayStatus tray_status; #ifdef WIN32 static guint tray_menu_timer; static gint64 tray_menu_inactivetime; #endif static hexchat_plugin *ph; static TrayIcon custom_icon1; static TrayIcon custom_icon2; static int tray_priv_count = 0; static int tray_pub_count = 0; static int tray_hilight_count = 0; static int tray_file_count = 0; static int tray_restore_timer = 0; void tray_apply_setup (void); static gboolean tray_menu_try_restore (); static void tray_cleanup (void); static void tray_init (void); static WinStatus tray_get_window_status (void) { const char *st; st = hexchat_get_info (ph, "win_status"); if (!st) return WS_HIDDEN; if (!strcmp (st, "active")) return WS_FOCUSED; if (!strcmp (st, "hidden")) return WS_HIDDEN; return WS_NORMAL; } static int tray_count_channels (void) { int cons = 0; GSList *list; session *sess; for (list = sess_list; list; list = list->next) { sess = list->data; if (sess->server->connected && sess->channel[0] && sess->type == SESS_CHANNEL) cons++; } return cons; } static int tray_count_networks (void) { int cons = 0; GSList *list; for (list = serv_list; list; list = list->next) { if (((server *)list->data)->connected) cons++; } return cons; } void fe_tray_set_tooltip (const char *text) { if (sticon) gtk_status_icon_set_tooltip_text (sticon, text); } void fe_tray_set_balloon (const char *title, const char *text) { #ifndef WIN32 #if 0 const char *argv[8]; const char *path; char time[16]; #endif WinStatus ws; /* no balloons if the window is focused */ ws = tray_get_window_status (); if ((prefs.hex_away_omit_alerts && hexchat_get_info(ph, "away")) || (prefs.hex_gui_focus_omitalerts && ws == WS_FOCUSED)) return; /* bit 1 of flags means "no balloons unless hidden/iconified" */ if (ws != WS_HIDDEN && prefs.hex_gui_tray_quiet) return; /* FIXME: this should close the current balloon */ if (!text) return; #ifdef USE_LIBNOTIFY static int notify_text_strip_flags = STRIP_ALL; NotifyNotification *notification; char *notify_text, *notify_title; if (!notify_is_initted()) { notify_init(PACKAGE_NAME); GList* server_caps = notify_get_server_caps (); if (g_list_find_custom (server_caps, "body-markup", (GCompareFunc)strcmp)) { notify_text_strip_flags |= STRIP_ESCMARKUP; } g_list_free_full (server_caps, g_free); } notify_text = strip_color (text, -1, notify_text_strip_flags); notify_title = strip_color (title, -1, STRIP_ALL); notification = XC_NOTIFY_NEW (notify_title, notify_text, HEXCHATSHAREDIR "/icons/hicolor/scalable/apps/hexchat.svg", NULL); #if NOTIFY_CHECK_VERSION(0,7,0) notify_notification_set_hint (notification, "desktop-entry", g_variant_new_string ("hexchat")); #endif g_free ((char *)notify_title); g_free ((char *)notify_text); notify_notification_set_timeout (notification, prefs.hex_input_balloon_time*1000); notify_notification_show (notification, NULL); g_object_unref (notification); #endif #endif } static void tray_set_balloonf (const char *text, const char *format, ...) { va_list args; char *buf; va_start (args, format); buf = g_strdup_vprintf (format, args); va_end (args); fe_tray_set_balloon (buf, text); g_free (buf); } static void tray_set_tipf (const char *format, ...) { va_list args; char *buf; va_start (args, format); buf = g_strdup_vprintf (format, args); va_end (args); fe_tray_set_tooltip (buf); g_free (buf); } static void tray_stop_flash (void) { int nets, chans; if (flash_tag) { g_source_remove (flash_tag); flash_tag = 0; } if (sticon) { gtk_status_icon_set_from_pixbuf (sticon, ICON_NORMAL); nets = tray_count_networks (); chans = tray_count_channels (); if (nets) tray_set_tipf (_(DISPLAY_NAME": Connected to %u networks and %u channels"), nets, chans); else tray_set_tipf (DISPLAY_NAME": %s", _("Not connected.")); } if (custom_icon1) { tray_icon_free (custom_icon1); custom_icon1 = NULL; } if (custom_icon2) { tray_icon_free (custom_icon2); custom_icon2 = NULL; } tray_status = TS_NONE; } static void tray_reset_counts (void) { tray_priv_count = 0; tray_pub_count = 0; tray_hilight_count = 0; tray_file_count = 0; } static int tray_timeout_cb (TrayIcon icon) { if (custom_icon1) { if (gtk_status_icon_get_pixbuf (sticon) == custom_icon1) { if (custom_icon2) gtk_status_icon_set_from_pixbuf (sticon, custom_icon2); else gtk_status_icon_set_from_pixbuf (sticon, ICON_NORMAL); } else { gtk_status_icon_set_from_pixbuf (sticon, custom_icon1); } } else { if (gtk_status_icon_get_pixbuf (sticon) == ICON_NORMAL) gtk_status_icon_set_from_pixbuf (sticon, icon); else gtk_status_icon_set_from_pixbuf (sticon, ICON_NORMAL); } return 1; } static void tray_set_flash (TrayIcon icon) { if (!sticon) return; /* already flashing the same icon */ if (flash_tag && gtk_status_icon_get_pixbuf (sticon) == icon) return; /* no flashing if window is focused */ if (tray_get_window_status () == WS_FOCUSED) return; tray_stop_flash (); gtk_status_icon_set_from_pixbuf (sticon, icon); if (prefs.hex_gui_tray_blink) flash_tag = g_timeout_add (TIMEOUT, (GSourceFunc) tray_timeout_cb, icon); } void fe_tray_set_flash (const char *filename1, const char *filename2, int tout) { tray_apply_setup (); if (!sticon) return; tray_stop_flash (); if (tout == -1) tout = TIMEOUT; custom_icon1 = tray_icon_from_file (filename1); if (filename2) custom_icon2 = tray_icon_from_file (filename2); gtk_status_icon_set_from_pixbuf (sticon, custom_icon1); flash_tag = g_timeout_add (tout, (GSourceFunc) tray_timeout_cb, NULL); tray_status = TS_CUSTOM; } void fe_tray_set_icon (feicon icon) { tray_apply_setup (); if (!sticon) return; tray_stop_flash (); switch (icon) { case FE_ICON_NORMAL: break; case FE_ICON_MESSAGE: case FE_ICON_PRIVMSG: tray_set_flash (ICON_MSG); break; case FE_ICON_HIGHLIGHT: tray_set_flash (ICON_HILIGHT); break; case FE_ICON_FILEOFFER: tray_set_flash (ICON_FILE); } } void fe_tray_set_file (const char *filename) { tray_apply_setup (); if (!sticon) return; tray_stop_flash (); if (filename) { custom_icon1 = tray_icon_from_file (filename); gtk_status_icon_set_from_pixbuf (sticon, custom_icon1); tray_status = TS_CUSTOM; } } gboolean tray_toggle_visibility (gboolean force_hide) { static int x, y; static GdkScreen *screen; static int maximized; static int fullscreen; GtkWindow *win; if (!sticon) return FALSE; /* ph may have an invalid context now */ hexchat_set_context (ph, hexchat_find_context (ph, NULL, NULL)); win = GTK_WINDOW (hexchat_get_info (ph, "gtkwin_ptr")); tray_stop_flash (); tray_reset_counts (); if (!win) return FALSE; if (force_hide || gtk_widget_get_visible (GTK_WIDGET (win))) { if (prefs.hex_gui_tray_away) hexchat_command (ph, "ALLSERV AWAY"); gtk_window_get_position (win, &x, &y); screen = gtk_window_get_screen (win); maximized = prefs.hex_gui_win_state; fullscreen = prefs.hex_gui_win_fullscreen; gtk_widget_hide (GTK_WIDGET (win)); } else { if (prefs.hex_gui_tray_away) hexchat_command (ph, "ALLSERV BACK"); gtk_window_set_screen (win, screen); gtk_window_move (win, x, y); if (maximized) gtk_window_maximize (win); if (fullscreen) gtk_window_fullscreen (win); gtk_widget_show (GTK_WIDGET (win)); gtk_window_present (win); } return TRUE; } static void tray_menu_restore_cb (GtkWidget *item, gpointer userdata) { tray_toggle_visibility (FALSE); } static void tray_menu_notify_cb (GObject *tray, GParamSpec *pspec, gpointer user_data) { if (sticon) { if (!gtk_status_icon_is_embedded (sticon)) { tray_restore_timer = g_timeout_add(500, (GSourceFunc)tray_menu_try_restore, NULL); } else { if (tray_restore_timer) { g_source_remove (tray_restore_timer); tray_restore_timer = 0; } } } } static gboolean tray_menu_try_restore () { tray_cleanup(); tray_init(); return TRUE; } static void tray_menu_quit_cb (GtkWidget *item, gpointer userdata) { mg_open_quit_dialog (FALSE); } /* returns 0-mixed 1-away 2-back */ static int tray_find_away_status (void) { GSList *list; server *serv; int away = 0; int back = 0; for (list = serv_list; list; list = list->next) { serv = list->data; if (serv->is_away || serv->reconnect_away) away++; else back++; } if (away && back) return 0; if (away) return 1; return 2; } static void tray_foreach_server (GtkWidget *item, char *cmd) { GSList *list; server *serv; for (list = serv_list; list; list = list->next) { serv = list->data; if (serv->connected) handle_command (serv->server_session, cmd, FALSE); } } static GtkWidget * tray_make_item (GtkWidget *menu, char *label, void *callback, void *userdata) { GtkWidget *item; if (label) item = gtk_menu_item_new_with_mnemonic (label); else item = gtk_menu_item_new (); gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (callback), userdata); gtk_widget_show (item); return item; } static void tray_toggle_cb (GtkCheckMenuItem *item, unsigned int *setting) { *setting = gtk_check_menu_item_get_active (item); } static void blink_item (unsigned int *setting, GtkWidget *menu, char *label) { menu_toggle_item (label, menu, tray_toggle_cb, setting, *setting); } static void tray_menu_destroy (GtkWidget *menu, gpointer userdata) { gtk_widget_destroy (menu); g_object_unref (menu); #ifdef WIN32 g_source_remove (tray_menu_timer); #endif } #ifdef WIN32 static void tray_menu_enter_cb (GtkWidget *menu) { tray_menu_inactivetime = 0; } static void tray_menu_left_cb (GtkWidget *menu) { tray_menu_inactivetime = g_get_real_time (); } static void tray_check_hide (GtkWidget *menu) { if (tray_menu_inactivetime && g_get_real_time () - tray_menu_inactivetime >= 2000000) { tray_menu_destroy (menu, NULL); } } static void tray_menu_settings (GtkWidget * wid, gpointer none) { extern void setup_open (void); setup_open (); } #endif static void tray_menu_cb (GtkWidget *widget, guint button, guint time, gpointer userdata) { static GtkWidget *menu; GtkWidget *submenu; GtkWidget *item; int away_status; /* ph may have an invalid context now */ hexchat_set_context (ph, hexchat_find_context (ph, NULL, NULL)); /* close any old menu */ if (G_IS_OBJECT (menu)) { tray_menu_destroy (menu, NULL); } menu = gtk_menu_new (); /*gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget));*/ if (tray_get_window_status () == WS_HIDDEN) tray_make_item (menu, _("_Restore Window"), tray_menu_restore_cb, NULL); else tray_make_item (menu, _("_Hide Window"), tray_menu_restore_cb, NULL); tray_make_item (menu, NULL, tray_menu_quit_cb, NULL); #ifndef WIN32 /* submenus are buggy on win32 */ submenu = mg_submenu (menu, _("_Blink on")); blink_item (&prefs.hex_input_tray_chans, submenu, _("Channel Message")); blink_item (&prefs.hex_input_tray_priv, submenu, _("Private Message")); blink_item (&prefs.hex_input_tray_hilight, submenu, _("Highlighted Message")); /*blink_item (BIT_FILEOFFER, submenu, _("File Offer"));*/ submenu = mg_submenu (menu, _("_Change status")); #else /* so show away/back in main tray menu */ submenu = menu; #endif away_status = tray_find_away_status (); item = tray_make_item (submenu, _("_Away"), tray_foreach_server, "away"); if (away_status == 1) gtk_widget_set_sensitive (item, FALSE); item = tray_make_item (submenu, _("_Back"), tray_foreach_server, "back"); if (away_status == 2) gtk_widget_set_sensitive (item, FALSE); menu_add_plugin_items (menu, "\x5$TRAY", NULL); #ifdef WIN32 tray_make_item (menu, NULL, tray_menu_quit_cb, NULL); mg_create_icon_item (_("_Preferences"), GTK_STOCK_PREFERENCES, menu, tray_menu_settings, NULL); #endif tray_make_item (menu, NULL, tray_menu_quit_cb, NULL); mg_create_icon_item (_("_Quit"), GTK_STOCK_QUIT, menu, tray_menu_quit_cb, NULL); g_object_ref (menu); g_object_ref_sink (menu); g_object_unref (menu); g_signal_connect (G_OBJECT (menu), "selection-done", G_CALLBACK (tray_menu_destroy), NULL); #ifdef WIN32 g_signal_connect (G_OBJECT (menu), "leave-notify-event", G_CALLBACK (tray_menu_left_cb), NULL); g_signal_connect (G_OBJECT (menu), "enter-notify-event", G_CALLBACK (tray_menu_enter_cb), NULL); tray_menu_timer = g_timeout_add(500, (GSourceFunc) tray_check_hide, menu); #endif gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, userdata, button, time); } static void tray_init (void) { flash_tag = 0; tray_status = TS_NONE; custom_icon1 = NULL; custom_icon2 = NULL; sticon = gtk_status_icon_new_from_pixbuf (ICON_NORMAL); if (!sticon) return; g_signal_connect (G_OBJECT (sticon), "popup-menu", G_CALLBACK (tray_menu_cb), sticon); g_signal_connect (G_OBJECT (sticon), "activate", G_CALLBACK (tray_menu_restore_cb), NULL); g_signal_connect (G_OBJECT (sticon), "notify::embedded", G_CALLBACK (tray_menu_notify_cb), NULL); } static int tray_hilight_cb (char *word[], void *userdata) { /*if (tray_status == TS_HIGHLIGHT) return HEXCHAT_EAT_NONE;*/ if (prefs.hex_input_tray_hilight) { tray_set_flash (ICON_HILIGHT); /* FIXME: hides any previous private messages */ tray_hilight_count++; if (tray_hilight_count == 1) tray_set_tipf (_(DISPLAY_NAME": Highlighted message from: %s (%s)"), word[1], hexchat_get_info (ph, "channel")); else tray_set_tipf (_(DISPLAY_NAME": %u highlighted messages, latest from: %s (%s)"), tray_hilight_count, word[1], hexchat_get_info (ph, "channel")); } if (prefs.hex_input_balloon_hilight) tray_set_balloonf (word[2], _("Highlighted message from: %s (%s)"), word[1], hexchat_get_info (ph, "channel")); return HEXCHAT_EAT_NONE; } static int tray_message_cb (char *word[], void *userdata) { if (/*tray_status == TS_MESSAGE ||*/ tray_status == TS_HIGHLIGHT) return HEXCHAT_EAT_NONE; if (prefs.hex_input_tray_chans) { tray_set_flash (ICON_MSG); tray_pub_count++; if (tray_pub_count == 1) tray_set_tipf (_(DISPLAY_NAME": Channel message from: %s (%s)"), word[1], hexchat_get_info (ph, "channel")); else tray_set_tipf (_(DISPLAY_NAME": %u channel messages."), tray_pub_count); } if (prefs.hex_input_balloon_chans) tray_set_balloonf (word[2], _("Channel message from: %s (%s)"), word[1], hexchat_get_info (ph, "channel")); return HEXCHAT_EAT_NONE; } static void tray_priv (char *from, char *text) { const char *network; if (alert_match_word (from, prefs.hex_irc_no_hilight)) return; network = hexchat_get_info (ph, "network"); if (!network) network = hexchat_get_info (ph, "server"); if (prefs.hex_input_tray_priv) { tray_set_flash (ICON_MSG); tray_priv_count++; if (tray_priv_count == 1) tray_set_tipf (_(DISPLAY_NAME": Private message from: %s (%s)"), from, network); else tray_set_tipf (_(DISPLAY_NAME": %u private messages, latest from: %s (%s)"), tray_priv_count, from, network); } if (prefs.hex_input_balloon_priv) tray_set_balloonf (text, _("Private message from: %s (%s)"), from, network); } static int tray_priv_cb (char *word[], void *userdata) { tray_priv (word[1], word[2]); return HEXCHAT_EAT_NONE; } static int tray_invited_cb (char *word[], void *userdata) { if (!prefs.hex_away_omit_alerts || tray_find_away_status () != 1) tray_priv (word[2], "Invited"); return HEXCHAT_EAT_NONE; } static int tray_dcc_cb (char *word[], void *userdata) { const char *network; /* if (tray_status == TS_FILEOFFER) return HEXCHAT_EAT_NONE;*/ network = hexchat_get_info (ph, "network"); if (!network) network = hexchat_get_info (ph, "server"); if (prefs.hex_input_tray_priv && (!prefs.hex_away_omit_alerts || tray_find_away_status () != 1)) { tray_set_flash (ICON_FILE); tray_file_count++; if (tray_file_count == 1) tray_set_tipf (_(DISPLAY_NAME": File offer from: %s (%s)"), word[1], network); else tray_set_tipf (_(DISPLAY_NAME": %u file offers, latest from: %s (%s)"), tray_file_count, word[1], network); } if (prefs.hex_input_balloon_priv && (!prefs.hex_away_omit_alerts || tray_find_away_status () != 1)) tray_set_balloonf ("", _("File offer from: %s (%s)"), word[1], network); return HEXCHAT_EAT_NONE; } static int tray_focus_cb (char *word[], void *userdata) { tray_stop_flash (); tray_reset_counts (); return HEXCHAT_EAT_NONE; } static void tray_cleanup (void) { tray_stop_flash (); if (sticon) { g_object_unref ((GObject *)sticon); sticon = NULL; } } void tray_apply_setup (void) { if (sticon) { if (!prefs.hex_gui_tray) tray_cleanup (); } else { if (prefs.hex_gui_tray && !unity_mode ()) tray_init (); } } int tray_plugin_init (hexchat_plugin *plugin_handle, char **plugin_name, char **plugin_desc, char **plugin_version, char *arg) { /* we need to save this for use with any hexchat_* functions */ ph = plugin_handle; *plugin_name = ""; *plugin_desc = ""; *plugin_version = ""; hexchat_hook_print (ph, "Channel Msg Hilight", -1, tray_hilight_cb, NULL); hexchat_hook_print (ph, "Channel Action Hilight", -1, tray_hilight_cb, NULL); hexchat_hook_print (ph, "Channel Message", -1, tray_message_cb, NULL); hexchat_hook_print (ph, "Channel Action", -1, tray_message_cb, NULL); hexchat_hook_print (ph, "Channel Notice", -1, tray_message_cb, NULL); hexchat_hook_print (ph, "Private Message", -1, tray_priv_cb, NULL); hexchat_hook_print (ph, "Private Message to Dialog", -1, tray_priv_cb, NULL); hexchat_hook_print (ph, "Notice", -1, tray_priv_cb, NULL); hexchat_hook_print (ph, "Invited", -1, tray_invited_cb, NULL); hexchat_hook_print (ph, "DCC Offer", -1, tray_dcc_cb, NULL); hexchat_hook_print (ph, "Focus Window", -1, tray_focus_cb, NULL); if (prefs.hex_gui_tray && !unity_mode ()) tray_init (); return 1; /* return 1 for success */ } int tray_plugin_deinit (hexchat_plugin *plugin_handle) { #ifdef WIN32 tray_cleanup (); #elif defined(USE_LIBNOTIFY) notify_uninit (); #endif return 1; }