diff options
author | Xavier Claessens <xclaesse@src.gnome.org> | 2008-05-17 00:36:33 +0800 |
---|---|---|
committer | Xavier Claessens <xclaesse@src.gnome.org> | 2008-05-17 00:36:33 +0800 |
commit | 2fa6db9433540394d8149609498cca5ddc9df54b (patch) | |
tree | e2a0f43e5054c4f2b2142406bf1a2f79adf23983 /trunk/src | |
parent | 7308e23186a01d08d29683aeeeba6cb198a834fa (diff) | |
download | gsoc2013-empathy-aa6420e91ef6ca94724de418b45861784331e6fd.tar gsoc2013-empathy-aa6420e91ef6ca94724de418b45861784331e6fd.tar.gz gsoc2013-empathy-aa6420e91ef6ca94724de418b45861784331e6fd.tar.bz2 gsoc2013-empathy-aa6420e91ef6ca94724de418b45861784331e6fd.tar.lz gsoc2013-empathy-aa6420e91ef6ca94724de418b45861784331e6fd.tar.xz gsoc2013-empathy-aa6420e91ef6ca94724de418b45861784331e6fd.tar.zst gsoc2013-empathy-aa6420e91ef6ca94724de418b45861784331e6fd.zip |
Tagged for release 0.23.2.EMPATHY_0_23_2
svn path=/tags/EMPATHY_0_23_2/; revision=1106
Diffstat (limited to 'trunk/src')
34 files changed, 10960 insertions, 0 deletions
diff --git a/trunk/src/.gitignore b/trunk/src/.gitignore new file mode 100644 index 000000000..e972a26f8 --- /dev/null +++ b/trunk/src/.gitignore @@ -0,0 +1,9 @@ +empathy +empathy-accounts +empathy-logs +empathy-chat-chandler +empathy-call-chandler +empathy-tubes-chandler +org.gnome.Empathy.Chat.service +org.gnome.Empathy.Call.service +org.gnome.Empathy.Tubes.service diff --git a/trunk/src/Makefile.am b/trunk/src/Makefile.am new file mode 100644 index 000000000..dc87087ec --- /dev/null +++ b/trunk/src/Makefile.am @@ -0,0 +1,58 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir) \ + -DPREFIX="\"$(prefix)"\" \ + -DSYSCONFDIR=\""$(sysconfdir)"\" \ + -DDATADIR=\""$(datadir)"\" \ + -DPKGDATADIR=\""$(pkgdatadir)"\" \ + -DLIBDIR=\""$(libdir)"\" \ + -DLOCALEDIR=\""$(datadir)/locale"\" \ + $(EMPATHY_CFLAGS) \ + $(WARN_CFLAGS) + +LDADD = \ + $(top_builddir)/libempathy-gtk/libempathy-gtk.la \ + $(top_builddir)/libempathy/libempathy.la \ + $(top_builddir)/extensions/libemp-extensions.la \ + $(EMPATHY_LIBS) + +bin_PROGRAMS = \ + empathy \ + empathy-accounts \ + empathy-logs + +empathy_SOURCES = \ + empathy.c \ + bacon-message-connection.c bacon-message-connection.h \ + empathy-chat-window.c empathy-chat-window.h \ + empathy-new-chatroom-dialog.c empathy-new-chatroom-dialog.h \ + empathy-status-icon.c empathy-status-icon.h \ + empathy-about-dialog.c empathy-about-dialog.h \ + empathy-chatrooms-window.c empathy-chatrooms-window.h \ + empathy-main-window.c empathy-main-window.h \ + empathy-preferences.c empathy-preferences.h \ + empathy-call-window.c empathy-call-window.h \ + ephy-spinner.c ephy-spinner.h + +empathy_accounts_SOURCES = empathy-accounts.c +empathy_logs_SOURCES = empathy-logs.c + +gladedir = $(datadir)/empathy +glade_DATA = \ + empathy-call-window.glade \ + empathy-main-window.glade \ + empathy-preferences.glade \ + empathy-chatrooms-window.glade \ + empathy-chat-window.glade \ + empathy-new-chatroom-dialog.glade \ + empathy-status-icon.glade + +dist_man_MANS = \ + empathy.1 \ + empathy-accounts.1 + +EXTRA_DIST = \ + $(autostart_DATA) \ + $(glade_DATA) + +CLEANFILES = $(BUILT_SOURCES) + diff --git a/trunk/src/bacon-message-connection.c b/trunk/src/bacon-message-connection.c new file mode 100644 index 000000000..c8000de24 --- /dev/null +++ b/trunk/src/bacon-message-connection.c @@ -0,0 +1,396 @@ +/* + * Copyright (C) 2003 Bastien Nocera <hadess@hadess.net> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <errno.h> + +#include "bacon-message-connection.h" + +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX 108 +#endif + +struct BaconMessageConnection { + /* A server accepts connections */ + gboolean is_server; + + /* The socket path itself */ + char *path; + + /* File descriptor of the socket */ + int fd; + /* Channel to watch */ + GIOChannel *chan; + /* Event id returned by g_io_add_watch() */ + int conn_id; + + /* Connections accepted by this connection */ + GSList *accepted_connections; + + /* callback */ + void (*func) (const char *message, gpointer user_data); + gpointer data; +}; + +static gboolean +test_is_socket (const char *path) +{ + struct stat s; + + if (stat (path, &s) == -1) + return FALSE; + + if (S_ISSOCK (s.st_mode)) + return TRUE; + + return FALSE; +} + +static gboolean +is_owned_by_user_and_socket (const char *path) +{ + struct stat s; + + if (stat (path, &s) == -1) + return FALSE; + + if (s.st_uid != geteuid ()) + return FALSE; + + if ((s.st_mode & S_IFSOCK) != S_IFSOCK) + return FALSE; + + return TRUE; +} + +static gboolean server_cb (GIOChannel *source, + GIOCondition condition, gpointer data); + +static gboolean +setup_connection (BaconMessageConnection *conn) +{ + g_return_val_if_fail (conn->chan == NULL, FALSE); + + conn->chan = g_io_channel_unix_new (conn->fd); + if (!conn->chan) { + return FALSE; + } + g_io_channel_set_line_term (conn->chan, "\n", 1); + conn->conn_id = g_io_add_watch (conn->chan, G_IO_IN, server_cb, conn); + + return TRUE; +} + +static void +accept_new_connection (BaconMessageConnection *server_conn) +{ + BaconMessageConnection *conn; + int alen; + + g_return_if_fail (server_conn->is_server); + + conn = g_new0 (BaconMessageConnection, 1); + conn->is_server = FALSE; + conn->func = server_conn->func; + conn->data = server_conn->data; + + conn->fd = accept (server_conn->fd, NULL, (guint *)&alen); + + server_conn->accepted_connections = + g_slist_prepend (server_conn->accepted_connections, conn); + + setup_connection (conn); +} + +static gboolean +server_cb (GIOChannel *source, GIOCondition condition, gpointer data) +{ + BaconMessageConnection *conn = (BaconMessageConnection *)data; + char *message, *subs, buf; + int cd, rc, offset; + gboolean finished; + + offset = 0; + if (conn->is_server && conn->fd == g_io_channel_unix_get_fd (source)) { + accept_new_connection (conn); + return TRUE; + } + message = g_malloc (1); + cd = conn->fd; + rc = read (cd, &buf, 1); + while (rc > 0 && buf != '\n') + { + message = g_realloc (message, rc + offset + 1); + message[offset] = buf; + offset = offset + rc; + rc = read (cd, &buf, 1); + } + if (rc <= 0) { + g_io_channel_shutdown (conn->chan, FALSE, NULL); + g_io_channel_unref (conn->chan); + conn->chan = NULL; + close (conn->fd); + conn->fd = -1; + g_free (message); + conn->conn_id = 0; + + return FALSE; + } + message[offset] = '\0'; + + subs = message; + finished = FALSE; + + while (finished == FALSE && *subs != '\0') + { + if (conn->func != NULL) + (*conn->func) (subs, conn->data); + + subs += strlen (subs) + 1; + if (subs - message >= offset) + finished = TRUE; + } + + g_free (message); + + return TRUE; +} + +static char * +find_file_with_pattern (const char *dir, const char *pattern) +{ + GDir *filedir; + char *found_filename; + const char *filename; + GPatternSpec *pat; + + filedir = g_dir_open (dir, 0, NULL); + if (filedir == NULL) + return NULL; + + pat = g_pattern_spec_new (pattern); + if (pat == NULL) + { + g_dir_close (filedir); + return NULL; + } + + found_filename = NULL; + + while ((filename = g_dir_read_name (filedir))) + { + if (g_pattern_match_string (pat, filename)) + { + char *tmp = g_build_filename (dir, filename, NULL); + if (is_owned_by_user_and_socket (tmp)) + found_filename = g_strdup (filename); + g_free (tmp); + } + + if (found_filename != NULL) + break; + } + + g_pattern_spec_free (pat); + g_dir_close (filedir); + + return found_filename; +} + +static char * +socket_filename (const char *prefix) +{ + char *pattern, *newfile, *path, *filename; + const char *tmpdir; + + pattern = g_strdup_printf ("%s.%s.*", prefix, g_get_user_name ()); + tmpdir = g_get_tmp_dir (); + filename = find_file_with_pattern (tmpdir, pattern); + if (filename == NULL) + { + newfile = g_strdup_printf ("%s.%s.%u", prefix, + g_get_user_name (), g_random_int ()); + path = g_build_filename (tmpdir, newfile, NULL); + g_free (newfile); + } else { + path = g_build_filename (tmpdir, filename, NULL); + g_free (filename); + } + + g_free (pattern); + return path; +} + +static gboolean +try_server (BaconMessageConnection *conn) +{ + struct sockaddr_un uaddr; + + uaddr.sun_family = AF_UNIX; + strncpy (uaddr.sun_path, conn->path, + MIN (strlen(conn->path)+1, UNIX_PATH_MAX)); + conn->fd = socket (PF_UNIX, SOCK_STREAM, 0); + if (bind (conn->fd, (struct sockaddr *) &uaddr, sizeof (uaddr)) == -1) + { + conn->fd = -1; + return FALSE; + } + listen (conn->fd, 5); + + if (!setup_connection (conn)) + return FALSE; + return TRUE; +} + +static gboolean +try_client (BaconMessageConnection *conn) +{ + struct sockaddr_un uaddr; + + uaddr.sun_family = AF_UNIX; + strncpy (uaddr.sun_path, conn->path, + MIN(strlen(conn->path)+1, UNIX_PATH_MAX)); + conn->fd = socket (PF_UNIX, SOCK_STREAM, 0); + if (connect (conn->fd, (struct sockaddr *) &uaddr, + sizeof (uaddr)) == -1) + { + conn->fd = -1; + return FALSE; + } + + return setup_connection (conn); +} + +BaconMessageConnection * +bacon_message_connection_new (const char *prefix) +{ + BaconMessageConnection *conn; + + g_return_val_if_fail (prefix != NULL, NULL); + + conn = g_new0 (BaconMessageConnection, 1); + conn->path = socket_filename (prefix); + + if (test_is_socket (conn->path) == FALSE) + { + if (!try_server (conn)) + { + bacon_message_connection_free (conn); + return NULL; + } + + conn->is_server = TRUE; + return conn; + } + + if (try_client (conn) == FALSE) + { + unlink (conn->path); + try_server (conn); + if (conn->fd == -1) + { + bacon_message_connection_free (conn); + return NULL; + } + + conn->is_server = TRUE; + return conn; + } + + conn->is_server = FALSE; + return conn; +} + +void +bacon_message_connection_free (BaconMessageConnection *conn) +{ + GSList *child_conn; + + g_return_if_fail (conn != NULL); + /* Only servers can accept other connections */ + g_return_if_fail (conn->is_server != FALSE || + conn->accepted_connections == NULL); + + child_conn = conn->accepted_connections; + while (child_conn != NULL) { + bacon_message_connection_free (child_conn->data); + child_conn = g_slist_next (child_conn); + } + g_slist_free (conn->accepted_connections); + + if (conn->conn_id) { + g_source_remove (conn->conn_id); + conn->conn_id = 0; + } + if (conn->chan) { + g_io_channel_shutdown (conn->chan, FALSE, NULL); + g_io_channel_unref (conn->chan); + } + + if (conn->is_server != FALSE) { + unlink (conn->path); + } + if (conn->fd != -1) { + close (conn->fd); + } + + g_free (conn->path); + g_free (conn); +} + +void +bacon_message_connection_set_callback (BaconMessageConnection *conn, + BaconMessageReceivedFunc func, + gpointer user_data) +{ + g_return_if_fail (conn != NULL); + + conn->func = func; + conn->data = user_data; +} + +void +bacon_message_connection_send (BaconMessageConnection *conn, + const char *message) +{ + g_return_if_fail (conn != NULL); + g_return_if_fail (message != NULL); + + g_io_channel_write_chars (conn->chan, message, strlen (message), + NULL, NULL); + g_io_channel_write_chars (conn->chan, "\n", 1, NULL, NULL); + g_io_channel_flush (conn->chan, NULL); +} + +gboolean +bacon_message_connection_get_is_server (BaconMessageConnection *conn) +{ + g_return_val_if_fail (conn != NULL, FALSE); + + return conn->is_server; +} + diff --git a/trunk/src/bacon-message-connection.h b/trunk/src/bacon-message-connection.h new file mode 100644 index 000000000..aac7a2d11 --- /dev/null +++ b/trunk/src/bacon-message-connection.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003 Bastien Nocera <hadess@hadess.net> + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + */ + +#ifndef BACON_MESSAGE_CONNECTION_H +#define BACON_MESSAGE_CONNECTION_H + +#include <glib.h> + +G_BEGIN_DECLS + +typedef void (*BaconMessageReceivedFunc) (const char *message, + gpointer user_data); + +typedef struct BaconMessageConnection BaconMessageConnection; + +BaconMessageConnection *bacon_message_connection_new (const char *prefix); +void bacon_message_connection_free (BaconMessageConnection *conn); +void bacon_message_connection_set_callback (BaconMessageConnection *conn, + BaconMessageReceivedFunc func, + gpointer user_data); +void bacon_message_connection_send (BaconMessageConnection *conn, + const char *message); +gboolean bacon_message_connection_get_is_server (BaconMessageConnection *conn); + +G_END_DECLS + +#endif /* BACON_MESSAGE_CONNECTION_H */ diff --git a/trunk/src/empathy-about-dialog.c b/trunk/src/empathy-about-dialog.c new file mode 100644 index 000000000..d8d9dfa95 --- /dev/null +++ b/trunk/src/empathy-about-dialog.c @@ -0,0 +1,114 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2006-2007 Imendio AB + * Copyright (C) 2007-2008 Collabora Ltd. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Martyn Russell <martyn@imendio.com> + * Xavier Claessens <xclaesse@gmail.com> + */ + +#include <config.h> + +#include <glib/gi18n.h> +#include <gtk/gtkaboutdialog.h> +#include <gtk/gtksizegroup.h> +#include <glade/glade.h> + +#include <libempathy-gtk/empathy-ui-utils.h> + +#include "empathy-about-dialog.h" + +#define WEB_SITE "http://live.gnome.org/Empathy" + +static void about_dialog_activate_link_cb (GtkAboutDialog *about, + const gchar *link, + gpointer data); + +static const char *authors[] = { + "Mikael Hallendal", + "Richard Hult", + "Martyn Russell", + "Geert-Jan Van den Bogaerde", + "Kevin Dougherty", + "Eitan Isaacson", + "Xavier Claessens", + NULL +}; + +static const char *documenters[] = { + NULL +}; + +static const char *artists[] = { + "Andreas Nilsson <nisses.mail@home.se>", + "Vinicius Depizzol <vdepizzol@gmail.com>", + NULL +}; + +static const char *license[] = { + N_("Empathy 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."), + N_("Empathy 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."), + N_("You should have received a copy of the GNU General Public License " + "along with Empathy; if not, write to the Free Software Foundation, Inc., " + "51 Franklin Street, Fifth Floor, Boston, MA 02110-130159 USA") +}; + +static void +about_dialog_activate_link_cb (GtkAboutDialog *about, + const gchar *link, + gpointer data) +{ + empathy_url_show (link); +} + +void +empathy_about_dialog_new (GtkWindow *parent) +{ + gchar *license_trans; + + gtk_about_dialog_set_url_hook (about_dialog_activate_link_cb, NULL, NULL); + + license_trans = g_strconcat (_(license[0]), "\n\n", + _(license[1]), "\n\n", + _(license[2]), "\n\n", + NULL); + + gtk_show_about_dialog (parent, + "artists", artists, + "authors", authors, + "comments", _("An Instant Messaging client for GNOME"), + "license", license_trans, + "wrap-license", TRUE, + "copyright", "Imendio AB 2002-2007\nCollabora Ltd 2007", + "documenters", documenters, + "logo-icon-name", "empathy", + "translator-credits", _("translator-credits"), + "version", PACKAGE_VERSION, + "website", WEB_SITE, + NULL); + + g_free (license_trans); +} + + diff --git a/trunk/src/empathy-about-dialog.h b/trunk/src/empathy-about-dialog.h new file mode 100644 index 000000000..e7eac5ff9 --- /dev/null +++ b/trunk/src/empathy-about-dialog.h @@ -0,0 +1,36 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2006-2007 Imendio AB + * Copyright (C) 2007-2008 Collabora Ltd. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Martyn Russell <martyn@imendio.com> + * Xavier Claessens <xclaesse@gmail.com> + */ + +#ifndef __EMPATHY_ABOUT_DIALOG_H__ +#define __EMPATHY_ABOUT_DIALOG_H__ + +#include <gtk/gtkwindow.h> + +G_BEGIN_DECLS + +void empathy_about_dialog_new (GtkWindow *parent); + +G_END_DECLS + +#endif /* __EMPATHY_ABOUT_DIALOG_H__ */ diff --git a/trunk/src/empathy-accounts.1 b/trunk/src/empathy-accounts.1 new file mode 100644 index 000000000..b615e875e --- /dev/null +++ b/trunk/src/empathy-accounts.1 @@ -0,0 +1,12 @@ +.TH EMPATHY-ACCOUNTS "1" "October 2007" "Telepathy project" "User Commands" +.SH NAME +empathy\-accounts \- configure Telepathy instant messaging accounts +.SH SYNOPSIS +empathy\-accounts +.SH DESCRIPTION +empathy-accounts displays a graphical (Gtk+) dialog where the instant messaging +accounts used by Empathy (via Telepathy Mission Control) can be configured. +.SH OPTIONS +There are no command-line options. +.SH SEE ALSO +\fIhttp://telepathy.freedesktop.org/\fR, \fIhttp://live.gnome.org/Empathy\fR diff --git a/trunk/src/empathy-accounts.c b/trunk/src/empathy-accounts.c new file mode 100644 index 000000000..65ede78ac --- /dev/null +++ b/trunk/src/empathy-accounts.c @@ -0,0 +1,66 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2007-2008 Collabora Ltd. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Xavier Claessens <xclaesse@gmail.com> + */ + +#include <config.h> + +#include <string.h> +#include <stdlib.h> + +#include <glib.h> +#include <gtk/gtk.h> + +#include <libempathy/empathy-debug.h> +#include <libempathy-gtk/empathy-accounts-dialog.h> + +static void +destroy_cb (GtkWidget *dialog, + gpointer user_data) +{ + gtk_main_quit (); +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *dialog; + + gtk_init (&argc, &argv); + + if (g_getenv ("EMPATHY_TIMING") != NULL) { + g_log_set_default_handler (tp_debug_timestamped_log_handler, NULL); + } + empathy_debug_set_flags (g_getenv ("EMPATHY_DEBUG")); + tp_debug_divert_messages (g_getenv ("EMPATHY_LOGFILE")); + + gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (), + PKGDATADIR G_DIR_SEPARATOR_S "icons"); + dialog = empathy_accounts_dialog_show (NULL); + + g_signal_connect (dialog, "destroy", + G_CALLBACK (destroy_cb), + NULL); + + gtk_main (); + + return EXIT_SUCCESS; +} + diff --git a/trunk/src/empathy-call-window.c b/trunk/src/empathy-call-window.c new file mode 100644 index 000000000..2d52fcf7c --- /dev/null +++ b/trunk/src/empathy-call-window.c @@ -0,0 +1,564 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2007 Elliot Fairweather + * Copyright (C) 2007-2008 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: Elliot Fairweather <elliot.fairweather@collabora.co.uk> + * Xavier Claessens <xclaesse@gmail.com> + */ + +#include <string.h> + +#include <glade/glade.h> +#include <glib/gi18n.h> + +#include <telepathy-glib/enums.h> + +#include <libempathy/empathy-contact.h> +#include <libempathy/empathy-tp-call.h> +#include <libempathy/empathy-utils.h> +#include <libempathy-gtk/empathy-ui-utils.h> + +#include "empathy-call-window.h" + +#define DEBUG_FLAG EMPATHY_DEBUG_OTHER +#include <libempathy/empathy-debug.h> + +typedef struct +{ + EmpathyTpCall *call; + GTimeVal start_time; + guint timeout_event_id; + gboolean is_drawing; + guint status; + + GtkWidget *window; + GtkWidget *main_hbox; + GtkWidget *controls_vbox; + GtkWidget *volume_hbox; + GtkWidget *status_label; + GtkWidget *input_volume_button; + GtkWidget *output_volume_button; + GtkWidget *preview_video_socket; + GtkWidget *output_video_socket; + GtkWidget *video_button; + GtkWidget *hang_up_button; + GtkWidget *confirmation_dialog; + GtkWidget *keypad_expander; +} EmpathyCallWindow; + +static gboolean +call_window_update_timer (gpointer data) +{ + EmpathyCallWindow *window = data; + GTimeVal current; + gchar *str; + glong now, then; + glong time, seconds, minutes, hours; + + g_get_current_time (¤t); + + now = current.tv_sec; + then = (window->start_time).tv_sec; + + time = now - then; + + seconds = time % 60; + time /= 60; + minutes = time % 60; + time /= 60; + hours = time % 60; + + if (hours > 0) + str = g_strdup_printf ("Connected - %02ld : %02ld : %02ld", hours, + minutes, seconds); + else + str = g_strdup_printf ("Connected - %02ld : %02ld", minutes, seconds); + + gtk_label_set_text (GTK_LABEL (window->status_label), str); + + g_free (str); + + return TRUE; +} + +static void +call_window_stop_timeout (EmpathyCallWindow *window) +{ + DEBUG ("Timer stopped"); + + if (window->timeout_event_id) + { + g_source_remove (window->timeout_event_id); + window->timeout_event_id = 0; + } +} + +static void +call_window_set_output_video_is_drawing (EmpathyCallWindow *window, + gboolean is_drawing) +{ + DEBUG ("Setting output video is drawing - %d", is_drawing); + + if (is_drawing && !window->is_drawing) + { + gtk_window_set_resizable (GTK_WINDOW (window->window), TRUE); + gtk_box_pack_end (GTK_BOX (window->main_hbox), + window->output_video_socket, TRUE, TRUE, 0); + empathy_tp_call_add_output_video (window->call, + gtk_socket_get_id (GTK_SOCKET (window->output_video_socket))); + } + if (!is_drawing && window->is_drawing) + { + gtk_window_set_resizable (GTK_WINDOW (window->window), FALSE); + empathy_tp_call_add_output_video (window->call, 0); + gtk_container_remove (GTK_CONTAINER (window->main_hbox), + window->output_video_socket); + } + + window->is_drawing = is_drawing; +} + +static void +call_window_finalize (EmpathyCallWindow *window) +{ + gtk_label_set_text (GTK_LABEL (window->status_label), _("Closed")); + gtk_widget_set_sensitive (window->hang_up_button, FALSE); + gtk_widget_set_sensitive (window->video_button, FALSE); + gtk_widget_set_sensitive (window->output_volume_button, FALSE); + gtk_widget_set_sensitive (window->input_volume_button, FALSE); + gtk_widget_set_sensitive (window->keypad_expander, FALSE); + + if (window->call) + { + call_window_stop_timeout (window); + call_window_set_output_video_is_drawing (window, FALSE); + empathy_tp_call_remove_preview_video (window->call, + gtk_socket_get_id (GTK_SOCKET (window->preview_video_socket))); + g_object_unref (window->call); + window->call = NULL; + } + + if (window->confirmation_dialog) + gtk_widget_destroy (window->confirmation_dialog); +} + +static void +call_window_socket_realized_cb (GtkWidget *widget, + EmpathyCallWindow *window) +{ + if (widget == window->preview_video_socket) + { + DEBUG ("Preview socket realized"); + empathy_tp_call_add_preview_video (window->call, + gtk_socket_get_id (GTK_SOCKET (window->preview_video_socket))); + } + else + DEBUG ("Output socket realized"); +} + +static void +call_window_video_button_toggled_cb (GtkWidget *button, + EmpathyCallWindow *window) +{ + gboolean is_sending; + guint status; + + is_sending = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button)); + + DEBUG ("Send video toggled - %d", is_sending); + + g_object_get (window->call, "status", &status, NULL); + if (status == EMPATHY_TP_CALL_STATUS_ACCEPTED) + empathy_tp_call_request_video_stream_direction (window->call, is_sending); +} + +static void +call_window_hang_up_button_clicked_cb (GtkWidget *widget, + EmpathyCallWindow *window) +{ + DEBUG ("Call clicked, end call"); + call_window_finalize (window); +} + +static void +call_window_output_volume_changed_cb (GtkScaleButton *button, + gdouble value, + EmpathyCallWindow *window) +{ + if (!window->call) + return; + + if (value <= 0) + empathy_tp_call_mute_output (window->call, TRUE); + else + { + empathy_tp_call_mute_output (window->call, FALSE); + empathy_tp_call_set_output_volume (window->call, value * 100); + } +} + +static void +call_window_input_volume_changed_cb (GtkScaleButton *button, + gdouble value, + EmpathyCallWindow *window) +{ + if (!window->call) + return; + + if (value <= 0) + empathy_tp_call_mute_input (window->call, TRUE); + else + { + empathy_tp_call_mute_input (window->call, FALSE); + /* FIXME: Not implemented? + empathy_tp_call_set_input_volume (window->call, value * 100);*/ + } +} + +static gboolean +call_window_delete_event_cb (GtkWidget *widget, + GdkEvent *event, + EmpathyCallWindow *window) +{ + GtkWidget *dialog; + gint result; + guint status = EMPATHY_TP_CALL_STATUS_CLOSED; + + DEBUG ("Delete event occurred"); + + if (window->call) + g_object_get (window->call, "status", &status, NULL); + + if (status == EMPATHY_TP_CALL_STATUS_ACCEPTED) + { + dialog = gtk_message_dialog_new (GTK_WINDOW (window->window), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_WARNING, GTK_BUTTONS_CANCEL, _("End this call?")); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + _("Closing this window will end the call in progress.")); + gtk_dialog_add_button (GTK_DIALOG (dialog), _("_End Call"), GTK_RESPONSE_OK); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + + result = gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + + if (result != GTK_RESPONSE_OK) + return TRUE; + } + + return FALSE; +} + +static void +call_window_destroy_cb (GtkWidget *widget, + EmpathyCallWindow *window) +{ + call_window_finalize (window); + g_object_unref (window->output_video_socket); + g_object_unref (window->preview_video_socket); + g_slice_free (EmpathyCallWindow, window); +} + +static void +call_window_confirmation_dialog_response_cb (GtkDialog *dialog, + gint response, + EmpathyCallWindow *window) +{ + if (response == GTK_RESPONSE_OK && window->call) + empathy_tp_call_accept_incoming_call (window->call); + else + call_window_finalize (window); + + gtk_widget_destroy (window->confirmation_dialog); + window->confirmation_dialog = NULL; +} + +static void +call_window_show_confirmation_dialog (EmpathyCallWindow *window) +{ + EmpathyContact *contact; + GtkWidget *button; + GtkWidget *image; + + if (window->confirmation_dialog) + return; + + g_object_get (window->call, "contact", &contact, NULL); + + window->confirmation_dialog = gtk_message_dialog_new (GTK_WINDOW (window->window), + GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_QUESTION, GTK_BUTTONS_NONE, _("Incoming call")); + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (window->confirmation_dialog), + _("%s is calling you, do you want to answer?"), + empathy_contact_get_name (contact)); + gtk_dialog_set_default_response (GTK_DIALOG (window->confirmation_dialog), + GTK_RESPONSE_OK); + + button = gtk_dialog_add_button (GTK_DIALOG (window->confirmation_dialog), + _("_Reject"), GTK_RESPONSE_CANCEL); + image = gtk_image_new_from_icon_name (GTK_STOCK_CANCEL, GTK_ICON_SIZE_BUTTON); + gtk_button_set_image (GTK_BUTTON (button), image); + + button = gtk_dialog_add_button (GTK_DIALOG (window->confirmation_dialog), + _("_Answer"), GTK_RESPONSE_OK); + image = gtk_image_new_from_icon_name (GTK_STOCK_APPLY, GTK_ICON_SIZE_BUTTON); + gtk_button_set_image (GTK_BUTTON (button), image); + + g_signal_connect (window->confirmation_dialog, "response", + G_CALLBACK (call_window_confirmation_dialog_response_cb), + window); + + gtk_widget_show (window->confirmation_dialog); + g_object_unref (contact); +} + +static void +call_window_update (EmpathyCallWindow *window) +{ + EmpathyContact *contact; + guint stream_state; + EmpathyTpCallStream *audio_stream; + EmpathyTpCallStream *video_stream; + gboolean is_incoming; + gchar *title; + + g_object_get (window->call, + "status", &window->status, + "audio-stream", &audio_stream, + "video-stream", &video_stream, + "contact", &contact, + "is-incoming", &is_incoming, + NULL); + + if (video_stream->state > audio_stream->state) + stream_state = video_stream->state; + else + stream_state = audio_stream->state; + + DEBUG ("Status changed - status: %d, stream state: %d, " + "is-incoming: %d video-stream direction: %d", + window->status, stream_state, is_incoming, video_stream->direction); + + /* Depending on the status we have to set: + * - window's title + * - status's label + * - sensibility of all buttons + * */ + if (window->status == EMPATHY_TP_CALL_STATUS_READYING) + { + gtk_window_set_title (GTK_WINDOW (window->window), _("Empathy Call")); + gtk_label_set_text (GTK_LABEL (window->status_label), _("Readying")); + gtk_widget_set_sensitive (window->video_button, FALSE); + gtk_widget_set_sensitive (window->output_volume_button, FALSE); + gtk_widget_set_sensitive (window->input_volume_button, FALSE); + gtk_widget_set_sensitive (window->hang_up_button, FALSE); + gtk_widget_set_sensitive (window->keypad_expander, FALSE); + } + else if (window->status == EMPATHY_TP_CALL_STATUS_PENDING) + { + title = g_strdup_printf (_("%s - Empathy Call"), + empathy_contact_get_name (contact)); + + gtk_window_set_title (GTK_WINDOW (window->window), title); + gtk_label_set_text (GTK_LABEL (window->status_label), _("Ringing")); + gtk_widget_set_sensitive (window->hang_up_button, TRUE); + if (is_incoming) + call_window_show_confirmation_dialog (window); + } + else if (window->status == EMPATHY_TP_CALL_STATUS_ACCEPTED) + { + gboolean receiving_video; + gboolean sending_video; + + if (stream_state == TP_MEDIA_STREAM_STATE_DISCONNECTED) + gtk_label_set_text (GTK_LABEL (window->status_label), _("Disconnected")); + if (stream_state == TP_MEDIA_STREAM_STATE_CONNECTING) + gtk_label_set_text (GTK_LABEL (window->status_label), _("Connecting")); + else if (stream_state == TP_MEDIA_STREAM_STATE_CONNECTED && + window->timeout_event_id == 0) + { + /* The call started, launch the timer */ + g_get_current_time (&(window->start_time)); + window->timeout_event_id = g_timeout_add_seconds (1, + call_window_update_timer, window); + call_window_update_timer (window); + } + + receiving_video = video_stream->direction & TP_MEDIA_STREAM_DIRECTION_RECEIVE; + sending_video = video_stream->direction & TP_MEDIA_STREAM_DIRECTION_SEND; + call_window_set_output_video_is_drawing (window, receiving_video); + g_signal_handlers_block_by_func (window->video_button, + call_window_video_button_toggled_cb, window); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (window->video_button), + sending_video); + g_signal_handlers_unblock_by_func (window->video_button, + call_window_video_button_toggled_cb, window); + + gtk_widget_set_sensitive (window->video_button, TRUE); + gtk_widget_set_sensitive (window->output_volume_button, TRUE); + gtk_widget_set_sensitive (window->input_volume_button, TRUE); + gtk_widget_set_sensitive (window->hang_up_button, TRUE); + gtk_widget_set_sensitive (window->keypad_expander, TRUE); + } + else if (window->status == EMPATHY_TP_CALL_STATUS_CLOSED) + call_window_finalize (window); + + if (contact) + g_object_unref (contact); +} + +static gboolean +call_window_dtmf_button_release_event_cb (GtkWidget *widget, + GdkEventButton *event, + EmpathyCallWindow *window) +{ + empathy_tp_call_stop_tone (window->call); + return FALSE; +} + +static gboolean +call_window_dtmf_button_press_event_cb (GtkWidget *widget, + GdkEventButton *event, + EmpathyCallWindow *window) +{ + TpDTMFEvent dtmf_event; + + dtmf_event = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), "code")); + empathy_tp_call_start_tone (window->call, dtmf_event); + return FALSE; +} + +static void +call_window_dtmf_connect (GladeXML *glade, + EmpathyCallWindow *window, + const gchar *name, + TpDTMFEvent event) +{ + GtkWidget *widget; + + widget = glade_xml_get_widget (glade, name); + g_object_set_data (G_OBJECT (widget), "code", GUINT_TO_POINTER (event)); + g_signal_connect (widget, "button-press-event", + G_CALLBACK (call_window_dtmf_button_press_event_cb), window); + g_signal_connect (widget, "button-release-event", + G_CALLBACK (call_window_dtmf_button_release_event_cb), window); + /* FIXME: Connect "key-[press/release]-event" to*/ +} + +GtkWidget * +empathy_call_window_new (EmpathyTpCall *call) +{ + EmpathyCallWindow *window; + GladeXML *glade; + gchar *filename; + const gchar *icons[] = {"audio-input-microphone", NULL}; + + g_return_val_if_fail (EMPATHY_IS_TP_CALL (call), NULL); + + window = g_slice_new0 (EmpathyCallWindow); + window->call = g_object_ref (call); + + filename = empathy_file_lookup ("empathy-call-window.glade", "src"); + glade = empathy_glade_get_file (filename, + "window", + NULL, + "window", &window->window, + "main_hbox", &window->main_hbox, + "controls_vbox", &window->controls_vbox, + "volume_hbox", &window->volume_hbox, + "status_label", &window->status_label, + "video_button", &window->video_button, + "hang_up_button", &window->hang_up_button, + "keypad_expander", &window->keypad_expander, + NULL); + g_free (filename); + + empathy_glade_connect (glade, + window, + "window", "destroy", call_window_destroy_cb, + "window", "delete_event", call_window_delete_event_cb, + "hang_up_button", "clicked", call_window_hang_up_button_clicked_cb, + "video_button", "toggled", call_window_video_button_toggled_cb, + NULL); + + /* Setup DTMF buttons */ + call_window_dtmf_connect (glade, window, "button_0", TP_DTMF_EVENT_DIGIT_0); + call_window_dtmf_connect (glade, window, "button_1", TP_DTMF_EVENT_DIGIT_1); + call_window_dtmf_connect (glade, window, "button_2", TP_DTMF_EVENT_DIGIT_2); + call_window_dtmf_connect (glade, window, "button_3", TP_DTMF_EVENT_DIGIT_3); + call_window_dtmf_connect (glade, window, "button_4", TP_DTMF_EVENT_DIGIT_4); + call_window_dtmf_connect (glade, window, "button_5", TP_DTMF_EVENT_DIGIT_5); + call_window_dtmf_connect (glade, window, "button_6", TP_DTMF_EVENT_DIGIT_6); + call_window_dtmf_connect (glade, window, "button_7", TP_DTMF_EVENT_DIGIT_7); + call_window_dtmf_connect (glade, window, "button_8", TP_DTMF_EVENT_DIGIT_8); + call_window_dtmf_connect (glade, window, "button_9", TP_DTMF_EVENT_DIGIT_9); + call_window_dtmf_connect (glade, window, "button_asterisk", TP_DTMF_EVENT_ASTERISK); + call_window_dtmf_connect (glade, window, "button_hash", TP_DTMF_EVENT_HASH); + + g_object_unref (glade); + + /* Output volume button */ + window->output_volume_button = gtk_volume_button_new (); + gtk_scale_button_set_value (GTK_SCALE_BUTTON (window->output_volume_button), 1); + gtk_box_pack_start (GTK_BOX (window->volume_hbox), + window->output_volume_button, FALSE, FALSE, 0); + gtk_widget_show (window->output_volume_button); + g_signal_connect (window->output_volume_button, "value-changed", + G_CALLBACK (call_window_output_volume_changed_cb), window); + + /* Input volume button */ + window->input_volume_button = gtk_volume_button_new (); + gtk_scale_button_set_icons (GTK_SCALE_BUTTON (window->input_volume_button), + icons); + gtk_scale_button_set_value (GTK_SCALE_BUTTON (window->input_volume_button), 1); + gtk_box_pack_start (GTK_BOX (window->volume_hbox), + window->input_volume_button, FALSE, FALSE, 0); + gtk_widget_show (window->input_volume_button); + g_signal_connect (window->input_volume_button, "value-changed", + G_CALLBACK (call_window_input_volume_changed_cb), window); + + /* Output video socket */ + window->output_video_socket = g_object_ref (gtk_socket_new ()); + gtk_widget_set_size_request (window->output_video_socket, 400, 300); + g_signal_connect (GTK_OBJECT (window->output_video_socket), "realize", + G_CALLBACK (call_window_socket_realized_cb), window); + gtk_widget_show (window->output_video_socket); + + /* Preview video socket */ + window->preview_video_socket = g_object_ref (gtk_socket_new ()); + gtk_widget_set_size_request (window->preview_video_socket, 176, 144); + g_signal_connect (GTK_OBJECT (window->preview_video_socket), "realize", + G_CALLBACK (call_window_socket_realized_cb), window); + gtk_widget_show (window->preview_video_socket); + + /* FIXME: We shouldn't do this if there is no video input */ + gtk_box_pack_start (GTK_BOX (window->controls_vbox), + window->preview_video_socket, FALSE, FALSE, 0); + gtk_box_reorder_child (GTK_BOX (window->controls_vbox), + window->preview_video_socket, 0); + + g_signal_connect_swapped (G_OBJECT (window->call), "notify", + G_CALLBACK (call_window_update), + window); + + call_window_update (window); + gtk_widget_show (window->window); + + return window->window; +} + diff --git a/trunk/src/empathy-call-window.glade b/trunk/src/empathy-call-window.glade new file mode 100644 index 000000000..684aed3ce --- /dev/null +++ b/trunk/src/empathy-call-window.glade @@ -0,0 +1,334 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd"> +<!--Generated with glade3 3.4.2 on Thu May 1 23:37:49 2008 --> +<glade-interface> + <widget class="GtkWindow" id="window"> + <child> + <widget class="GtkHBox" id="main_hbox"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="border_width">5</property> + <property name="spacing">5</property> + <child> + <widget class="GtkVBox" id="controls_vbox"> + <property name="visible">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="spacing">6</property> + <child> + <widget class="GtkCheckButton" id="video_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="label" translatable="yes">Send Video</property> + <property name="response_id">0</property> + <property name="draw_indicator">True</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <widget class="GtkFrame" id="frame2"> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <property name="shadow_type">GTK_SHADOW_NONE</property> + <child> + <widget class="GtkHBox" id="volume_hbox"> + <property name="visible">True</property> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + </widget> + </child> + <child> + <widget class="GtkLabel" id="label1"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>Volume</b></property> + <property name="use_markup">True</property> + </widget> + <packing> + <property name="type">label_item</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <widget class="GtkExpander" id="keypad_expander"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <child> + <widget class="GtkTable" id="keypad_table"> + <property name="visible">True</property> + <property name="n_rows">4</property> + <property name="n_columns">3</property> + <property name="homogeneous">True</property> + <child> + <widget class="GtkButton" id="button_1"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">1</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + </widget> + <packing> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkButton" id="button_2"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">2</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkButton" id="button_3"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">3</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + </widget> + <packing> + <property name="left_attach">2</property> + <property name="right_attach">3</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkButton" id="button_4"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">4</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + </widget> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkButton" id="button_5"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">5</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkButton" id="button_6"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">6</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + </widget> + <packing> + <property name="left_attach">2</property> + <property name="right_attach">3</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkButton" id="button_7"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">7</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + </widget> + <packing> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkButton" id="button_8"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">8</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkButton" id="button_9"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">9</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + </widget> + <packing> + <property name="left_attach">2</property> + <property name="right_attach">3</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkButton" id="button_asterisk"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">*</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + </widget> + <packing> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkButton" id="button_0"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">0</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkButton" id="button_hash"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">#</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + </widget> + <packing> + <property name="left_attach">2</property> + <property name="right_attach">3</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + <property name="y_options"></property> + </packing> + </child> + </widget> + </child> + <child> + <widget class="GtkLabel" id="label4"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>Keypad</b></property> + <property name="use_markup">True</property> + </widget> + <packing> + <property name="type">label_item</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="status_label"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="ellipsize">PANGO_ELLIPSIZE_END</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="pack_type">GTK_PACK_END</property> + <property name="position">4</property> + </packing> + </child> + <child> + <widget class="GtkButton" id="hang_up_button"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="receives_default">True</property> + <property name="response_id">1</property> + <child> + <widget class="GtkHBox" id="hbox1"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <widget class="GtkImage" id="image1"> + <property name="visible">True</property> + <property name="stock">gtk-cancel</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label2"> + <property name="visible">True</property> + <property name="label" translatable="yes">Hang Up</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </widget> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="pack_type">GTK_PACK_END</property> + <property name="position">3</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </widget> + </child> + </widget> +</glade-interface> diff --git a/trunk/src/empathy-call-window.h b/trunk/src/empathy-call-window.h new file mode 100644 index 000000000..eef1c9a99 --- /dev/null +++ b/trunk/src/empathy-call-window.h @@ -0,0 +1,37 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2007 Elliot Fairweather + * Copyright (C) 2007-2008 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: Elliot Fairweather <elliot.fairweather@collabora.co.uk> + * Xavier Claessens <xclaesse@gmail.com> + */ + +#ifndef __EMPATHY_CALL_WINDOW_H__ +#define __EMPATHY_CALL_WINDOW_H__ + +#include <gtk/gtk.h> + +#include <libempathy/empathy-tp-call.h> + +G_BEGIN_DECLS + +GtkWidget *empathy_call_window_new (EmpathyTpCall *call); + +G_END_DECLS + +#endif /* __EMPATHY_CALL_WINDOW_H__ */ diff --git a/trunk/src/empathy-chat-window.c b/trunk/src/empathy-chat-window.c new file mode 100644 index 000000000..a43e524cd --- /dev/null +++ b/trunk/src/empathy-chat-window.c @@ -0,0 +1,1508 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2003-2007 Imendio AB + * Copyright (C) 2007-2008 Collabora Ltd. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Mikael Hallendal <micke@imendio.com> + * Richard Hult <richard@imendio.com> + * Martyn Russell <martyn@imendio.com> + * Geert-Jan Van den Bogaerde <geertjan@gnome.org> + * Xavier Claessens <xclaesse@gmail.com> + */ + +#include <config.h> + +#include <string.h> + +#include <gtk/gtk.h> +#include <gdk/gdkkeysyms.h> +#include <glade/glade.h> +#include <glib/gi18n.h> + +#include <telepathy-glib/util.h> +#include <libmissioncontrol/mission-control.h> + +#include <libempathy/empathy-contact-factory.h> +#include <libempathy/empathy-contact.h> +#include <libempathy/empathy-message.h> +#include <libempathy/empathy-utils.h> + +#include <libempathy-gtk/empathy-images.h> +#include <libempathy-gtk/empathy-conf.h> +#include <libempathy-gtk/empathy-contact-dialogs.h> +#include <libempathy-gtk/empathy-log-window.h> +#include <libempathy-gtk/empathy-geometry.h> +#include <libempathy-gtk/empathy-ui-utils.h> + +#include "empathy-chat-window.h" +#include "empathy-about-dialog.h" + +#define DEBUG_FLAG EMPATHY_DEBUG_CHAT +#include <libempathy/empathy-debug.h> + +#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyChatWindow) +typedef struct { + EmpathyChat *current_chat; + GList *chats; + GList *chats_new_msg; + GList *chats_composing; + gboolean page_added; + gboolean dnd_same_window; + guint save_geometry_id; + GtkWidget *dialog; + GtkWidget *notebook; + + /* Menu items. */ + GtkWidget *menu_conv_clear; + GtkWidget *menu_conv_insert_smiley; + GtkWidget *menu_conv_contact; + GtkWidget *menu_conv_close; + + GtkWidget *menu_edit_cut; + GtkWidget *menu_edit_copy; + GtkWidget *menu_edit_paste; + + GtkWidget *menu_tabs_next; + GtkWidget *menu_tabs_prev; + GtkWidget *menu_tabs_left; + GtkWidget *menu_tabs_right; + GtkWidget *menu_tabs_detach; + + GtkWidget *menu_help_contents; + GtkWidget *menu_help_about; +} EmpathyChatWindowPriv; + +static GList *chat_windows = NULL; + +static const guint tab_accel_keys[] = { + GDK_1, GDK_2, GDK_3, GDK_4, GDK_5, + GDK_6, GDK_7, GDK_8, GDK_9, GDK_0 +}; + +typedef enum { + DND_DRAG_TYPE_CONTACT_ID, + DND_DRAG_TYPE_TAB +} DndDragType; + +static const GtkTargetEntry drag_types_dest[] = { + { "text/contact-id", 0, DND_DRAG_TYPE_CONTACT_ID }, + { "GTK_NOTEBOOK_TAB", GTK_TARGET_SAME_APP, DND_DRAG_TYPE_TAB }, +}; + +G_DEFINE_TYPE (EmpathyChatWindow, empathy_chat_window, G_TYPE_OBJECT); + +static void +chat_window_accel_cb (GtkAccelGroup *accelgroup, + GObject *object, + guint key, + GdkModifierType mod, + EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv; + gint num = -1; + gint i; + + priv = GET_PRIV (window); + + for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) { + if (tab_accel_keys[i] == key) { + num = i; + break; + } + } + + if (num != -1) { + gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), num); + } +} + +static EmpathyChatWindow * +chat_window_find_chat (EmpathyChat *chat) +{ + EmpathyChatWindowPriv *priv; + GList *l, *ll; + + for (l = chat_windows; l; l = l->next) { + priv = GET_PRIV (l->data); + ll = g_list_find (priv->chats, chat); + if (ll) { + return l->data; + } + } + + return NULL; +} + +static void +chat_window_close_clicked_cb (GtkWidget *button, + EmpathyChat *chat) +{ + EmpathyChatWindow *window; + + window = chat_window_find_chat (chat); + empathy_chat_window_remove_chat (window, chat); +} + +static void +chat_window_close_button_style_set_cb (GtkWidget *button, + GtkStyle *previous_style, + gpointer user_data) +{ + gint h, w; + + gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (button), + GTK_ICON_SIZE_MENU, &w, &h); + + gtk_widget_set_size_request (button, w, h); +} + +static GtkWidget * +chat_window_create_label (EmpathyChatWindow *window, + EmpathyChat *chat) +{ + EmpathyChatWindowPriv *priv; + GtkWidget *hbox; + GtkWidget *name_label; + GtkWidget *status_image; + GtkWidget *close_button; + GtkWidget *close_image; + GtkWidget *event_box; + GtkWidget *event_box_hbox; + PangoAttrList *attr_list; + PangoAttribute *attr; + + priv = GET_PRIV (window); + + /* The spacing between the button and the label. */ + hbox = gtk_hbox_new (FALSE, 0); + + event_box = gtk_event_box_new (); + gtk_event_box_set_visible_window (GTK_EVENT_BOX (event_box), FALSE); + + name_label = gtk_label_new (NULL); + gtk_label_set_ellipsize (GTK_LABEL (name_label), PANGO_ELLIPSIZE_END); + + attr_list = pango_attr_list_new (); + attr = pango_attr_scale_new (1/1.2); + attr->start_index = 0; + attr->end_index = -1; + pango_attr_list_insert (attr_list, attr); + gtk_label_set_attributes (GTK_LABEL (name_label), attr_list); + pango_attr_list_unref (attr_list); + + gtk_misc_set_padding (GTK_MISC (name_label), 2, 0); + gtk_misc_set_alignment (GTK_MISC (name_label), 0.0, 0.5); + g_object_set_data (G_OBJECT (chat), "chat-window-tab-label", name_label); + + status_image = gtk_image_new (); + + /* Spacing between the icon and label. */ + event_box_hbox = gtk_hbox_new (FALSE, 0); + + gtk_box_pack_start (GTK_BOX (event_box_hbox), status_image, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (event_box_hbox), name_label, TRUE, TRUE, 0); + + g_object_set_data (G_OBJECT (chat), "chat-window-tab-image", status_image); + g_object_set_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget", event_box); + + close_button = gtk_button_new (); + gtk_button_set_relief (GTK_BUTTON (close_button), GTK_RELIEF_NONE); + + /* We don't want focus/keynav for the button to avoid clutter, and + * Ctrl-W works anyway. + */ + GTK_WIDGET_UNSET_FLAGS (close_button, GTK_CAN_FOCUS); + GTK_WIDGET_UNSET_FLAGS (close_button, GTK_CAN_DEFAULT); + + /* Set the name to make the special rc style match. */ + gtk_widget_set_name (close_button, "empathy-close-button"); + + close_image = gtk_image_new_from_stock (GTK_STOCK_CLOSE, GTK_ICON_SIZE_MENU); + + gtk_container_add (GTK_CONTAINER (close_button), close_image); + + gtk_container_add (GTK_CONTAINER (event_box), event_box_hbox); + gtk_box_pack_start (GTK_BOX (hbox), event_box, TRUE, TRUE, 0); + gtk_box_pack_end (GTK_BOX (hbox), close_button, FALSE, FALSE, 0); + + /* React to theme changes and also used to setup the initial size + * correctly. + */ + g_signal_connect (close_button, + "style-set", + G_CALLBACK (chat_window_close_button_style_set_cb), + chat); + + g_signal_connect (close_button, + "clicked", + G_CALLBACK (chat_window_close_clicked_cb), + chat); + + gtk_widget_show_all (hbox); + + return hbox; +} + +static const gchar * +chat_window_get_chat_name (EmpathyChat *chat) +{ + EmpathyContact *remote_contact = NULL; + const gchar *name = NULL; + + name = empathy_chat_get_name (chat); + if (!name) { + remote_contact = empathy_chat_get_remote_contact (chat); + if (remote_contact) { + name = empathy_contact_get_name (remote_contact); + } + } + + return name ? name : _("Conversation"); +} + +static void +chat_window_update (EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv = GET_PRIV (window); + gboolean first_page; + gboolean last_page; + gboolean is_connected; + gint num_pages; + gint page_num; + const gchar *name; + guint n_chats; + + /* Get information */ + page_num = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook)); + num_pages = gtk_notebook_get_n_pages (GTK_NOTEBOOK (priv->notebook)); + first_page = (page_num == 0); + last_page = (page_num == (num_pages - 1)); + is_connected = empathy_chat_get_tp_chat (priv->current_chat) != NULL; + name = chat_window_get_chat_name (priv->current_chat); + n_chats = g_list_length (priv->chats); + + DEBUG ("Update window"); + + /* Update menu */ + gtk_widget_set_sensitive (priv->menu_tabs_next, !last_page); + gtk_widget_set_sensitive (priv->menu_tabs_prev, !first_page); + gtk_widget_set_sensitive (priv->menu_tabs_detach, num_pages > 1); + gtk_widget_set_sensitive (priv->menu_tabs_left, !first_page); + gtk_widget_set_sensitive (priv->menu_tabs_right, !last_page); + gtk_widget_set_sensitive (priv->menu_conv_insert_smiley, is_connected); + + /* Update window title */ + if (n_chats == 1) { + gtk_window_set_title (GTK_WINDOW (priv->dialog), name); + } else { + gchar *title; + + title = g_strdup_printf (_("Conversations (%d)"), n_chats); + gtk_window_set_title (GTK_WINDOW (priv->dialog), title); + g_free (title); + } + + /* Update window icon */ + if (priv->chats_new_msg) { + gtk_window_set_icon_name (GTK_WINDOW (priv->dialog), + EMPATHY_IMAGE_MESSAGE); + } else { + gtk_window_set_icon_name (GTK_WINDOW (priv->dialog), NULL); + } +} + +static void +chat_window_update_chat_tab (EmpathyChat *chat) +{ + EmpathyChatWindow *window; + EmpathyChatWindowPriv *priv; + EmpathyContact *remote_contact; + const gchar *name; + const gchar *subject; + GtkWidget *widget; + GString *tooltip; + gchar *str; + const gchar *icon_name; + + window = chat_window_find_chat (chat); + if (!window) { + return; + } + priv = GET_PRIV (window); + + /* Get information */ + name = chat_window_get_chat_name (chat); + subject = empathy_chat_get_subject (chat); + remote_contact = empathy_chat_get_remote_contact (chat); + + DEBUG ("Updating chat tab, name=%s, subject=%s, remote_contact=%p", + name, subject, remote_contact); + + /* Update tab image */ + if (g_list_find (priv->chats_new_msg, chat)) { + icon_name = EMPATHY_IMAGE_MESSAGE; + } + else if (g_list_find (priv->chats_composing, chat)) { + icon_name = EMPATHY_IMAGE_TYPING; + } + else if (remote_contact) { + icon_name = empathy_icon_name_for_contact (remote_contact); + } else { + icon_name = EMPATHY_IMAGE_GROUP_MESSAGE; + } + widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-image"); + gtk_image_set_from_icon_name (GTK_IMAGE (widget), icon_name, GTK_ICON_SIZE_MENU); + + /* Update tab tooltip */ + tooltip = g_string_new (NULL); + if (remote_contact) { + g_string_append_printf (tooltip, "%s\n%s", + empathy_contact_get_id (remote_contact), + empathy_contact_get_status (remote_contact)); + } + else { + g_string_append (tooltip, name); + } + if (subject) { + g_string_append_printf (tooltip, "\n%s %s", _("Topic:"), subject); + } + if (g_list_find (priv->chats_composing, chat)) { + g_string_append_printf (tooltip, "\n%s", _("Typing a message.")); + } + str = g_string_free (tooltip, FALSE); + widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-tooltip-widget"); + gtk_widget_set_tooltip_text (widget, str); + g_free (str); + + /* Update tab label */ + widget = g_object_get_data (G_OBJECT (chat), "chat-window-tab-label"); + gtk_label_set_text (GTK_LABEL (widget), name); + + /* Update the window if it's the current chat */ + if (priv->current_chat == chat) { + chat_window_update (window); + } +} + +static void +chat_window_chat_notify_cb (EmpathyChat *chat) +{ + EmpathyContact *old_remote_contact; + EmpathyContact *remote_contact = NULL; + + old_remote_contact = g_object_get_data (G_OBJECT (chat), "chat-window-remote-contact"); + remote_contact = empathy_chat_get_remote_contact (chat); + + if (old_remote_contact != remote_contact) { + /* The remote-contact associated with the chat changed, we need + * to keep track of any change of that contact and update the + * window each time. */ + if (remote_contact) { + g_signal_connect_swapped (remote_contact, "notify", + G_CALLBACK (chat_window_update_chat_tab), + chat); + } + if (old_remote_contact) { + g_signal_handlers_disconnect_by_func (old_remote_contact, + chat_window_update_chat_tab, + chat); + } + + g_object_set_data (G_OBJECT (chat), "chat-window-remote-contact", + remote_contact); + } + + chat_window_update_chat_tab (chat); +} + +static void +chat_window_insert_smiley_activate_cb (GtkWidget *menuitem, + EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv; + EmpathyChat *chat; + GtkTextBuffer *buffer; + GtkTextIter iter; + const gchar *smiley; + + priv = GET_PRIV (window); + + chat = priv->current_chat; + + smiley = g_object_get_data (G_OBJECT (menuitem), "smiley_text"); + + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (chat->input_text_view)); + gtk_text_buffer_get_end_iter (buffer, &iter); + gtk_text_buffer_insert (buffer, &iter, + smiley, -1); +} + +static void +chat_window_conv_activate_cb (GtkWidget *menuitem, + EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv = GET_PRIV (window); + GtkWidget *submenu = NULL; + + submenu = empathy_chat_get_contact_menu (priv->current_chat); + if (submenu) { + gtk_menu_item_set_submenu (GTK_MENU_ITEM (priv->menu_conv_contact), + submenu); + gtk_widget_show (priv->menu_conv_contact); + gtk_widget_show (submenu); + } else { + gtk_widget_hide (priv->menu_conv_contact); + } +} + +static void +chat_window_clear_activate_cb (GtkWidget *menuitem, + EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv = GET_PRIV (window); + + empathy_chat_clear (priv->current_chat); +} + +static const gchar * +chat_get_window_id_for_geometry (EmpathyChat *chat) +{ + const gchar *res = NULL; + gboolean separate_windows; + + empathy_conf_get_bool (empathy_conf_get (), + EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS, + &separate_windows); + + if (separate_windows) { + res = empathy_chat_get_id (chat); + } + + return res ? res : "chat-window"; +} + +static gboolean +chat_window_save_geometry_timeout_cb (EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv; + gint x, y, w, h; + + priv = GET_PRIV (window); + + gtk_window_get_size (GTK_WINDOW (priv->dialog), &w, &h); + gtk_window_get_position (GTK_WINDOW (priv->dialog), &x, &y); + + empathy_geometry_save (chat_get_window_id_for_geometry (priv->current_chat), + x, y, w, h); + + priv->save_geometry_id = 0; + + return FALSE; +} + +static gboolean +chat_window_configure_event_cb (GtkWidget *widget, + GdkEventConfigure *event, + EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv; + + priv = GET_PRIV (window); + + if (priv->save_geometry_id != 0) { + g_source_remove (priv->save_geometry_id); + } + + priv->save_geometry_id = + g_timeout_add_seconds (1, + (GSourceFunc) chat_window_save_geometry_timeout_cb, + window); + + return FALSE; +} + +static void +chat_window_close_activate_cb (GtkWidget *menuitem, + EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv; + + priv = GET_PRIV (window); + + g_return_if_fail (priv->current_chat != NULL); + + empathy_chat_window_remove_chat (window, priv->current_chat); +} + +static void +chat_window_edit_activate_cb (GtkWidget *menuitem, + EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv; + GtkClipboard *clipboard; + GtkTextBuffer *buffer; + gboolean text_available; + + priv = GET_PRIV (window); + + g_return_if_fail (priv->current_chat != NULL); + + if (!empathy_chat_get_tp_chat (priv->current_chat)) { + gtk_widget_set_sensitive (priv->menu_edit_copy, FALSE); + gtk_widget_set_sensitive (priv->menu_edit_cut, FALSE); + gtk_widget_set_sensitive (priv->menu_edit_paste, FALSE); + return; + } + + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->current_chat->input_text_view)); + if (gtk_text_buffer_get_selection_bounds (buffer, NULL, NULL)) { + gtk_widget_set_sensitive (priv->menu_edit_copy, TRUE); + gtk_widget_set_sensitive (priv->menu_edit_cut, TRUE); + } else { + gboolean selection; + + selection = empathy_chat_view_get_selection_bounds (priv->current_chat->view, + NULL, NULL); + + gtk_widget_set_sensitive (priv->menu_edit_cut, FALSE); + gtk_widget_set_sensitive (priv->menu_edit_copy, selection); + } + + clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); + text_available = gtk_clipboard_wait_is_text_available (clipboard); + gtk_widget_set_sensitive (priv->menu_edit_paste, text_available); +} + +static void +chat_window_cut_activate_cb (GtkWidget *menuitem, + EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv; + + g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window)); + + priv = GET_PRIV (window); + + empathy_chat_cut (priv->current_chat); +} + +static void +chat_window_copy_activate_cb (GtkWidget *menuitem, + EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv; + + g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window)); + + priv = GET_PRIV (window); + + empathy_chat_copy (priv->current_chat); +} + +static void +chat_window_paste_activate_cb (GtkWidget *menuitem, + EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv; + + g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (window)); + + priv = GET_PRIV (window); + + empathy_chat_paste (priv->current_chat); +} + +static void +chat_window_tabs_left_activate_cb (GtkWidget *menuitem, + EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv; + EmpathyChat *chat; + gint index; + + priv = GET_PRIV (window); + + chat = priv->current_chat; + index = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook)); + if (index <= 0) { + return; + } + + gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook), + GTK_WIDGET (chat), + index - 1); +} + +static void +chat_window_tabs_right_activate_cb (GtkWidget *menuitem, + EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv; + EmpathyChat *chat; + gint index; + + priv = GET_PRIV (window); + + chat = priv->current_chat; + index = gtk_notebook_get_current_page (GTK_NOTEBOOK (priv->notebook)); + + gtk_notebook_reorder_child (GTK_NOTEBOOK (priv->notebook), + GTK_WIDGET (chat), + index + 1); +} + +static void +chat_window_detach_activate_cb (GtkWidget *menuitem, + EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv; + EmpathyChatWindow *new_window; + EmpathyChat *chat; + + priv = GET_PRIV (window); + + chat = priv->current_chat; + new_window = empathy_chat_window_new (); + + empathy_chat_window_move_chat (window, new_window, chat); + + priv = GET_PRIV (new_window); + gtk_widget_show (priv->dialog); +} + +static void +chat_window_help_contents_cb (GtkWidget *menuitem, + EmpathyChatWindow *window) +{ + empathy_url_show ("ghelp:empathy?chat"); +} + +static void +chat_window_help_about_cb (GtkWidget *menuitem, + EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv = GET_PRIV (window); + + empathy_about_dialog_new (GTK_WINDOW (priv->dialog)); +} + +static gboolean +chat_window_delete_event_cb (GtkWidget *dialog, + GdkEvent *event, + EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv = GET_PRIV (window); + + DEBUG ("Delete event received"); + + g_object_ref (window); + while (priv->chats) { + empathy_chat_window_remove_chat (window, priv->chats->data); + } + g_object_unref (window); + + return TRUE; +} + +static void +chat_window_composing_cb (EmpathyChat *chat, + gboolean is_composing, + EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv; + + priv = GET_PRIV (window); + + if (is_composing && !g_list_find (priv->chats_composing, chat)) { + priv->chats_composing = g_list_prepend (priv->chats_composing, chat); + } else { + priv->chats_composing = g_list_remove (priv->chats_composing, chat); + } + + chat_window_update_chat_tab (chat); +} + +static void +chat_window_set_urgency_hint (EmpathyChatWindow *window, + gboolean urgent) +{ + EmpathyChatWindowPriv *priv; + + priv = GET_PRIV (window); + + DEBUG ("Turning %s urgency hint", urgent ? "on" : "off"); + gtk_window_set_urgency_hint (GTK_WINDOW (priv->dialog), urgent); +} + +static void +chat_window_new_message_cb (EmpathyChat *chat, + EmpathyMessage *message, + EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv; + gboolean has_focus; + gboolean needs_urgency; + + priv = GET_PRIV (window); + + has_focus = empathy_chat_window_has_focus (window); + + if (has_focus && priv->current_chat == chat) { + return; + } + + if (empathy_chat_get_members_count (chat) > 2) { + needs_urgency = empathy_message_should_highlight (message); + } else { + needs_urgency = TRUE; + } + + if (needs_urgency && !has_focus) { + chat_window_set_urgency_hint (window, TRUE); + } + + if (!g_list_find (priv->chats_new_msg, chat)) { + priv->chats_new_msg = g_list_prepend (priv->chats_new_msg, chat); + chat_window_update_chat_tab (chat); + } +} + +static GtkNotebook * +chat_window_detach_hook (GtkNotebook *source, + GtkWidget *page, + gint x, + gint y, + gpointer user_data) +{ + EmpathyChatWindowPriv *priv; + EmpathyChatWindow *window, *new_window; + EmpathyChat *chat; + + chat = EMPATHY_CHAT (page); + window = chat_window_find_chat (chat); + + new_window = empathy_chat_window_new (); + priv = GET_PRIV (new_window); + + DEBUG ("Detach hook called"); + + empathy_chat_window_move_chat (window, new_window, chat); + + gtk_window_move (GTK_WINDOW (priv->dialog), x, y); + gtk_widget_show (priv->dialog); + + return NULL; +} + +static void +chat_window_page_switched_cb (GtkNotebook *notebook, + GtkNotebookPage *page, + gint page_num, + EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv; + EmpathyChat *chat; + GtkWidget *child; + + DEBUG ("Page switched"); + + priv = GET_PRIV (window); + + child = gtk_notebook_get_nth_page (notebook, page_num); + chat = EMPATHY_CHAT (child); + + if (priv->page_added) { + priv->page_added = FALSE; + empathy_chat_scroll_down (chat); + } + else if (priv->current_chat == chat) { + return; + } + + priv->current_chat = chat; + priv->chats_new_msg = g_list_remove (priv->chats_new_msg, chat); + + chat_window_update_chat_tab (chat); +} + +static void +chat_window_page_added_cb (GtkNotebook *notebook, + GtkWidget *child, + guint page_num, + EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv; + EmpathyChat *chat; + + priv = GET_PRIV (window); + + /* If we just received DND to the same window, we don't want + * to do anything here like removing the tab and then readding + * it, so we return here and in "page-added". + */ + if (priv->dnd_same_window) { + DEBUG ("Page added (back to the same window)"); + priv->dnd_same_window = FALSE; + return; + } + + DEBUG ("Page added"); + + /* Get chat object */ + chat = EMPATHY_CHAT (child); + + /* Connect chat signals for this window */ + g_signal_connect (chat, "composing", + G_CALLBACK (chat_window_composing_cb), + window); + g_signal_connect (chat, "new-message", + G_CALLBACK (chat_window_new_message_cb), + window); + + /* Set flag so we know to perform some special operations on + * switch page due to the new page being added. + */ + priv->page_added = TRUE; + + /* Get list of chats up to date */ + priv->chats = g_list_append (priv->chats, chat); + + chat_window_update_chat_tab (chat); +} + +static void +chat_window_page_removed_cb (GtkNotebook *notebook, + GtkWidget *child, + guint page_num, + EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv; + EmpathyChat *chat; + + priv = GET_PRIV (window); + + /* If we just received DND to the same window, we don't want + * to do anything here like removing the tab and then readding + * it, so we return here and in "page-added". + */ + if (priv->dnd_same_window) { + DEBUG ("Page removed (and will be readded to same window)"); + return; + } + + DEBUG ("Page removed"); + + /* Get chat object */ + chat = EMPATHY_CHAT (child); + + /* Disconnect all signal handlers for this chat and this window */ + g_signal_handlers_disconnect_by_func (chat, + G_CALLBACK (chat_window_composing_cb), + window); + g_signal_handlers_disconnect_by_func (chat, + G_CALLBACK (chat_window_new_message_cb), + window); + + /* Keep list of chats up to date */ + priv->chats = g_list_remove (priv->chats, chat); + priv->chats_new_msg = g_list_remove (priv->chats_new_msg, chat); + priv->chats_composing = g_list_remove (priv->chats_composing, chat); + + if (priv->chats == NULL) { + g_object_unref (window); + } else { + chat_window_update (window); + } +} + +static gboolean +chat_window_focus_in_event_cb (GtkWidget *widget, + GdkEvent *event, + EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv; + + DEBUG ("Focus in event, updating title"); + + priv = GET_PRIV (window); + + priv->chats_new_msg = g_list_remove (priv->chats_new_msg, priv->current_chat); + + chat_window_set_urgency_hint (window, FALSE); + + /* Update the title, since we now mark all unread messages as read. */ + chat_window_update_chat_tab (priv->current_chat); + + return FALSE; +} + +static void +chat_window_drag_data_received (GtkWidget *widget, + GdkDragContext *context, + int x, + int y, + GtkSelectionData *selection, + guint info, + guint time, + EmpathyChatWindow *window) +{ + if (info == DND_DRAG_TYPE_CONTACT_ID) { + EmpathyChat *chat; + EmpathyChatWindow *old_window; + McAccount *account; + const gchar *id; + gchar **strv; + + id = (const gchar*) selection->data; + + DEBUG ("DND contact from roster with id:'%s'", id); + + strv = g_strsplit (id, "/", 2); + account = mc_account_lookup (strv[0]); + chat = empathy_chat_window_find_chat (account, strv[1]); + + if (!chat) { + empathy_chat_with_contact_id (account, strv[2]); + g_object_unref (account); + g_strfreev (strv); + return; + } + g_object_unref (account); + g_strfreev (strv); + + old_window = chat_window_find_chat (chat); + if (old_window) { + if (old_window == window) { + gtk_drag_finish (context, TRUE, FALSE, time); + return; + } + + empathy_chat_window_move_chat (old_window, window, chat); + } else { + empathy_chat_window_add_chat (window, chat); + } + + /* Added to take care of any outstanding chat events */ + empathy_chat_window_present_chat (chat); + + /* We should return TRUE to remove the data when doing + * GDK_ACTION_MOVE, but we don't here otherwise it has + * weird consequences, and we handle that internally + * anyway with add_chat() and remove_chat(). + */ + gtk_drag_finish (context, TRUE, FALSE, time); + } + else if (info == DND_DRAG_TYPE_TAB) { + EmpathyChat **chat; + EmpathyChatWindow *old_window = NULL; + + DEBUG ("DND tab"); + + chat = (void*) selection->data; + old_window = chat_window_find_chat (*chat); + + if (old_window) { + EmpathyChatWindowPriv *priv; + + priv = GET_PRIV (window); + + if (old_window == window) { + DEBUG ("DND tab (within same window)"); + priv->dnd_same_window = TRUE; + gtk_drag_finish (context, TRUE, FALSE, time); + return; + } + + priv->dnd_same_window = FALSE; + } + + /* We should return TRUE to remove the data when doing + * GDK_ACTION_MOVE, but we don't here otherwise it has + * weird consequences, and we handle that internally + * anyway with add_chat() and remove_chat(). + */ + gtk_drag_finish (context, TRUE, FALSE, time); + } else { + DEBUG ("DND from unknown source"); + gtk_drag_finish (context, FALSE, FALSE, time); + } +} + +static void +chat_window_finalize (GObject *object) +{ + EmpathyChatWindow *window; + EmpathyChatWindowPriv *priv; + + window = EMPATHY_CHAT_WINDOW (object); + priv = GET_PRIV (window); + + DEBUG ("Finalized: %p", object); + + if (priv->save_geometry_id != 0) { + g_source_remove (priv->save_geometry_id); + } + + chat_windows = g_list_remove (chat_windows, window); + gtk_widget_destroy (priv->dialog); + + G_OBJECT_CLASS (empathy_chat_window_parent_class)->finalize (object); +} + +static void +empathy_chat_window_class_init (EmpathyChatWindowClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = chat_window_finalize; + + g_type_class_add_private (object_class, sizeof (EmpathyChatWindowPriv)); + + /* Set up a style for the close button with no focus padding. */ + gtk_rc_parse_string ( + "style \"empathy-close-button-style\"\n" + "{\n" + " GtkWidget::focus-padding = 0\n" + " xthickness = 0\n" + " ythickness = 0\n" + "}\n" + "widget \"*.empathy-close-button\" style \"empathy-close-button-style\""); + + gtk_notebook_set_window_creation_hook (chat_window_detach_hook, NULL, NULL); +} + +static void +empathy_chat_window_init (EmpathyChatWindow *window) +{ + GladeXML *glade; + GtkAccelGroup *accel_group; + GClosure *closure; + GtkWidget *menu_conv; + GtkWidget *menu; + gint i; + GtkWidget *chat_vbox; + gchar *filename; + EmpathyChatWindowPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (window, + EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowPriv); + + window->priv = priv; + filename = empathy_file_lookup ("empathy-chat-window.glade", "src"); + glade = empathy_glade_get_file (filename, + "chat_window", + NULL, + "chat_window", &priv->dialog, + "chat_vbox", &chat_vbox, + "menu_conv", &menu_conv, + "menu_conv_clear", &priv->menu_conv_clear, + "menu_conv_insert_smiley", &priv->menu_conv_insert_smiley, + "menu_conv_contact", &priv->menu_conv_contact, + "menu_conv_close", &priv->menu_conv_close, + "menu_edit_cut", &priv->menu_edit_cut, + "menu_edit_copy", &priv->menu_edit_copy, + "menu_edit_paste", &priv->menu_edit_paste, + "menu_tabs_next", &priv->menu_tabs_next, + "menu_tabs_prev", &priv->menu_tabs_prev, + "menu_tabs_left", &priv->menu_tabs_left, + "menu_tabs_right", &priv->menu_tabs_right, + "menu_tabs_detach", &priv->menu_tabs_detach, + "menu_help_contents", &priv->menu_help_contents, + "menu_help_about", &priv->menu_help_about, + NULL); + g_free (filename); + + empathy_glade_connect (glade, + window, + "chat_window", "configure-event", chat_window_configure_event_cb, + "menu_conv", "activate", chat_window_conv_activate_cb, + "menu_conv_clear", "activate", chat_window_clear_activate_cb, + "menu_conv_close", "activate", chat_window_close_activate_cb, + "menu_edit", "activate", chat_window_edit_activate_cb, + "menu_edit_cut", "activate", chat_window_cut_activate_cb, + "menu_edit_copy", "activate", chat_window_copy_activate_cb, + "menu_edit_paste", "activate", chat_window_paste_activate_cb, + "menu_tabs_left", "activate", chat_window_tabs_left_activate_cb, + "menu_tabs_right", "activate", chat_window_tabs_right_activate_cb, + "menu_tabs_detach", "activate", chat_window_detach_activate_cb, + "menu_help_contents", "activate", chat_window_help_contents_cb, + "menu_help_about", "activate", chat_window_help_about_cb, + NULL); + + g_object_unref (glade); + + priv->notebook = gtk_notebook_new (); + gtk_notebook_set_group (GTK_NOTEBOOK (priv->notebook), "EmpathyChatWindow"); + gtk_box_pack_start (GTK_BOX (chat_vbox), priv->notebook, TRUE, TRUE, 0); + gtk_widget_show (priv->notebook); + + /* Set up accels */ + accel_group = gtk_accel_group_new (); + gtk_window_add_accel_group (GTK_WINDOW (priv->dialog), accel_group); + + for (i = 0; i < G_N_ELEMENTS (tab_accel_keys); i++) { + closure = g_cclosure_new (G_CALLBACK (chat_window_accel_cb), + window, + NULL); + gtk_accel_group_connect (accel_group, + tab_accel_keys[i], + GDK_MOD1_MASK, + 0, + closure); + } + + g_object_unref (accel_group); + + /* Set up smiley menu */ + menu = empathy_chat_view_get_smiley_menu ( + G_CALLBACK (chat_window_insert_smiley_activate_cb), + window); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (priv->menu_conv_insert_smiley), menu); + + /* Set up signals we can't do with glade since we may need to + * block/unblock them at some later stage. + */ + + g_signal_connect (priv->dialog, + "delete_event", + G_CALLBACK (chat_window_delete_event_cb), + window); + + g_signal_connect_swapped (priv->menu_tabs_prev, + "activate", + G_CALLBACK (gtk_notebook_prev_page), + priv->notebook); + g_signal_connect_swapped (priv->menu_tabs_next, + "activate", + G_CALLBACK (gtk_notebook_next_page), + priv->notebook); + + g_signal_connect (priv->dialog, + "focus_in_event", + G_CALLBACK (chat_window_focus_in_event_cb), + window); + g_signal_connect_after (priv->notebook, + "switch_page", + G_CALLBACK (chat_window_page_switched_cb), + window); + g_signal_connect (priv->notebook, + "page_added", + G_CALLBACK (chat_window_page_added_cb), + window); + g_signal_connect (priv->notebook, + "page_removed", + G_CALLBACK (chat_window_page_removed_cb), + window); + + /* Set up drag and drop */ + gtk_drag_dest_set (GTK_WIDGET (priv->notebook), + GTK_DEST_DEFAULT_ALL, + drag_types_dest, + G_N_ELEMENTS (drag_types_dest), + GDK_ACTION_MOVE); + + g_signal_connect (priv->notebook, + "drag-data-received", + G_CALLBACK (chat_window_drag_data_received), + window); + + chat_windows = g_list_prepend (chat_windows, window); + + /* Set up private details */ + priv->chats = NULL; + priv->chats_new_msg = NULL; + priv->chats_composing = NULL; + priv->current_chat = NULL; +} + +EmpathyChatWindow * +empathy_chat_window_new (void) +{ + return EMPATHY_CHAT_WINDOW (g_object_new (EMPATHY_TYPE_CHAT_WINDOW, NULL)); +} + +/* Returns the window to open a new tab in if there is only one window + * visble, otherwise, returns NULL indicating that a new window should + * be added. + */ +EmpathyChatWindow * +empathy_chat_window_get_default (void) +{ + GList *l; + gboolean separate_windows = TRUE; + + empathy_conf_get_bool (empathy_conf_get (), + EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS, + &separate_windows); + + if (separate_windows) { + /* Always create a new window */ + return NULL; + } + + for (l = chat_windows; l; l = l->next) { + EmpathyChatWindow *chat_window; + GtkWidget *dialog; + + chat_window = l->data; + + dialog = empathy_chat_window_get_dialog (chat_window); + if (empathy_window_get_is_visible (GTK_WINDOW (GTK_WINDOW (dialog)))) { + /* Found a visible window on this desktop */ + return chat_window; + } + } + + return NULL; +} + +GtkWidget * +empathy_chat_window_get_dialog (EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv; + + g_return_val_if_fail (window != NULL, NULL); + + priv = GET_PRIV (window); + + return priv->dialog; +} + +void +empathy_chat_window_add_chat (EmpathyChatWindow *window, + EmpathyChat *chat) +{ + EmpathyChatWindowPriv *priv; + GtkWidget *label; + GtkWidget *child; + gint x, y, w, h; + + g_return_if_fail (window != NULL); + g_return_if_fail (EMPATHY_IS_CHAT (chat)); + + priv = GET_PRIV (window); + + /* Reference the chat object */ + g_object_ref (chat); + + empathy_geometry_load (chat_get_window_id_for_geometry (chat), &x, &y, &w, &h); + + if (x >= 0 && y >= 0) { + /* Let the window manager position it if we don't have + * good x, y coordinates. + */ + gtk_window_move (GTK_WINDOW (priv->dialog), x, y); + } + + if (w > 0 && h > 0) { + /* Use the defaults from the glade file if we don't have + * good w, h geometry. + */ + gtk_window_resize (GTK_WINDOW (priv->dialog), w, h); + } + + child = GTK_WIDGET (chat); + label = chat_window_create_label (window, chat); + gtk_widget_show (child); + + g_signal_connect (chat, "notify::name", + G_CALLBACK (chat_window_chat_notify_cb), + NULL); + g_signal_connect (chat, "notify::subject", + G_CALLBACK (chat_window_chat_notify_cb), + NULL); + g_signal_connect (chat, "notify::remote-contact", + G_CALLBACK (chat_window_chat_notify_cb), + NULL); + chat_window_chat_notify_cb (chat); + + gtk_notebook_append_page (GTK_NOTEBOOK (priv->notebook), child, label); + gtk_notebook_set_tab_reorderable (GTK_NOTEBOOK (priv->notebook), child, TRUE); + gtk_notebook_set_tab_detachable (GTK_NOTEBOOK (priv->notebook), child, TRUE); + gtk_notebook_set_tab_label_packing (GTK_NOTEBOOK (priv->notebook), child, + TRUE, TRUE, GTK_PACK_START); + + DEBUG ("Chat added (%d references)", G_OBJECT (chat)->ref_count); +} + +void +empathy_chat_window_remove_chat (EmpathyChatWindow *window, + EmpathyChat *chat) +{ + EmpathyChatWindowPriv *priv; + gint position; + EmpathyContact *remote_contact; + + g_return_if_fail (window != NULL); + g_return_if_fail (EMPATHY_IS_CHAT (chat)); + + priv = GET_PRIV (window); + + g_signal_handlers_disconnect_by_func (chat, + chat_window_chat_notify_cb, + NULL); + remote_contact = g_object_get_data (G_OBJECT (chat), + "chat-window-remote-contact"); + if (remote_contact) { + g_signal_handlers_disconnect_by_func (remote_contact, + chat_window_update_chat_tab, + chat); + } + + position = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook), + GTK_WIDGET (chat)); + gtk_notebook_remove_page (GTK_NOTEBOOK (priv->notebook), position); + + DEBUG ("Chat removed (%d references)", G_OBJECT (chat)->ref_count - 1); + + g_object_unref (chat); +} + +void +empathy_chat_window_move_chat (EmpathyChatWindow *old_window, + EmpathyChatWindow *new_window, + EmpathyChat *chat) +{ + GtkWidget *widget; + + g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (old_window)); + g_return_if_fail (EMPATHY_IS_CHAT_WINDOW (new_window)); + g_return_if_fail (EMPATHY_IS_CHAT (chat)); + + widget = GTK_WIDGET (chat); + + DEBUG ("Chat moving with widget:%p (%d references)", widget, + G_OBJECT (widget)->ref_count); + + /* We reference here to make sure we don't loose the widget + * and the EmpathyChat object during the move. + */ + g_object_ref (chat); + g_object_ref (widget); + + empathy_chat_window_remove_chat (old_window, chat); + empathy_chat_window_add_chat (new_window, chat); + + g_object_unref (widget); + g_object_unref (chat); +} + +void +empathy_chat_window_switch_to_chat (EmpathyChatWindow *window, + EmpathyChat *chat) +{ + EmpathyChatWindowPriv *priv; + gint page_num; + + g_return_if_fail (window != NULL); + g_return_if_fail (EMPATHY_IS_CHAT (chat)); + + priv = GET_PRIV (window); + + page_num = gtk_notebook_page_num (GTK_NOTEBOOK (priv->notebook), + GTK_WIDGET (chat)); + gtk_notebook_set_current_page (GTK_NOTEBOOK (priv->notebook), + page_num); +} + +gboolean +empathy_chat_window_has_focus (EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv; + gboolean has_focus; + + g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE); + + priv = GET_PRIV (window); + + g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL); + + return has_focus; +} + +EmpathyChat * +empathy_chat_window_find_chat (McAccount *account, + const gchar *id) +{ + GList *l; + + g_return_val_if_fail (MC_IS_ACCOUNT (account), NULL); + g_return_val_if_fail (!G_STR_EMPTY (id), NULL); + + for (l = chat_windows; l; l = l->next) { + EmpathyChatWindowPriv *priv; + EmpathyChatWindow *window; + GList *ll; + + window = l->data; + priv = GET_PRIV (window); + + for (ll = priv->chats; ll; ll = ll->next) { + EmpathyChat *chat; + + chat = ll->data; + + if (empathy_account_equal (account, empathy_chat_get_account (chat)) && + !tp_strdiff (id, empathy_chat_get_id (chat))) { + return chat; + } + } + } + + return NULL; +} + +void +empathy_chat_window_present_chat (EmpathyChat *chat) +{ + EmpathyChatWindow *window; + EmpathyChatWindowPriv *priv; + + g_return_if_fail (EMPATHY_IS_CHAT (chat)); + + window = chat_window_find_chat (chat); + + /* If the chat has no window, create one */ + if (window == NULL) { + window = empathy_chat_window_get_default (); + if (!window) { + window = empathy_chat_window_new (); + } + + empathy_chat_window_add_chat (window, chat); + } + + priv = GET_PRIV (window); + empathy_chat_window_switch_to_chat (window, chat); + empathy_window_present (GTK_WINDOW (priv->dialog), TRUE); + + gtk_widget_grab_focus (chat->input_text_view); +} + +#if 0 +static gboolean +chat_window_should_play_sound (EmpathyChatWindow *window) +{ + EmpathyChatWindowPriv *priv = GET_PRIV (window); + gboolean has_focus = FALSE; + + g_return_val_if_fail (EMPATHY_IS_CHAT_WINDOW (window), FALSE); + + g_object_get (priv->dialog, "has-toplevel-focus", &has_focus, NULL); + + return !has_focus; +} +#endif diff --git a/trunk/src/empathy-chat-window.glade b/trunk/src/empathy-chat-window.glade new file mode 100644 index 000000000..e54072473 --- /dev/null +++ b/trunk/src/empathy-chat-window.glade @@ -0,0 +1,344 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd"> +<!--*- mode: xml -*--> +<glade-interface> + <widget class="GtkWindow" id="chat_window"> + <property name="title" translatable="yes">Chat</property> + <property name="default_width">350</property> + <property name="default_height">250</property> + <child> + <widget class="GtkVBox" id="chat_vbox"> + <property name="visible">True</property> + <child> + <widget class="GtkMenuBar" id="chats_menubar"> + <property name="visible">True</property> + <child> + <widget class="GtkMenuItem" id="menu_conv"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Conversation</property> + <property name="use_underline">True</property> + <child> + <widget class="GtkMenu" id="menu_conv_menu"> + <child> + <widget class="GtkImageMenuItem" id="menu_conv_clear"> + <property name="visible">True</property> + <property name="label" translatable="yes">C_lear</property> + <property name="use_underline">True</property> + <accelerator key="L" modifiers="GDK_CONTROL_MASK" signal="activate"/> + <child internal-child="image"> + <widget class="GtkImage" id="image262"> + <property name="visible">True</property> + <property name="stock">gtk-clear</property> + <property name="icon_size">1</property> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkMenuItem" id="menu_conv_insert_smiley"> + <property name="visible">True</property> + <property name="label" translatable="yes">Insert _Smiley</property> + <property name="use_underline">True</property> + </widget> + </child> + <child> + <widget class="GtkMenuItem" id="menu_conv_contact"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Contact</property> + <property name="use_underline">True</property> + </widget> + </child> + <child> + <widget class="GtkSeparatorMenuItem" id="separator7"> + <property name="visible">True</property> + </widget> + </child> + <child> + <widget class="GtkImageMenuItem" id="menu_conv_close"> + <property name="visible">True</property> + <property name="label">gtk-close</property> + <property name="use_underline">True</property> + <property name="use_stock">True</property> + <accelerator key="W" modifiers="GDK_CONTROL_MASK" signal="activate"/> + </widget> + </child> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkMenuItem" id="menu_edit"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Edit</property> + <property name="use_underline">True</property> + <child> + <widget class="GtkMenu" id="menu_edit_menu"> + <child> + <widget class="GtkImageMenuItem" id="menu_edit_cut"> + <property name="visible">True</property> + <property name="label">gtk-cut</property> + <property name="use_underline">True</property> + <property name="use_stock">True</property> + <accelerator key="X" modifiers="GDK_CONTROL_MASK" signal="activate"/> + </widget> + </child> + <child> + <widget class="GtkImageMenuItem" id="menu_edit_copy"> + <property name="visible">True</property> + <property name="label">gtk-copy</property> + <property name="use_underline">True</property> + <property name="use_stock">True</property> + <accelerator key="C" modifiers="GDK_CONTROL_MASK" signal="activate"/> + </widget> + </child> + <child> + <widget class="GtkImageMenuItem" id="menu_edit_paste"> + <property name="visible">True</property> + <property name="label">gtk-paste</property> + <property name="use_underline">True</property> + <property name="use_stock">True</property> + <accelerator key="V" modifiers="GDK_CONTROL_MASK" signal="activate"/> + </widget> + </child> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkMenuItem" id="menu_tabs"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Tabs</property> + <property name="use_underline">True</property> + <child> + <widget class="GtkMenu" id="menu_tabs_menu"> + <child> + <widget class="GtkMenuItem" id="menu_tabs_prev"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Previous Tab</property> + <property name="use_underline">True</property> + <accelerator key="Page_Up" modifiers="GDK_CONTROL_MASK" signal="activate"/> + </widget> + </child> + <child> + <widget class="GtkMenuItem" id="menu_tabs_next"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Next Tab</property> + <property name="use_underline">True</property> + <accelerator key="Page_Down" modifiers="GDK_CONTROL_MASK" signal="activate"/> + </widget> + </child> + <child> + <widget class="GtkSeparatorMenuItem" id="separator4"> + <property name="visible">True</property> + </widget> + </child> + <child> + <widget class="GtkMenuItem" id="menu_tabs_left"> + <property name="visible">True</property> + <property name="label" translatable="yes">Move Tab _Left</property> + <property name="use_underline">True</property> + </widget> + </child> + <child> + <widget class="GtkMenuItem" id="menu_tabs_right"> + <property name="visible">True</property> + <property name="label" translatable="yes">Move Tab _Right</property> + <property name="use_underline">True</property> + </widget> + </child> + <child> + <widget class="GtkMenuItem" id="menu_tabs_detach"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Detach Tab</property> + <property name="use_underline">True</property> + </widget> + </child> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkMenuItem" id="menu_help"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Help</property> + <property name="use_underline">True</property> + <child> + <widget class="GtkMenu" id="menu_help_menu"> + <child> + <widget class="GtkImageMenuItem" id="menu_help_contents"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Contents</property> + <property name="use_underline">True</property> + <accelerator key="F1" modifiers="" signal="activate"/> + <child internal-child="image"> + <widget class="GtkImage" id="image289"> + <property name="visible">True</property> + <property name="stock">gtk-help</property> + <property name="icon_size">1</property> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkImageMenuItem" id="menu_help_about"> + <property name="visible">True</property> + <property name="label">gtk-about</property> + <property name="use_underline">True</property> + <property name="use_stock">True</property> + </widget> + </child> + </widget> + </child> + </widget> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <placeholder/> + </child> + </widget> + </child> + </widget> + <widget class="GtkDialog" id="chat_invite_dialog"> + <property name="border_width">5</property> + <property name="title" translatable="yes">Invite</property> + <property name="modal">True</property> + <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property> + <property name="default_width">275</property> + <property name="default_height">225</property> + <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property> + <property name="has_separator">False</property> + <child internal-child="vbox"> + <widget class="GtkVBox" id="vbox6"> + <property name="visible">True</property> + <child> + <widget class="GtkVBox" id="vbox7"> + <property name="visible">True</property> + <property name="border_width">5</property> + <property name="spacing">18</property> + <child> + <widget class="GtkVBox" id="vbox7"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <widget class="GtkLabel" id="label"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Select who would you like to invite:</property> + <property name="use_markup">True</property> + <property name="wrap">True</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <widget class="GtkScrolledWindow" id="scrolledwindow1"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="shadow_type">GTK_SHADOW_IN</property> + <child> + <widget class="GtkTreeView" id="treeview"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="headers_visible">False</property> + </widget> + </child> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + </widget> + </child> + <child> + <widget class="GtkVBox" id="vbox8"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <widget class="GtkLabel" id="label4"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Invitation _message:</property> + <property name="use_markup">True</property> + <property name="use_underline">True</property> + <property name="wrap">True</property> + <property name="mnemonic_widget">entry</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <widget class="GtkEntry" id="entry"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="activates_default">True</property> + <property name="width_chars">40</property> + <property name="text" translatable="yes">You have been invited to join a chat conference.</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + <child internal-child="action_area"> + <widget class="GtkHButtonBox" id="hbuttonbox1"> + <property name="visible">True</property> + <property name="layout_style">GTK_BUTTONBOX_END</property> + <child> + <widget class="GtkButton" id="button_cancel"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="label">gtk-cancel</property> + <property name="use_stock">True</property> + <property name="response_id">-6</property> + </widget> + </child> + <child> + <widget class="GtkButton" id="button_invite"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="has_default">True</property> + <property name="label">In_vite</property> + <property name="use_underline">True</property> + <property name="response_id">-5</property> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="pack_type">GTK_PACK_END</property> + </packing> + </child> + </widget> + </child> + </widget> +</glade-interface> diff --git a/trunk/src/empathy-chat-window.h b/trunk/src/empathy-chat-window.h new file mode 100644 index 000000000..8b7fe06a9 --- /dev/null +++ b/trunk/src/empathy-chat-window.h @@ -0,0 +1,79 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2003-2007 Imendio AB + * Copyright (C) 2007-2008 Collabora Ltd. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Mikael Hallendal <micke@imendio.com> + * Richard Hult <richard@imendio.com> + * Martyn Russell <martyn@imendio.com> + * Geert-Jan Van den Bogaerde <geertjan@gnome.org> + * Xavier Claessens <xclaesse@gmail.com> + */ + +#ifndef __EMPATHY_CHAT_WINDOW_H__ +#define __EMPATHY_CHAT_WINDOW_H__ + +#include <glib-object.h> +#include <gtk/gtkwidget.h> + +#include <libmissioncontrol/mc-account.h> +#include <libempathy-gtk/empathy-chat.h> + +G_BEGIN_DECLS + +#define EMPATHY_TYPE_CHAT_WINDOW (empathy_chat_window_get_type ()) +#define EMPATHY_CHAT_WINDOW(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindow)) +#define EMPATHY_CHAT_WINDOW_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowClass)) +#define EMPATHY_IS_CHAT_WINDOW(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EMPATHY_TYPE_CHAT_WINDOW)) +#define EMPATHY_IS_CHAT_WINDOW_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EMPATHY_TYPE_CHAT_WINDOW)) +#define EMPATHY_CHAT_WINDOW_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EMPATHY_TYPE_CHAT_WINDOW, EmpathyChatWindowClass)) + +typedef struct _EmpathyChatWindow EmpathyChatWindow; +typedef struct _EmpathyChatWindowClass EmpathyChatWindowClass; + +struct _EmpathyChatWindow { + GObject parent; + gpointer priv; +}; + +struct _EmpathyChatWindowClass { + GObjectClass parent_class; +}; + +GType empathy_chat_window_get_type (void); +EmpathyChatWindow *empathy_chat_window_get_default (void); +EmpathyChatWindow *empathy_chat_window_new (void); +GtkWidget * empathy_chat_window_get_dialog (EmpathyChatWindow *window); +void empathy_chat_window_add_chat (EmpathyChatWindow *window, + EmpathyChat *chat); +void empathy_chat_window_remove_chat (EmpathyChatWindow *window, + EmpathyChat *chat); +void empathy_chat_window_move_chat (EmpathyChatWindow *old_window, + EmpathyChatWindow *new_window, + EmpathyChat *chat); +void empathy_chat_window_switch_to_chat (EmpathyChatWindow *window, + EmpathyChat *chat); +gboolean empathy_chat_window_has_focus (EmpathyChatWindow *window); +EmpathyChat * empathy_chat_window_find_chat (McAccount *account, + const gchar *id); +void empathy_chat_window_present_chat (EmpathyChat *chat); + + +G_END_DECLS + +#endif /* __EMPATHY_CHAT_WINDOW_H__ */ diff --git a/trunk/src/empathy-chatrooms-window.c b/trunk/src/empathy-chatrooms-window.c new file mode 100644 index 000000000..52c6635bf --- /dev/null +++ b/trunk/src/empathy-chatrooms-window.c @@ -0,0 +1,586 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2004-2007 Imendio AB + * Copyright (C) 2007-2008 Collabora Ltd. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Xavier Claessens <xclaesse@gmail.com> + * Martyn Russell <martyn@imendio.com> + * Mikael Hallendal <micke@imendio.com> + */ + +#include <config.h> + +#include <string.h> +#include <stdio.h> + +#include <gtk/gtk.h> +#include <glade/glade.h> +#include <glib.h> +#include <glib/gi18n.h> + +#include <libempathy/empathy-chatroom-manager.h> +#include <libempathy/empathy-utils.h> + +#include <libempathy-gtk/empathy-account-chooser.h> +#include <libempathy-gtk/empathy-ui-utils.h> + +#include "empathy-chatrooms-window.h" +#include "empathy-new-chatroom-dialog.h" + +typedef struct { + EmpathyChatroomManager *manager; + + GtkWidget *window; + GtkWidget *hbox_account; + GtkWidget *label_account; + GtkWidget *account_chooser; + GtkWidget *treeview; + GtkWidget *button_remove; + GtkWidget *button_edit; + GtkWidget *button_close; + + gint room_column; +} EmpathyChatroomsWindow; + +static void chatrooms_window_destroy_cb (GtkWidget *widget, + EmpathyChatroomsWindow *window); +static void chatrooms_window_model_setup (EmpathyChatroomsWindow *window); +static void chatrooms_window_model_add_columns (EmpathyChatroomsWindow *window); +static void chatrooms_window_model_refresh_data (EmpathyChatroomsWindow *window, + gboolean first_time); +static void chatrooms_window_model_add (EmpathyChatroomsWindow *window, + EmpathyChatroom *chatroom, + gboolean set_active); +static void chatrooms_window_model_cell_auto_connect_toggled (GtkCellRendererToggle *cell, + gchar *path_string, + EmpathyChatroomsWindow *window); +static EmpathyChatroom * chatrooms_window_model_get_selected (EmpathyChatroomsWindow *window); +static void chatrooms_window_model_action_selected (EmpathyChatroomsWindow *window); +static void chatrooms_window_row_activated_cb (GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + EmpathyChatroomsWindow *window); +static void chatrooms_window_button_remove_clicked_cb (GtkWidget *widget, + EmpathyChatroomsWindow *window); +static void chatrooms_window_button_edit_clicked_cb (GtkWidget *widget, + EmpathyChatroomsWindow *window); +static void chatrooms_window_button_close_clicked_cb (GtkWidget *widget, + EmpathyChatroomsWindow *window); +static void chatrooms_window_chatroom_added_cb (EmpathyChatroomManager *manager, + EmpathyChatroom *chatroom, + EmpathyChatroomsWindow *window); +static void chatrooms_window_chatroom_removed_cb (EmpathyChatroomManager *manager, + EmpathyChatroom *chatroom, + EmpathyChatroomsWindow *window); +static gboolean chatrooms_window_remove_chatroom_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + EmpathyChatroom *chatroom); +static void chatrooms_window_account_changed_cb (GtkWidget *combo_box, + EmpathyChatroomsWindow *window); + +enum { + COL_IMAGE, + COL_NAME, + COL_ROOM, + COL_AUTO_CONNECT, + COL_POINTER, + COL_COUNT +}; + +void +empathy_chatrooms_window_show (GtkWindow *parent) +{ + static EmpathyChatroomsWindow *window = NULL; + GladeXML *glade; + gchar *filename; + + if (window) { + gtk_window_present (GTK_WINDOW (window->window)); + return; + } + + window = g_new0 (EmpathyChatroomsWindow, 1); + + filename = empathy_file_lookup ("empathy-chatrooms-window.glade", "src"); + glade = empathy_glade_get_file (filename, + "chatrooms_window", + NULL, + "chatrooms_window", &window->window, + "hbox_account", &window->hbox_account, + "label_account", &window->label_account, + "treeview", &window->treeview, + "button_edit", &window->button_edit, + "button_remove", &window->button_remove, + "button_close", &window->button_close, + NULL); + g_free (filename); + + empathy_glade_connect (glade, + window, + "chatrooms_window", "destroy", chatrooms_window_destroy_cb, + "button_remove", "clicked", chatrooms_window_button_remove_clicked_cb, + "button_edit", "clicked", chatrooms_window_button_edit_clicked_cb, + "button_close", "clicked", chatrooms_window_button_close_clicked_cb, + NULL); + + g_object_unref (glade); + + g_object_add_weak_pointer (G_OBJECT (window->window), (gpointer) &window); + + /* Get the session and chat room manager */ + window->manager = empathy_chatroom_manager_new (); + + g_signal_connect (window->manager, "chatroom-added", + G_CALLBACK (chatrooms_window_chatroom_added_cb), + window); + g_signal_connect (window->manager, "chatroom-removed", + G_CALLBACK (chatrooms_window_chatroom_removed_cb), + window); + + /* Account chooser for chat rooms */ + window->account_chooser = empathy_account_chooser_new (); + empathy_account_chooser_set_filter (EMPATHY_ACCOUNT_CHOOSER (window->account_chooser), + empathy_account_chooser_filter_is_connected, + NULL); + g_object_set (window->account_chooser, + "has-all-option", TRUE, + NULL); + empathy_account_chooser_set_account (EMPATHY_ACCOUNT_CHOOSER (window->account_chooser), NULL); + + gtk_box_pack_start (GTK_BOX (window->hbox_account), + window->account_chooser, + TRUE, TRUE, 0); + + g_signal_connect (window->account_chooser, "changed", + G_CALLBACK (chatrooms_window_account_changed_cb), + window); + + gtk_widget_show (window->account_chooser); + + /* Set up chatrooms */ + chatrooms_window_model_setup (window); + + /* Set focus */ + gtk_widget_grab_focus (window->treeview); + + /* Last touches */ + if (parent) { + gtk_window_set_transient_for (GTK_WINDOW (window->window), + GTK_WINDOW (parent)); + } + + gtk_widget_show (window->window); +} + +static void +chatrooms_window_destroy_cb (GtkWidget *widget, + EmpathyChatroomsWindow *window) +{ + g_signal_handlers_disconnect_by_func (window->manager, + chatrooms_window_chatroom_added_cb, + window); + g_signal_handlers_disconnect_by_func (window->manager, + chatrooms_window_chatroom_removed_cb, + window); + g_object_unref (window->manager); + g_free (window); +} + +static void +chatrooms_window_model_setup (EmpathyChatroomsWindow *window) +{ + GtkTreeView *view; + GtkListStore *store; + GtkTreeSelection *selection; + + /* View */ + view = GTK_TREE_VIEW (window->treeview); + + g_signal_connect (view, "row-activated", + G_CALLBACK (chatrooms_window_row_activated_cb), + window); + + /* Store */ + store = gtk_list_store_new (COL_COUNT, + G_TYPE_STRING, /* Image */ + G_TYPE_STRING, /* Name */ + G_TYPE_STRING, /* Room */ + G_TYPE_BOOLEAN, /* Auto start */ + EMPATHY_TYPE_CHATROOM); /* Chatroom */ + + gtk_tree_view_set_model (view, GTK_TREE_MODEL (store)); + + /* Selection */ + selection = gtk_tree_view_get_selection (view); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); + + /* Columns */ + chatrooms_window_model_add_columns (window); + + /* Add data */ + chatrooms_window_model_refresh_data (window, TRUE); + + /* Clean up */ + g_object_unref (store); +} + +static void +chatrooms_window_model_add_columns (EmpathyChatroomsWindow *window) +{ + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeViewColumn *column; + GtkCellRenderer *cell; + gint count; + + view = GTK_TREE_VIEW (window->treeview); + model = gtk_tree_view_get_model (view); + + gtk_tree_view_set_headers_visible (view, TRUE); + gtk_tree_view_set_headers_clickable (view, TRUE); + + /* Name & Status */ + column = gtk_tree_view_column_new (); + count = gtk_tree_view_append_column (view, column); + + gtk_tree_view_column_set_title (column, _("Name")); + gtk_tree_view_column_set_expand (column, TRUE); + gtk_tree_view_column_set_sort_column_id (column, count - 1); + + cell = gtk_cell_renderer_pixbuf_new (); + gtk_tree_view_column_pack_start (column, cell, FALSE); + gtk_tree_view_column_add_attribute (column, cell, "icon-name", COL_IMAGE); + + cell = gtk_cell_renderer_text_new (); + g_object_set (cell, + "xpad", 4, + "ypad", 1, + NULL); + gtk_tree_view_column_pack_start (column, cell, TRUE); + gtk_tree_view_column_add_attribute (column, cell, "text", COL_NAME); + + /* Room */ + cell = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (_("Room"), cell, + "text", COL_ROOM, + NULL); + count = gtk_tree_view_append_column (view, column); + gtk_tree_view_column_set_sort_column_id (column, count - 1); + window->room_column = count - 1; + + /* Chatroom auto connect */ + cell = gtk_cell_renderer_toggle_new (); + column = gtk_tree_view_column_new_with_attributes (_("Auto-Connect"), cell, + "active", COL_AUTO_CONNECT, + NULL); + count = gtk_tree_view_append_column (view, column); + gtk_tree_view_column_set_sort_column_id (column, count - 1); + + g_signal_connect (cell, "toggled", + G_CALLBACK (chatrooms_window_model_cell_auto_connect_toggled), + window); + + /* Sort model */ + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (model), 0, + GTK_SORT_ASCENDING); +} + +static void +chatrooms_window_model_refresh_data (EmpathyChatroomsWindow *window, + gboolean first_time) +{ + GtkTreeView *view; + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkListStore *store; + GtkTreeIter iter; + GtkTreeViewColumn *column; + EmpathyAccountChooser *account_chooser; + McAccount *account; + GList *chatrooms, *l; + + view = GTK_TREE_VIEW (window->treeview); + selection = gtk_tree_view_get_selection (view); + model = gtk_tree_view_get_model (view); + store = GTK_LIST_STORE (model); + + /* Look up chatrooms */ + account_chooser = EMPATHY_ACCOUNT_CHOOSER (window->account_chooser); + account = empathy_account_chooser_get_account (account_chooser); + + chatrooms = empathy_chatroom_manager_get_chatrooms (window->manager, account); + + /* Sort out columns, we only show the server column for + * selected protocol types, such as Jabber. + */ + if (account) { + column = gtk_tree_view_get_column (view, window->room_column); + gtk_tree_view_column_set_visible (column, TRUE); + } else { + column = gtk_tree_view_get_column (view, window->room_column); + gtk_tree_view_column_set_visible (column, FALSE); + } + + /* Clean out the store */ + gtk_list_store_clear (store); + + /* Populate with chatroom list. */ + for (l = chatrooms; l; l = l->next) { + chatrooms_window_model_add (window, l->data, FALSE); + } + + if (gtk_tree_model_get_iter_first (model, &iter)) { + gtk_tree_selection_select_iter (selection, &iter); + } + + if (account) { + g_object_unref (account); + } + + g_list_free (chatrooms); +} + +static void +chatrooms_window_model_add (EmpathyChatroomsWindow *window, + EmpathyChatroom *chatroom, + gboolean set_active) +{ + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkListStore *store; + GtkTreeIter iter; + + view = GTK_TREE_VIEW (window->treeview); + selection = gtk_tree_view_get_selection (view); + model = gtk_tree_view_get_model (view); + store = GTK_LIST_STORE (model); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COL_NAME, empathy_chatroom_get_name (chatroom), + COL_ROOM, empathy_chatroom_get_room (chatroom), + COL_AUTO_CONNECT, empathy_chatroom_get_auto_connect (chatroom), + COL_POINTER, chatroom, + -1); + + if (set_active) { + gtk_tree_selection_select_iter (selection, &iter); + } +} + +static void +chatrooms_window_model_cell_auto_connect_toggled (GtkCellRendererToggle *cell, + gchar *path_string, + EmpathyChatroomsWindow *window) +{ + EmpathyChatroom *chatroom; + gboolean enabled; + GtkTreeView *view; + GtkTreeModel *model; + GtkListStore *store; + GtkTreePath *path; + GtkTreeIter iter; + + view = GTK_TREE_VIEW (window->treeview); + model = gtk_tree_view_get_model (view); + store = GTK_LIST_STORE (model); + + path = gtk_tree_path_new_from_string (path_string); + + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, + COL_AUTO_CONNECT, &enabled, + COL_POINTER, &chatroom, + -1); + + enabled = !enabled; + + empathy_chatroom_set_auto_connect (chatroom, enabled); + empathy_chatroom_manager_store (window->manager); + + gtk_list_store_set (store, &iter, COL_AUTO_CONNECT, enabled, -1); + gtk_tree_path_free (path); + g_object_unref (chatroom); +} + +static EmpathyChatroom * +chatrooms_window_model_get_selected (EmpathyChatroomsWindow *window) +{ + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeIter iter; + EmpathyChatroom *chatroom = NULL; + + view = GTK_TREE_VIEW (window->treeview); + selection = gtk_tree_view_get_selection (view); + + if (gtk_tree_selection_get_selected (selection, &model, &iter)) { + gtk_tree_model_get (model, &iter, COL_POINTER, &chatroom, -1); + } + + return chatroom; +} + +static void +chatrooms_window_model_action_selected (EmpathyChatroomsWindow *window) +{ + EmpathyChatroom *chatroom; + GtkTreeView *view; + GtkTreeModel *model; + + view = GTK_TREE_VIEW (window->treeview); + model = gtk_tree_view_get_model (view); + + chatroom = chatrooms_window_model_get_selected (window); + if (!chatroom) { + return; + } + + //empathy_edit_chatroom_dialog_show (GTK_WINDOW (window->window), chatroom); + + g_object_unref (chatroom); +} + +static void +chatrooms_window_row_activated_cb (GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + EmpathyChatroomsWindow *window) +{ + if (GTK_WIDGET_IS_SENSITIVE (window->button_edit)) { + chatrooms_window_model_action_selected (window); + } +} + +static void +chatrooms_window_button_remove_clicked_cb (GtkWidget *widget, + EmpathyChatroomsWindow *window) +{ + EmpathyChatroom *chatroom; + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeSelection *selection; + GtkTreeIter iter; + + /* Remove from treeview */ + view = GTK_TREE_VIEW (window->treeview); + selection = gtk_tree_view_get_selection (view); + + if (!gtk_tree_selection_get_selected (selection, &model, &iter)) { + return; + } + + gtk_tree_model_get (model, &iter, COL_POINTER, &chatroom, -1); + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); + + /* Remove from config */ + empathy_chatroom_manager_remove (window->manager, chatroom); + + g_object_unref (chatroom); +} + +static void +chatrooms_window_button_edit_clicked_cb (GtkWidget *widget, + EmpathyChatroomsWindow *window) +{ + EmpathyChatroom *chatroom; + + chatroom = chatrooms_window_model_get_selected (window); + if (!chatroom) { + return; + } + + //empathy_edit_chatroom_dialog_show (GTK_WINDOW (window->window), chatroom); + + g_object_unref (chatroom); +} + +static void +chatrooms_window_button_close_clicked_cb (GtkWidget *widget, + EmpathyChatroomsWindow *window) +{ + gtk_widget_destroy (window->window); +} + +static void +chatrooms_window_chatroom_added_cb (EmpathyChatroomManager *manager, + EmpathyChatroom *chatroom, + EmpathyChatroomsWindow *window) +{ + EmpathyAccountChooser *account_chooser; + McAccount *account; + + account_chooser = EMPATHY_ACCOUNT_CHOOSER (window->account_chooser); + account = empathy_account_chooser_get_account (account_chooser); + + if (!account) { + chatrooms_window_model_add (window, chatroom, FALSE); + } else { + if (empathy_account_equal (account, empathy_chatroom_get_account (chatroom))) { + chatrooms_window_model_add (window, chatroom, FALSE); + } + + g_object_unref (account); + } +} + +static void +chatrooms_window_chatroom_removed_cb (EmpathyChatroomManager *manager, + EmpathyChatroom *chatroom, + EmpathyChatroomsWindow *window) +{ + GtkTreeModel *model; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (window->treeview)); + + gtk_tree_model_foreach (model, + (GtkTreeModelForeachFunc) chatrooms_window_remove_chatroom_foreach, + chatroom); +} + +static gboolean +chatrooms_window_remove_chatroom_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + EmpathyChatroom *chatroom) +{ + EmpathyChatroom *this_chatroom; + + gtk_tree_model_get (model, iter, COL_POINTER, &this_chatroom, -1); + + if (empathy_chatroom_equal (chatroom, this_chatroom)) { + gtk_list_store_remove (GTK_LIST_STORE (model), iter); + g_object_unref (this_chatroom); + return TRUE; + } + + g_object_unref (this_chatroom); + + return FALSE; +} + +static void +chatrooms_window_account_changed_cb (GtkWidget *combo_box, + EmpathyChatroomsWindow *window) +{ + chatrooms_window_model_refresh_data (window, FALSE); +} + diff --git a/trunk/src/empathy-chatrooms-window.glade b/trunk/src/empathy-chatrooms-window.glade new file mode 100644 index 000000000..d6b23d76d --- /dev/null +++ b/trunk/src/empathy-chatrooms-window.glade @@ -0,0 +1,306 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd"> +<!--*- mode: xml -*--> +<glade-interface> + <widget class="GtkDialog" id="edit_chatroom_dialog"> + <property name="visible">True</property> + <property name="border_width">5</property> + <property name="title" translatable="yes">Edit Favorite Room</property> + <property name="resizable">False</property> + <property name="icon_name">gtk-edit</property> + <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property> + <property name="has_separator">False</property> + <child internal-child="vbox"> + <widget class="GtkVBox" id="dialog-vbox3"> + <property name="visible">True</property> + <property name="spacing">2</property> + <child> + <widget class="GtkTable" id="table4"> + <property name="visible">True</property> + <property name="border_width">5</property> + <property name="n_rows">5</property> + <property name="n_columns">2</property> + <property name="column_spacing">12</property> + <property name="row_spacing">6</property> + <child> + <placeholder/> + </child> + <child> + <widget class="GtkCheckButton" id="checkbutton_auto_connect"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="tooltip" translatable="yes">Join this chat room when Empathy starts and you are connected</property> + <property name="label" translatable="yes">Join room on start_up</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + <property name="draw_indicator">True</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">4</property> + <property name="bottom_attach">5</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkEntry" id="entry_name"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="width_chars">25</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label_name"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">N_ame:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">entry_name</property> + </widget> + <packing> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label_nickname"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">_Nickname:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">entry_nickname</property> + </widget> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label_server"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">S_erver:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">entry_server</property> + </widget> + <packing> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label_room"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">_Room:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">entry_room</property> + </widget> + <packing> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkEntry" id="entry_nickname"> + <property name="visible">True</property> + <property name="can_focus">True</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkEntry" id="entry_server"> + <property name="visible">True</property> + <property name="can_focus">True</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkEntry" id="entry_room"> + <property name="visible">True</property> + <property name="can_focus">True</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">3</property> + <property name="bottom_attach">4</property> + <property name="y_options"></property> + </packing> + </child> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + <child internal-child="action_area"> + <widget class="GtkHButtonBox" id="dialog-action_area3"> + <property name="visible">True</property> + <property name="layout_style">GTK_BUTTONBOX_END</property> + <child> + <widget class="GtkButton" id="button_cancel"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="label">gtk-cancel</property> + <property name="use_stock">True</property> + <property name="response_id">-6</property> + </widget> + </child> + <child> + <widget class="GtkButton" id="button_save"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="label">gtk-save</property> + <property name="use_stock">True</property> + <property name="response_id">-5</property> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="pack_type">GTK_PACK_END</property> + </packing> + </child> + </widget> + </child> + </widget> + <widget class="GtkWindow" id="chatrooms_window"> + <property name="visible">True</property> + <property name="border_width">12</property> + <property name="title" translatable="yes">Manage Favorite Rooms</property> + <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property> + <child> + <widget class="GtkVBox" id="vbox12"> + <property name="visible">True</property> + <property name="spacing">12</property> + <child> + <widget class="GtkVBox" id="vbox18"> + <property name="visible">True</property> + <property name="spacing">18</property> + <child> + <widget class="GtkHBox" id="hbox_account"> + <property name="visible">True</property> + <property name="spacing">12</property> + <child> + <widget class="GtkLabel" id="label_account"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Account:</property> + </widget> + <packing> + <property name="expand">False</property> + </packing> + </child> + <child> + <placeholder/> + </child> + </widget> + <packing> + <property name="expand">False</property> + </packing> + </child> + <child> + <widget class="GtkScrolledWindow" id="scrolledwindow1"> + <property name="height_request">150</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">GTK_POLICY_NEVER</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="shadow_type">GTK_SHADOW_IN</property> + <child> + <widget class="GtkTreeView" id="treeview"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="has_focus">True</property> + </widget> + </child> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + </widget> + </child> + <child> + <widget class="GtkHButtonBox" id="hbuttonbox3"> + <property name="visible">True</property> + <property name="spacing">6</property> + <property name="layout_style">GTK_BUTTONBOX_END</property> + <child> + <widget class="GtkButton" id="button_remove"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="label">gtk-remove</property> + <property name="use_stock">True</property> + <property name="response_id">0</property> + </widget> + </child> + <child> + <widget class="GtkButton" id="button_edit"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="label">gtk-edit</property> + <property name="use_stock">True</property> + <property name="response_id">0</property> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + <child> + <widget class="GtkButton" id="button_close"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="label">gtk-close</property> + <property name="use_stock">True</property> + <property name="response_id">0</property> + </widget> + <packing> + <property name="position">2</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </widget> + </child> + </widget> +</glade-interface> diff --git a/trunk/src/empathy-chatrooms-window.h b/trunk/src/empathy-chatrooms-window.h new file mode 100644 index 000000000..727188f01 --- /dev/null +++ b/trunk/src/empathy-chatrooms-window.h @@ -0,0 +1,37 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2004-2007 Imendio AB + * Copyright (C) 2007-2008 Collabora Ltd. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Xavier Claessens <xclaesse@gmail.com> + * Martyn Russell <martyn@imendio.com> + * Mikael Hallendal <micke@imendio.com> + */ + +#ifndef __EMPATHY_CHATROOMS_WINDOW_H__ +#define __EMPATHY_CHATROOMS_WINDOW_H__ + +#include <gtk/gtk.h> + +G_BEGIN_DECLS + +void empathy_chatrooms_window_show (GtkWindow *parent); + +G_END_DECLS + +#endif /* __EMPATHY_CHATROOMS_WINDOW_H__ */ diff --git a/trunk/src/empathy-logs.c b/trunk/src/empathy-logs.c new file mode 100644 index 000000000..f49207ebf --- /dev/null +++ b/trunk/src/empathy-logs.c @@ -0,0 +1,66 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2008 Collabora Ltd. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Xavier Claessens <xclaesse@gmail.com> + */ + +#include <config.h> + +#include <string.h> +#include <stdlib.h> + +#include <glib.h> +#include <gtk/gtk.h> + +#include <libempathy/empathy-debug.h> +#include <libempathy-gtk/empathy-log-window.h> + +static void +destroy_cb (GtkWidget *dialog, + gpointer user_data) +{ + gtk_main_quit (); +} + +int +main (int argc, char *argv[]) +{ + GtkWidget *window; + + gtk_init (&argc, &argv); + + if (g_getenv ("EMPATHY_TIMING") != NULL) { + g_log_set_default_handler (tp_debug_timestamped_log_handler, NULL); + } + empathy_debug_set_flags (g_getenv ("EMPATHY_DEBUG")); + tp_debug_divert_messages (g_getenv ("EMPATHY_LOGFILE")); + + gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (), + PKGDATADIR G_DIR_SEPARATOR_S "icons"); + window = empathy_log_window_show (NULL, NULL, FALSE, NULL); + + g_signal_connect (window, "destroy", + G_CALLBACK (destroy_cb), + NULL); + + gtk_main (); + + return EXIT_SUCCESS; +} + diff --git a/trunk/src/empathy-main-window.c b/trunk/src/empathy-main-window.c new file mode 100644 index 000000000..8a8117f6e --- /dev/null +++ b/trunk/src/empathy-main-window.c @@ -0,0 +1,1160 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2002-2007 Imendio AB + * Copyright (C) 2007-2008 Collabora Ltd. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Xavier Claessens <xclaesse@gmail.com> + */ + +#include <config.h> + +#include <sys/stat.h> +#include <gtk/gtk.h> +#include <glade/glade.h> +#include <glib/gi18n.h> + +#include <libempathy/empathy-contact.h> +#include <libempathy/empathy-utils.h> +#include <libempathy/empathy-chatroom-manager.h> +#include <libempathy/empathy-chatroom.h> +#include <libempathy/empathy-contact-list.h> +#include <libempathy/empathy-contact-manager.h> +#include <libempathy/empathy-contact-factory.h> +#include <libempathy/empathy-status-presets.h> + +#include <libempathy-gtk/empathy-contact-dialogs.h> +#include <libempathy-gtk/empathy-contact-list-store.h> +#include <libempathy-gtk/empathy-contact-list-view.h> +#include <libempathy-gtk/empathy-presence-chooser.h> +#include <libempathy-gtk/empathy-ui-utils.h> +#include <libempathy-gtk/empathy-geometry.h> +#include <libempathy-gtk/empathy-conf.h> +#include <libempathy-gtk/empathy-accounts-dialog.h> +#include <libempathy-gtk/empathy-log-window.h> +#include <libempathy-gtk/empathy-new-message-dialog.h> +#include <libempathy-gtk/empathy-gtk-enum-types.h> + +#include "empathy-main-window.h" +#include "ephy-spinner.h" +#include "empathy-preferences.h" +#include "empathy-about-dialog.h" +#include "empathy-new-chatroom-dialog.h" +#include "empathy-chatrooms-window.h" + +#define DEBUG_FLAG EMPATHY_DEBUG_OTHER +#include <libempathy/empathy-debug.h> + +/* Minimum width of roster window if something goes wrong. */ +#define MIN_WIDTH 50 + +/* Accels (menu shortcuts) can be configured and saved */ +#define ACCELS_FILENAME "accels.txt" + +/* Name in the geometry file */ +#define GEOMETRY_NAME "main-window" + +typedef struct { + EmpathyContactListView *list_view; + EmpathyContactListStore *list_store; + MissionControl *mc; + EmpathyChatroomManager *chatroom_manager; + gpointer token; + + GtkWidget *window; + GtkWidget *main_vbox; + GtkWidget *throbber; + GtkWidget *presence_toolbar; + GtkWidget *presence_chooser; + GtkWidget *errors_vbox; + + GtkWidget *room; + GtkWidget *room_menu; + GtkWidget *room_sep; + GtkWidget *room_join_favorites; + GtkWidget *edit_context; + GtkWidget *edit_context_separator; + + guint size_timeout_id; + GHashTable *errors; + + /* Widgets that are enabled when there is... */ + GList *widgets_connected; /* ... connected accounts */ + GList *widgets_disconnected; /* ... disconnected accounts */ +} EmpathyMainWindow; + +static void main_window_destroy_cb (GtkWidget *widget, + EmpathyMainWindow *window); +static void main_window_favorite_chatroom_menu_setup (EmpathyMainWindow *window); +static void main_window_favorite_chatroom_menu_added_cb (EmpathyChatroomManager *manager, + EmpathyChatroom *chatroom, + EmpathyMainWindow *window); +static void main_window_favorite_chatroom_menu_removed_cb (EmpathyChatroomManager *manager, + EmpathyChatroom *chatroom, + EmpathyMainWindow *window); +static void main_window_favorite_chatroom_menu_activate_cb (GtkMenuItem *menu_item, + EmpathyChatroom *chatroom); +static void main_window_favorite_chatroom_menu_update (EmpathyMainWindow *window); +static void main_window_favorite_chatroom_menu_add (EmpathyMainWindow *window, + EmpathyChatroom *chatroom); +static void main_window_favorite_chatroom_join (EmpathyChatroom *chatroom); +static void main_window_chat_quit_cb (GtkWidget *widget, + EmpathyMainWindow *window); +static void main_window_chat_new_message_cb (GtkWidget *widget, + EmpathyMainWindow *window); +static void main_window_chat_history_cb (GtkWidget *widget, + EmpathyMainWindow *window); +static void main_window_room_join_new_cb (GtkWidget *widget, + EmpathyMainWindow *window); +static void main_window_room_join_favorites_cb (GtkWidget *widget, + EmpathyMainWindow *window); +static void main_window_room_manage_favorites_cb (GtkWidget *widget, + EmpathyMainWindow *window); +static void main_window_chat_add_contact_cb (GtkWidget *widget, + EmpathyMainWindow *window); +static void main_window_chat_show_offline_cb (GtkCheckMenuItem *item, + EmpathyMainWindow *window); +static gboolean main_window_edit_button_press_event_cb (GtkWidget *widget, + GdkEventButton *event, + EmpathyMainWindow *window); +static void main_window_edit_accounts_cb (GtkWidget *widget, + EmpathyMainWindow *window); +static void main_window_edit_personal_information_cb (GtkWidget *widget, + EmpathyMainWindow *window); +static void main_window_edit_preferences_cb (GtkWidget *widget, + EmpathyMainWindow *window); +static void main_window_help_about_cb (GtkWidget *widget, + EmpathyMainWindow *window); +static void main_window_help_contents_cb (GtkWidget *widget, + EmpathyMainWindow *window); +static gboolean main_window_throbber_button_press_event_cb (GtkWidget *throbber_ebox, + GdkEventButton *event, + EmpathyMainWindow *window); +static void main_window_status_changed_cb (MissionControl *mc, + TpConnectionStatus status, + McPresence presence, + TpConnectionStatusReason reason, + const gchar *unique_name, + EmpathyMainWindow *window); +static void main_window_update_status (EmpathyMainWindow *window); +static void main_window_accels_load (void); +static void main_window_accels_save (void); +static void main_window_connection_items_setup (EmpathyMainWindow *window, + GladeXML *glade); +static gboolean main_window_configure_event_timeout_cb (EmpathyMainWindow *window); +static gboolean main_window_configure_event_cb (GtkWidget *widget, + GdkEventConfigure *event, + EmpathyMainWindow *window); +static void main_window_notify_show_offline_cb (EmpathyConf *conf, + const gchar *key, + gpointer check_menu_item); +static void main_window_notify_show_avatars_cb (EmpathyConf *conf, + const gchar *key, + EmpathyMainWindow *window); +static void main_window_notify_compact_contact_list_cb (EmpathyConf *conf, + const gchar *key, + EmpathyMainWindow *window); +static void main_window_notify_sort_criterium_cb (EmpathyConf *conf, + const gchar *key, + EmpathyMainWindow *window); + +GtkWidget * +empathy_main_window_show (void) +{ + static EmpathyMainWindow *window = NULL; + EmpathyContactList *list_iface; + GladeXML *glade; + EmpathyConf *conf; + GtkWidget *sw; + GtkWidget *show_offline_widget; + GtkWidget *ebox; + GtkToolItem *item; + gboolean show_offline; + gboolean show_avatars; + gboolean compact_contact_list; + gint x, y, w, h; + gchar *filename; + + if (window) { + empathy_window_present (GTK_WINDOW (window->window), TRUE); + return window->window; + } + + window = g_new0 (EmpathyMainWindow, 1); + + /* Set up interface */ + filename = empathy_file_lookup ("empathy-main-window.glade", "src"); + glade = empathy_glade_get_file (filename, + "main_window", + NULL, + "main_window", &window->window, + "main_vbox", &window->main_vbox, + "errors_vbox", &window->errors_vbox, + "chat_show_offline", &show_offline_widget, + "room", &window->room, + "room_sep", &window->room_sep, + "room_join_favorites", &window->room_join_favorites, + "edit_context", &window->edit_context, + "edit_context_separator", &window->edit_context_separator, + "presence_toolbar", &window->presence_toolbar, + "roster_scrolledwindow", &sw, + NULL); + g_free (filename); + + empathy_glade_connect (glade, + window, + "main_window", "destroy", main_window_destroy_cb, + "main_window", "configure_event", main_window_configure_event_cb, + "chat_quit", "activate", main_window_chat_quit_cb, + "chat_new_message", "activate", main_window_chat_new_message_cb, + "chat_history", "activate", main_window_chat_history_cb, + "room_join_new", "activate", main_window_room_join_new_cb, + "room_join_favorites", "activate", main_window_room_join_favorites_cb, + "room_manage_favorites", "activate", main_window_room_manage_favorites_cb, + "chat_add_contact", "activate", main_window_chat_add_contact_cb, + "chat_show_offline", "toggled", main_window_chat_show_offline_cb, + "edit", "button-press-event", main_window_edit_button_press_event_cb, + "edit_accounts", "activate", main_window_edit_accounts_cb, + "edit_personal_information", "activate", main_window_edit_personal_information_cb, + "edit_preferences", "activate", main_window_edit_preferences_cb, + "help_about", "activate", main_window_help_about_cb, + "help_contents", "activate", main_window_help_contents_cb, + NULL); + + /* Set up connection related widgets. */ + main_window_connection_items_setup (window, glade); + g_object_unref (glade); + + window->mc = empathy_mission_control_new (); + window->token = empathy_connect_to_account_status_changed (window->mc, + G_CALLBACK (main_window_status_changed_cb), + window, NULL); + + window->errors = g_hash_table_new_full (empathy_account_hash, + empathy_account_equal, + g_object_unref, + NULL); + + /* Set up menu */ + main_window_favorite_chatroom_menu_setup (window); + + gtk_widget_hide (window->edit_context); + gtk_widget_hide (window->edit_context_separator); + + /* Set up presence chooser */ + window->presence_chooser = empathy_presence_chooser_new (); + gtk_widget_show (window->presence_chooser); + item = gtk_tool_item_new (); + gtk_widget_show (GTK_WIDGET (item)); + gtk_container_add (GTK_CONTAINER (item), window->presence_chooser); + gtk_tool_item_set_is_important (item, TRUE); + gtk_tool_item_set_expand (item, TRUE); + gtk_toolbar_insert (GTK_TOOLBAR (window->presence_toolbar), item, -1); + + /* Set up the throbber */ + ebox = gtk_event_box_new (); + gtk_event_box_set_visible_window (GTK_EVENT_BOX (ebox), FALSE); + gtk_widget_set_tooltip_text (ebox, _("Show and edit accounts")); + g_signal_connect (ebox, + "button-press-event", + G_CALLBACK (main_window_throbber_button_press_event_cb), + window); + gtk_widget_show (ebox); + + window->throbber = ephy_spinner_new (); + ephy_spinner_set_size (EPHY_SPINNER (window->throbber), GTK_ICON_SIZE_LARGE_TOOLBAR); + gtk_container_add (GTK_CONTAINER (ebox), window->throbber); + gtk_widget_show (window->throbber); + + item = gtk_tool_item_new (); + gtk_container_add (GTK_CONTAINER (item), ebox); + gtk_toolbar_insert (GTK_TOOLBAR (window->presence_toolbar), item, -1); + gtk_widget_show (GTK_WIDGET (item)); + + /* Set up contact list. */ + empathy_status_presets_get_all (); + + list_iface = EMPATHY_CONTACT_LIST (empathy_contact_manager_new ()); + window->list_store = empathy_contact_list_store_new (list_iface); + window->list_view = empathy_contact_list_view_new (window->list_store, + EMPATHY_CONTACT_LIST_FEATURE_ALL, + EMPATHY_CONTACT_FEATURE_ALL); + g_object_unref (list_iface); + + gtk_widget_show (GTK_WIDGET (window->list_view)); + gtk_container_add (GTK_CONTAINER (sw), + GTK_WIDGET (window->list_view)); + + /* Load user-defined accelerators. */ + main_window_accels_load (); + + /* Set window size. */ + empathy_geometry_load (GEOMETRY_NAME, &x, &y, &w, &h); + + if (w >= 1 && h >= 1) { + /* Use the defaults from the glade file if we + * don't have good w, h geometry. + */ + DEBUG ("Configuring window default size w:%d, h:%d", w, h); + gtk_window_set_default_size (GTK_WINDOW (window->window), w, h); + } + + if (x >= 0 && y >= 0) { + /* Let the window manager position it if we + * don't have good x, y coordinates. + */ + DEBUG ("Configuring window default position x:%d, y:%d", x, y); + gtk_window_move (GTK_WINDOW (window->window), x, y); + } + + conf = empathy_conf_get (); + + /* Show offline ? */ + empathy_conf_get_bool (conf, + EMPATHY_PREFS_CONTACTS_SHOW_OFFLINE, + &show_offline); + empathy_conf_notify_add (conf, + EMPATHY_PREFS_CONTACTS_SHOW_OFFLINE, + main_window_notify_show_offline_cb, + show_offline_widget); + + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (show_offline_widget), + show_offline); + + /* Show avatars ? */ + empathy_conf_get_bool (conf, + EMPATHY_PREFS_UI_SHOW_AVATARS, + &show_avatars); + empathy_conf_notify_add (conf, + EMPATHY_PREFS_UI_SHOW_AVATARS, + (EmpathyConfNotifyFunc) main_window_notify_show_avatars_cb, + window); + empathy_contact_list_store_set_show_avatars (window->list_store, show_avatars); + + /* Is compact ? */ + empathy_conf_get_bool (conf, + EMPATHY_PREFS_UI_COMPACT_CONTACT_LIST, + &compact_contact_list); + empathy_conf_notify_add (conf, + EMPATHY_PREFS_UI_COMPACT_CONTACT_LIST, + (EmpathyConfNotifyFunc) main_window_notify_compact_contact_list_cb, + window); + empathy_contact_list_store_set_is_compact (window->list_store, compact_contact_list); + + /* Sort criterium */ + empathy_conf_notify_add (conf, + EMPATHY_PREFS_CONTACTS_SORT_CRITERIUM, + (EmpathyConfNotifyFunc) main_window_notify_sort_criterium_cb, + window); + main_window_notify_sort_criterium_cb (conf, + EMPATHY_PREFS_CONTACTS_SORT_CRITERIUM, + window); + + main_window_update_status (window); + + return window->window; +} + +static void +main_window_destroy_cb (GtkWidget *widget, + EmpathyMainWindow *window) +{ + /* Save user-defined accelerators. */ + main_window_accels_save (); + + empathy_disconnect_account_status_changed (window->token); + + if (window->size_timeout_id) { + g_source_remove (window->size_timeout_id); + } + + g_list_free (window->widgets_connected); + g_list_free (window->widgets_disconnected); + + g_object_unref (window->mc); + g_object_unref (window->list_store); + g_hash_table_destroy (window->errors); + + g_free (window); +} + +static void +main_window_favorite_chatroom_menu_setup (EmpathyMainWindow *window) +{ + GList *chatrooms, *l; + + window->chatroom_manager = empathy_chatroom_manager_new (); + chatrooms = empathy_chatroom_manager_get_chatrooms (window->chatroom_manager, NULL); + window->room_menu = gtk_menu_item_get_submenu (GTK_MENU_ITEM (window->room)); + + for (l = chatrooms; l; l = l->next) { + main_window_favorite_chatroom_menu_add (window, l->data); + } + + if (!chatrooms) { + gtk_widget_hide (window->room_sep); + } + + gtk_widget_set_sensitive (window->room_join_favorites, chatrooms != NULL); + + g_signal_connect (window->chatroom_manager, "chatroom-added", + G_CALLBACK (main_window_favorite_chatroom_menu_added_cb), + window); + g_signal_connect (window->chatroom_manager, "chatroom-removed", + G_CALLBACK (main_window_favorite_chatroom_menu_removed_cb), + window); + + g_list_free (chatrooms); +} + +static void +main_window_favorite_chatroom_menu_added_cb (EmpathyChatroomManager *manager, + EmpathyChatroom *chatroom, + EmpathyMainWindow *window) +{ + main_window_favorite_chatroom_menu_add (window, chatroom); + gtk_widget_show (window->room_sep); + gtk_widget_set_sensitive (window->room_join_favorites, TRUE); +} + +static void +main_window_favorite_chatroom_menu_removed_cb (EmpathyChatroomManager *manager, + EmpathyChatroom *chatroom, + EmpathyMainWindow *window) +{ + GtkWidget *menu_item; + + menu_item = g_object_get_data (G_OBJECT (chatroom), "menu_item"); + + g_object_set_data (G_OBJECT (chatroom), "menu_item", NULL); + gtk_widget_destroy (menu_item); + + main_window_favorite_chatroom_menu_update (window); +} + +static void +main_window_favorite_chatroom_menu_activate_cb (GtkMenuItem *menu_item, + EmpathyChatroom *chatroom) +{ + main_window_favorite_chatroom_join (chatroom); +} + +static void +main_window_favorite_chatroom_menu_update (EmpathyMainWindow *window) +{ + GList *chatrooms; + + chatrooms = empathy_chatroom_manager_get_chatrooms (window->chatroom_manager, NULL); + + if (chatrooms) { + gtk_widget_show (window->room_sep); + } else { + gtk_widget_hide (window->room_sep); + } + + gtk_widget_set_sensitive (window->room_join_favorites, chatrooms != NULL); + g_list_free (chatrooms); +} + +static void +main_window_favorite_chatroom_menu_add (EmpathyMainWindow *window, + EmpathyChatroom *chatroom) +{ + GtkWidget *menu_item; + const gchar *name; + + if (g_object_get_data (G_OBJECT (chatroom), "menu_item")) { + return; + } + + name = empathy_chatroom_get_name (chatroom); + menu_item = gtk_menu_item_new_with_label (name); + + g_object_set_data (G_OBJECT (chatroom), "menu_item", menu_item); + g_signal_connect (menu_item, "activate", + G_CALLBACK (main_window_favorite_chatroom_menu_activate_cb), + chatroom); + + gtk_menu_shell_insert (GTK_MENU_SHELL (window->room_menu), + menu_item, 3); + + gtk_widget_show (menu_item); +} + +static void +main_window_favorite_chatroom_join (EmpathyChatroom *chatroom) +{ + MissionControl *mc; + McAccount *account; + const gchar *room; + + mc = empathy_mission_control_new (); + account = empathy_chatroom_get_account (chatroom); + room = empathy_chatroom_get_room (chatroom); + + DEBUG ("Requesting channel for '%s'", room); + + mission_control_request_channel_with_string_handle (mc, + account, + TP_IFACE_CHANNEL_TYPE_TEXT, + room, + TP_HANDLE_TYPE_ROOM, + NULL, NULL); + g_object_unref (mc); +} + +static void +main_window_chat_quit_cb (GtkWidget *widget, + EmpathyMainWindow *window) +{ + gtk_main_quit (); +} + +static void +main_window_chat_new_message_cb (GtkWidget *widget, + EmpathyMainWindow *window) +{ + empathy_new_message_dialog_show (GTK_WINDOW (window->window)); +} + +static void +main_window_chat_history_cb (GtkWidget *widget, + EmpathyMainWindow *window) +{ + empathy_log_window_show (NULL, NULL, FALSE, GTK_WINDOW (window->window)); +} + +static void +main_window_room_join_new_cb (GtkWidget *widget, + EmpathyMainWindow *window) +{ + empathy_new_chatroom_dialog_show (GTK_WINDOW (window->window)); +} + +static void +main_window_room_join_favorites_cb (GtkWidget *widget, + EmpathyMainWindow *window) +{ + GList *chatrooms, *l; + + chatrooms = empathy_chatroom_manager_get_chatrooms (window->chatroom_manager, NULL); + for (l = chatrooms; l; l = l->next) { + main_window_favorite_chatroom_join (l->data); + } + g_list_free (chatrooms); +} + +static void +main_window_room_manage_favorites_cb (GtkWidget *widget, + EmpathyMainWindow *window) +{ + empathy_chatrooms_window_show (GTK_WINDOW (window->window)); +} + +static void +main_window_chat_add_contact_cb (GtkWidget *widget, + EmpathyMainWindow *window) +{ + empathy_new_contact_dialog_show (GTK_WINDOW (window->window)); +} + +static void +main_window_chat_show_offline_cb (GtkCheckMenuItem *item, + EmpathyMainWindow *window) +{ + gboolean current; + + current = gtk_check_menu_item_get_active (item); + + empathy_conf_set_bool (empathy_conf_get (), + EMPATHY_PREFS_CONTACTS_SHOW_OFFLINE, + current); + + /* Turn off sound just while we alter the contact list. */ + // FIXME: empathy_sound_set_enabled (FALSE); + empathy_contact_list_store_set_show_offline (window->list_store, current); + //empathy_sound_set_enabled (TRUE); +} + +static gboolean +main_window_edit_button_press_event_cb (GtkWidget *widget, + GdkEventButton *event, + EmpathyMainWindow *window) +{ + GtkWidget *submenu; + + if (!event->button == 1) { + return FALSE; + } + + submenu = empathy_contact_list_view_get_contact_menu (window->list_view); + if (submenu) { + GtkMenuItem *item; + GtkWidget *label; + + item = GTK_MENU_ITEM (window->edit_context); + label = gtk_bin_get_child (GTK_BIN (item)); + gtk_label_set_text (GTK_LABEL (label), _("Contact")); + + gtk_widget_show (window->edit_context); + gtk_widget_show (window->edit_context_separator); + + gtk_menu_item_set_submenu (item, submenu); + + return FALSE; + } + + submenu = empathy_contact_list_view_get_group_menu (window->list_view); + if (submenu) { + GtkMenuItem *item; + GtkWidget *label; + + item = GTK_MENU_ITEM (window->edit_context); + label = gtk_bin_get_child (GTK_BIN (item)); + gtk_label_set_text (GTK_LABEL (label), _("Group")); + + gtk_widget_show (window->edit_context); + gtk_widget_show (window->edit_context_separator); + + gtk_menu_item_set_submenu (item, submenu); + + return FALSE; + } + + gtk_widget_hide (window->edit_context); + gtk_widget_hide (window->edit_context_separator); + + return FALSE; +} + +static void +main_window_edit_accounts_cb (GtkWidget *widget, + EmpathyMainWindow *window) +{ + empathy_accounts_dialog_show (GTK_WINDOW (window->window)); +} + +static void +main_window_edit_personal_information_cb (GtkWidget *widget, + EmpathyMainWindow *window) +{ + GSList *accounts; + + accounts = mission_control_get_online_connections (window->mc, NULL); + if (accounts) { + EmpathyContactFactory *factory; + EmpathyContact *contact; + McAccount *account; + + account = accounts->data; + factory = empathy_contact_factory_new (); + contact = empathy_contact_factory_get_user (factory, account); + empathy_contact_run_until_ready (contact, + EMPATHY_CONTACT_READY_HANDLE | + EMPATHY_CONTACT_READY_ID, + NULL); + + empathy_contact_information_dialog_show (contact, + GTK_WINDOW (window->window), + TRUE, TRUE); + + g_slist_foreach (accounts, (GFunc) g_object_unref, NULL); + g_slist_free (accounts); + g_object_unref (factory); + g_object_unref (contact); + } +} + +static void +main_window_edit_preferences_cb (GtkWidget *widget, + EmpathyMainWindow *window) +{ + empathy_preferences_show (GTK_WINDOW (window->window)); +} + +static void +main_window_help_about_cb (GtkWidget *widget, + EmpathyMainWindow *window) +{ + empathy_about_dialog_new (GTK_WINDOW (window->window)); +} + +static void +main_window_help_contents_cb (GtkWidget *widget, + EmpathyMainWindow *window) +{ + empathy_url_show ("ghelp:empathy"); +} + +static gboolean +main_window_throbber_button_press_event_cb (GtkWidget *throbber_ebox, + GdkEventButton *event, + EmpathyMainWindow *window) +{ + if (event->type != GDK_BUTTON_PRESS || + event->button != 1) { + return FALSE; + } + + empathy_accounts_dialog_show (GTK_WINDOW (window->window)); + + return FALSE; +} + +static void +main_window_error_edit_clicked_cb (GtkButton *button, + EmpathyMainWindow *window) +{ + McAccount *account; + GtkWidget *error_widget; + + empathy_accounts_dialog_show (GTK_WINDOW (window->window)); + + account = g_object_get_data (G_OBJECT (button), "account"); + error_widget = g_hash_table_lookup (window->errors, account); + gtk_widget_destroy (error_widget); + g_hash_table_remove (window->errors, account); +} + +static void +main_window_error_clear_clicked_cb (GtkButton *button, + EmpathyMainWindow *window) +{ + McAccount *account; + GtkWidget *error_widget; + + account = g_object_get_data (G_OBJECT (button), "account"); + error_widget = g_hash_table_lookup (window->errors, account); + gtk_widget_destroy (error_widget); + g_hash_table_remove (window->errors, account); +} + +static void +main_window_error_display (EmpathyMainWindow *window, + McAccount *account, + const gchar *message) +{ + GtkWidget *child; + GtkWidget *table; + GtkWidget *image; + GtkWidget *button_edit; + GtkWidget *alignment; + GtkWidget *hbox; + GtkWidget *label; + GtkWidget *fixed; + GtkWidget *vbox; + GtkWidget *button_close; + gchar *str; + + child = g_hash_table_lookup (window->errors, account); + if (child) { + label = g_object_get_data (G_OBJECT (child), "label"); + + /* Just set the latest error and return */ + str = g_markup_printf_escaped ("<b>%s</b>\n%s", + mc_account_get_display_name (account), + message); + gtk_label_set_markup (GTK_LABEL (label), str); + g_free (str); + + return; + } + + child = gtk_vbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (window->errors_vbox), child, FALSE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (child), 6); + gtk_widget_show (child); + + table = gtk_table_new (2, 4, FALSE); + gtk_widget_show (table); + gtk_box_pack_start (GTK_BOX (child), table, TRUE, TRUE, 0); + gtk_table_set_row_spacings (GTK_TABLE (table), 12); + gtk_table_set_col_spacings (GTK_TABLE (table), 6); + + image = gtk_image_new_from_stock (GTK_STOCK_DISCONNECT, GTK_ICON_SIZE_MENU); + gtk_widget_show (image); + gtk_table_attach (GTK_TABLE (table), image, 0, 1, 0, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0); + + button_edit = gtk_button_new (); + gtk_widget_show (button_edit); + gtk_table_attach (GTK_TABLE (table), button_edit, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + alignment = gtk_alignment_new (0.5, 0.5, 0, 0); + gtk_widget_show (alignment); + gtk_container_add (GTK_CONTAINER (button_edit), alignment); + + hbox = gtk_hbox_new (FALSE, 2); + gtk_widget_show (hbox); + gtk_container_add (GTK_CONTAINER (alignment), hbox); + + image = gtk_image_new_from_stock (GTK_STOCK_EDIT, GTK_ICON_SIZE_BUTTON); + gtk_widget_show (image); + gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0); + + label = gtk_label_new_with_mnemonic (_("_Edit account")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + + fixed = gtk_fixed_new (); + gtk_widget_show (fixed); + gtk_table_attach (GTK_TABLE (table), fixed, 2, 3, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + + vbox = gtk_vbox_new (FALSE, 6); + gtk_widget_show (vbox); + gtk_table_attach (GTK_TABLE (table), vbox, 3, 4, 0, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + + button_close = gtk_button_new (); + gtk_widget_show (button_close); + gtk_box_pack_start (GTK_BOX (vbox), button_close, FALSE, FALSE, 0); + gtk_button_set_relief (GTK_BUTTON (button_close), GTK_RELIEF_NONE); + + + image = gtk_image_new_from_stock ("gtk-close", GTK_ICON_SIZE_MENU); + gtk_widget_show (image); + gtk_container_add (GTK_CONTAINER (button_close), image); + + label = gtk_label_new (""); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 1, 3, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_SHRINK | GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND | GTK_SHRINK | GTK_FILL), 0, 0); + gtk_widget_set_size_request (label, 175, -1); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + + str = g_markup_printf_escaped ("<b>%s</b>\n%s", + mc_account_get_display_name (account), + message); + gtk_label_set_markup (GTK_LABEL (label), str); + g_free (str); + + g_object_set_data (G_OBJECT (child), "label", label); + g_object_set_data_full (G_OBJECT (button_edit), + "account", g_object_ref (account), + g_object_unref); + g_object_set_data_full (G_OBJECT (button_close), + "account", g_object_ref (account), + g_object_unref); + + g_signal_connect (button_edit, "clicked", + G_CALLBACK (main_window_error_edit_clicked_cb), + window); + + g_signal_connect (button_close, "clicked", + G_CALLBACK (main_window_error_clear_clicked_cb), + window); + + gtk_widget_show (window->errors_vbox); + + g_hash_table_insert (window->errors, g_object_ref (account), child); +} + +static void +main_window_status_changed_cb (MissionControl *mc, + TpConnectionStatus status, + McPresence presence, + TpConnectionStatusReason reason, + const gchar *unique_name, + EmpathyMainWindow *window) +{ + McAccount *account; + + main_window_update_status (window); + + account = mc_account_lookup (unique_name); + + if (status == TP_CONNECTION_STATUS_DISCONNECTED && + reason > TP_CONNECTION_STATUS_REASON_REQUESTED) { + const gchar *message; + + switch (reason) { + case TP_CONNECTION_STATUS_REASON_NETWORK_ERROR: + message = _("Network error"); + break; + case TP_CONNECTION_STATUS_REASON_AUTHENTICATION_FAILED: + message = _("Authentication failed"); + break; + case TP_CONNECTION_STATUS_REASON_ENCRYPTION_ERROR: + message = _("Encryption error"); + break; + case TP_CONNECTION_STATUS_REASON_NAME_IN_USE: + message = _("Name in use"); + break; + case TP_CONNECTION_STATUS_REASON_CERT_NOT_PROVIDED: + message = _("Certificate not provided"); + break; + case TP_CONNECTION_STATUS_REASON_CERT_UNTRUSTED: + message = _("Certificate untrusted"); + break; + case TP_CONNECTION_STATUS_REASON_CERT_EXPIRED: + message = _("Certificate expired"); + break; + case TP_CONNECTION_STATUS_REASON_CERT_NOT_ACTIVATED: + message = _("Certificate not activated"); + break; + case TP_CONNECTION_STATUS_REASON_CERT_HOSTNAME_MISMATCH: + message = _("Certificate hostname mismatch"); + break; + case TP_CONNECTION_STATUS_REASON_CERT_FINGERPRINT_MISMATCH: + message = _("Certificate fingerprint mismatch"); + break; + case TP_CONNECTION_STATUS_REASON_CERT_SELF_SIGNED: + message = _("Certificate self-signed"); + break; + case TP_CONNECTION_STATUS_REASON_CERT_OTHER_ERROR: + message = _("Certificate error"); + break; + default: + message = _("Unknown error"); + break; + } + + main_window_error_display (window, account, message); + } + + if (status == TP_CONNECTION_STATUS_CONNECTED) { + GtkWidget *error_widget; + + /* Account connected without error, remove error message if any */ + error_widget = g_hash_table_lookup (window->errors, account); + if (error_widget) { + gtk_widget_destroy (error_widget); + g_hash_table_remove (window->errors, account); + } + } + + g_object_unref (account); +} + +static void +main_window_update_status (EmpathyMainWindow *window) +{ + GList *accounts, *l; + guint connected = 0; + guint connecting = 0; + guint disconnected = 0; + + /* Count number of connected/connecting/disconnected accounts */ + accounts = mc_accounts_list (); + for (l = accounts; l; l = l->next) { + McAccount *account; + guint status; + + account = l->data; + + status = mission_control_get_connection_status (window->mc, + account, + NULL); + + if (status == 0) { + connected++; + } else if (status == 1) { + connecting++; + } else if (status == 2) { + disconnected++; + } + + g_object_unref (account); + } + g_list_free (accounts); + + /* Update the spinner state */ + if (connecting > 0) { + ephy_spinner_start (EPHY_SPINNER (window->throbber)); + } else { + ephy_spinner_stop (EPHY_SPINNER (window->throbber)); + } + + /* Update widgets sensibility */ + for (l = window->widgets_connected; l; l = l->next) { + gtk_widget_set_sensitive (l->data, (connected > 0)); + } + + for (l = window->widgets_disconnected; l; l = l->next) { + gtk_widget_set_sensitive (l->data, (disconnected > 0)); + } +} + +/* + * Accels + */ +static void +main_window_accels_load (void) +{ + gchar *filename; + + filename = g_build_filename (g_get_home_dir (), ".gnome2", PACKAGE_NAME, ACCELS_FILENAME, NULL); + if (g_file_test (filename, G_FILE_TEST_EXISTS)) { + DEBUG ("Loading from:'%s'", filename); + gtk_accel_map_load (filename); + } + + g_free (filename); +} + +static void +main_window_accels_save (void) +{ + gchar *dir; + gchar *file_with_path; + + dir = g_build_filename (g_get_home_dir (), ".gnome2", PACKAGE_NAME, NULL); + g_mkdir_with_parents (dir, S_IRUSR | S_IWUSR | S_IXUSR); + file_with_path = g_build_filename (dir, ACCELS_FILENAME, NULL); + g_free (dir); + + DEBUG ("Saving to:'%s'", file_with_path); + gtk_accel_map_save (file_with_path); + + g_free (file_with_path); +} + +static void +main_window_connection_items_setup (EmpathyMainWindow *window, + GladeXML *glade) +{ + GList *list; + GtkWidget *w; + gint i; + const gchar *widgets_connected[] = { + "room", + "chat_new_message", + "chat_add_contact", + "edit_personal_information" + }; + const gchar *widgets_disconnected[] = { + }; + + for (i = 0, list = NULL; i < G_N_ELEMENTS (widgets_connected); i++) { + w = glade_xml_get_widget (glade, widgets_connected[i]); + list = g_list_prepend (list, w); + } + + window->widgets_connected = list; + + for (i = 0, list = NULL; i < G_N_ELEMENTS (widgets_disconnected); i++) { + w = glade_xml_get_widget (glade, widgets_disconnected[i]); + list = g_list_prepend (list, w); + } + + window->widgets_disconnected = list; +} + +static gboolean +main_window_configure_event_timeout_cb (EmpathyMainWindow *window) +{ + gint x, y, w, h; + + gtk_window_get_size (GTK_WINDOW (window->window), &w, &h); + gtk_window_get_position (GTK_WINDOW (window->window), &x, &y); + + empathy_geometry_save (GEOMETRY_NAME, x, y, w, h); + + window->size_timeout_id = 0; + + return FALSE; +} + +static gboolean +main_window_configure_event_cb (GtkWidget *widget, + GdkEventConfigure *event, + EmpathyMainWindow *window) +{ + if (window->size_timeout_id) { + g_source_remove (window->size_timeout_id); + } + + window->size_timeout_id = g_timeout_add_seconds (1, + (GSourceFunc) main_window_configure_event_timeout_cb, + window); + + return FALSE; +} + +static void +main_window_notify_show_offline_cb (EmpathyConf *conf, + const gchar *key, + gpointer check_menu_item) +{ + gboolean show_offline; + + if (empathy_conf_get_bool (conf, key, &show_offline)) { + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (check_menu_item), + show_offline); + } +} + +static void +main_window_notify_show_avatars_cb (EmpathyConf *conf, + const gchar *key, + EmpathyMainWindow *window) +{ + gboolean show_avatars; + + if (empathy_conf_get_bool (conf, key, &show_avatars)) { + empathy_contact_list_store_set_show_avatars (window->list_store, + show_avatars); + } +} + +static void +main_window_notify_compact_contact_list_cb (EmpathyConf *conf, + const gchar *key, + EmpathyMainWindow *window) +{ + gboolean compact_contact_list; + + if (empathy_conf_get_bool (conf, key, &compact_contact_list)) { + empathy_contact_list_store_set_is_compact (window->list_store, + compact_contact_list); + } +} + +static void +main_window_notify_sort_criterium_cb (EmpathyConf *conf, + const gchar *key, + EmpathyMainWindow *window) +{ + gchar *str = NULL; + + if (empathy_conf_get_string (conf, key, &str) && str) { + GType type; + GEnumClass *enum_class; + GEnumValue *enum_value; + + type = empathy_contact_list_store_sort_get_type (); + enum_class = G_ENUM_CLASS (g_type_class_peek (type)); + enum_value = g_enum_get_value_by_nick (enum_class, str); + g_free (str); + + if (enum_value) { + empathy_contact_list_store_set_sort_criterium (window->list_store, + enum_value->value); + } + } +} + diff --git a/trunk/src/empathy-main-window.glade b/trunk/src/empathy-main-window.glade new file mode 100644 index 000000000..437dcc043 --- /dev/null +++ b/trunk/src/empathy-main-window.glade @@ -0,0 +1,298 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd"> +<!--*- mode: xml -*--> +<glade-interface> + <widget class="GtkWindow" id="main_window"> + <property name="title" translatable="yes">Contact List</property> + <property name="default_width">225</property> + <property name="default_height">325</property> + <child> + <widget class="GtkVBox" id="main_vbox"> + <property name="visible">True</property> + <child> + <widget class="GtkMenuBar" id="menubar2"> + <property name="visible">True</property> + <child> + <widget class="GtkMenuItem" id="chat"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Chat</property> + <property name="use_underline">True</property> + <child> + <widget class="GtkMenu" id="chat_menu"> + <child> + <widget class="GtkImageMenuItem" id="chat_new_message"> + <property name="visible">True</property> + <property name="label" translatable="yes">_New Conversation...</property> + <property name="use_underline">True</property> + <accelerator key="N" modifiers="GDK_CONTROL_MASK" signal="activate"/> + <child internal-child="image"> + <widget class="GtkImage" id="image885"> + <property name="visible">True</property> + <property name="icon_size">1</property> + <property name="icon_name">im-message-new</property> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkImageMenuItem" id="chat_history"> + <property name="visible">True</property> + <property name="label" translatable="yes">_View Previous Conversations</property> + <property name="use_underline">True</property> + <accelerator key="F3" modifiers="" signal="activate"/> + <child internal-child="image"> + <widget class="GtkImage" id="image886"> + <property name="visible">True</property> + <property name="icon_size">1</property> + <property name="icon_name">document-open-recent</property> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkSeparatorMenuItem" id="separator5"> + <property name="visible">True</property> + </widget> + </child> + <child> + <widget class="GtkImageMenuItem" id="chat_add_contact"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Add Contact...</property> + <property name="use_underline">True</property> + <child internal-child="image"> + <widget class="GtkImage" id="image887"> + <property name="visible">True</property> + <property name="stock">gtk-add</property> + <property name="icon_size">1</property> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkSeparatorMenuItem" id="separator3"> + <property name="visible">True</property> + </widget> + </child> + <child> + <widget class="GtkCheckMenuItem" id="chat_show_offline"> + <property name="visible">True</property> + <property name="label" translatable="yes">Show _Offline Contacts</property> + <property name="use_underline">True</property> + <accelerator key="H" modifiers="GDK_CONTROL_MASK" signal="activate"/> + </widget> + </child> + <child> + <widget class="GtkSeparatorMenuItem" id="separator6"> + <property name="visible">True</property> + </widget> + </child> + <child> + <widget class="GtkImageMenuItem" id="chat_quit"> + <property name="visible">True</property> + <property name="label">gtk-quit</property> + <property name="use_underline">True</property> + <property name="use_stock">True</property> + <accelerator key="Q" modifiers="GDK_CONTROL_MASK" signal="activate"/> + </widget> + </child> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkMenuItem" id="edit"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Edit</property> + <property name="use_underline">True</property> + <child> + <widget class="GtkMenu" id="edit_menu"> + <child> + <widget class="GtkMenuItem" id="edit_context"> + <property name="visible">True</property> + <property name="label" translatable="yes">Context</property> + <property name="use_underline">True</property> + </widget> + </child> + <child> + <widget class="GtkSeparatorMenuItem" id="edit_context_separator"> + <property name="visible">True</property> + </widget> + </child> + <child> + <widget class="GtkMenuItem" id="edit_accounts"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Accounts</property> + <property name="use_underline">True</property> + <accelerator key="F4" modifiers="" signal="activate"/> + </widget> + </child> + <child> + <widget class="GtkImageMenuItem" id="edit_personal_information"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Personal Information</property> + <property name="use_underline">True</property> + <child internal-child="image"> + <widget class="GtkImage" id="image894"> + <property name="visible">True</property> + <property name="icon_size">1</property> + <property name="icon_name">user-info</property> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkSeparatorMenuItem" id="separator2"> + <property name="visible">True</property> + </widget> + </child> + <child> + <widget class="GtkImageMenuItem" id="edit_preferences"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Preferences</property> + <property name="use_underline">True</property> + <child internal-child="image"> + <widget class="GtkImage" id="image891"> + <property name="visible">True</property> + <property name="stock">gtk-preferences</property> + <property name="icon_size">1</property> + </widget> + </child> + </widget> + </child> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkMenuItem" id="room"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Room</property> + <property name="use_underline">True</property> + <child> + <widget class="GtkMenu" id="room_menu"> + <child> + <widget class="GtkMenuItem" id="room_join_new"> + <property name="visible">True</property> + <property name="label" translatable="yes">Join _New...</property> + <property name="use_underline">True</property> + </widget> + </child> + <child> + <widget class="GtkMenuItem" id="room_join_favorites"> + <property name="visible">True</property> + <property name="label" translatable="yes">Join _Favorites</property> + <property name="use_underline">True</property> + <accelerator key="F5" modifiers="" signal="activate"/> + </widget> + </child> + <child> + <widget class="GtkSeparatorMenuItem" id="room_sep"> + <property name="visible">True</property> + </widget> + </child> + <child> + <widget class="GtkSeparatorMenuItem" id="room_sep2"> + <property name="visible">True</property> + </widget> + </child> + <child> + <widget class="GtkImageMenuItem" id="room_manage_favorites"> + <property name="visible">True</property> + <property name="label" translatable="yes">Manage Favorites</property> + <property name="use_underline">True</property> + <child internal-child="image"> + <widget class="GtkImage" id="image890"> + <property name="visible">True</property> + <property name="icon_size">1</property> + <property name="icon_name">system-users</property> + </widget> + </child> + </widget> + </child> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkMenuItem" id="help"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Help</property> + <property name="use_underline">True</property> + <child> + <widget class="GtkMenu" id="help_menu"> + <child> + <widget class="GtkImageMenuItem" id="help_contents"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Contents</property> + <property name="use_underline">True</property> + <accelerator key="F1" modifiers="" signal="activate"/> + <child internal-child="image"> + <widget class="GtkImage" id="image892"> + <property name="visible">True</property> + <property name="stock">gtk-help</property> + <property name="icon_size">1</property> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkImageMenuItem" id="help_about"> + <property name="visible">True</property> + <property name="label">gtk-about</property> + <property name="use_underline">True</property> + <property name="use_stock">True</property> + </widget> + </child> + </widget> + </child> + </widget> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <widget class="GtkToolbar" id="presence_toolbar"> + <property name="visible">True</property> + <property name="toolbar_style">GTK_TOOLBAR_BOTH</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <widget class="GtkVBox" id="errors_vbox"> + <child> + <placeholder/> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + <child> + <widget class="GtkScrolledWindow" id="roster_scrolledwindow"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="has_focus">True</property> + <property name="hscrollbar_policy">GTK_POLICY_NEVER</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="shadow_type">GTK_SHADOW_IN</property> + <child> + <placeholder/> + </child> + </widget> + <packing> + <property name="position">3</property> + </packing> + </child> + </widget> + </child> + </widget> +</glade-interface> diff --git a/trunk/src/empathy-main-window.h b/trunk/src/empathy-main-window.h new file mode 100644 index 000000000..916dd242f --- /dev/null +++ b/trunk/src/empathy-main-window.h @@ -0,0 +1,35 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2002-2007 Imendio AB + * Copyright (C) 2007-2008 Collabora Ltd. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Xavier Claessens <xclaesse@gmail.com> + */ + +#ifndef __EMPATHY_MAIN_WINDOW_H__ +#define __EMPATHY_MAIN_WINDOW_H__ + +#include <gtk/gtkwidget.h> + +G_BEGIN_DECLS + +GtkWidget *empathy_main_window_show (void); + +G_END_DECLS + +#endif /* __EMPATHY_MAIN_WINDOW_H__ */ diff --git a/trunk/src/empathy-new-chatroom-dialog.c b/trunk/src/empathy-new-chatroom-dialog.c new file mode 100644 index 000000000..6aac17ada --- /dev/null +++ b/trunk/src/empathy-new-chatroom-dialog.c @@ -0,0 +1,574 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2006-2007 Imendio AB + * Copyright (C) 2007-2008 Collabora Ltd. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Martyn Russell <martyn@imendio.com> + * Xavier Claessens <xclaesse@gmail.com> + */ + +#include <config.h> + +#include <string.h> +#include <stdio.h> + +#include <gtk/gtk.h> +#include <glade/glade.h> +#include <glib.h> +#include <glib/gi18n.h> + +#include <libmissioncontrol/mission-control.h> +#include <libmissioncontrol/mc-account.h> +#include <libmissioncontrol/mc-profile.h> + +#include <libempathy/empathy-tp-roomlist.h> +#include <libempathy/empathy-chatroom.h> +#include <libempathy/empathy-utils.h> + +#include <libempathy-gtk/empathy-account-chooser.h> +#include <libempathy-gtk/empathy-ui-utils.h> + +#include "empathy-new-chatroom-dialog.h" +#include "ephy-spinner.h" + +#define DEBUG_FLAG EMPATHY_DEBUG_OTHER +#include <libempathy/empathy-debug.h> + +typedef struct { + EmpathyTpRoomlist *room_list; + + GtkWidget *window; + GtkWidget *vbox_widgets; + GtkWidget *table_info; + GtkWidget *label_account; + GtkWidget *account_chooser; + GtkWidget *label_server; + GtkWidget *entry_server; + GtkWidget *togglebutton_refresh; + GtkWidget *label_room; + GtkWidget *entry_room; + GtkWidget *vbox_browse; + GtkWidget *image_status; + GtkWidget *label_status; + GtkWidget *hbox_status; + GtkWidget *throbber; + GtkWidget *treeview; + GtkTreeModel *model; + GtkWidget *button_join; + GtkWidget *button_close; +} EmpathyNewChatroomDialog; + +enum { + COL_NAME, + COL_ROOM, + COL_COUNT +}; + +static void new_chatroom_dialog_response_cb (GtkWidget *widget, + gint response, + EmpathyNewChatroomDialog *dialog); +static void new_chatroom_dialog_destroy_cb (GtkWidget *widget, + EmpathyNewChatroomDialog *dialog); +static void new_chatroom_dialog_model_setup (EmpathyNewChatroomDialog *dialog); +static void new_chatroom_dialog_model_add_columns (EmpathyNewChatroomDialog *dialog); +static void new_chatroom_dialog_update_widgets (EmpathyNewChatroomDialog *dialog); +static void new_chatroom_dialog_account_changed_cb (GtkComboBox *combobox, + EmpathyNewChatroomDialog *dialog); +static void new_chatroom_dialog_roomlist_destroy_cb (EmpathyTpRoomlist *room_list, + EmpathyNewChatroomDialog *dialog); +static void new_chatroom_dialog_new_room_cb (EmpathyTpRoomlist *room_list, + EmpathyChatroom *chatroom, + EmpathyNewChatroomDialog *dialog); +static void new_chatroom_dialog_listing_cb (EmpathyTpRoomlist *room_list, + gpointer unused, + EmpathyNewChatroomDialog *dialog); +static void new_chatroom_dialog_model_clear (EmpathyNewChatroomDialog *dialog); +static void new_chatroom_dialog_model_row_activated_cb (GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + EmpathyNewChatroomDialog *dialog); +static void new_chatroom_dialog_model_selection_changed (GtkTreeSelection *selection, + EmpathyNewChatroomDialog *dialog); +static void new_chatroom_dialog_join (EmpathyNewChatroomDialog *dialog); +static void new_chatroom_dialog_entry_changed_cb (GtkWidget *entry, + EmpathyNewChatroomDialog *dialog); +static void new_chatroom_dialog_browse_start (EmpathyNewChatroomDialog *dialog); +static void new_chatroom_dialog_browse_stop (EmpathyNewChatroomDialog *dialog); +static void new_chatroom_dialog_entry_server_activate_cb (GtkWidget *widget, + EmpathyNewChatroomDialog *dialog); +static void new_chatroom_dialog_togglebutton_refresh_toggled_cb (GtkWidget *widget, + EmpathyNewChatroomDialog *dialog); + +static EmpathyNewChatroomDialog *dialog_p = NULL; + +void +empathy_new_chatroom_dialog_show (GtkWindow *parent) +{ + EmpathyNewChatroomDialog *dialog; + GladeXML *glade; + GtkSizeGroup *size_group; + gchar *filename; + + if (dialog_p) { + gtk_window_present (GTK_WINDOW (dialog_p->window)); + return; + } + + dialog_p = dialog = g_new0 (EmpathyNewChatroomDialog, 1); + + filename = empathy_file_lookup ("empathy-new-chatroom-dialog.glade", "src"); + glade = empathy_glade_get_file (filename, + "new_chatroom_dialog", + NULL, + "new_chatroom_dialog", &dialog->window, + "table_info", &dialog->table_info, + "label_account", &dialog->label_account, + "label_server", &dialog->label_server, + "label_room", &dialog->label_room, + "entry_server", &dialog->entry_server, + "entry_room", &dialog->entry_room, + "togglebutton_refresh", &dialog->togglebutton_refresh, + "vbox_browse", &dialog->vbox_browse, + "image_status", &dialog->image_status, + "label_status", &dialog->label_status, + "hbox_status", &dialog->hbox_status, + "treeview", &dialog->treeview, + "button_join", &dialog->button_join, + NULL); + g_free (filename); + + empathy_glade_connect (glade, + dialog, + "new_chatroom_dialog", "response", new_chatroom_dialog_response_cb, + "new_chatroom_dialog", "destroy", new_chatroom_dialog_destroy_cb, + "entry_server", "changed", new_chatroom_dialog_entry_changed_cb, + "entry_server", "activate", new_chatroom_dialog_entry_server_activate_cb, + "entry_room", "changed", new_chatroom_dialog_entry_changed_cb, + "togglebutton_refresh", "toggled", new_chatroom_dialog_togglebutton_refresh_toggled_cb, + NULL); + + g_object_unref (glade); + + g_object_add_weak_pointer (G_OBJECT (dialog->window), (gpointer) &dialog_p); + + /* Label alignment */ + size_group = gtk_size_group_new (GTK_SIZE_GROUP_HORIZONTAL); + + gtk_size_group_add_widget (size_group, dialog->label_account); + gtk_size_group_add_widget (size_group, dialog->label_server); + gtk_size_group_add_widget (size_group, dialog->label_room); + + g_object_unref (size_group); + + /* Set up chatrooms treeview */ + new_chatroom_dialog_model_setup (dialog); + + /* Add throbber */ + dialog->throbber = ephy_spinner_new (); + ephy_spinner_set_size (EPHY_SPINNER (dialog->throbber), GTK_ICON_SIZE_LARGE_TOOLBAR); + gtk_widget_show (dialog->throbber); + + gtk_box_pack_start (GTK_BOX (dialog->hbox_status), dialog->throbber, + FALSE, FALSE, 0); + + /* Account chooser for custom */ + dialog->account_chooser = empathy_account_chooser_new (); + empathy_account_chooser_set_filter (EMPATHY_ACCOUNT_CHOOSER (dialog->account_chooser), + empathy_account_chooser_filter_is_connected, + NULL); + gtk_table_attach_defaults (GTK_TABLE (dialog->table_info), + dialog->account_chooser, + 1, 3, 0, 1); + gtk_widget_show (dialog->account_chooser); + + g_signal_connect (GTK_COMBO_BOX (dialog->account_chooser), "changed", + G_CALLBACK (new_chatroom_dialog_account_changed_cb), + dialog); + new_chatroom_dialog_account_changed_cb (GTK_COMBO_BOX (dialog->account_chooser), + dialog); + + if (parent) { + gtk_window_set_transient_for (GTK_WINDOW (dialog->window), + GTK_WINDOW (parent)); + } + + gtk_widget_show (dialog->window); +} + +static void +new_chatroom_dialog_response_cb (GtkWidget *widget, + gint response, + EmpathyNewChatroomDialog *dialog) +{ + if (response == GTK_RESPONSE_OK) { + new_chatroom_dialog_join (dialog); + } + + gtk_widget_destroy (widget); +} + +static void +new_chatroom_dialog_destroy_cb (GtkWidget *widget, + EmpathyNewChatroomDialog *dialog) +{ + if (dialog->room_list) { + g_object_unref (dialog->room_list); + } + g_object_unref (dialog->model); + + g_free (dialog); +} + +static void +new_chatroom_dialog_model_setup (EmpathyNewChatroomDialog *dialog) +{ + GtkTreeView *view; + GtkListStore *store; + GtkTreeSelection *selection; + + /* View */ + view = GTK_TREE_VIEW (dialog->treeview); + + g_signal_connect (view, "row-activated", + G_CALLBACK (new_chatroom_dialog_model_row_activated_cb), + dialog); + + /* Store/Model */ + store = gtk_list_store_new (COL_COUNT, + G_TYPE_STRING, /* Image */ + G_TYPE_STRING, /* Text */ + G_TYPE_STRING); /* Room */ + + dialog->model = GTK_TREE_MODEL (store); + gtk_tree_view_set_model (view, dialog->model); + + /* Selection */ + selection = gtk_tree_view_get_selection (view); + gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store), + COL_NAME, GTK_SORT_ASCENDING); + + g_signal_connect (selection, "changed", + G_CALLBACK (new_chatroom_dialog_model_selection_changed), + dialog); + + /* Columns */ + new_chatroom_dialog_model_add_columns (dialog); +} + +static void +new_chatroom_dialog_model_add_columns (EmpathyNewChatroomDialog *dialog) +{ + GtkTreeView *view; + GtkTreeViewColumn *column; + GtkCellRenderer *cell; + + view = GTK_TREE_VIEW (dialog->treeview); + gtk_tree_view_set_headers_visible (view, FALSE); + + cell = gtk_cell_renderer_text_new (); + g_object_set (cell, + "xpad", (guint) 4, + "ypad", (guint) 1, + "ellipsize", PANGO_ELLIPSIZE_END, + NULL); + + column = gtk_tree_view_column_new_with_attributes (_("Chat Rooms"), + cell, + "text", COL_NAME, + NULL); + + gtk_tree_view_column_set_expand (column, TRUE); + gtk_tree_view_append_column (view, column); +} + +static void +new_chatroom_dialog_update_widgets (EmpathyNewChatroomDialog *dialog) +{ + EmpathyAccountChooser *account_chooser; + McAccount *account; + McProfile *profile; + const gchar *protocol; + const gchar *room; + + account_chooser = EMPATHY_ACCOUNT_CHOOSER (dialog->account_chooser); + account = empathy_account_chooser_get_account (account_chooser); + profile = mc_account_get_profile (account); + protocol = mc_profile_get_protocol_name (profile); + + gtk_entry_set_text (GTK_ENTRY (dialog->entry_server), ""); + + /* hardcode here known protocols */ + if (strcmp (protocol, "jabber") == 0) { + gtk_widget_set_sensitive (dialog->entry_server, TRUE); + gtk_widget_show (dialog->vbox_browse); + + } + else if (strcmp (protocol, "local-xmpp") == 0) { + gtk_widget_set_sensitive (dialog->entry_server, FALSE); + gtk_widget_show (dialog->vbox_browse); + } + else if (strcmp (protocol, "irc") == 0) { + gtk_widget_set_sensitive (dialog->entry_server, FALSE); + gtk_widget_show (dialog->vbox_browse); + } else { + gtk_widget_set_sensitive (dialog->entry_server, TRUE); + gtk_widget_show (dialog->vbox_browse); + } + + room = gtk_entry_get_text (GTK_ENTRY (dialog->entry_room)); + gtk_widget_set_sensitive (dialog->button_join, !G_STR_EMPTY (room)); + + /* Final set up of the dialog */ + gtk_widget_grab_focus (dialog->entry_room); + + g_object_unref (account); + g_object_unref (profile); +} + +static void +new_chatroom_dialog_account_changed_cb (GtkComboBox *combobox, + EmpathyNewChatroomDialog *dialog) +{ + EmpathyAccountChooser *account_chooser; + McAccount *account; + gboolean listing = FALSE; + + if (dialog->room_list) { + g_object_unref (dialog->room_list); + } + + ephy_spinner_stop (EPHY_SPINNER (dialog->throbber)); + new_chatroom_dialog_model_clear (dialog); + + account_chooser = EMPATHY_ACCOUNT_CHOOSER (dialog->account_chooser); + account = empathy_account_chooser_get_account (account_chooser); + dialog->room_list = empathy_tp_roomlist_new (account); + + if (dialog->room_list) { + g_signal_connect (dialog->room_list, "destroy", + G_CALLBACK (new_chatroom_dialog_roomlist_destroy_cb), + dialog); + g_signal_connect (dialog->room_list, "new-room", + G_CALLBACK (new_chatroom_dialog_new_room_cb), + dialog); + g_signal_connect (dialog->room_list, "notify::listing", + G_CALLBACK (new_chatroom_dialog_listing_cb), + dialog); + + listing = empathy_tp_roomlist_is_listing (dialog->room_list); + if (listing) { + ephy_spinner_start (EPHY_SPINNER (dialog->throbber)); + } + } + + new_chatroom_dialog_update_widgets (dialog); +} + +static void +new_chatroom_dialog_roomlist_destroy_cb (EmpathyTpRoomlist *room_list, + EmpathyNewChatroomDialog *dialog) +{ + g_object_unref (dialog->room_list); + dialog->room_list = NULL; +} + +static void +new_chatroom_dialog_new_room_cb (EmpathyTpRoomlist *room_list, + EmpathyChatroom *chatroom, + EmpathyNewChatroomDialog *dialog) +{ + GtkTreeView *view; + GtkTreeSelection *selection; + GtkListStore *store; + GtkTreeIter iter; + + DEBUG ("New chatroom listed: %s (%s)", + empathy_chatroom_get_name (chatroom), + empathy_chatroom_get_room (chatroom)); + + /* Add to model */ + view = GTK_TREE_VIEW (dialog->treeview); + selection = gtk_tree_view_get_selection (view); + store = GTK_LIST_STORE (dialog->model); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COL_NAME, empathy_chatroom_get_name (chatroom), + COL_ROOM, empathy_chatroom_get_room (chatroom), + -1); +} + +static void +new_chatroom_dialog_listing_cb (EmpathyTpRoomlist *room_list, + gpointer unused, + EmpathyNewChatroomDialog *dialog) +{ + gboolean listing; + + listing = empathy_tp_roomlist_is_listing (room_list); + + /* Update the throbber */ + if (listing) { + ephy_spinner_start (EPHY_SPINNER (dialog->throbber)); + } else { + ephy_spinner_stop (EPHY_SPINNER (dialog->throbber)); + } + + /* Update the refresh toggle button */ + g_signal_handlers_block_by_func (dialog->togglebutton_refresh, + new_chatroom_dialog_togglebutton_refresh_toggled_cb, + dialog); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (dialog->togglebutton_refresh), + listing); + g_signal_handlers_unblock_by_func (dialog->togglebutton_refresh, + new_chatroom_dialog_togglebutton_refresh_toggled_cb, + dialog); +} + +static void +new_chatroom_dialog_model_clear (EmpathyNewChatroomDialog *dialog) +{ + GtkListStore *store; + + store = GTK_LIST_STORE (dialog->model); + gtk_list_store_clear (store); +} + +static void +new_chatroom_dialog_model_row_activated_cb (GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + EmpathyNewChatroomDialog *dialog) +{ + gtk_widget_activate (dialog->button_join); +} + +static void +new_chatroom_dialog_model_selection_changed (GtkTreeSelection *selection, + EmpathyNewChatroomDialog *dialog) +{ + GtkTreeModel *model; + GtkTreeIter iter; + gchar *room = NULL; + gchar *server = NULL; + + if (!gtk_tree_selection_get_selected (selection, &model, &iter)) { + return; + } + + gtk_tree_model_get (model, &iter, COL_ROOM, &room, -1); + server = strstr (room, "@"); + if (server) { + *server = '\0'; + server++; + } + + gtk_entry_set_text (GTK_ENTRY (dialog->entry_server), server ? server : ""); + gtk_entry_set_text (GTK_ENTRY (dialog->entry_room), room ? room : ""); + + g_free (room); +} + +static void +new_chatroom_dialog_join (EmpathyNewChatroomDialog *dialog) +{ + McAccount *account; + EmpathyAccountChooser *account_chooser; + MissionControl *mc; + const gchar *room; + const gchar *server = NULL; + gchar *room_name = NULL; + + room = gtk_entry_get_text (GTK_ENTRY (dialog->entry_room)); + server = gtk_entry_get_text (GTK_ENTRY (dialog->entry_server)); + + account_chooser = EMPATHY_ACCOUNT_CHOOSER (dialog->account_chooser); + account = empathy_account_chooser_get_account (account_chooser); + + if (!G_STR_EMPTY (server)) { + room_name = g_strconcat (room, "@", server, NULL); + } else { + room_name = g_strdup (room); + } + + DEBUG ("Requesting channel for '%s'", room_name); + + mc = empathy_mission_control_new (); + mission_control_request_channel_with_string_handle (mc, + account, + TP_IFACE_CHANNEL_TYPE_TEXT, + room_name, + TP_HANDLE_TYPE_ROOM, + NULL, NULL); + g_free (room_name); + g_object_unref (mc); +} + +static void +new_chatroom_dialog_entry_changed_cb (GtkWidget *entry, + EmpathyNewChatroomDialog *dialog) +{ + if (entry == dialog->entry_room) { + const gchar *room; + + room = gtk_entry_get_text (GTK_ENTRY (dialog->entry_room)); + gtk_widget_set_sensitive (dialog->button_join, !G_STR_EMPTY (room)); + /* FIXME: Select the room in the list */ + } +} + +static void +new_chatroom_dialog_browse_start (EmpathyNewChatroomDialog *dialog) +{ + new_chatroom_dialog_model_clear (dialog); + if (dialog->room_list) { + empathy_tp_roomlist_start (dialog->room_list); + } +} + +static void +new_chatroom_dialog_browse_stop (EmpathyNewChatroomDialog *dialog) +{ + if (dialog->room_list) { + empathy_tp_roomlist_stop (dialog->room_list); + } +} + +static void +new_chatroom_dialog_entry_server_activate_cb (GtkWidget *widget, + EmpathyNewChatroomDialog *dialog) +{ + new_chatroom_dialog_togglebutton_refresh_toggled_cb (dialog->togglebutton_refresh, + dialog); +} + +static void +new_chatroom_dialog_togglebutton_refresh_toggled_cb (GtkWidget *widget, + EmpathyNewChatroomDialog *dialog) +{ + gboolean toggled; + + toggled = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget)); + + if (toggled) { + new_chatroom_dialog_browse_start (dialog); + } else { + new_chatroom_dialog_browse_stop (dialog); + } +} + diff --git a/trunk/src/empathy-new-chatroom-dialog.glade b/trunk/src/empathy-new-chatroom-dialog.glade new file mode 100644 index 000000000..4a4065701 --- /dev/null +++ b/trunk/src/empathy-new-chatroom-dialog.glade @@ -0,0 +1,271 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd"> +<!--*- mode: xml -*--> +<glade-interface> + <widget class="GtkDialog" id="new_chatroom_dialog"> + <property name="visible">True</property> + <property name="border_width">5</property> + <property name="title" translatable="yes">Join New</property> + <property name="resizable">False</property> + <property name="default_width">350</property> + <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property> + <property name="has_separator">False</property> + <child internal-child="vbox"> + <widget class="GtkVBox" id="dialog-vbox4"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <widget class="GtkVBox" id="vbox_widgets"> + <property name="visible">True</property> + <property name="border_width">5</property> + <property name="spacing">18</property> + <child> + <widget class="GtkTable" id="table_info"> + <property name="visible">True</property> + <property name="n_rows">3</property> + <property name="n_columns">3</property> + <property name="column_spacing">6</property> + <property name="row_spacing">6</property> + <child> + <placeholder/> + </child> + <child> + <placeholder/> + </child> + <child> + <widget class="GtkEntry" id="entry_room"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="tooltip" translatable="yes">Enter the room name to join here or click on one or more rooms in the list.</property> + <property name="activates_default">True</property> + <property name="width_chars">32</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">3</property> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkToggleButton" id="togglebutton_refresh"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">Re_fresh</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + </widget> + <packing> + <property name="left_attach">2</property> + <property name="right_attach">3</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkEntry" id="entry_server"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="tooltip" translatable="yes">Enter the server which hosts the room, or leave it empty if the room is on the current account's server</property> + <property name="width_chars">25</property> + </widget> + <packing> + <property name="left_attach">1</property> + <property name="right_attach">2</property> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label_room"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">_Room:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">entry_room</property> + </widget> + <packing> + <property name="top_attach">2</property> + <property name="bottom_attach">3</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label_server"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">_Server:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">entry_server</property> + </widget> + <packing> + <property name="top_attach">1</property> + <property name="bottom_attach">2</property> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label_account"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Account:</property> + </widget> + <packing> + <property name="x_options">GTK_FILL</property> + <property name="y_options"></property> + </packing> + </child> + </widget> + </child> + <child> + <widget class="GtkVBox" id="vbox_browse"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <widget class="GtkHBox" id="hbox_status"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <widget class="GtkHBox" id="hbox35"> + <property name="visible">True</property> + <property name="spacing">3</property> + <child> + <widget class="GtkImage" id="image_status"> + <property name="visible">True</property> + <property name="icon_size">2</property> + <property name="icon_name">gtk-find</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label_status"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes">Browse:</property> + <property name="wrap">True</property> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + </widget> + </child> + <child> + <placeholder/> + </child> + </widget> + </child> + <child> + <widget class="GtkScrolledWindow" id="scrolledwindow2"> + <property name="height_request">150</property> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="hscrollbar_policy">GTK_POLICY_NEVER</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="shadow_type">GTK_SHADOW_IN</property> + <child> + <widget class="GtkTreeView" id="treeview"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="tooltip" translatable="yes">This list represents all chat rooms hosted on the server you have entered.</property> + <property name="headers_visible">False</property> + </widget> + </child> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child internal-child="action_area"> + <widget class="GtkHButtonBox" id="dialog-action_area4"> + <property name="visible">True</property> + <property name="layout_style">GTK_BUTTONBOX_END</property> + <child> + <widget class="GtkButton" id="button_cancel"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="label">gtk-close</property> + <property name="use_stock">True</property> + <property name="response_id">-7</property> + </widget> + </child> + <child> + <widget class="GtkButton" id="button_join"> + <property name="visible">True</property> + <property name="sensitive">False</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="has_default">True</property> + <property name="response_id">-5</property> + <child> + <widget class="GtkAlignment" id="alignment4"> + <property name="visible">True</property> + <property name="xscale">0</property> + <property name="yscale">0</property> + <child> + <widget class="GtkHBox" id="hbox29"> + <property name="visible">True</property> + <property name="spacing">2</property> + <child> + <widget class="GtkImage" id="image4"> + <property name="visible">True</property> + <property name="stock">gtk-execute</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label79"> + <property name="visible">True</property> + <property name="label" translatable="yes">Join</property> + <property name="use_underline">True</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </widget> + </child> + </widget> + </child> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="pack_type">GTK_PACK_END</property> + </packing> + </child> + </widget> + </child> + </widget> +</glade-interface> diff --git a/trunk/src/empathy-new-chatroom-dialog.h b/trunk/src/empathy-new-chatroom-dialog.h new file mode 100644 index 000000000..ae8b2385e --- /dev/null +++ b/trunk/src/empathy-new-chatroom-dialog.h @@ -0,0 +1,34 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2006-2007 Imendio AB + * Copyright (C) 2007-2008 Collabora Ltd. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Martyn Russell <martyn@imendio.com> + * Xavier Claessens <xclaesse@gmail.com> + */ + +#ifndef __EMPATHY_NEW_CHATROOMS_WINDOW_H__ +#define __EMPATHY_NEW_CHATROOMS_WINDOW_H__ + +G_BEGIN_DECLS + +void empathy_new_chatroom_dialog_show (GtkWindow *parent); + +G_END_DECLS + +#endif /* __EMPATHY_NEW_CHATROOMS_WINDOW_H__ */ diff --git a/trunk/src/empathy-preferences.c b/trunk/src/empathy-preferences.c new file mode 100644 index 000000000..865fdc82e --- /dev/null +++ b/trunk/src/empathy-preferences.c @@ -0,0 +1,994 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2003-2007 Imendio AB + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Mikael Hallendal <micke@imendio.com> + * Richard Hult <richard@imendio.com> + * Martyn Russell <martyn@imendio.com> + */ + +#include <config.h> + +#include <string.h> + +#include <gtk/gtk.h> +#include <glade/glade.h> +#include <glib/gi18n.h> + +#include <libempathy/empathy-utils.h> + +#include <libempathy-gtk/empathy-conf.h> +#include <libempathy-gtk/empathy-ui-utils.h> +#include <libempathy-gtk/empathy-theme-manager.h> +#include <libempathy-gtk/empathy-spell.h> +#include <libempathy-gtk/empathy-contact-list-store.h> +#include <libempathy-gtk/empathy-gtk-enum-types.h> + +#include "empathy-preferences.h" + +typedef struct { + GtkWidget *dialog; + + GtkWidget *notebook; + + GtkWidget *checkbutton_show_avatars; + GtkWidget *checkbutton_compact_contact_list; + GtkWidget *checkbutton_show_smileys; + GtkWidget *combobox_chat_theme; + GtkWidget *checkbutton_separate_chat_windows; + GtkWidget *checkbutton_autoconnect; + GtkWidget *radiobutton_contact_list_sort_by_name; + GtkWidget *radiobutton_contact_list_sort_by_state; + + GtkWidget *checkbutton_sounds_for_messages; + GtkWidget *checkbutton_sounds_when_busy; + GtkWidget *checkbutton_sounds_when_away; + GtkWidget *checkbutton_popups_when_available; + + GtkWidget *treeview_spell_checker; + + GList *notify_ids; +} EmpathyPreferences; + +static void preferences_setup_widgets (EmpathyPreferences *preferences); +static void preferences_languages_setup (EmpathyPreferences *preferences); +static void preferences_languages_add (EmpathyPreferences *preferences); +static void preferences_languages_save (EmpathyPreferences *preferences); +static gboolean preferences_languages_save_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gchar **languages); +static void preferences_languages_load (EmpathyPreferences *preferences); +static gboolean preferences_languages_load_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gchar **languages); +static void preferences_languages_cell_toggled_cb (GtkCellRendererToggle *cell, + gchar *path_string, + EmpathyPreferences *preferences); +static void preferences_themes_setup (EmpathyPreferences *preferences); +static void preferences_widget_sync_bool (const gchar *key, + GtkWidget *widget); +static void preferences_widget_sync_string (const gchar *key, + GtkWidget *widget); +static void preferences_widget_sync_string_combo (const gchar *key, + GtkWidget *widget); +static void preferences_notify_string_cb (EmpathyConf *conf, + const gchar *key, + gpointer user_data); +static void preferences_notify_string_combo_cb (EmpathyConf *conf, + const gchar *key, + gpointer user_data); +static void preferences_notify_bool_cb (EmpathyConf *conf, + const gchar *key, + gpointer user_data); +static void preferences_notify_sensitivity_cb (EmpathyConf *conf, + const gchar *key, + gpointer user_data); +static void preferences_hookup_toggle_button (EmpathyPreferences *preferences, + const gchar *key, + GtkWidget *widget); +static void preferences_hookup_radio_button (EmpathyPreferences *preferences, + const gchar *key, + GtkWidget *widget); +static void preferences_hookup_string_combo (EmpathyPreferences *preferences, + const gchar *key, + GtkWidget *widget); +static void preferences_hookup_sensitivity (EmpathyPreferences *preferences, + const gchar *key, + GtkWidget *widget); +static void preferences_toggle_button_toggled_cb (GtkWidget *button, + gpointer user_data); +static void preferences_radio_button_toggled_cb (GtkWidget *button, + gpointer user_data); +static void preferences_string_combo_changed_cb (GtkWidget *button, + gpointer user_data); +static void preferences_destroy_cb (GtkWidget *widget, + EmpathyPreferences *preferences); +static void preferences_response_cb (GtkWidget *widget, + gint response, + EmpathyPreferences *preferences); + +enum { + COL_LANG_ENABLED, + COL_LANG_CODE, + COL_LANG_NAME, + COL_LANG_COUNT +}; + +enum { + COL_COMBO_VISIBLE_NAME, + COL_COMBO_NAME, + COL_COMBO_COUNT +}; + +static void +preferences_add_id (EmpathyPreferences *preferences, guint id) +{ + preferences->notify_ids = g_list_prepend (preferences->notify_ids, + GUINT_TO_POINTER (id)); +} + +static void +preferences_compact_contact_list_changed_cb (EmpathyConf *conf, + const gchar *key, + gpointer user_data) +{ + EmpathyPreferences *preferences = user_data; + gboolean value; + + if (empathy_conf_get_bool (empathy_conf_get (), key, &value)) { + gtk_widget_set_sensitive (preferences->checkbutton_show_avatars, + !value); + } +} + +static void +preferences_setup_widgets (EmpathyPreferences *preferences) +{ + guint id; + + preferences_hookup_toggle_button (preferences, + EMPATHY_PREFS_SOUNDS_FOR_MESSAGES, + preferences->checkbutton_sounds_for_messages); + preferences_hookup_toggle_button (preferences, + EMPATHY_PREFS_SOUNDS_WHEN_AWAY, + preferences->checkbutton_sounds_when_away); + preferences_hookup_toggle_button (preferences, + EMPATHY_PREFS_SOUNDS_WHEN_BUSY, + preferences->checkbutton_sounds_when_busy); + preferences_hookup_toggle_button (preferences, + EMPATHY_PREFS_POPUPS_WHEN_AVAILABLE, + preferences->checkbutton_popups_when_available); + + preferences_hookup_sensitivity (preferences, + EMPATHY_PREFS_SOUNDS_FOR_MESSAGES, + preferences->checkbutton_sounds_when_away); + preferences_hookup_sensitivity (preferences, + EMPATHY_PREFS_SOUNDS_FOR_MESSAGES, + preferences->checkbutton_sounds_when_busy); + + preferences_hookup_toggle_button (preferences, + EMPATHY_PREFS_UI_SEPARATE_CHAT_WINDOWS, + preferences->checkbutton_separate_chat_windows); + + preferences_hookup_toggle_button (preferences, + EMPATHY_PREFS_UI_SHOW_AVATARS, + preferences->checkbutton_show_avatars); + + preferences_hookup_toggle_button (preferences, + EMPATHY_PREFS_UI_COMPACT_CONTACT_LIST, + preferences->checkbutton_compact_contact_list); + + preferences_hookup_toggle_button (preferences, + EMPATHY_PREFS_CHAT_SHOW_SMILEYS, + preferences->checkbutton_show_smileys); + + preferences_hookup_string_combo (preferences, + EMPATHY_PREFS_CHAT_THEME, + preferences->combobox_chat_theme); + + preferences_hookup_radio_button (preferences, + EMPATHY_PREFS_CONTACTS_SORT_CRITERIUM, + preferences->radiobutton_contact_list_sort_by_name); + + preferences_hookup_toggle_button (preferences, + EMPATHY_PREFS_AUTOCONNECT, + preferences->checkbutton_autoconnect); + + id = empathy_conf_notify_add (empathy_conf_get (), + EMPATHY_PREFS_UI_COMPACT_CONTACT_LIST, + preferences_compact_contact_list_changed_cb, + preferences); + if (id) { + preferences_add_id (preferences, id); + } + preferences_compact_contact_list_changed_cb (empathy_conf_get (), + EMPATHY_PREFS_UI_COMPACT_CONTACT_LIST, + preferences); +} + +static void +preferences_languages_setup (EmpathyPreferences *preferences) +{ + GtkTreeView *view; + GtkListStore *store; + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkTreeViewColumn *column; + GtkCellRenderer *renderer; + guint col_offset; + + view = GTK_TREE_VIEW (preferences->treeview_spell_checker); + + store = gtk_list_store_new (COL_LANG_COUNT, + G_TYPE_BOOLEAN, /* enabled */ + G_TYPE_STRING, /* code */ + G_TYPE_STRING); /* name */ + + gtk_tree_view_set_model (view, GTK_TREE_MODEL (store)); + + selection = gtk_tree_view_get_selection (view); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); + + model = GTK_TREE_MODEL (store); + + renderer = gtk_cell_renderer_toggle_new (); + g_signal_connect (renderer, "toggled", + G_CALLBACK (preferences_languages_cell_toggled_cb), + preferences); + + column = gtk_tree_view_column_new_with_attributes (NULL, renderer, + "active", COL_LANG_ENABLED, + NULL); + + gtk_tree_view_append_column (view, column); + + renderer = gtk_cell_renderer_text_new (); + col_offset = gtk_tree_view_insert_column_with_attributes (view, + -1, _("Language"), + renderer, + "text", COL_LANG_NAME, + NULL); + + g_object_set_data (G_OBJECT (renderer), + "column", GINT_TO_POINTER (COL_LANG_NAME)); + + column = gtk_tree_view_get_column (view, col_offset - 1); + gtk_tree_view_column_set_sort_column_id (column, COL_LANG_NAME); + gtk_tree_view_column_set_resizable (column, FALSE); + gtk_tree_view_column_set_clickable (GTK_TREE_VIEW_COLUMN (column), TRUE); + + g_object_unref (store); +} + +static void +preferences_languages_add (EmpathyPreferences *preferences) +{ + GtkTreeView *view; + GtkListStore *store; + GList *codes, *l; + + view = GTK_TREE_VIEW (preferences->treeview_spell_checker); + store = GTK_LIST_STORE (gtk_tree_view_get_model (view)); + + codes = empathy_spell_get_language_codes (); + + empathy_conf_set_bool (empathy_conf_get(), + EMPATHY_PREFS_CHAT_SPELL_CHECKER_ENABLED, + codes != NULL); + if (!codes) { + gtk_widget_set_sensitive (preferences->treeview_spell_checker, FALSE); + } + + for (l = codes; l; l = l->next) { + GtkTreeIter iter; + const gchar *code; + const gchar *name; + + code = l->data; + name = empathy_spell_get_language_name (code); + if (!name) { + continue; + } + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COL_LANG_CODE, code, + COL_LANG_NAME, name, + -1); + } + + empathy_spell_free_language_codes (codes); +} + +static void +preferences_languages_save (EmpathyPreferences *preferences) +{ + GtkTreeView *view; + GtkTreeModel *model; + + gchar *languages = NULL; + + view = GTK_TREE_VIEW (preferences->treeview_spell_checker); + model = gtk_tree_view_get_model (view); + + gtk_tree_model_foreach (model, + (GtkTreeModelForeachFunc) preferences_languages_save_foreach, + &languages); + + /* if user selects no languages, we don't want spell check */ + empathy_conf_set_bool (empathy_conf_get (), + EMPATHY_PREFS_CHAT_SPELL_CHECKER_ENABLED, + languages != NULL); + + empathy_conf_set_string (empathy_conf_get (), + EMPATHY_PREFS_CHAT_SPELL_CHECKER_LANGUAGES, + languages ? languages : ""); + + g_free (languages); +} + +static gboolean +preferences_languages_save_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gchar **languages) +{ + gboolean enabled; + gchar *code; + + if (!languages) { + return TRUE; + } + + gtk_tree_model_get (model, iter, COL_LANG_ENABLED, &enabled, -1); + if (!enabled) { + return FALSE; + } + + gtk_tree_model_get (model, iter, COL_LANG_CODE, &code, -1); + if (!code) { + return FALSE; + } + + if (!(*languages)) { + *languages = g_strdup (code); + } else { + gchar *str = *languages; + *languages = g_strdup_printf ("%s,%s", str, code); + g_free (str); + } + + g_free (code); + + return FALSE; +} + +static void +preferences_languages_load (EmpathyPreferences *preferences) +{ + GtkTreeView *view; + GtkTreeModel *model; + gchar *value; + gchar **vlanguages; + + if (!empathy_conf_get_string (empathy_conf_get (), + EMPATHY_PREFS_CHAT_SPELL_CHECKER_LANGUAGES, + &value) || !value) { + return; + } + + vlanguages = g_strsplit (value, ",", -1); + g_free (value); + + view = GTK_TREE_VIEW (preferences->treeview_spell_checker); + model = gtk_tree_view_get_model (view); + + gtk_tree_model_foreach (model, + (GtkTreeModelForeachFunc) preferences_languages_load_foreach, + vlanguages); + + g_strfreev (vlanguages); +} + +static gboolean +preferences_languages_load_foreach (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gchar **languages) +{ + gchar *code; + gchar *lang; + gint i; + gboolean found = FALSE; + + if (!languages) { + return TRUE; + } + + gtk_tree_model_get (model, iter, COL_LANG_CODE, &code, -1); + if (!code) { + return FALSE; + } + + for (i = 0, lang = languages[i]; lang; lang = languages[++i]) { + if (strcmp (lang, code) == 0) { + found = TRUE; + } + } + + gtk_list_store_set (GTK_LIST_STORE (model), iter, COL_LANG_ENABLED, found, -1); + return FALSE; +} + +static void +preferences_languages_cell_toggled_cb (GtkCellRendererToggle *cell, + gchar *path_string, + EmpathyPreferences *preferences) +{ + GtkTreeView *view; + GtkTreeModel *model; + GtkListStore *store; + GtkTreePath *path; + GtkTreeIter iter; + gboolean enabled; + + view = GTK_TREE_VIEW (preferences->treeview_spell_checker); + model = gtk_tree_view_get_model (view); + store = GTK_LIST_STORE (model); + + path = gtk_tree_path_new_from_string (path_string); + + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, COL_LANG_ENABLED, &enabled, -1); + + enabled ^= 1; + + gtk_list_store_set (store, &iter, COL_LANG_ENABLED, enabled, -1); + gtk_tree_path_free (path); + + preferences_languages_save (preferences); +} + +static void +preferences_themes_setup (EmpathyPreferences *preferences) +{ + GtkComboBox *combo; + GtkListStore *model; + GtkTreeIter iter; + const gchar **themes; + gint i; + + combo = GTK_COMBO_BOX (preferences->combobox_chat_theme); + + model = gtk_list_store_new (COL_COMBO_COUNT, + G_TYPE_STRING, + G_TYPE_STRING); + + themes = empathy_theme_manager_get_themes (); + for (i = 0; themes[i]; i += 2) { + gtk_list_store_append (model, &iter); + gtk_list_store_set (model, &iter, + COL_COMBO_VISIBLE_NAME, _(themes[i + 1]), + COL_COMBO_NAME, themes[i], + -1); + } + + gtk_combo_box_set_model (combo, GTK_TREE_MODEL (model)); + g_object_unref (model); +} + +static void +preferences_widget_sync_bool (const gchar *key, GtkWidget *widget) +{ + gboolean value; + + if (empathy_conf_get_bool (empathy_conf_get (), key, &value)) { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (widget), value); + } +} + +static void +preferences_widget_sync_string (const gchar *key, GtkWidget *widget) +{ + gchar *value; + + if (empathy_conf_get_string (empathy_conf_get (), key, &value) && value) { + if (GTK_IS_ENTRY (widget)) { + gtk_entry_set_text (GTK_ENTRY (widget), value); + } else if (GTK_IS_RADIO_BUTTON (widget)) { + if (strcmp (key, EMPATHY_PREFS_CONTACTS_SORT_CRITERIUM) == 0) { + GType type; + GEnumClass *enum_class; + GEnumValue *enum_value; + GSList *list; + GtkWidget *toggle_widget; + + /* Get index from new string */ + type = empathy_contact_list_store_sort_get_type (); + enum_class = G_ENUM_CLASS (g_type_class_peek (type)); + enum_value = g_enum_get_value_by_nick (enum_class, value); + + if (enum_value) { + list = gtk_radio_button_get_group (GTK_RADIO_BUTTON (widget)); + toggle_widget = g_slist_nth_data (list, enum_value->value); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle_widget), TRUE); + } + } else { + g_warning ("Unhandled key:'%s' just had string change", key); + } + } + + g_free (value); + } +} + +static void +preferences_widget_sync_string_combo (const gchar *key, GtkWidget *widget) +{ + gchar *value; + GtkTreeModel *model; + GtkTreeIter iter; + gboolean found; + + if (!empathy_conf_get_string (empathy_conf_get (), key, &value)) { + return; + } + + model = gtk_combo_box_get_model (GTK_COMBO_BOX (widget)); + + found = FALSE; + if (value && gtk_tree_model_get_iter_first (model, &iter)) { + gchar *name; + + do { + gtk_tree_model_get (model, &iter, + COL_COMBO_NAME, &name, + -1); + + if (strcmp (name, value) == 0) { + found = TRUE; + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (widget), &iter); + break; + } else { + found = FALSE; + } + + g_free (name); + } while (gtk_tree_model_iter_next (model, &iter)); + } + + /* Fallback to the first one. */ + if (!found) { + if (gtk_tree_model_get_iter_first (model, &iter)) { + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (widget), &iter); + } + } + + g_free (value); +} + +static void +preferences_notify_string_cb (EmpathyConf *conf, + const gchar *key, + gpointer user_data) +{ + preferences_widget_sync_string (key, user_data); +} + +static void +preferences_notify_string_combo_cb (EmpathyConf *conf, + const gchar *key, + gpointer user_data) +{ + preferences_widget_sync_string_combo (key, user_data); +} + +static void +preferences_notify_bool_cb (EmpathyConf *conf, + const gchar *key, + gpointer user_data) +{ + preferences_widget_sync_bool (key, user_data); +} + +static void +preferences_notify_sensitivity_cb (EmpathyConf *conf, + const gchar *key, + gpointer user_data) +{ + gboolean value; + + if (empathy_conf_get_bool (conf, key, &value)) { + gtk_widget_set_sensitive (GTK_WIDGET (user_data), value); + } +} + +#if 0 +static void +preferences_widget_sync_int (const gchar *key, GtkWidget *widget) +{ + gint value; + + if (empathy_conf_get_int (empathy_conf_get (), key, &value)) { + if (GTK_IS_SPIN_BUTTON (widget)) { + gtk_spin_button_set_value (GTK_SPIN_BUTTON (widget), value); + } + } +} + +static void +preferences_notify_int_cb (EmpathyConf *conf, + const gchar *key, + gpointer user_data) +{ + preferences_widget_sync_int (key, user_data); +} + +static void +preferences_hookup_spin_button (EmpathyPreferences *preferences, + const gchar *key, + GtkWidget *widget) +{ + guint id; + + preferences_widget_sync_int (key, widget); + + g_object_set_data_full (G_OBJECT (widget), "key", + g_strdup (key), g_free); + + g_signal_connect (widget, + "value_changed", + G_CALLBACK (preferences_spin_button_value_changed_cb), + NULL); + + id = empathy_conf_notify_add (empathy_conf_get (), + key, + preferences_notify_int_cb, + widget); + if (id) { + preferences_add_id (preferences, id); + } +} + +static void +preferences_hookup_entry (EmpathyPreferences *preferences, + const gchar *key, + GtkWidget *widget) +{ + guint id; + + preferences_widget_sync_string (key, widget); + + g_object_set_data_full (G_OBJECT (widget), "key", + g_strdup (key), g_free); + + g_signal_connect (widget, + "changed", + G_CALLBACK (preferences_entry_value_changed_cb), + NULL); + + id = empathy_conf_notify_add (empathy_conf_get (), + key, + preferences_notify_string_cb, + widget); + if (id) { + preferences_add_id (preferences, id); + } +} + +static void +preferences_spin_button_value_changed_cb (GtkWidget *button, + gpointer user_data) +{ + const gchar *key; + + key = g_object_get_data (G_OBJECT (button), "key"); + + empathy_conf_set_int (empathy_conf_get (), + key, + gtk_spin_button_get_value (GTK_SPIN_BUTTON (button))); +} + +static void +preferences_entry_value_changed_cb (GtkWidget *entry, + gpointer user_data) +{ + const gchar *key; + + key = g_object_get_data (G_OBJECT (entry), "key"); + + empathy_conf_set_string (empathy_conf_get (), + key, + gtk_entry_get_text (GTK_ENTRY (entry))); +} +#endif + +static void +preferences_hookup_toggle_button (EmpathyPreferences *preferences, + const gchar *key, + GtkWidget *widget) +{ + guint id; + + preferences_widget_sync_bool (key, widget); + + g_object_set_data_full (G_OBJECT (widget), "key", + g_strdup (key), g_free); + + g_signal_connect (widget, + "toggled", + G_CALLBACK (preferences_toggle_button_toggled_cb), + NULL); + + id = empathy_conf_notify_add (empathy_conf_get (), + key, + preferences_notify_bool_cb, + widget); + if (id) { + preferences_add_id (preferences, id); + } +} + +static void +preferences_hookup_radio_button (EmpathyPreferences *preferences, + const gchar *key, + GtkWidget *widget) +{ + GSList *group, *l; + guint id; + + preferences_widget_sync_string (key, widget); + + group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (widget)); + for (l = group; l; l = l->next) { + g_signal_connect (l->data, + "toggled", + G_CALLBACK (preferences_radio_button_toggled_cb), + NULL); + + g_object_set_data_full (G_OBJECT (l->data), "key", + g_strdup (key), g_free); + } + + id = empathy_conf_notify_add (empathy_conf_get (), + key, + preferences_notify_string_cb, + widget); + if (id) { + preferences_add_id (preferences, id); + } +} + +static void +preferences_hookup_string_combo (EmpathyPreferences *preferences, + const gchar *key, + GtkWidget *widget) +{ + guint id; + + preferences_widget_sync_string_combo (key, widget); + + g_object_set_data_full (G_OBJECT (widget), "key", + g_strdup (key), g_free); + + g_signal_connect (widget, + "changed", + G_CALLBACK (preferences_string_combo_changed_cb), + NULL); + + id = empathy_conf_notify_add (empathy_conf_get (), + key, + preferences_notify_string_combo_cb, + widget); + if (id) { + preferences_add_id (preferences, id); + } +} + +static void +preferences_hookup_sensitivity (EmpathyPreferences *preferences, + const gchar *key, + GtkWidget *widget) +{ + gboolean value; + guint id; + + if (empathy_conf_get_bool (empathy_conf_get (), key, &value)) { + gtk_widget_set_sensitive (widget, value); + } + + id = empathy_conf_notify_add (empathy_conf_get (), + key, + preferences_notify_sensitivity_cb, + widget); + if (id) { + preferences_add_id (preferences, id); + } +} + +static void +preferences_toggle_button_toggled_cb (GtkWidget *button, + gpointer user_data) +{ + const gchar *key; + + key = g_object_get_data (G_OBJECT (button), "key"); + + empathy_conf_set_bool (empathy_conf_get (), + key, + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))); +} + +static void +preferences_radio_button_toggled_cb (GtkWidget *button, + gpointer user_data) +{ + const gchar *key; + const gchar *value = NULL; + + if (!gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (button))) { + return; + } + + key = g_object_get_data (G_OBJECT (button), "key"); + + if (key && strcmp (key, EMPATHY_PREFS_CONTACTS_SORT_CRITERIUM) == 0) { + GSList *group; + GType type; + GEnumClass *enum_class; + GEnumValue *enum_value; + + group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (button)); + + /* Get string from index */ + type = empathy_contact_list_store_sort_get_type (); + enum_class = G_ENUM_CLASS (g_type_class_peek (type)); + enum_value = g_enum_get_value (enum_class, g_slist_index (group, button)); + + if (!enum_value) { + g_warning ("No GEnumValue for EmpathyContactListSort with GtkRadioButton index:%d", + g_slist_index (group, button)); + return; + } + + value = enum_value->value_nick; + } + + empathy_conf_set_string (empathy_conf_get (), key, value); +} + +static void +preferences_string_combo_changed_cb (GtkWidget *combo, + gpointer user_data) +{ + const gchar *key; + GtkTreeModel *model; + GtkTreeIter iter; + gchar *name; + + key = g_object_get_data (G_OBJECT (combo), "key"); + + if (gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo), &iter)) { + model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo)); + + gtk_tree_model_get (model, &iter, + COL_COMBO_NAME, &name, + -1); + empathy_conf_set_string (empathy_conf_get (), key, name); + g_free (name); + } +} + +static void +preferences_response_cb (GtkWidget *widget, + gint response, + EmpathyPreferences *preferences) +{ + gtk_widget_destroy (widget); +} + +static void +preferences_destroy_cb (GtkWidget *widget, + EmpathyPreferences *preferences) +{ + GList *l; + + for (l = preferences->notify_ids; l; l = l->next) { + guint id; + + id = GPOINTER_TO_UINT (l->data); + empathy_conf_notify_remove (empathy_conf_get (), id); + } + + g_list_free (preferences->notify_ids); + g_free (preferences); +} + +GtkWidget * +empathy_preferences_show (GtkWindow *parent) +{ + static EmpathyPreferences *preferences; + GladeXML *glade; + gchar *filename; + + if (preferences) { + gtk_window_present (GTK_WINDOW (preferences->dialog)); + return preferences->dialog; + } + + preferences = g_new0 (EmpathyPreferences, 1); + + filename = empathy_file_lookup ("empathy-preferences.glade", "src"); + glade = empathy_glade_get_file (filename, + "preferences_dialog", + NULL, + "preferences_dialog", &preferences->dialog, + "notebook", &preferences->notebook, + "checkbutton_show_avatars", &preferences->checkbutton_show_avatars, + "checkbutton_compact_contact_list", &preferences->checkbutton_compact_contact_list, + "checkbutton_show_smileys", &preferences->checkbutton_show_smileys, + "combobox_chat_theme", &preferences->combobox_chat_theme, + "checkbutton_separate_chat_windows", &preferences->checkbutton_separate_chat_windows, + "checkbutton_autoconnect", &preferences->checkbutton_autoconnect, + "radiobutton_contact_list_sort_by_name", &preferences->radiobutton_contact_list_sort_by_name, + "radiobutton_contact_list_sort_by_state", &preferences->radiobutton_contact_list_sort_by_state, + "checkbutton_sounds_for_messages", &preferences->checkbutton_sounds_for_messages, + "checkbutton_sounds_when_busy", &preferences->checkbutton_sounds_when_busy, + "checkbutton_sounds_when_away", &preferences->checkbutton_sounds_when_away, + "checkbutton_popups_when_available", &preferences->checkbutton_popups_when_available, + "treeview_spell_checker", &preferences->treeview_spell_checker, + NULL); + g_free (filename); + + empathy_glade_connect (glade, + preferences, + "preferences_dialog", "destroy", preferences_destroy_cb, + "preferences_dialog", "response", preferences_response_cb, + NULL); + + g_object_unref (glade); + + g_object_add_weak_pointer (G_OBJECT (preferences->dialog), (gpointer) &preferences); + + preferences_themes_setup (preferences); + + preferences_setup_widgets (preferences); + + preferences_languages_setup (preferences); + preferences_languages_add (preferences); + preferences_languages_load (preferences); + + if (empathy_spell_supported ()) { + GtkWidget *page; + + page = gtk_notebook_get_nth_page (GTK_NOTEBOOK (preferences->notebook), 2); + gtk_widget_show (page); + } + + if (parent) { + gtk_window_set_transient_for (GTK_WINDOW (preferences->dialog), + GTK_WINDOW (parent)); + } + + gtk_widget_show (preferences->dialog); + + return preferences->dialog; +} + diff --git a/trunk/src/empathy-preferences.glade b/trunk/src/empathy-preferences.glade new file mode 100644 index 000000000..11a3a2a7f --- /dev/null +++ b/trunk/src/empathy-preferences.glade @@ -0,0 +1,604 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd"> +<!--*- mode: xml -*--> +<glade-interface> + <widget class="GtkDialog" id="preferences_dialog"> + <property name="border_width">5</property> + <property name="title" translatable="yes">Preferences</property> + <property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property> + <property name="icon_name">gtk-preferences</property> + <property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property> + <property name="has_separator">False</property> + <child internal-child="vbox"> + <widget class="GtkVBox" id="dialog-vbox5"> + <property name="visible">True</property> + <property name="spacing">2</property> + <child> + <widget class="GtkNotebook" id="notebook"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="border_width">5</property> + <child> + <widget class="GtkVBox" id="vbox197"> + <property name="visible">True</property> + <property name="border_width">12</property> + <property name="spacing">18</property> + <child> + <widget class="GtkFrame" id="frame3"> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <property name="shadow_type">GTK_SHADOW_NONE</property> + <child> + <widget class="GtkAlignment" id="alignment11"> + <property name="visible">True</property> + <property name="top_padding">6</property> + <property name="left_padding">12</property> + <child> + <widget class="GtkVBox" id="vbox199"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <widget class="GtkCheckButton" id="checkbutton_compact_contact_list"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">Show co_mpact contact list</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + <property name="draw_indicator">True</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <widget class="GtkCheckButton" id="checkbutton_show_avatars"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="tooltip" translatable="yes">Avatars are user chosen images shown in the contact list</property> + <property name="label" translatable="yes">Show _avatars</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + <property name="active">True</property> + <property name="draw_indicator">True</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <widget class="GtkCheckButton" id="checkbutton_show_smileys"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">Show _smileys as images</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + <property name="active">True</property> + <property name="draw_indicator">True</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkLabel" id="label611"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>Appearance</b></property> + <property name="use_markup">True</property> + </widget> + <packing> + <property name="type">label_item</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <widget class="GtkFrame" id="frame4"> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <property name="shadow_type">GTK_SHADOW_NONE</property> + <child> + <widget class="GtkAlignment" id="alignment12"> + <property name="visible">True</property> + <property name="top_padding">6</property> + <property name="left_padding">12</property> + <child> + <widget class="GtkVBox" id="vbox218"> + <property name="visible">True</property> + <child> + <widget class="GtkCheckButton" id="checkbutton_separate_chat_windows"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">_Open new chats in separate windows</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + <property name="draw_indicator">True</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <widget class="GtkCheckButton" id="checkbutton_autoconnect"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">Automatically _connect on startup </property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + <property name="active">True</property> + <property name="draw_indicator">True</property> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkLabel" id="label612"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>Behaviour</b></property> + <property name="use_markup">True</property> + </widget> + <packing> + <property name="type">label_item</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <widget class="GtkFrame" id="frame13"> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <property name="shadow_type">GTK_SHADOW_NONE</property> + <child> + <widget class="GtkAlignment" id="alignment31"> + <property name="visible">True</property> + <property name="top_padding">6</property> + <property name="left_padding">12</property> + <child> + <widget class="GtkVBox" id="vbox217"> + <property name="visible">True</property> + <child> + <widget class="GtkRadioButton" id="radiobutton_contact_list_sort_by_name"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">Sort by _name</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + <property name="draw_indicator">True</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <widget class="GtkRadioButton" id="radiobutton_contact_list_sort_by_state"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">Sort by s_tate</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + <property name="draw_indicator">True</property> + <property name="group">radiobutton_contact_list_sort_by_name</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkLabel" id="label644"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>Contact List</b></property> + <property name="use_markup">True</property> + </widget> + <packing> + <property name="type">label_item</property> + </packing> + </child> + </widget> + <packing> + <property name="position">2</property> + </packing> + </child> + </widget> + </child> + <child> + <widget class="GtkLabel" id="label602"> + <property name="visible">True</property> + <property name="label" translatable="yes">General</property> + </widget> + <packing> + <property name="type">tab</property> + <property name="tab_fill">False</property> + </packing> + </child> + <child> + <widget class="GtkVBox" id="outer_vbox"> + <property name="visible">True</property> + <property name="border_width">12</property> + <property name="spacing">18</property> + <child> + <widget class="GtkFrame" id="frame5"> + <property name="label_xalign">0</property> + <property name="shadow_type">GTK_SHADOW_NONE</property> + <child> + <widget class="GtkAlignment" id="alignment13"> + <property name="visible">True</property> + <property name="top_padding">6</property> + <property name="left_padding">12</property> + <child> + <widget class="GtkVBox" id="vbox106"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <widget class="GtkCheckButton" id="checkbutton_sounds_for_messages"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">_Play sound when messages arrive</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + <property name="draw_indicator">True</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <widget class="GtkCheckButton" id="checkbutton_sounds_when_busy"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">Enable sounds when _busy</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + <property name="draw_indicator">True</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <widget class="GtkCheckButton" id="checkbutton_sounds_when_away"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">Enable sounds when _away</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + <property name="draw_indicator">True</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> + </child> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkLabel" id="label613"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>Audio</b></property> + <property name="use_markup">True</property> + </widget> + <packing> + <property name="type">label_item</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <widget class="GtkFrame" id="frame6"> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <property name="shadow_type">GTK_SHADOW_NONE</property> + <child> + <widget class="GtkAlignment" id="alignment14"> + <property name="visible">True</property> + <property name="top_padding">6</property> + <property name="left_padding">12</property> + <child> + <widget class="GtkCheckButton" id="checkbutton_popups_when_available"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="label" translatable="yes">Display notifications when contacts come _online</property> + <property name="use_underline">True</property> + <property name="response_id">0</property> + <property name="draw_indicator">True</property> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkLabel" id="label614"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>Visual</b></property> + <property name="use_markup">True</property> + </widget> + <packing> + <property name="type">label_item</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label607"> + <property name="visible">True</property> + <property name="label" translatable="yes">Notifications</property> + </widget> + <packing> + <property name="type">tab</property> + <property name="position">1</property> + <property name="tab_fill">False</property> + </packing> + </child> + <child> + <widget class="GtkVBox" id="vbox168"> + <property name="border_width">12</property> + <property name="spacing">18</property> + <child> + <widget class="GtkFrame" id="frame7"> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <property name="shadow_type">GTK_SHADOW_NONE</property> + <child> + <widget class="GtkAlignment" id="alignment15"> + <property name="visible">True</property> + <property name="top_padding">6</property> + <property name="left_padding">12</property> + <child> + <widget class="GtkVBox" id="vbox201"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <widget class="GtkHBox" id="hbox153"> + <property name="visible">True</property> + <child> + <widget class="GtkHBox" id="hbox154"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <widget class="GtkScrolledWindow" id="scrolledwindow18"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property> + <property name="shadow_type">GTK_SHADOW_IN</property> + <child> + <widget class="GtkTreeView" id="treeview_spell_checker"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="headers_visible">False</property> + </widget> + </child> + </widget> + </child> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkHBox" id="hbox155"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <widget class="GtkImage" id="image422"> + <property name="visible">True</property> + <property name="yalign">0</property> + <property name="stock">gtk-dialog-info</property> + </widget> + <packing> + <property name="expand">False</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label616"> + <property name="visible">True</property> + <property name="xalign">0</property> + <property name="label" translatable="yes"><small>The list of languages reflects only the languages for which you have a dictionary installed.</small></property> + <property name="use_markup">True</property> + <property name="wrap">True</property> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="position">1</property> + </packing> + </child> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkLabel" id="label615"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>Enable spell checking for languages:</b></property> + <property name="use_markup">True</property> + </widget> + <packing> + <property name="type">label_item</property> + </packing> + </child> + </widget> + </child> + <child> + <placeholder/> + </child> + </widget> + <packing> + <property name="position">2</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label567"> + <property name="visible">True</property> + <property name="label" translatable="yes">Spell Checking</property> + </widget> + <packing> + <property name="type">tab</property> + <property name="position">2</property> + <property name="tab_fill">False</property> + </packing> + </child> + <child> + <widget class="GtkVBox" id="vbox206"> + <property name="visible">True</property> + <property name="border_width">12</property> + <property name="spacing">18</property> + <child> + <widget class="GtkFrame" id="frame11"> + <property name="visible">True</property> + <property name="label_xalign">0</property> + <property name="shadow_type">GTK_SHADOW_NONE</property> + <child> + <widget class="GtkAlignment" id="alignment19"> + <property name="visible">True</property> + <property name="top_padding">6</property> + <property name="left_padding">12</property> + <child> + <widget class="GtkVBox" id="vbox207"> + <property name="visible">True</property> + <property name="spacing">6</property> + <child> + <widget class="GtkHBox" id="hbox139"> + <property name="visible">True</property> + <property name="spacing">12</property> + <child> + <widget class="GtkLabel" id="label586"> + <property name="visible">True</property> + <property name="label" translatable="yes">Chat Th_eme:</property> + <property name="use_underline">True</property> + <property name="mnemonic_widget">combobox_chat_theme</property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + <child> + <widget class="GtkComboBox" id="combobox_chat_theme"> + <property name="visible">True</property> + <property name="items" translatable="yes"></property> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + </widget> + </child> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkLabel" id="label626"> + <property name="visible">True</property> + <property name="label" translatable="yes"><b>Appearance</b></property> + <property name="use_markup">True</property> + </widget> + <packing> + <property name="type">label_item</property> + </packing> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + </packing> + </child> + </widget> + <packing> + <property name="position">3</property> + </packing> + </child> + <child> + <widget class="GtkLabel" id="label624"> + <property name="visible">True</property> + <property name="label" translatable="yes">Themes</property> + </widget> + <packing> + <property name="type">tab</property> + <property name="position">3</property> + <property name="tab_fill">False</property> + </packing> + </child> + </widget> + <packing> + <property name="position">1</property> + </packing> + </child> + <child internal-child="action_area"> + <widget class="GtkHButtonBox" id="dialog-action_area5"> + <property name="visible">True</property> + <property name="layout_style">GTK_BUTTONBOX_END</property> + <child> + <widget class="GtkButton" id="button_close"> + <property name="visible">True</property> + <property name="can_focus">True</property> + <property name="can_default">True</property> + <property name="label">gtk-close</property> + <property name="use_stock">True</property> + <property name="response_id">-6</property> + </widget> + </child> + </widget> + <packing> + <property name="expand">False</property> + <property name="pack_type">GTK_PACK_END</property> + </packing> + </child> + </widget> + </child> + </widget> +</glade-interface> diff --git a/trunk/src/empathy-preferences.h b/trunk/src/empathy-preferences.h new file mode 100644 index 000000000..07ae6b523 --- /dev/null +++ b/trunk/src/empathy-preferences.h @@ -0,0 +1,38 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2003-2007 Imendio AB + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Mikael Hallendal <micke@imendio.com> + * Richard Hult <richard@imendio.com> + * Martyn Russell <martyn@imendio.com> + */ + +#ifndef __EMPATHY_PREFERENCES_H__ +#define __EMPATHY_PREFERENCES_H__ + +#include <gtk/gtkwindow.h> + +G_BEGIN_DECLS + +GtkWidget * empathy_preferences_show (GtkWindow *parent); + +G_END_DECLS + +#endif /* __EMPATHY_PREFERENCES_H__ */ + + diff --git a/trunk/src/empathy-status-icon.c b/trunk/src/empathy-status-icon.c new file mode 100644 index 000000000..9d8a8c8c2 --- /dev/null +++ b/trunk/src/empathy-status-icon.c @@ -0,0 +1,695 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2007-2008 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: Xavier Claessens <xclaesse@gmail.com> + */ + +#include <config.h> + +#include <string.h> + +#include <gtk/gtk.h> +#include <glade/glade.h> +#include <glib/gi18n.h> + +#include <telepathy-glib/util.h> + +#include <libempathy/empathy-utils.h> +#include <libempathy/empathy-idle.h> +#include <libempathy/empathy-contact-manager.h> +#include <libempathy/empathy-dispatcher.h> +#include <libempathy/empathy-tp-chat.h> +#include <libempathy/empathy-tp-group.h> + +#include <libempathy-gtk/empathy-presence-chooser.h> +#include <libempathy-gtk/empathy-conf.h> +#include <libempathy-gtk/empathy-ui-utils.h> +#include <libempathy-gtk/empathy-accounts-dialog.h> +#include <libempathy-gtk/empathy-images.h> +#include <libempathy-gtk/empathy-new-message-dialog.h> +#include <libempathy-gtk/empathy-contact-dialogs.h> + +#include "empathy-status-icon.h" +#include "empathy-preferences.h" + +#define DEBUG_FLAG EMPATHY_DEBUG_DISPATCHER +#include <libempathy/empathy-debug.h> + +/* Number of ms to wait when blinking */ +#define BLINK_TIMEOUT 500 + +typedef struct _StatusIconEvent StatusIconEvent; + +#define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyStatusIcon) +typedef struct { + GtkStatusIcon *icon; + EmpathyIdle *idle; + MissionControl *mc; + EmpathyDispatcher *dispatcher; + EmpathyContactManager *contact_manager; + GSList *events; + gboolean showing_event_icon; + guint blink_timeout; + gpointer token; + + GtkWindow *window; + GtkWidget *popup_menu; + GtkWidget *show_window_item; + GtkWidget *message_item; + GtkWidget *status_item; +} EmpathyStatusIconPriv; + +typedef void (*StatusIconEventFunc) (EmpathyStatusIcon *icon, + gpointer user_data); + +struct _StatusIconEvent { + gchar *icon_name; + gchar *message; + StatusIconEventFunc func; + gpointer user_data; +}; + +G_DEFINE_TYPE (EmpathyStatusIcon, empathy_status_icon, G_TYPE_OBJECT); + +static void +status_icon_event_free (StatusIconEvent *event) +{ + g_free (event->icon_name); + g_free (event->message); + g_slice_free (StatusIconEvent, event); +} + +static void +status_icon_set_visibility (EmpathyStatusIcon *icon, + gboolean visible, + gboolean store) +{ + EmpathyStatusIconPriv *priv = GET_PRIV (icon); + + if (store) { + empathy_conf_set_bool (empathy_conf_get (), + EMPATHY_PREFS_UI_MAIN_WINDOW_HIDDEN, !visible); + } + + if (!visible) { + empathy_window_iconify (priv->window, priv->icon); + } else { + GList *accounts; + + empathy_window_present (GTK_WINDOW (priv->window), TRUE); + + /* Show the accounts dialog if there is no enabled accounts */ + accounts = mc_accounts_list_by_enabled (TRUE); + if (accounts) { + mc_accounts_list_free (accounts); + } else { + DEBUG ("No enabled account, Showing account dialog"); + empathy_accounts_dialog_show (GTK_WINDOW (priv->window)); + } + } +} + +static void +status_icon_notify_visibility_cb (EmpathyConf *conf, + const gchar *key, + gpointer user_data) +{ + EmpathyStatusIcon *icon = user_data; + gboolean hidden = FALSE; + + if (empathy_conf_get_bool (conf, key, &hidden)) { + status_icon_set_visibility (icon, !hidden, FALSE); + } +} + +static void +status_icon_toggle_visibility (EmpathyStatusIcon *icon) +{ + EmpathyStatusIconPriv *priv = GET_PRIV (icon); + gboolean visible; + + visible = gtk_window_is_active (priv->window); + status_icon_set_visibility (icon, !visible, TRUE); +} + +static void +status_icon_update_tooltip (EmpathyStatusIcon *icon) +{ + EmpathyStatusIconPriv *priv = GET_PRIV (icon); + const gchar *tooltip = NULL; + + if (priv->events) { + tooltip = ((StatusIconEvent*)priv->events->data)->message; + } + + if (!tooltip) { + tooltip = empathy_idle_get_status (priv->idle); + } + + gtk_status_icon_set_tooltip (priv->icon, tooltip); +} + +static void +status_icon_update_icon (EmpathyStatusIcon *icon) +{ + EmpathyStatusIconPriv *priv = GET_PRIV (icon); + const gchar *icon_name; + + if (priv->events && priv->showing_event_icon) { + icon_name = ((StatusIconEvent*)priv->events->data)->icon_name; + } else { + McPresence state; + + state = empathy_idle_get_state (priv->idle); + icon_name = empathy_icon_name_for_presence (state); + } + + gtk_status_icon_set_from_icon_name (priv->icon, icon_name); +} + +static void +status_icon_idle_notify_cb (EmpathyStatusIcon *icon) +{ + status_icon_update_icon (icon); + status_icon_update_tooltip (icon); +} + +static gboolean +status_icon_delete_event_cb (GtkWidget *widget, + GdkEvent *event, + EmpathyStatusIcon *icon) +{ + status_icon_set_visibility (icon, FALSE, TRUE); + return TRUE; +} + +static void +status_icon_event_activate (EmpathyStatusIcon *icon, + StatusIconEvent *event) +{ + EmpathyStatusIconPriv *priv = GET_PRIV (icon); + + if (event->func) { + event->func (icon, event->user_data); + } + + priv->events = g_slist_remove (priv->events, event); + status_icon_event_free (event); + status_icon_update_tooltip (icon); + status_icon_update_icon (icon); + + if (!priv->events && priv->blink_timeout) { + g_source_remove (priv->blink_timeout); + priv->blink_timeout = 0; + } +} + +static void +status_icon_activate_cb (GtkStatusIcon *status_icon, + EmpathyStatusIcon *icon) +{ + EmpathyStatusIconPriv *priv = GET_PRIV (icon); + + DEBUG ("Activated: %s", priv->events ? "event" : "toggle"); + + if (priv->events) { + status_icon_event_activate (icon, priv->events->data); + } else { + status_icon_toggle_visibility (icon); + } +} + +static void +status_icon_show_hide_window_cb (GtkWidget *widget, + EmpathyStatusIcon *icon) +{ + gboolean visible; + + visible = gtk_check_menu_item_get_active (GTK_CHECK_MENU_ITEM (widget)); + status_icon_set_visibility (icon, visible, TRUE); +} + +static void +status_icon_new_message_cb (GtkWidget *widget, + EmpathyStatusIcon *icon) +{ + empathy_new_message_dialog_show (NULL); +} + +static void +status_icon_quit_cb (GtkWidget *window, + EmpathyStatusIcon *icon) +{ + gtk_main_quit (); +} + +static void +status_icon_popup_menu_cb (GtkStatusIcon *status_icon, + guint button, + guint activate_time, + EmpathyStatusIcon *icon) +{ + EmpathyStatusIconPriv *priv = GET_PRIV (icon); + GtkWidget *submenu; + gboolean show; + + show = empathy_window_get_is_visible (GTK_WINDOW (priv->window)); + + g_signal_handlers_block_by_func (priv->show_window_item, + status_icon_show_hide_window_cb, + icon); + gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (priv->show_window_item), + show); + g_signal_handlers_unblock_by_func (priv->show_window_item, + status_icon_show_hide_window_cb, + icon); + + submenu = empathy_presence_chooser_create_menu (); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (priv->status_item), + submenu); + + gtk_menu_popup (GTK_MENU (priv->popup_menu), + NULL, NULL, + gtk_status_icon_position_menu, + priv->icon, + button, + activate_time); +} + +static void +status_icon_create_menu (EmpathyStatusIcon *icon) +{ + EmpathyStatusIconPriv *priv = GET_PRIV (icon); + GladeXML *glade; + gchar *filename; + + filename = empathy_file_lookup ("empathy-status-icon.glade", "src"); + glade = empathy_glade_get_file (filename, + "tray_menu", + NULL, + "tray_menu", &priv->popup_menu, + "tray_show_list", &priv->show_window_item, + "tray_new_message", &priv->message_item, + "tray_status", &priv->status_item, + NULL); + g_free (filename); + + empathy_glade_connect (glade, + icon, + "tray_show_list", "toggled", status_icon_show_hide_window_cb, + "tray_new_message", "activate", status_icon_new_message_cb, + "tray_quit", "activate", status_icon_quit_cb, + NULL); + + g_object_unref (glade); +} + +static gboolean +status_icon_blink_timeout_cb (EmpathyStatusIcon *icon) +{ + EmpathyStatusIconPriv *priv = GET_PRIV (icon); + + priv->showing_event_icon = !priv->showing_event_icon; + status_icon_update_icon (icon); + + return TRUE; +} + +static void +status_icon_event_add (EmpathyStatusIcon *icon, + const gchar *icon_name, + const gchar *message, + StatusIconEventFunc func, + gpointer user_data) +{ + EmpathyStatusIconPriv *priv = GET_PRIV (icon); + StatusIconEvent *event; + gboolean had_events; + + DEBUG ("Adding event: %s", message); + + event = g_slice_new (StatusIconEvent); + event->icon_name = g_strdup (icon_name); + event->message = g_strdup (message); + event->func = func; + event->user_data = user_data; + + had_events = (priv->events != NULL); + priv->events = g_slist_append (priv->events, event); + if (!had_events) { + priv->showing_event_icon = TRUE; + status_icon_update_icon (icon); + status_icon_update_tooltip (icon); + + if (!priv->blink_timeout) { + priv->blink_timeout = g_timeout_add (BLINK_TIMEOUT, + (GSourceFunc) status_icon_blink_timeout_cb, + icon); + } + } +} + +static void +status_icon_channel_process (EmpathyStatusIcon *icon, + gpointer user_data) +{ + EmpathyStatusIconPriv *priv = GET_PRIV (icon); + TpChannel *channel = TP_CHANNEL (user_data); + + empathy_dispatcher_channel_process (priv->dispatcher, channel); + g_object_unref (channel); +} + +static gboolean +status_icon_chat_unref_idle (gpointer user_data) +{ + g_object_unref (user_data); + return FALSE; +} + +static void +status_icon_chat_message_received_cb (EmpathyTpChat *tp_chat, + EmpathyMessage *message, + EmpathyStatusIcon *icon) +{ + EmpathyContact *sender; + gchar *msg; + TpChannel *channel; + + g_idle_add (status_icon_chat_unref_idle, tp_chat); + g_signal_handlers_disconnect_by_func (tp_chat, + status_icon_chat_message_received_cb, + icon); + + sender = empathy_message_get_sender (message); + msg = g_strdup_printf (_("New message from %s:\n%s"), + empathy_contact_get_name (sender), + empathy_message_get_body (message)); + + channel = empathy_tp_chat_get_channel (tp_chat); + status_icon_event_add (icon, EMPATHY_IMAGE_NEW_MESSAGE, msg, + status_icon_channel_process, + g_object_ref (channel)); + + g_free (msg); +} + +static void +status_icon_filter_channel_cb (EmpathyDispatcher *dispatcher, + TpChannel *channel, + EmpathyStatusIcon *icon) +{ + gchar *channel_type; + + g_object_get (channel, "channel-type", &channel_type, NULL); + if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TEXT)) { + EmpathyTpChat *tp_chat; + + tp_chat = empathy_tp_chat_new (channel); + g_signal_connect (tp_chat, "message-received", + G_CALLBACK (status_icon_chat_message_received_cb), + icon); + } + else if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA)) { + EmpathyTpGroup *tp_group; + EmpathyContact *contact; + gchar *msg; + + tp_group = empathy_tp_group_new (channel); + empathy_run_until_ready (tp_group); + empathy_tp_group_get_invitation (tp_group, &contact); + empathy_contact_run_until_ready (contact, + EMPATHY_CONTACT_READY_NAME, + NULL); + + msg = g_strdup_printf (_("Incoming call from %s"), + empathy_contact_get_name (contact)); + + status_icon_event_add (icon, EMPATHY_IMAGE_VOIP, msg, + status_icon_channel_process, + g_object_ref (channel)); + + g_free (msg); + g_object_unref (contact); + g_object_unref (tp_group); + } + + g_free (channel_type); +} + +static void +status_icon_tube_process (EmpathyStatusIcon *icon, + gpointer user_data) +{ + EmpathyStatusIconPriv *priv = GET_PRIV (icon); + EmpathyDispatcherTube *tube = (EmpathyDispatcherTube*) user_data; + + if (tube->activatable) { + empathy_dispatcher_tube_process (priv->dispatcher, tube); + } else { + GtkWidget *dialog; + gchar *str; + + /* Tell the user that the tube can't be handled */ + str = g_strdup_printf (_("%s offered you an invitation, but " + "you don't have the needed external " + "application to handle it."), + empathy_contact_get_name (tube->initiator)); + + dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL, + GTK_MESSAGE_ERROR, + GTK_BUTTONS_OK, str); + gtk_window_set_title (GTK_WINDOW (dialog), + _("Invitation Error")); + g_free (str); + + gtk_widget_show (dialog); + g_signal_connect (dialog, "response", + G_CALLBACK (gtk_widget_destroy), + NULL); + } + + empathy_dispatcher_tube_unref (tube); +} + +static void +status_icon_filter_tube_cb (EmpathyDispatcher *dispatcher, + EmpathyDispatcherTube *tube, + EmpathyStatusIcon *icon) +{ + const gchar *icon_name; + gchar *msg; + + empathy_contact_run_until_ready (tube->initiator, + EMPATHY_CONTACT_READY_NAME, NULL); + + if (tube->activatable) { + icon_name = GTK_STOCK_EXECUTE; + msg = g_strdup_printf (_("%s is offering you an invitation. An external " + "application will be started to handle it."), + empathy_contact_get_name (tube->initiator)); + } else { + icon_name = GTK_STOCK_DIALOG_ERROR; + msg = g_strdup_printf (_("%s is offering you an invitation, but " + "you don't have the needed external " + "application to handle it."), + empathy_contact_get_name (tube->initiator)); + } + + status_icon_event_add (icon, icon_name, msg, status_icon_tube_process, + empathy_dispatcher_tube_ref (tube)); + + g_free (msg); +} + +static void +status_icon_pending_subscribe (EmpathyStatusIcon *icon, + gpointer user_data) +{ + EmpathyContact *contact = EMPATHY_CONTACT (user_data); + + empathy_subscription_dialog_show (contact, NULL); + g_object_unref (contact); +} + +static void +status_icon_pendings_changed_cb (EmpathyContactList *list, + EmpathyContact *contact, + EmpathyContact *actor, + guint reason, + gchar *message, + gboolean is_pending, + EmpathyStatusIcon *icon) +{ + GString *str; + + if (!is_pending) { + /* FIXME: remove event if any */ + return; + } + + DEBUG ("New local pending contact"); + + empathy_contact_run_until_ready (contact, + EMPATHY_CONTACT_READY_NAME, + NULL); + + str = g_string_new (NULL); + g_string_printf (str, _("Subscription requested by %s"), + empathy_contact_get_name (contact)); + if (!G_STR_EMPTY (message)) { + g_string_append_printf (str, _("\nMessage: %s"), message); + } + + status_icon_event_add (icon, GTK_STOCK_DIALOG_QUESTION, str->str, + status_icon_pending_subscribe, + g_object_ref (contact)); + + g_string_free (str, TRUE); +} + +static void +status_icon_finalize (GObject *object) +{ + EmpathyStatusIconPriv *priv = GET_PRIV (object); + + if (priv->blink_timeout) { + g_source_remove (priv->blink_timeout); + } + + empathy_disconnect_account_status_changed (priv->token); + g_slist_foreach (priv->events, (GFunc) status_icon_event_free, NULL); + g_slist_free (priv->events); + + g_object_unref (priv->icon); + g_object_unref (priv->idle); + g_object_unref (priv->mc); + g_object_unref (priv->contact_manager); +} + +static void +empathy_status_icon_class_init (EmpathyStatusIconClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = status_icon_finalize; + + g_type_class_add_private (object_class, sizeof (EmpathyStatusIconPriv)); +} + +static void +status_icon_status_changed_cb (MissionControl *mc, + TpConnectionStatus status, + McPresence presence, + TpConnectionStatusReason reason, + const gchar *unique_name, + EmpathyStatusIcon *icon) +{ + EmpathyStatusIconPriv *priv = GET_PRIV (icon); + GList *accounts, *l; + guint connection_status = 1; + + /* Check for a connected account */ + accounts = mc_accounts_list_by_enabled (TRUE); + for (l = accounts; l; l = l->next) { + connection_status = mission_control_get_connection_status (priv->mc, + l->data, + NULL); + if (connection_status == 0) { + break; + } + } + mc_accounts_list_free (accounts); + + gtk_widget_set_sensitive (priv->message_item, connection_status == 0); +} + +static void +empathy_status_icon_init (EmpathyStatusIcon *icon) +{ + EmpathyStatusIconPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (icon, + EMPATHY_TYPE_STATUS_ICON, EmpathyStatusIconPriv); + + icon->priv = priv; + priv->icon = gtk_status_icon_new (); + priv->mc = empathy_mission_control_new (); + priv->idle = empathy_idle_new (); + priv->dispatcher = empathy_dispatcher_new (); + priv->contact_manager = empathy_contact_manager_new (); + priv->token = empathy_connect_to_account_status_changed (priv->mc, + G_CALLBACK (status_icon_status_changed_cb), + icon, NULL); + + /* make icon listen and respond to MAIN_WINDOW_HIDDEN changes */ + empathy_conf_notify_add (empathy_conf_get (), + EMPATHY_PREFS_UI_MAIN_WINDOW_HIDDEN, + status_icon_notify_visibility_cb, + icon); + + status_icon_create_menu (icon); + status_icon_idle_notify_cb (icon); + + g_signal_connect_swapped (priv->idle, "notify", + G_CALLBACK (status_icon_idle_notify_cb), + icon); + g_signal_connect (priv->dispatcher, "filter-channel", + G_CALLBACK (status_icon_filter_channel_cb), + icon); + g_signal_connect (priv->dispatcher, "filter-tube", + G_CALLBACK (status_icon_filter_tube_cb), + icon); + g_signal_connect (priv->contact_manager, "pendings-changed", + G_CALLBACK (status_icon_pendings_changed_cb), + icon); + g_signal_connect (priv->icon, "activate", + G_CALLBACK (status_icon_activate_cb), + icon); + g_signal_connect (priv->icon, "popup-menu", + G_CALLBACK (status_icon_popup_menu_cb), + icon); +} + +EmpathyStatusIcon * +empathy_status_icon_new (GtkWindow *window) +{ + EmpathyStatusIconPriv *priv; + EmpathyStatusIcon *icon; + gboolean should_hide; + + g_return_val_if_fail (GTK_IS_WINDOW (window), NULL); + + icon = g_object_new (EMPATHY_TYPE_STATUS_ICON, NULL); + priv = GET_PRIV (icon); + + priv->window = g_object_ref (window); + + g_signal_connect (priv->window, "delete-event", + G_CALLBACK (status_icon_delete_event_cb), + icon); + + empathy_conf_get_bool (empathy_conf_get (), + EMPATHY_PREFS_UI_MAIN_WINDOW_HIDDEN, + &should_hide); + + if (gtk_window_is_active (priv->window) == should_hide) { + status_icon_set_visibility (icon, !should_hide, FALSE); + } + + return icon; +} + diff --git a/trunk/src/empathy-status-icon.glade b/trunk/src/empathy-status-icon.glade new file mode 100644 index 000000000..ab0f09f84 --- /dev/null +++ b/trunk/src/empathy-status-icon.glade @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!DOCTYPE glade-interface SYSTEM "glade-2.0.dtd"> +<!--*- mode: xml -*--> +<glade-interface> + <widget class="GtkMenu" id="tray_menu"> + <child> + <widget class="GtkCheckMenuItem" id="tray_show_list"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Show Contact List</property> + <property name="use_underline">True</property> + </widget> + </child> + <child> + <widget class="GtkSeparatorMenuItem" id="avskiljare5"> + <property name="visible">True</property> + </widget> + </child> + <child> + <widget class="GtkImageMenuItem" id="tray_new_message"> + <property name="visible">True</property> + <property name="label" translatable="yes">_New Conversation...</property> + <property name="use_underline">True</property> + <child internal-child="image"> + <widget class="GtkImage" id="image599"> + <property name="visible">True</property> + <property name="icon_size">1</property> + <property name="icon_name">im-message-new</property> + </widget> + </child> + </widget> + </child> + <child> + <widget class="GtkMenuItem" id="tray_status"> + <property name="visible">True</property> + <property name="label" translatable="yes">Status</property> + <property name="use_underline">True</property> + </widget> + </child> + <child> + <widget class="GtkSeparatorMenuItem" id="avskiljare6"> + <property name="visible">True</property> + </widget> + </child> + <child> + <widget class="GtkImageMenuItem" id="tray_quit"> + <property name="visible">True</property> + <property name="label" translatable="yes">_Quit</property> + <property name="use_underline">True</property> + <child internal-child="image"> + <widget class="GtkImage" id="image600"> + <property name="visible">True</property> + <property name="stock">gtk-quit</property> + <property name="icon_size">1</property> + </widget> + </child> + </widget> + </child> + </widget> +</glade-interface> diff --git a/trunk/src/empathy-status-icon.h b/trunk/src/empathy-status-icon.h new file mode 100644 index 000000000..bb33b1c3b --- /dev/null +++ b/trunk/src/empathy-status-icon.h @@ -0,0 +1,53 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2007-2008 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * Authors: Xavier Claessens <xclaesse@gmail.com> + */ + +#ifndef __EMPATHY_STATUS_ICON_H__ +#define __EMPATHY_STATUS_ICON_H__ + +#include <glib.h> + +G_BEGIN_DECLS + +#define EMPATHY_TYPE_STATUS_ICON (empathy_status_icon_get_type ()) +#define EMPATHY_STATUS_ICON(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EMPATHY_TYPE_STATUS_ICON, EmpathyStatusIcon)) +#define EMPATHY_STATUS_ICON_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EMPATHY_TYPE_STATUS_ICON, EmpathyStatusIconClass)) +#define EMPATHY_IS_STATUS_ICON(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EMPATHY_TYPE_STATUS_ICON)) +#define EMPATHY_IS_STATUS_ICON_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EMPATHY_TYPE_STATUS_ICON)) +#define EMPATHY_STATUS_ICON_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EMPATHY_TYPE_STATUS_ICON, EmpathyStatusIconClass)) + +typedef struct _EmpathyStatusIcon EmpathyStatusIcon; +typedef struct _EmpathyStatusIconClass EmpathyStatusIconClass; + +struct _EmpathyStatusIcon { + GObject parent; + gpointer priv; +}; + +struct _EmpathyStatusIconClass { + GObjectClass parent_class; +}; + +GType empathy_status_icon_get_type (void) G_GNUC_CONST; +EmpathyStatusIcon *empathy_status_icon_new (GtkWindow *window); + +G_END_DECLS + +#endif /* __EMPATHY_STATUS_ICON_H__ */ diff --git a/trunk/src/empathy.1 b/trunk/src/empathy.1 new file mode 100644 index 000000000..bb191a0ac --- /dev/null +++ b/trunk/src/empathy.1 @@ -0,0 +1,24 @@ +.TH EMPATHY "1" "October 2007" "Telepathy project" "User Commands" +.SH NAME +empathy \- GNOME instant messaging client using Telepathy +.SH SYNOPSIS +empathy +.SH DESCRIPTION +Empathy consists of a rich set of reusable instant messaging widgets, and a +GNOME client using those widgets. +It uses Telepathy and Nokia's Mission Control, and reuses Gossip's UI. +.PP +The main user interface consists of a contact list window and an icon in the +notification area. +.SH OPTIONS +There are no command-line options. +.SH ENVIRONMENT +.TP +\fBEMPATHY_LOGFILE\fR=\fIfilename\fR +If set, debug output will go to the given file rather than to stderr. +.TP +\fBEMPATHY_DEBUG\fR=\fItype\fR +May be set to "all" for full debug output, or various undocumented options +(which may change from release to release) to filter the output. +.SH SEE ALSO +\fIhttp://telepathy.freedesktop.org/\fR, \fIhttp://live.gnome.org/Empathy\fR diff --git a/trunk/src/empathy.c b/trunk/src/empathy.c new file mode 100644 index 000000000..242f946a1 --- /dev/null +++ b/trunk/src/empathy.c @@ -0,0 +1,479 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */ +/* + * Copyright (C) 2007-2008 Collabora Ltd. + * + * 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., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * + * Authors: Xavier Claessens <xclaesse@gmail.com> + */ + +#include <config.h> + +#include <stdlib.h> +#include <errno.h> + +#include <glib.h> +#include <glib/gi18n.h> +#include <gtk/gtk.h> +#include <gdk/gdkx.h> + +#include <libebook/e-book.h> + +#include <telepathy-glib/util.h> +#include <libmissioncontrol/mc-account.h> +#include <libmissioncontrol/mission-control.h> + +#include <libempathy/empathy-idle.h> +#include <libempathy/empathy-utils.h> +#include <libempathy/empathy-dispatcher.h> +#include <libempathy/empathy-tp-chat.h> +#include <libempathy/empathy-tp-call.h> + +#include <libempathy-gtk/empathy-conf.h> + +#include "empathy-main-window.h" +#include "empathy-status-icon.h" +#include "empathy-call-window.h" +#include "empathy-chat-window.h" +#include "bacon-message-connection.h" + +#define DEBUG_FLAG EMPATHY_DEBUG_OTHER +#include <libempathy/empathy-debug.h> + +static BaconMessageConnection *connection = NULL; + +static void +dispatch_channel_cb (EmpathyDispatcher *dispatcher, + TpChannel *channel, + gpointer user_data) +{ + gchar *channel_type; + + g_object_get (channel, "channel-type", &channel_type, NULL); + if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_TEXT)) { + EmpathyTpChat *tp_chat; + EmpathyChat *chat = NULL; + const gchar *id; + + tp_chat = empathy_tp_chat_new (channel); + empathy_run_until_ready (tp_chat); + + id = empathy_tp_chat_get_id (tp_chat); + if (!id) { + EmpathyContact *contact; + + contact = empathy_tp_chat_get_remote_contact (tp_chat); + if (contact) { + id = empathy_contact_get_id (contact); + } + } + + if (id) { + McAccount *account; + + account = empathy_tp_chat_get_account (tp_chat); + chat = empathy_chat_window_find_chat (account, id); + } + + if (chat) { + empathy_chat_set_tp_chat (chat, tp_chat); + } else { + chat = empathy_chat_new (tp_chat); + } + + empathy_chat_window_present_chat (chat); + g_object_unref (tp_chat); + } + else if (!tp_strdiff (channel_type, TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA)) { + EmpathyTpCall *tp_call; + + tp_call = empathy_tp_call_new (channel); + empathy_call_window_new (tp_call); + g_object_unref (tp_call); + } +} + +static void +service_ended_cb (MissionControl *mc, + gpointer user_data) +{ + DEBUG ("Mission Control stopped"); +} + +static void +operation_error_cb (MissionControl *mc, + guint operation_id, + guint error_code, + gpointer user_data) +{ + const gchar *message; + + switch (error_code) { + case MC_DISCONNECTED_ERROR: + message = _("Disconnected"); + break; + case MC_INVALID_HANDLE_ERROR: + message = _("Invalid handle"); + break; + case MC_NO_MATCHING_CONNECTION_ERROR: + message = _("No matching connection"); + break; + case MC_INVALID_ACCOUNT_ERROR: + message = _("Invalid account"); + break; + case MC_PRESENCE_FAILURE_ERROR: + message = _("Presence failure"); + break; + case MC_NO_ACCOUNTS_ERROR: + message = _("No accounts"); + break; + case MC_NETWORK_ERROR: + message = _("Network error"); + break; + case MC_CONTACT_DOES_NOT_SUPPORT_VOICE_ERROR: + message = _("Contact does not support voice"); + break; + case MC_LOWMEM_ERROR: + message = _("Lowmem"); + break; + case MC_CHANNEL_REQUEST_GENERIC_ERROR: + message = _("Channel request generic error"); + break; + case MC_CHANNEL_BANNED_ERROR: + message = _("Channel banned"); + break; + case MC_CHANNEL_FULL_ERROR: + message = _("Channel full"); + break; + case MC_CHANNEL_INVITE_ONLY_ERROR: + message = _("Channel invite only"); + break; + default: + message = _("Unknown error code"); + } + + DEBUG ("Error during operation %d: %s", operation_id, message); +} + +static void +use_nm_notify_cb (EmpathyConf *conf, + const gchar *key, + gpointer user_data) +{ + EmpathyIdle *idle = user_data; + gboolean use_nm; + + if (empathy_conf_get_bool (conf, key, &use_nm)) { + empathy_idle_set_use_nm (idle, use_nm); + } +} + +static void +create_salut_account (void) +{ + McProfile *profile; + McProtocol *protocol; + gboolean salut_created = FALSE; + McAccount *account; + GList *accounts; + EBook *book; + EContact *contact; + gchar *nickname = NULL; + gchar *first_name = NULL; + gchar *last_name = NULL; + gchar *email = NULL; + gchar *jid = NULL; + GError *error = NULL; + + /* Check if we already created a salut account */ + empathy_conf_get_bool (empathy_conf_get(), + EMPATHY_PREFS_SALUT_ACCOUNT_CREATED, + &salut_created); + if (salut_created) { + return; + } + + DEBUG ("Try to add a salut account..."); + + /* Check if the salut CM is installed */ + profile = mc_profile_lookup ("salut"); + protocol = mc_profile_get_protocol (profile); + if (!protocol) { + DEBUG ("Salut not installed"); + g_object_unref (profile); + return; + } + g_object_unref (protocol); + + /* Get self EContact from EDS */ + if (!e_book_get_self (&contact, &book, &error)) { + DEBUG ("Failed to get self econtact: %s", + error ? error->message : "No error given"); + g_clear_error (&error); + g_object_unref (profile); + return; + } + + empathy_conf_set_bool (empathy_conf_get (), + EMPATHY_PREFS_SALUT_ACCOUNT_CREATED, + TRUE); + + /* Check if there is already a salut account */ + accounts = mc_accounts_list_by_profile (profile); + if (accounts) { + DEBUG ("There is already a salut account"); + mc_accounts_list_free (accounts); + g_object_unref (profile); + return; + } + + account = mc_account_create (profile); + mc_account_set_display_name (account, _("People nearby")); + + nickname = e_contact_get (contact, E_CONTACT_NICKNAME); + first_name = e_contact_get (contact, E_CONTACT_GIVEN_NAME); + last_name = e_contact_get (contact, E_CONTACT_FAMILY_NAME); + email = e_contact_get (contact, E_CONTACT_EMAIL_1); + jid = e_contact_get (contact, E_CONTACT_IM_JABBER_HOME_1); + + if (!tp_strdiff (nickname, "nickname")) { + g_free (nickname); + nickname = NULL; + } + + DEBUG ("Salut account created:\nnickname=%s\nfirst-name=%s\n" + "last-name=%s\nemail=%s\njid=%s\n", + nickname, first_name, last_name, email, jid); + + mc_account_set_param_string (account, "nickname", nickname ? nickname : ""); + mc_account_set_param_string (account, "first-name", first_name ? first_name : ""); + mc_account_set_param_string (account, "last-name", last_name ? last_name : ""); + mc_account_set_param_string (account, "email", email ? email : ""); + mc_account_set_param_string (account, "jid", jid ? jid : ""); + + g_free (nickname); + g_free (first_name); + g_free (last_name); + g_free (email); + g_free (jid); + g_object_unref (account); + g_object_unref (profile); + g_object_unref (contact); + g_object_unref (book); +} + +/* The code that handles single-instance and startup notification is + * copied from gedit. + * + * Copyright (C) 2005 - Paolo Maggi + */ +static void +on_bacon_message_received (const char *message, + gpointer data) +{ + GtkWidget *window = data; + guint32 startup_timestamp; + + g_return_if_fail (message != NULL); + + DEBUG ("Other instance launched, presenting the main window. message='%s'", + message); + + startup_timestamp = atoi (message); + + /* Set the proper interaction time on the window. + * Fall back to roundtripping to the X server when we + * don't have the timestamp, e.g. when launched from + * terminal. We also need to make sure that the window + * has been realized otherwise it will not work. lame. */ + if (startup_timestamp == 0) { + /* Work if launched from the terminal */ + DEBUG ("Using X server timestamp as a fallback"); + + if (!GTK_WIDGET_REALIZED (window)) { + gtk_widget_realize (GTK_WIDGET (window)); + } + + startup_timestamp = gdk_x11_get_server_time (window->window); + } + + gtk_window_present_with_time (GTK_WINDOW (window), startup_timestamp); +} + +static guint32 +get_startup_timestamp () +{ + const gchar *startup_id_env; + gchar *startup_id = NULL; + gchar *time_str; + gchar *end; + gulong retval = 0; + + /* we don't unset the env, since startup-notification + * may still need it */ + startup_id_env = g_getenv ("DESKTOP_STARTUP_ID"); + if (startup_id_env == NULL) { + goto out; + } + + startup_id = g_strdup (startup_id_env); + + time_str = g_strrstr (startup_id, "_TIME"); + if (time_str == NULL) { + goto out; + } + + errno = 0; + + /* Skip past the "_TIME" part */ + time_str += 5; + + retval = strtoul (time_str, &end, 0); + if (end == time_str || errno != 0) + retval = 0; + + out: + g_free (startup_id); + + return (retval > 0) ? retval : 0; +} + +int +main (int argc, char *argv[]) +{ + guint32 startup_timestamp; + EmpathyStatusIcon *icon; + EmpathyDispatcher *dispatcher; + GtkWidget *window; + MissionControl *mc; + EmpathyIdle *idle; + gboolean autoconnect = TRUE; + gboolean no_connect = FALSE; + GError *error = NULL; + GOptionEntry options[] = { + { "no-connect", 'n', + 0, G_OPTION_ARG_NONE, &no_connect, + N_("Don't connect on startup"), + NULL }, + { NULL } + }; + + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); + + startup_timestamp = get_startup_timestamp (); + + if (!gtk_init_with_args (&argc, &argv, + _("- Empathy Instant Messenger"), + options, GETTEXT_PACKAGE, &error)) { + g_warning ("Error in gtk init: %s", error->message); + return EXIT_FAILURE; + } + + if (g_getenv ("EMPATHY_TIMING") != NULL) { + g_log_set_default_handler (tp_debug_timestamped_log_handler, NULL); + } + empathy_debug_set_flags (g_getenv ("EMPATHY_DEBUG")); + tp_debug_divert_messages (g_getenv ("EMPATHY_LOGFILE")); + + g_set_application_name (PACKAGE_NAME); + + gtk_window_set_default_icon_name ("empathy"); + gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (), + PKGDATADIR G_DIR_SEPARATOR_S "icons"); + + /* Setting up the bacon connection */ + connection = bacon_message_connection_new ("empathy"); + if (connection != NULL) { + if (!bacon_message_connection_get_is_server (connection)) { + gchar *message; + + DEBUG ("Activating existing instance"); + + message = g_strdup_printf ("%" G_GUINT32_FORMAT, + startup_timestamp); + bacon_message_connection_send (connection, message); + + /* We never popup a window, so tell startup-notification + * that we are done. */ + gdk_notify_startup_complete (); + + g_free (message); + bacon_message_connection_free (connection); + + return EXIT_SUCCESS; + } + } else { + g_warning ("Cannot create the 'empathy' bacon connection."); + } + + /* Setting up MC */ + mc = empathy_mission_control_new (); + g_signal_connect (mc, "ServiceEnded", + G_CALLBACK (service_ended_cb), + NULL); + g_signal_connect (mc, "Error", + G_CALLBACK (operation_error_cb), + NULL); + + /* Setting up Idle */ + idle = empathy_idle_new (); + empathy_idle_set_auto_away (idle, TRUE); + use_nm_notify_cb (empathy_conf_get (), EMPATHY_PREFS_USE_NM, idle); + empathy_conf_notify_add (empathy_conf_get (), EMPATHY_PREFS_USE_NM, + use_nm_notify_cb, idle); + + /* Autoconnect */ + empathy_conf_get_bool (empathy_conf_get(), + EMPATHY_PREFS_AUTOCONNECT, + &autoconnect); + if (autoconnect && ! no_connect && + empathy_idle_get_state (idle) <= MC_PRESENCE_OFFLINE) { + empathy_idle_set_state (idle, MC_PRESENCE_AVAILABLE); + } + + create_salut_account (); + + /* Setting up UI */ + window = empathy_main_window_show (); + icon = empathy_status_icon_new (GTK_WINDOW (window)); + + if (connection) { + /* We se the callback here because we need window */ + bacon_message_connection_set_callback (connection, + on_bacon_message_received, + window); + } + + /* Handle channels */ + dispatcher = empathy_dispatcher_new (); + g_signal_connect (dispatcher, "dispatch-channel", + G_CALLBACK (dispatch_channel_cb), + NULL); + + gtk_main (); + + empathy_idle_set_state (idle, MC_PRESENCE_OFFLINE); + + g_object_unref (mc); + g_object_unref (idle); + g_object_unref (icon); + g_object_unref (dispatcher); + + return EXIT_SUCCESS; +} + diff --git a/trunk/src/ephy-spinner.c b/trunk/src/ephy-spinner.c new file mode 100644 index 000000000..b67b90f76 --- /dev/null +++ b/trunk/src/ephy-spinner.c @@ -0,0 +1,977 @@ +/* + * Copyright © 2000 Eazel, Inc. + * Copyright © 2002-2004 Marco Pesenti Gritti + * Copyright © 2004, 2006 Christian Persch + * + * Nautilus 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. + * + * Nautilus 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Andy Hertzfeld <andy@eazel.com> + * + * Ephy port by Marco Pesenti Gritti <marco@it.gnome.org> + * + * $Id: ephy-spinner.c 2114 2006-12-25 12:15:00Z mr $ + */ + +#include <config.h> + +#include "ephy-spinner.h" + +/* #include "ephy-debug.h" */ +#define LOG(msg, args...) +#define START_PROFILER(name) +#define STOP_PROFILER(name) + +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <gtk/gtkicontheme.h> +#include <gtk/gtkiconfactory.h> +#include <gtk/gtksettings.h> + +/* Spinner cache implementation */ + +#define EPHY_TYPE_SPINNER_CACHE (ephy_spinner_cache_get_type()) +#define EPHY_SPINNER_CACHE(object) (G_TYPE_CHECK_INSTANCE_CAST((object), EPHY_TYPE_SPINNER_CACHE, EphySpinnerCache)) +#define EPHY_SPINNER_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), EPHY_TYPE_SPINNER_CACHE, EphySpinnerCacheClass)) +#define EPHY_IS_SPINNER_CACHE(object) (G_TYPE_CHECK_INSTANCE_TYPE((object), EPHY_TYPE_SPINNER_CACHE)) +#define EPHY_IS_SPINNER_CACHE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), EPHY_TYPE_SPINNER_CACHE)) +#define EPHY_SPINNER_CACHE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), EPHY_TYPE_SPINNER_CACHE, EphySpinnerCacheClass)) + +typedef struct _EphySpinnerCache EphySpinnerCache; +typedef struct _EphySpinnerCacheClass EphySpinnerCacheClass; +typedef struct _EphySpinnerCachePrivate EphySpinnerCachePrivate; + +struct _EphySpinnerCacheClass +{ + GObjectClass parent_class; +}; + +struct _EphySpinnerCache +{ + GObject parent_object; + + /*< private >*/ + EphySpinnerCachePrivate *priv; +}; + +#define EPHY_SPINNER_CACHE_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_SPINNER_CACHE, EphySpinnerCachePrivate)) + +struct _EphySpinnerCachePrivate +{ + /* Hash table of GdkScreen -> EphySpinnerCacheData */ + GHashTable *hash; +}; + +typedef struct +{ + guint ref_count; + GtkIconSize size; + int width; + int height; + GdkPixbuf **animation_pixbufs; + guint n_animation_pixbufs; +} EphySpinnerImages; + +#define LAST_ICON_SIZE GTK_ICON_SIZE_DIALOG + 1 +#define SPINNER_ICON_NAME "process-working" +#define SPINNER_FALLBACK_ICON_NAME "gnome-spinner" +#define EPHY_SPINNER_IMAGES_INVALID ((EphySpinnerImages *) 0x1) + +typedef struct +{ + GdkScreen *screen; + GtkIconTheme *icon_theme; + EphySpinnerImages *images[LAST_ICON_SIZE]; +} EphySpinnerCacheData; + +static void ephy_spinner_cache_class_init (EphySpinnerCacheClass *klass); +static void ephy_spinner_cache_init (EphySpinnerCache *cache); + +static GObjectClass *ephy_spinner_cache_parent_class; + +static GType +ephy_spinner_cache_get_type (void) +{ + static GType type = 0; + + if (G_UNLIKELY (type == 0)) + { + const GTypeInfo our_info = + { + sizeof (EphySpinnerCacheClass), + NULL, + NULL, + (GClassInitFunc) ephy_spinner_cache_class_init, + NULL, + NULL, + sizeof (EphySpinnerCache), + 0, + (GInstanceInitFunc) ephy_spinner_cache_init + }; + + type = g_type_register_static (G_TYPE_OBJECT, + "EphySpinnerCache", + &our_info, 0); + } + + return type; +} + +static EphySpinnerImages * +ephy_spinner_images_ref (EphySpinnerImages *images) +{ + g_return_val_if_fail (images != NULL, NULL); + + images->ref_count++; + + return images; +} + +static void +ephy_spinner_images_unref (EphySpinnerImages *images) +{ + g_return_if_fail (images != NULL); + + images->ref_count--; + if (images->ref_count == 0) + { + guint i; + + LOG ("Freeing spinner images %p for size %d", images, images->size); + + for (i = 0; i < images->n_animation_pixbufs; ++i) + { + g_object_unref (images->animation_pixbufs[i]); + } + g_free (images->animation_pixbufs); + + g_free (images); + } +} + +static void +ephy_spinner_cache_data_unload (EphySpinnerCacheData *data) +{ + GtkIconSize size; + EphySpinnerImages *images; + + g_return_if_fail (data != NULL); + + LOG ("EphySpinnerDataCache unload for screen %p", data->screen); + + for (size = GTK_ICON_SIZE_INVALID; size < LAST_ICON_SIZE; ++size) + { + images = data->images[size]; + data->images[size] = NULL; + + if (images != NULL && images != EPHY_SPINNER_IMAGES_INVALID) + { + ephy_spinner_images_unref (images); + } + } +} + +static GdkPixbuf * +extract_frame (GdkPixbuf *grid_pixbuf, + int x, + int y, + int size) +{ + GdkPixbuf *pixbuf; + + if (x + size > gdk_pixbuf_get_width (grid_pixbuf) || + y + size > gdk_pixbuf_get_height (grid_pixbuf)) + { + return NULL; + } + + pixbuf = gdk_pixbuf_new_subpixbuf (grid_pixbuf, + x, y, + size, size); + g_return_val_if_fail (pixbuf != NULL, NULL); + + return pixbuf; +} + +static GdkPixbuf * +scale_to_size (GdkPixbuf *pixbuf, + int dw, + int dh) +{ + GdkPixbuf *result; + int pw, ph; + + g_return_val_if_fail (pixbuf != NULL, NULL); + + pw = gdk_pixbuf_get_width (pixbuf); + ph = gdk_pixbuf_get_height (pixbuf); + + if (pw != dw || ph != dh) + { + result = gdk_pixbuf_scale_simple (pixbuf, dw, dh, + GDK_INTERP_BILINEAR); + g_object_unref (pixbuf); + return result; + } + + return pixbuf; +} + +static EphySpinnerImages * +ephy_spinner_images_load (GdkScreen *screen, + GtkIconTheme *icon_theme, + GtkIconSize icon_size) +{ + EphySpinnerImages *images; + GdkPixbuf *icon_pixbuf, *pixbuf; + GtkIconInfo *icon_info = NULL; + int grid_width, grid_height, x, y, requested_size, size, isw, ish, n; + const char *icon; + GSList *list = NULL, *l; + + LOG ("EphySpinnerCacheData loading for screen %p at size %d", screen, icon_size); + + START_PROFILER ("loading spinner animation") + + if (!gtk_icon_size_lookup_for_settings (gtk_settings_get_for_screen (screen), + icon_size, &isw, &ish)) goto loser; + + requested_size = MAX (ish, isw); + + /* Load the animation. The 'rest icon' is the 0th frame */ + icon_info = gtk_icon_theme_lookup_icon (icon_theme, + SPINNER_ICON_NAME, + requested_size, 0); + if (icon_info == NULL) + { + g_warning ("Throbber animation not found"); + + /* If the icon naming spec compliant name wasn't found, try the old name */ + icon_info = gtk_icon_theme_lookup_icon (icon_theme, + SPINNER_FALLBACK_ICON_NAME, + requested_size, 0); + if (icon_info == NULL) + { + g_warning ("Throbber fallback animation not found either"); + goto loser; + } + } + g_assert (icon_info != NULL); + + size = gtk_icon_info_get_base_size (icon_info); + icon = gtk_icon_info_get_filename (icon_info); + if (icon == NULL) goto loser; + + icon_pixbuf = gdk_pixbuf_new_from_file (icon, NULL); + gtk_icon_info_free (icon_info); + icon_info = NULL; + + if (icon_pixbuf == NULL) + { + g_warning ("Could not load the spinner file"); + goto loser; + } + + grid_width = gdk_pixbuf_get_width (icon_pixbuf); + grid_height = gdk_pixbuf_get_height (icon_pixbuf); + + n = 0; + for (y = 0; y < grid_height; y += size) + { + for (x = 0; x < grid_width ; x += size) + { + pixbuf = extract_frame (icon_pixbuf, x, y, size); + + if (pixbuf) + { + list = g_slist_prepend (list, pixbuf); + ++n; + } + else + { + g_warning ("Cannot extract frame (%d, %d) from the grid\n", x, y); + } + } + } + + g_object_unref (icon_pixbuf); + + if (list == NULL) goto loser; + g_assert (n > 0); + + if (size > requested_size) + { + for (l = list; l != NULL; l = l->next) + { + l->data = scale_to_size (l->data, isw, ish); + } + } + + /* Now we've successfully got all the data */ + images = g_new (EphySpinnerImages, 1); + images->ref_count = 1; + + images->size = icon_size; + images->width = images->height = requested_size; + + images->n_animation_pixbufs = n; + images->animation_pixbufs = g_new (GdkPixbuf *, n); + + for (l = list; l != NULL; l = l->next) + { + g_assert (l->data != NULL); + images->animation_pixbufs[--n] = l->data; + } + g_assert (n == 0); + + g_slist_free (list); + + STOP_PROFILER ("loading spinner animation") + + return images; + +loser: + if (icon_info) + { + gtk_icon_info_free (icon_info); + } + g_slist_foreach (list, (GFunc) g_object_unref, NULL); + + STOP_PROFILER ("loading spinner animation") + + return NULL; +} + +static EphySpinnerCacheData * +ephy_spinner_cache_data_new (GdkScreen *screen) +{ + EphySpinnerCacheData *data; + + data = g_new0 (EphySpinnerCacheData, 1); + + data->screen = screen; + data->icon_theme = gtk_icon_theme_get_for_screen (screen); + g_signal_connect_swapped (data->icon_theme, "changed", + G_CALLBACK (ephy_spinner_cache_data_unload), + data); + + return data; +} + +static void +ephy_spinner_cache_data_free (EphySpinnerCacheData *data) +{ + g_return_if_fail (data != NULL); + g_return_if_fail (data->icon_theme != NULL); + + g_signal_handlers_disconnect_by_func + (data->icon_theme, + G_CALLBACK (ephy_spinner_cache_data_unload), data); + + ephy_spinner_cache_data_unload (data); + + g_free (data); +} + +static EphySpinnerImages * +ephy_spinner_cache_get_images (EphySpinnerCache *cache, + GdkScreen *screen, + GtkIconSize icon_size) +{ + EphySpinnerCachePrivate *priv = cache->priv; + EphySpinnerCacheData *data; + EphySpinnerImages *images; + + LOG ("Getting animation images for screen %p at size %d", screen, icon_size); + + g_return_val_if_fail (icon_size >= 0 && icon_size < LAST_ICON_SIZE, NULL); + + /* Backward compat: "invalid" meant "native" size which doesn't exist anymore */ + if (icon_size == GTK_ICON_SIZE_INVALID) + { + icon_size = GTK_ICON_SIZE_DIALOG; + } + + data = g_hash_table_lookup (priv->hash, screen); + if (data == NULL) + { + data = ephy_spinner_cache_data_new (screen); + /* FIXME: think about what happens when the screen's display is closed later on */ + g_hash_table_insert (priv->hash, screen, data); + } + + images = data->images[icon_size]; + if (images == EPHY_SPINNER_IMAGES_INVALID) + { + /* Load failed, but don't try endlessly again! */ + return NULL; + } + + if (images != NULL) + { + /* Return cached data */ + return ephy_spinner_images_ref (images); + } + + images = ephy_spinner_images_load (screen, data->icon_theme, icon_size); + + if (images == NULL) + { + /* Mark as failed-to-load */ + data->images[icon_size] = EPHY_SPINNER_IMAGES_INVALID; + + return NULL; + } + + data->images[icon_size] = images; + + return ephy_spinner_images_ref (images); +} + +static void +ephy_spinner_cache_init (EphySpinnerCache *cache) +{ + EphySpinnerCachePrivate *priv; + + priv = cache->priv = EPHY_SPINNER_CACHE_GET_PRIVATE (cache); + + LOG ("EphySpinnerCache initialising"); + + priv->hash = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, + (GDestroyNotify) ephy_spinner_cache_data_free); +} + +static void +ephy_spinner_cache_finalize (GObject *object) +{ + EphySpinnerCache *cache = EPHY_SPINNER_CACHE (object); + EphySpinnerCachePrivate *priv = cache->priv; + + g_hash_table_destroy (priv->hash); + + LOG ("EphySpinnerCache finalised"); + + G_OBJECT_CLASS (ephy_spinner_cache_parent_class)->finalize (object); +} + +static void +ephy_spinner_cache_class_init (EphySpinnerCacheClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + ephy_spinner_cache_parent_class = g_type_class_peek_parent (klass); + + object_class->finalize = ephy_spinner_cache_finalize; + + g_type_class_add_private (object_class, sizeof (EphySpinnerCachePrivate)); +} + +static EphySpinnerCache *spinner_cache = NULL; + +static EphySpinnerCache * +ephy_spinner_cache_ref (void) +{ + if (spinner_cache == NULL) + { + EphySpinnerCache **cache_ptr; + + spinner_cache = g_object_new (EPHY_TYPE_SPINNER_CACHE, NULL); + cache_ptr = &spinner_cache; + g_object_add_weak_pointer (G_OBJECT (spinner_cache), + (gpointer *) cache_ptr); + + return spinner_cache; + } + + return g_object_ref (spinner_cache); +} + +/* Spinner implementation */ + +#define SPINNER_TIMEOUT 125 /* ms */ + +#define EPHY_SPINNER_GET_PRIVATE(object)(G_TYPE_INSTANCE_GET_PRIVATE ((object), EPHY_TYPE_SPINNER, EphySpinnerDetails)) + +struct _EphySpinnerDetails +{ + GtkIconTheme *icon_theme; + EphySpinnerCache *cache; + GtkIconSize size; + EphySpinnerImages *images; + guint current_image; + guint timeout; + guint timer_task; + guint spinning : 1; + guint need_load : 1; +}; + +static void ephy_spinner_class_init (EphySpinnerClass *class); +static void ephy_spinner_init (EphySpinner *spinner); + +static GObjectClass *parent_class; + +GType +ephy_spinner_get_type (void) +{ + static GType type = 0; + + if (G_UNLIKELY (type == 0)) + { + const GTypeInfo our_info = + { + sizeof (EphySpinnerClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) ephy_spinner_class_init, + NULL, + NULL, /* class_data */ + sizeof (EphySpinner), + 0, /* n_preallocs */ + (GInstanceInitFunc) ephy_spinner_init + }; + + type = g_type_register_static (GTK_TYPE_WIDGET, + "EphySpinner", + &our_info, 0); + } + + return type; +} + +static gboolean +ephy_spinner_load_images (EphySpinner *spinner) +{ + EphySpinnerDetails *details = spinner->details; + + if (details->need_load) + { + START_PROFILER ("ephy_spinner_load_images") + + details->images = + ephy_spinner_cache_get_images + (details->cache, + gtk_widget_get_screen (GTK_WIDGET (spinner)), + details->size); + + STOP_PROFILER ("ephy_spinner_load_images") + + details->current_image = 0; /* 'rest' icon */ + details->need_load = FALSE; + } + + return details->images != NULL; +} + +static void +ephy_spinner_unload_images (EphySpinner *spinner) +{ + EphySpinnerDetails *details = spinner->details; + + if (details->images != NULL) + { + ephy_spinner_images_unref (details->images); + details->images = NULL; + } + + details->current_image = 0; + details->need_load = TRUE; +} + +static void +icon_theme_changed_cb (GtkIconTheme *icon_theme, + EphySpinner *spinner) +{ + ephy_spinner_unload_images (spinner); + gtk_widget_queue_resize (GTK_WIDGET (spinner)); +} + +static void +ephy_spinner_init (EphySpinner *spinner) +{ + EphySpinnerDetails *details; + + details = spinner->details = EPHY_SPINNER_GET_PRIVATE (spinner); + + GTK_WIDGET_SET_FLAGS (GTK_WIDGET (spinner), GTK_NO_WINDOW); + + details->cache = ephy_spinner_cache_ref (); + details->size = GTK_ICON_SIZE_DIALOG; + details->spinning = FALSE; + details->timeout = SPINNER_TIMEOUT; + details->need_load = TRUE; +} + +static int +ephy_spinner_expose (GtkWidget *widget, + GdkEventExpose *event) +{ + EphySpinner *spinner = EPHY_SPINNER (widget); + EphySpinnerDetails *details = spinner->details; + EphySpinnerImages *images; + GdkPixbuf *pixbuf; + GdkGC *gc; + int x_offset, y_offset, width, height; + GdkRectangle pix_area, dest; + + if (!GTK_WIDGET_DRAWABLE (spinner)) + { + return FALSE; + } + + if (details->need_load && + !ephy_spinner_load_images (spinner)) + { + return FALSE; + } + + images = details->images; + if (images == NULL) + { + return FALSE; + } + + /* Otherwise |images| will be NULL anyway */ + g_assert (images->n_animation_pixbufs > 0); + + g_assert (details->current_image >= 0 && + details->current_image < images->n_animation_pixbufs); + + pixbuf = images->animation_pixbufs[details->current_image]; + + g_assert (pixbuf != NULL); + + width = gdk_pixbuf_get_width (pixbuf); + height = gdk_pixbuf_get_height (pixbuf); + + /* Compute the offsets for the image centered on our allocation */ + x_offset = (widget->allocation.width - width) / 2; + y_offset = (widget->allocation.height - height) / 2; + + pix_area.x = x_offset + widget->allocation.x; + pix_area.y = y_offset + widget->allocation.y; + pix_area.width = width; + pix_area.height = height; + + if (!gdk_rectangle_intersect (&event->area, &pix_area, &dest)) + { + return FALSE; + } + + gc = gdk_gc_new (widget->window); + gdk_draw_pixbuf (widget->window, gc, pixbuf, + dest.x - x_offset - widget->allocation.x, + dest.y - y_offset - widget->allocation.y, + dest.x, dest.y, + dest.width, dest.height, + GDK_RGB_DITHER_MAX, 0, 0); + g_object_unref (gc); + + return FALSE; +} + +static gboolean +bump_spinner_frame_cb (EphySpinner *spinner) +{ + EphySpinnerDetails *details = spinner->details; + + /* This can happen when we've unloaded the images on a theme + * change, but haven't been in the queued size request yet. + * Just skip this update. + */ + if (details->images == NULL) return TRUE; + + details->current_image++; + if (details->current_image >= details->images->n_animation_pixbufs) + { + /* the 0th frame is the 'rest' icon */ + details->current_image = MIN (1, details->images->n_animation_pixbufs); + } + + gtk_widget_queue_draw (GTK_WIDGET (spinner)); + + /* run again */ + return TRUE; +} + +/** + * ephy_spinner_start: + * @spinner: a #EphySpinner + * + * Start the spinner animation. + **/ +void +ephy_spinner_start (EphySpinner *spinner) +{ + EphySpinnerDetails *details = spinner->details; + + details->spinning = TRUE; + + if (GTK_WIDGET_MAPPED (GTK_WIDGET (spinner)) && + details->timer_task == 0 && + ephy_spinner_load_images (spinner)) + { + /* the 0th frame is the 'rest' icon */ + details->current_image = MIN (1, details->images->n_animation_pixbufs); + + details->timer_task = + g_timeout_add_full (G_PRIORITY_LOW, + details->timeout, + (GSourceFunc) bump_spinner_frame_cb, + spinner, + NULL); + } +} + +static void +ephy_spinner_remove_update_callback (EphySpinner *spinner) +{ + EphySpinnerDetails *details = spinner->details; + + if (details->timer_task != 0) + { + g_source_remove (details->timer_task); + details->timer_task = 0; + } +} + +/** + * ephy_spinner_stop: + * @spinner: a #EphySpinner + * + * Stop the spinner animation. + **/ +void +ephy_spinner_stop (EphySpinner *spinner) +{ + EphySpinnerDetails *details = spinner->details; + + details->spinning = FALSE; + details->current_image = 0; + + if (details->timer_task != 0) + { + ephy_spinner_remove_update_callback (spinner); + + if (GTK_WIDGET_MAPPED (GTK_WIDGET (spinner))) + { + gtk_widget_queue_draw (GTK_WIDGET (spinner)); + } + } +} + +/* + * ephy_spinner_set_size: + * @spinner: a #EphySpinner + * @size: the size of type %GtkIconSize + * + * Set the size of the spinner. + **/ +void +ephy_spinner_set_size (EphySpinner *spinner, + GtkIconSize size) +{ + if (size == GTK_ICON_SIZE_INVALID) + { + size = GTK_ICON_SIZE_DIALOG; + } + + if (size != spinner->details->size) + { + ephy_spinner_unload_images (spinner); + + spinner->details->size = size; + + gtk_widget_queue_resize (GTK_WIDGET (spinner)); + } +} + +#if 0 +/* + * ephy_spinner_set_timeout: + * @spinner: a #EphySpinner + * @timeout: time delay between updates to the spinner. + * + * Sets the timeout delay for spinner updates. + **/ +void +ephy_spinner_set_timeout (EphySpinner *spinner, + guint timeout) +{ + EphySpinnerDetails *details = spinner->details; + + if (timeout != details->timeout) + { + ephy_spinner_stop (spinner); + + details->timeout = timeout; + + if (details->spinning) + { + ephy_spinner_start (spinner); + } + } +} +#endif + +static void +ephy_spinner_size_request (GtkWidget *widget, + GtkRequisition *requisition) +{ + EphySpinner *spinner = EPHY_SPINNER (widget); + EphySpinnerDetails *details = spinner->details; + + if ((details->need_load && + !ephy_spinner_load_images (spinner)) || + details->images == NULL) + { + requisition->width = requisition->height = 0; + gtk_icon_size_lookup_for_settings (gtk_widget_get_settings (widget), + details->size, + &requisition->width, + &requisition->height); + return; + } + + requisition->width = details->images->width; + requisition->height = details->images->height; + + /* FIXME fix this hack */ + /* allocate some extra margin so we don't butt up against toolbar edges */ + if (details->size != GTK_ICON_SIZE_MENU) + { + requisition->width += 2; + requisition->height += 2; + } +} + +static void +ephy_spinner_map (GtkWidget *widget) +{ + EphySpinner *spinner = EPHY_SPINNER (widget); + EphySpinnerDetails *details = spinner->details; + + GTK_WIDGET_CLASS (parent_class)->map (widget); + + if (details->spinning) + { + ephy_spinner_start (spinner); + } +} + +static void +ephy_spinner_unmap (GtkWidget *widget) +{ + EphySpinner *spinner = EPHY_SPINNER (widget); + + ephy_spinner_remove_update_callback (spinner); + + GTK_WIDGET_CLASS (parent_class)->unmap (widget); +} + +static void +ephy_spinner_dispose (GObject *object) +{ + EphySpinner *spinner = EPHY_SPINNER (object); + + g_signal_handlers_disconnect_by_func + (spinner->details->icon_theme, + G_CALLBACK (icon_theme_changed_cb), spinner); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +ephy_spinner_finalize (GObject *object) +{ + EphySpinner *spinner = EPHY_SPINNER (object); + + ephy_spinner_remove_update_callback (spinner); + ephy_spinner_unload_images (spinner); + + g_object_unref (spinner->details->cache); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +ephy_spinner_screen_changed (GtkWidget *widget, + GdkScreen *old_screen) +{ + EphySpinner *spinner = EPHY_SPINNER (widget); + EphySpinnerDetails *details = spinner->details; + GdkScreen *screen; + + if (GTK_WIDGET_CLASS (parent_class)->screen_changed) + { + GTK_WIDGET_CLASS (parent_class)->screen_changed (widget, old_screen); + } + + screen = gtk_widget_get_screen (widget); + + /* FIXME: this seems to be happening when then spinner is destroyed!? */ + if (old_screen == screen) return; + + /* We'll get mapped again on the new screen, but not unmapped from + * the old screen, so remove timeout here. + */ + ephy_spinner_remove_update_callback (spinner); + + ephy_spinner_unload_images (spinner); + + if (old_screen != NULL) + { + g_signal_handlers_disconnect_by_func + (gtk_icon_theme_get_for_screen (old_screen), + G_CALLBACK (icon_theme_changed_cb), spinner); + } + + details->icon_theme = gtk_icon_theme_get_for_screen (screen); + g_signal_connect (details->icon_theme, "changed", + G_CALLBACK (icon_theme_changed_cb), spinner); +} + +static void +ephy_spinner_class_init (EphySpinnerClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + + parent_class = g_type_class_peek_parent (class); + + object_class->dispose = ephy_spinner_dispose; + object_class->finalize = ephy_spinner_finalize; + + widget_class->expose_event = ephy_spinner_expose; + widget_class->size_request = ephy_spinner_size_request; + widget_class->map = ephy_spinner_map; + widget_class->unmap = ephy_spinner_unmap; + widget_class->screen_changed = ephy_spinner_screen_changed; + + g_type_class_add_private (object_class, sizeof (EphySpinnerDetails)); +} + +/* + * ephy_spinner_new: + * + * Create a new #EphySpinner. The spinner is a widget + * that gives the user feedback about network status with + * an animated image. + * + * Return Value: the spinner #GtkWidget + **/ +GtkWidget * +ephy_spinner_new (void) +{ + return GTK_WIDGET (g_object_new (EPHY_TYPE_SPINNER, NULL)); +} diff --git a/trunk/src/ephy-spinner.h b/trunk/src/ephy-spinner.h new file mode 100644 index 000000000..4435fe371 --- /dev/null +++ b/trunk/src/ephy-spinner.h @@ -0,0 +1,70 @@ +/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ +/* + * Copyright © 2000 Eazel, Inc. + * Copyright © 2004, 2006 Christian Persch + * + * Nautilus 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. + * + * Nautilus 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * Author: Andy Hertzfeld <andy@eazel.com> + * + * $Id: ephy-spinner.h 2114 2006-12-25 12:15:00Z mr $ + */ + +#ifndef EPHY_SPINNER_H +#define EPHY_SPINNER_H + +#include <gtk/gtkwidget.h> +#include <gtk/gtkenums.h> + +G_BEGIN_DECLS + +#define EPHY_TYPE_SPINNER (ephy_spinner_get_type ()) +#define EPHY_SPINNER(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EPHY_TYPE_SPINNER, EphySpinner)) +#define EPHY_SPINNER_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EPHY_TYPE_SPINNER, EphySpinnerClass)) +#define EPHY_IS_SPINNER(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EPHY_TYPE_SPINNER)) +#define EPHY_IS_SPINNER_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EPHY_TYPE_SPINNER)) +#define EPHY_SPINNER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EPHY_TYPE_SPINNER, EphySpinnerClass)) + +typedef struct _EphySpinner EphySpinner; +typedef struct _EphySpinnerClass EphySpinnerClass; +typedef struct _EphySpinnerDetails EphySpinnerDetails; + +struct _EphySpinner +{ + GtkWidget parent; + + /*< private >*/ + EphySpinnerDetails *details; +}; + +struct _EphySpinnerClass +{ + GtkWidgetClass parent_class; +}; + +GType ephy_spinner_get_type (void); + +GtkWidget *ephy_spinner_new (void); + +void ephy_spinner_start (EphySpinner *throbber); + +void ephy_spinner_stop (EphySpinner *throbber); + +void ephy_spinner_set_size (EphySpinner *spinner, + GtkIconSize size); + +G_END_DECLS + +#endif /* EPHY_SPINNER_H */ |