diff options
Diffstat (limited to 'src/fe-gtk/maingui.c')
-rw-r--r-- | src/fe-gtk/maingui.c | 3811 |
1 files changed, 3811 insertions, 0 deletions
diff --git a/src/fe-gtk/maingui.c b/src/fe-gtk/maingui.c new file mode 100644 index 00000000..ef269f95 --- /dev/null +++ b/src/fe-gtk/maingui.c @@ -0,0 +1,3811 @@ +/* X-Chat + * Copyright (C) 1998-2005 Peter Zelezny. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> + +#include <gtk/gtkarrow.h> +#include <gtk/gtktogglebutton.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkvbox.h> +#include <gtk/gtkeventbox.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkhpaned.h> +#include <gtk/gtkvpaned.h> +#include <gtk/gtkframe.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkmenuitem.h> +#include <gtk/gtkprogressbar.h> +#include <gtk/gtkscrolledwindow.h> +#include <gtk/gtkstock.h> +#include <gtk/gtktable.h> +#include <gtk/gtknotebook.h> +#include <gtk/gtkimage.h> +#include <gtk/gtkmessagedialog.h> +#include <gtk/gtkcheckmenuitem.h> +#include <gtk/gtkcheckbutton.h> +#include <gtk/gtkbbox.h> +#include <gtk/gtkvscrollbar.h> + +#include "../common/xchat.h" +#include "../common/fe.h" +#include "../common/server.h" +#include "../common/xchatc.h" +#include "../common/outbound.h" +#include "../common/inbound.h" +#include "../common/plugin.h" +#include "../common/modes.h" +#include "../common/url.h" +#include "fe-gtk.h" +#include "banlist.h" +#include "gtkutil.h" +#include "joind.h" +#include "palette.h" +#include "maingui.h" +#include "menu.h" +#include "fkeys.h" +#include "userlistgui.h" +#include "chanview.h" +#include "pixmaps.h" +#include "plugin-tray.h" +#include "xtext.h" + +#ifdef USE_GTKSPELL +#include <gtk/gtktextview.h> +#include <gtkspell/gtkspell.h> +#endif + +#ifdef USE_LIBSEXY +#include "sexy-spell-entry.h" +#endif + +#define GUI_SPACING (3) +#define GUI_BORDER (0) +#define SCROLLBAR_SPACING (2) + +enum +{ + POS_INVALID = 0, + POS_TOPLEFT = 1, + POS_BOTTOMLEFT = 2, + POS_TOPRIGHT = 3, + POS_BOTTOMRIGHT = 4, + POS_TOP = 5, /* for tabs only */ + POS_BOTTOM = 6, + POS_HIDDEN = 7 +}; + +/* two different types of tabs */ +#define TAG_IRC 0 /* server, channel, dialog */ +#define TAG_UTIL 1 /* dcc, notify, chanlist */ + +static void mg_create_entry (session *sess, GtkWidget *box); +static void mg_link_irctab (session *sess, int focus); + +static session_gui static_mg_gui; +static session_gui *mg_gui = NULL; /* the shared irc tab */ +static int ignore_chanmode = FALSE; +static const char chan_flags[] = { 't', 'n', 's', 'i', 'p', 'm', 'l', 'k' }; + +static chan *active_tab = NULL; /* active tab */ +GtkWidget *parent_window = NULL; /* the master window */ + +GtkStyle *input_style; + +static PangoAttrList *away_list; +static PangoAttrList *newdata_list; +static PangoAttrList *nickseen_list; +static PangoAttrList *newmsg_list; +static PangoAttrList *plain_list = NULL; + + +#ifdef USE_GTKSPELL + +/* use these when it's a GtkTextView instead of GtkEntry */ + +char * +SPELL_ENTRY_GET_TEXT (GtkWidget *entry) +{ + static char *last = NULL; /* warning: don't overlap 2 GET_TEXT calls! */ + GtkTextBuffer *buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (entry)); + GtkTextIter start_iter, end_iter; + + gtk_text_buffer_get_iter_at_offset (buf, &start_iter, 0); + gtk_text_buffer_get_end_iter (buf, &end_iter); + g_free (last); + last = gtk_text_buffer_get_text (buf, &start_iter, &end_iter, FALSE); + return last; +} + +void +SPELL_ENTRY_SET_POS (GtkWidget *entry, int pos) +{ + GtkTextIter iter; + GtkTextBuffer *buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (entry)); + + gtk_text_buffer_get_iter_at_offset (buf, &iter, pos); + gtk_text_buffer_place_cursor (buf, &iter); +} + +int +SPELL_ENTRY_GET_POS (GtkWidget *entry) +{ + GtkTextIter cursor; + GtkTextBuffer *buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (entry)); + + gtk_text_buffer_get_iter_at_mark (buf, &cursor, gtk_text_buffer_get_insert (buf)); + return gtk_text_iter_get_offset (&cursor); +} + +void +SPELL_ENTRY_INSERT (GtkWidget *entry, const char *text, int len, int *pos) +{ + GtkTextIter iter; + GtkTextBuffer *buf = gtk_text_view_get_buffer (GTK_TEXT_VIEW (entry)); + + /* len is bytes. pos is chars. */ + gtk_text_buffer_get_iter_at_offset (buf, &iter, *pos); + gtk_text_buffer_insert (buf, &iter, text, len); + *pos += g_utf8_strlen (text, len); +} + +#endif + +static PangoAttrList * +mg_attr_list_create (GdkColor *col, int size) +{ + PangoAttribute *attr; + PangoAttrList *list; + + list = pango_attr_list_new (); + + if (col) + { + attr = pango_attr_foreground_new (col->red, col->green, col->blue); + attr->start_index = 0; + attr->end_index = 0xffff; + pango_attr_list_insert (list, attr); + } + + if (size > 0) + { + attr = pango_attr_scale_new (size == 1 ? PANGO_SCALE_SMALL : PANGO_SCALE_X_SMALL); + attr->start_index = 0; + attr->end_index = 0xffff; + pango_attr_list_insert (list, attr); + } + + return list; +} + +static void +mg_create_tab_colors (void) +{ + if (plain_list) + { + pango_attr_list_unref (plain_list); + pango_attr_list_unref (newmsg_list); + pango_attr_list_unref (newdata_list); + pango_attr_list_unref (nickseen_list); + pango_attr_list_unref (away_list); + } + + plain_list = mg_attr_list_create (NULL, prefs.tab_small); + newdata_list = mg_attr_list_create (&colors[COL_NEW_DATA], prefs.tab_small); + nickseen_list = mg_attr_list_create (&colors[COL_HILIGHT], prefs.tab_small); + newmsg_list = mg_attr_list_create (&colors[COL_NEW_MSG], prefs.tab_small); + away_list = mg_attr_list_create (&colors[COL_AWAY], FALSE); +} + +#ifdef WIN32 +#define WINVER 0x0501 /* needed for vc6? */ +#include <windows.h> +#include <gdk/gdkwin32.h> + +/* Flash the taskbar button on Windows when there's a highlight event. */ + +static void +flash_window (GtkWidget *win) +{ + FLASHWINFO fi; + static HMODULE user = NULL; + static BOOL (*flash) (PFLASHWINFO) = NULL; + + if (!user) + { + user = GetModuleHandleA ("USER32"); + if (!user) + return; /* this should never fail */ + } + + if (!flash) + { + flash = (void *)GetProcAddress (user, "FlashWindowEx"); + if (!flash) + return; /* this fails on NT4.0 and Win95 */ + } + + fi.cbSize = sizeof (fi); + fi.hwnd = GDK_WINDOW_HWND (win->window); + fi.dwFlags = FLASHW_ALL | FLASHW_TIMERNOFG; + fi.uCount = 0; + fi.dwTimeout = 500; + flash (&fi); + /*FlashWindowEx (&fi);*/ +} +#else + +#ifdef USE_XLIB +#include <gdk/gdkx.h> + +static void +set_window_urgency (GtkWidget *win, gboolean set) +{ + XWMHints *hints; + + hints = XGetWMHints(GDK_WINDOW_XDISPLAY(win->window), GDK_WINDOW_XWINDOW(win->window)); + if (set) + hints->flags |= XUrgencyHint; + else + hints->flags &= ~XUrgencyHint; + XSetWMHints(GDK_WINDOW_XDISPLAY(win->window), + GDK_WINDOW_XWINDOW(win->window), hints); + XFree(hints); +} + +static void +flash_window (GtkWidget *win) +{ + set_window_urgency (win, TRUE); +} + +static void +unflash_window (GtkWidget *win) +{ + set_window_urgency (win, FALSE); +} +#endif +#endif + +/* flash the taskbar button */ + +void +fe_flash_window (session *sess) +{ +#if defined(WIN32) || defined(USE_XLIB) + if (fe_gui_info (sess, 0) != 1) /* only do it if not focused */ + flash_window (sess->gui->window); +#endif +} + +/* set a tab plain, red, light-red, or blue */ + +void +fe_set_tab_color (struct session *sess, int col) +{ + struct session *server_sess = sess->server->server_session; + if (sess->gui->is_tab && (col == 0 || sess != current_tab)) + { + switch (col) + { + case 0: /* no particular color (theme default) */ + sess->new_data = FALSE; + sess->msg_said = FALSE; + sess->nick_said = FALSE; + chan_set_color (sess->res->tab, plain_list); + break; + case 1: /* new data has been displayed (dark red) */ + sess->new_data = TRUE; + sess->msg_said = FALSE; + sess->nick_said = FALSE; + chan_set_color (sess->res->tab, newdata_list); + + if (chan_is_collapsed (sess->res->tab)) + { + server_sess->new_data = TRUE; + server_sess->msg_said = FALSE; + server_sess->nick_said = FALSE; + chan_set_color (chan_get_parent (sess->res->tab), newdata_list); + } + + break; + case 2: /* new message arrived in channel (light red) */ + sess->new_data = FALSE; + sess->msg_said = TRUE; + sess->nick_said = FALSE; + chan_set_color (sess->res->tab, newmsg_list); + + if (chan_is_collapsed (sess->res->tab)) + { + server_sess->new_data = FALSE; + server_sess->msg_said = TRUE; + server_sess->nick_said = FALSE; + chan_set_color (chan_get_parent (sess->res->tab), newmsg_list); + } + + break; + case 3: /* your nick has been seen (blue) */ + sess->new_data = FALSE; + sess->msg_said = FALSE; + sess->nick_said = TRUE; + chan_set_color (sess->res->tab, nickseen_list); + + if (chan_is_collapsed (sess->res->tab)) + { + server_sess->new_data = FALSE; + server_sess->msg_said = FALSE; + server_sess->nick_said = TRUE; + chan_set_color (chan_get_parent (sess->res->tab), nickseen_list); + } + + break; + } + } +} + +static void +mg_set_myself_away (session_gui *gui, gboolean away) +{ + gtk_label_set_attributes (GTK_LABEL (GTK_BIN (gui->nick_label)->child), + away ? away_list : NULL); +} + +/* change the little icon to the left of your nickname */ + +void +mg_set_access_icon (session_gui *gui, GdkPixbuf *pix, gboolean away) +{ + if (gui->op_xpm) + { + if (pix == gtk_image_get_pixbuf (GTK_IMAGE (gui->op_xpm))) /* no change? */ + { + mg_set_myself_away (gui, away); + return; + } + + gtk_widget_destroy (gui->op_xpm); + gui->op_xpm = NULL; + } + + if (pix) + { + gui->op_xpm = gtk_image_new_from_pixbuf (pix); + gtk_box_pack_start (GTK_BOX (gui->nick_box), gui->op_xpm, 0, 0, 0); + gtk_widget_show (gui->op_xpm); + } + + mg_set_myself_away (gui, away); +} + +static gboolean +mg_inputbox_focus (GtkWidget *widget, GdkEventFocus *event, session_gui *gui) +{ + GSList *list; + session *sess; + + if (gui->is_tab) + return FALSE; + + list = sess_list; + while (list) + { + sess = list->data; + if (sess->gui == gui) + { + current_sess = sess; + if (!sess->server->server_session) + sess->server->server_session = sess; + break; + } + list = list->next; + } + + return FALSE; +} + +void +mg_inputbox_cb (GtkWidget *igad, session_gui *gui) +{ + char *cmd; + static int ignore = FALSE; + GSList *list; + session *sess = NULL; + + if (ignore) + return; + + cmd = SPELL_ENTRY_GET_TEXT (igad); + if (cmd[0] == 0) + return; + + cmd = strdup (cmd); + + /* avoid recursive loop */ + ignore = TRUE; + SPELL_ENTRY_SET_TEXT (igad, ""); + ignore = FALSE; + + /* where did this event come from? */ + if (gui->is_tab) + { + sess = current_tab; + } else + { + list = sess_list; + while (list) + { + sess = list->data; + if (sess->gui == gui) + break; + list = list->next; + } + if (!list) + sess = NULL; + } + + if (sess) + handle_multiline (sess, cmd, TRUE, FALSE); + + free (cmd); +} + +static gboolean +has_key (char *modes) +{ + if (!modes) + return FALSE; + /* this is a crude check, but "-k" can't exist, so it works. */ + while (*modes) + { + if (*modes == 'k') + return TRUE; + if (*modes == ' ') + return FALSE; + modes++; + } + return FALSE; +} + +void +fe_set_title (session *sess) +{ + char tbuf[512]; + int type; + + if (sess->gui->is_tab && sess != current_tab) + return; + + type = sess->type; + + if (sess->server->connected == FALSE && sess->type != SESS_DIALOG) + goto def; + + switch (type) + { + case SESS_DIALOG: + snprintf (tbuf, sizeof (tbuf), DISPLAY_NAME": %s %s @ %s", + _("Dialog with"), sess->channel, server_get_network (sess->server, TRUE)); + break; + case SESS_SERVER: + snprintf (tbuf, sizeof (tbuf), DISPLAY_NAME": %s @ %s", + sess->server->nick, server_get_network (sess->server, TRUE)); + break; + case SESS_CHANNEL: + /* don't display keys in the titlebar */ + if ((!(prefs.gui_tweaks & 16)) && has_key (sess->current_modes)) + snprintf (tbuf, sizeof (tbuf), + DISPLAY_NAME": %s @ %s / %s", + sess->server->nick, server_get_network (sess->server, TRUE), + sess->channel); + else + snprintf (tbuf, sizeof (tbuf), + DISPLAY_NAME": %s @ %s / %s (%s)", + sess->server->nick, server_get_network (sess->server, TRUE), + sess->channel, sess->current_modes ? sess->current_modes : ""); + if (prefs.gui_tweaks & 1) + snprintf (tbuf + strlen (tbuf), 9, " (%d)", sess->total); + break; + case SESS_NOTICES: + case SESS_SNOTICES: + snprintf (tbuf, sizeof (tbuf), DISPLAY_NAME": %s @ %s (notices)", + sess->server->nick, server_get_network (sess->server, TRUE)); + break; + default: + def: + gtk_window_set_title (GTK_WINDOW (sess->gui->window), DISPLAY_NAME); + return; + } + + gtk_window_set_title (GTK_WINDOW (sess->gui->window), tbuf); +} + +static gboolean +mg_windowstate_cb (GtkWindow *wid, GdkEventWindowState *event, gpointer userdata) +{ + prefs.gui_win_state = 0; + if (event->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) + prefs.gui_win_state = 1; + + if ((event->changed_mask & GDK_WINDOW_STATE_ICONIFIED) && + (event->new_window_state & GDK_WINDOW_STATE_ICONIFIED) && + (prefs.gui_tray_flags & 4)) + { + tray_toggle_visibility (TRUE); + gtk_window_deiconify (wid); + } + + return FALSE; +} + +static gboolean +mg_configure_cb (GtkWidget *wid, GdkEventConfigure *event, session *sess) +{ + if (sess == NULL) /* for the main_window */ + { + if (mg_gui) + { + if (prefs.mainwindow_save) + { + sess = current_sess; + gtk_window_get_position (GTK_WINDOW (wid), &prefs.mainwindow_left, + &prefs.mainwindow_top); + gtk_window_get_size (GTK_WINDOW (wid), &prefs.mainwindow_width, + &prefs.mainwindow_height); + } + } + } + + if (sess) + { + if (sess->type == SESS_DIALOG && prefs.mainwindow_save) + { + gtk_window_get_position (GTK_WINDOW (wid), &prefs.dialog_left, + &prefs.dialog_top); + gtk_window_get_size (GTK_WINDOW (wid), &prefs.dialog_width, + &prefs.dialog_height); + } + + if (((GtkXText *) sess->gui->xtext)->transparent) + gtk_widget_queue_draw (sess->gui->xtext); + } + + return FALSE; +} + +/* move to a non-irc tab */ + +static void +mg_show_generic_tab (GtkWidget *box) +{ + int num; + GtkWidget *f = NULL; + +#if defined(GTK_WIDGET_HAS_FOCUS) + if (current_sess && GTK_WIDGET_HAS_FOCUS (current_sess->gui->input_box)) +#else + if (current_sess && gtk_widget_has_focus (current_sess->gui->input_box)) +#endif + f = current_sess->gui->input_box; + + num = gtk_notebook_page_num (GTK_NOTEBOOK (mg_gui->note_book), box); + gtk_notebook_set_current_page (GTK_NOTEBOOK (mg_gui->note_book), num); + gtk_tree_view_set_model (GTK_TREE_VIEW (mg_gui->user_tree), NULL); + gtk_window_set_title (GTK_WINDOW (mg_gui->window), + g_object_get_data (G_OBJECT (box), "title")); + gtk_widget_set_sensitive (mg_gui->menu, FALSE); + + if (f) + gtk_widget_grab_focus (f); +} + +/* a channel has been focused */ + +static void +mg_focus (session *sess) +{ + if (sess->gui->is_tab) + current_tab = sess; + current_sess = sess; + + /* dirty trick to avoid auto-selection */ + SPELL_ENTRY_SET_EDITABLE (sess->gui->input_box, FALSE); + gtk_widget_grab_focus (sess->gui->input_box); + SPELL_ENTRY_SET_EDITABLE (sess->gui->input_box, TRUE); + + sess->server->front_session = sess; + + if (sess->server->server_session != NULL) + { + if (sess->server->server_session->type != SESS_SERVER) + sess->server->server_session = sess; + } else + { + sess->server->server_session = sess; + } + + if (sess->new_data || sess->nick_said || sess->msg_said) + { + sess->nick_said = FALSE; + sess->msg_said = FALSE; + sess->new_data = FALSE; + /* when called via mg_changui_new, is_tab might be true, but + sess->res->tab is still NULL. */ + if (sess->res->tab) + fe_set_tab_color (sess, 0); + } +} + +static int +mg_progressbar_update (GtkWidget *bar) +{ + static int type = 0; + static float pos = 0; + + pos += 0.05; + if (pos >= 0.99) + { + if (type == 0) + { + type = 1; + gtk_progress_bar_set_orientation ((GtkProgressBar *) bar, + GTK_PROGRESS_RIGHT_TO_LEFT); + } else + { + type = 0; + gtk_progress_bar_set_orientation ((GtkProgressBar *) bar, + GTK_PROGRESS_LEFT_TO_RIGHT); + } + pos = 0.05; + } + gtk_progress_bar_set_fraction ((GtkProgressBar *) bar, pos); + return 1; +} + +void +mg_progressbar_create (session_gui *gui) +{ + gui->bar = gtk_progress_bar_new (); + gtk_box_pack_start (GTK_BOX (gui->nick_box), gui->bar, 0, 0, 0); + gtk_widget_show (gui->bar); + gui->bartag = fe_timeout_add (50, mg_progressbar_update, gui->bar); +} + +void +mg_progressbar_destroy (session_gui *gui) +{ + fe_timeout_remove (gui->bartag); + gtk_widget_destroy (gui->bar); + gui->bar = 0; + gui->bartag = 0; +} + +/* switching tabs away from this one, so remember some info about it! */ + +static void +mg_unpopulate (session *sess) +{ + restore_gui *res; + session_gui *gui; + int i; + + gui = sess->gui; + res = sess->res; + + res->input_text = strdup (SPELL_ENTRY_GET_TEXT (gui->input_box)); + res->topic_text = strdup (GTK_ENTRY (gui->topic_entry)->text); + res->limit_text = strdup (GTK_ENTRY (gui->limit_entry)->text); + res->key_text = strdup (GTK_ENTRY (gui->key_entry)->text); + if (gui->laginfo) + res->lag_text = strdup (gtk_label_get_text (GTK_LABEL (gui->laginfo))); + if (gui->throttleinfo) + res->queue_text = strdup (gtk_label_get_text (GTK_LABEL (gui->throttleinfo))); + + for (i = 0; i < NUM_FLAG_WIDS - 1; i++) + res->flag_wid_state[i] = GTK_TOGGLE_BUTTON (gui->flag_wid[i])->active; + + res->old_ul_value = userlist_get_value (gui->user_tree); + if (gui->lagometer) + res->lag_value = gtk_progress_bar_get_fraction ( + GTK_PROGRESS_BAR (gui->lagometer)); + if (gui->throttlemeter) + res->queue_value = gtk_progress_bar_get_fraction ( + GTK_PROGRESS_BAR (gui->throttlemeter)); + + if (gui->bar) + { + res->c_graph = TRUE; /* still have a graph, just not visible now */ + mg_progressbar_destroy (gui); + } +} + +static void +mg_restore_label (GtkWidget *label, char **text) +{ + if (!label) + return; + + if (*text) + { + gtk_label_set_text (GTK_LABEL (label), *text); + free (*text); + *text = NULL; + } else + { + gtk_label_set_text (GTK_LABEL (label), ""); + } +} + +static void +mg_restore_entry (GtkWidget *entry, char **text) +{ + if (*text) + { + gtk_entry_set_text (GTK_ENTRY (entry), *text); + free (*text); + *text = NULL; + } else + { + gtk_entry_set_text (GTK_ENTRY (entry), ""); + } + gtk_editable_set_position (GTK_EDITABLE (entry), -1); +} + +static void +mg_restore_speller (GtkWidget *entry, char **text) +{ + if (*text) + { + SPELL_ENTRY_SET_TEXT (entry, *text); + free (*text); + *text = NULL; + } else + { + SPELL_ENTRY_SET_TEXT (entry, ""); + } + SPELL_ENTRY_SET_POS (entry, -1); +} + +void +mg_set_topic_tip (session *sess) +{ + char *text; + + switch (sess->type) + { + case SESS_CHANNEL: + if (sess->topic) + { + text = g_strdup_printf (_("Topic for %s is: %s"), sess->channel, + sess->topic); + add_tip (sess->gui->topic_entry, text); + g_free (text); + } else + add_tip (sess->gui->topic_entry, _("No topic is set")); + break; + default: + if (GTK_ENTRY (sess->gui->topic_entry)->text && + GTK_ENTRY (sess->gui->topic_entry)->text[0]) + add_tip (sess->gui->topic_entry, GTK_ENTRY (sess->gui->topic_entry)->text); + else + add_tip (sess->gui->topic_entry, NULL); + } +} + +static void +mg_hide_empty_pane (GtkPaned *pane) +{ +#if defined(GTK_WIDGET_VISIBLE) + if ((pane->child1 == NULL || !GTK_WIDGET_VISIBLE (pane->child1)) && + (pane->child2 == NULL || !GTK_WIDGET_VISIBLE (pane->child2))) +#else + if ((pane->child1 == NULL || !gtk_widget_get_visible (pane->child1)) && + (pane->child2 == NULL || !gtk_widget_get_visible (pane->child2))) +#endif + { + gtk_widget_hide (GTK_WIDGET (pane)); + return; + } + + gtk_widget_show (GTK_WIDGET (pane)); +} + +static void +mg_hide_empty_boxes (session_gui *gui) +{ + /* hide empty vpanes - so the handle is not shown */ + mg_hide_empty_pane ((GtkPaned*)gui->vpane_right); + mg_hide_empty_pane ((GtkPaned*)gui->vpane_left); +} + +static void +mg_userlist_showhide (session *sess, int show) +{ + session_gui *gui = sess->gui; + int handle_size; + + if (show) + { + gtk_widget_show (gui->user_box); + gui->ul_hidden = 0; + + gtk_widget_style_get (GTK_WIDGET (gui->hpane_right), "handle-size", &handle_size, NULL); + gtk_paned_set_position (GTK_PANED (gui->hpane_right), GTK_WIDGET (gui->hpane_right)->allocation.width - (prefs.gui_pane_right_size + handle_size)); + } + else + { + gtk_widget_hide (gui->user_box); + gui->ul_hidden = 1; + } + + mg_hide_empty_boxes (gui); +} + +static gboolean +mg_is_userlist_and_tree_combined (void) +{ + if (prefs.tab_pos == POS_TOPLEFT && prefs.gui_ulist_pos == POS_BOTTOMLEFT) + return TRUE; + if (prefs.tab_pos == POS_BOTTOMLEFT && prefs.gui_ulist_pos == POS_TOPLEFT) + return TRUE; + + if (prefs.tab_pos == POS_TOPRIGHT && prefs.gui_ulist_pos == POS_BOTTOMRIGHT) + return TRUE; + if (prefs.tab_pos == POS_BOTTOMRIGHT && prefs.gui_ulist_pos == POS_TOPRIGHT) + return TRUE; + + return FALSE; +} + +/* decide if the userlist should be shown or hidden for this tab */ + +void +mg_decide_userlist (session *sess, gboolean switch_to_current) +{ + /* when called from menu.c we need this */ + if (sess->gui == mg_gui && switch_to_current) + sess = current_tab; + + if (prefs.hideuserlist) + { + mg_userlist_showhide (sess, FALSE); + return; + } + + switch (sess->type) + { + case SESS_SERVER: + case SESS_DIALOG: + case SESS_NOTICES: + case SESS_SNOTICES: + if (mg_is_userlist_and_tree_combined ()) + mg_userlist_showhide (sess, TRUE); /* show */ + else + mg_userlist_showhide (sess, FALSE); /* hide */ + break; + default: + mg_userlist_showhide (sess, TRUE); /* show */ + } +} + +static void +mg_userlist_toggle_cb (GtkWidget *button, gpointer userdata) +{ + prefs.hideuserlist = !prefs.hideuserlist; + mg_decide_userlist (current_sess, FALSE); + gtk_widget_grab_focus (current_sess->gui->input_box); +} + +static int ul_tag = 0; + +static gboolean +mg_populate_userlist (session *sess) +{ + session_gui *gui; + + if (!sess) + sess = current_tab; + + if (is_session (sess)) + { + gui = sess->gui; + if (sess->type == SESS_DIALOG) + mg_set_access_icon (sess->gui, NULL, sess->server->is_away); + else + mg_set_access_icon (sess->gui, get_user_icon (sess->server, sess->me), sess->server->is_away); + userlist_show (sess); + userlist_set_value (sess->gui->user_tree, sess->res->old_ul_value); + } + + ul_tag = 0; + return 0; +} + +/* fill the irc tab with a new channel */ + +static void +mg_populate (session *sess) +{ + session_gui *gui = sess->gui; + restore_gui *res = sess->res; + int i, render = TRUE; + guint16 vis = gui->ul_hidden; + + switch (sess->type) + { + case SESS_DIALOG: + /* show the dialog buttons */ + gtk_widget_show (gui->dialogbutton_box); + /* hide the chan-mode buttons */ + gtk_widget_hide (gui->topicbutton_box); + /* hide the userlist */ + mg_decide_userlist (sess, FALSE); + /* shouldn't edit the topic */ + gtk_editable_set_editable (GTK_EDITABLE (gui->topic_entry), FALSE); + break; + case SESS_SERVER: + if (prefs.chanmodebuttons) + gtk_widget_show (gui->topicbutton_box); + /* hide the dialog buttons */ + gtk_widget_hide (gui->dialogbutton_box); + /* hide the userlist */ + mg_decide_userlist (sess, FALSE); + /* shouldn't edit the topic */ + gtk_editable_set_editable (GTK_EDITABLE (gui->topic_entry), FALSE); + break; + default: + /* hide the dialog buttons */ + gtk_widget_hide (gui->dialogbutton_box); + if (prefs.chanmodebuttons) + gtk_widget_show (gui->topicbutton_box); + /* show the userlist */ + mg_decide_userlist (sess, FALSE); + /* let the topic be editted */ + gtk_editable_set_editable (GTK_EDITABLE (gui->topic_entry), TRUE); + } + + /* move to THE irc tab */ + if (gui->is_tab) + gtk_notebook_set_current_page (GTK_NOTEBOOK (gui->note_book), 0); + + /* xtext size change? Then don't render, wait for the expose caused + by showing/hidding the userlist */ + if (vis != gui->ul_hidden && gui->user_box->allocation.width > 1) + render = FALSE; + + gtk_xtext_buffer_show (GTK_XTEXT (gui->xtext), res->buffer, render); + + if (gui->is_tab) + gtk_widget_set_sensitive (gui->menu, TRUE); + + /* restore all the GtkEntry's */ + mg_restore_entry (gui->topic_entry, &res->topic_text); + mg_restore_speller (gui->input_box, &res->input_text); + mg_restore_entry (gui->key_entry, &res->key_text); + mg_restore_entry (gui->limit_entry, &res->limit_text); + mg_restore_label (gui->laginfo, &res->lag_text); + mg_restore_label (gui->throttleinfo, &res->queue_text); + + mg_focus (sess); + fe_set_title (sess); + + /* this one flickers, so only change if necessary */ + if (strcmp (sess->server->nick, gtk_button_get_label (GTK_BUTTON (gui->nick_label))) != 0) + gtk_button_set_label (GTK_BUTTON (gui->nick_label), sess->server->nick); + + /* this is slow, so make it a timeout event */ + if (!gui->is_tab) + { + mg_populate_userlist (sess); + } else + { + if (ul_tag == 0) + ul_tag = g_idle_add ((GSourceFunc)mg_populate_userlist, NULL); + } + + fe_userlist_numbers (sess); + + /* restore all the channel mode buttons */ + ignore_chanmode = TRUE; + for (i = 0; i < NUM_FLAG_WIDS - 1; i++) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (gui->flag_wid[i]), + res->flag_wid_state[i]); + ignore_chanmode = FALSE; + + if (gui->lagometer) + { + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (gui->lagometer), + res->lag_value); + if (res->lag_tip) + add_tip (sess->gui->lagometer->parent, res->lag_tip); + } + if (gui->throttlemeter) + { + gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (gui->throttlemeter), + res->queue_value); + if (res->queue_tip) + add_tip (sess->gui->throttlemeter->parent, res->queue_tip); + } + + /* did this tab have a connecting graph? restore it.. */ + if (res->c_graph) + { + res->c_graph = FALSE; + mg_progressbar_create (gui); + } + + /* menu items */ + GTK_CHECK_MENU_ITEM (gui->menu_item[MENU_ID_AWAY])->active = sess->server->is_away; + gtk_widget_set_sensitive (gui->menu_item[MENU_ID_AWAY], sess->server->connected); + gtk_widget_set_sensitive (gui->menu_item[MENU_ID_JOIN], sess->server->end_of_motd); + gtk_widget_set_sensitive (gui->menu_item[MENU_ID_DISCONNECT], + sess->server->connected || sess->server->recondelay_tag); + + mg_set_topic_tip (sess); + + plugin_emit_dummy_print (sess, "Focus Tab"); +} + +void +mg_bring_tofront_sess (session *sess) /* IRC tab or window */ +{ + if (sess->gui->is_tab) + chan_focus (sess->res->tab); + else + gtk_window_present (GTK_WINDOW (sess->gui->window)); +} + +void +mg_bring_tofront (GtkWidget *vbox) /* non-IRC tab or window */ +{ + chan *ch; + + ch = g_object_get_data (G_OBJECT (vbox), "ch"); + if (ch) + chan_focus (ch); + else + gtk_window_present (GTK_WINDOW (gtk_widget_get_toplevel (vbox))); +} + +void +mg_switch_page (int relative, int num) +{ + if (mg_gui) + chanview_move_focus (mg_gui->chanview, relative, num); +} + +/* a toplevel IRC window was destroyed */ + +static void +mg_topdestroy_cb (GtkWidget *win, session *sess) +{ +/* printf("enter mg_topdestroy. sess %p was destroyed\n", sess);*/ + + /* kill the text buffer */ + gtk_xtext_buffer_free (sess->res->buffer); + /* kill the user list */ + g_object_unref (G_OBJECT (sess->res->user_model)); + + session_free (sess); /* tell xchat.c about it */ +} + +/* cleanup an IRC tab */ + +static void +mg_ircdestroy (session *sess) +{ + GSList *list; + + /* kill the text buffer */ + gtk_xtext_buffer_free (sess->res->buffer); + /* kill the user list */ + g_object_unref (G_OBJECT (sess->res->user_model)); + + session_free (sess); /* tell xchat.c about it */ + + if (mg_gui == NULL) + { +/* puts("-> mg_gui is already NULL");*/ + return; + } + + list = sess_list; + while (list) + { + sess = list->data; + if (sess->gui->is_tab) + { +/* puts("-> some tabs still remain");*/ + return; + } + list = list->next; + } + +/* puts("-> no tabs left, killing main tabwindow");*/ + gtk_widget_destroy (mg_gui->window); + active_tab = NULL; + mg_gui = NULL; + parent_window = NULL; +} + +static void +mg_tab_close_cb (GtkWidget *dialog, gint arg1, session *sess) +{ + GSList *list, *next; + + gtk_widget_destroy (dialog); + if (arg1 == GTK_RESPONSE_OK && is_session (sess)) + { + /* force it NOT to send individual PARTs */ + sess->server->sent_quit = TRUE; + + for (list = sess_list; list;) + { + next = list->next; + if (((session *)list->data)->server == sess->server && + ((session *)list->data) != sess) + fe_close_window ((session *)list->data); + list = next; + } + + /* just send one QUIT - better for BNCs */ + sess->server->sent_quit = FALSE; + fe_close_window (sess); + } +} + +void +mg_tab_close (session *sess) +{ + GtkWidget *dialog; + GSList *list; + int i; + + if (chan_remove (sess->res->tab, FALSE)) + mg_ircdestroy (sess); + else + { + for (i = 0, list = sess_list; list; list = list->next) + if (((session *)list->data)->server == sess->server) + i++; + dialog = gtk_message_dialog_new (GTK_WINDOW (parent_window), 0, + GTK_MESSAGE_WARNING, GTK_BUTTONS_OK_CANCEL, + _("This server still has %d channels or dialogs associated with it. " + "Close them all?"), i); + g_signal_connect (G_OBJECT (dialog), "response", + G_CALLBACK (mg_tab_close_cb), sess); + if (prefs.tab_layout) + { + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + } + else + { + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER_ON_PARENT); + } + gtk_widget_show (dialog); + } +} + +static void +mg_menu_destroy (GtkWidget *menu, gpointer userdata) +{ + gtk_widget_destroy (menu); + g_object_unref (menu); +} + +void +mg_create_icon_item (char *label, char *stock, GtkWidget *menu, + void *callback, void *userdata) +{ + GtkWidget *item; + + item = create_icon_menu (label, stock, TRUE); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + g_signal_connect (G_OBJECT (item), "activate", G_CALLBACK (callback), + userdata); + gtk_widget_show (item); +} + +static int +mg_count_networks (void) +{ + int cons = 0; + GSList *list; + + for (list = serv_list; list; list = list->next) + { + if (((server *)list->data)->connected) + cons++; + } + return cons; +} + +static int +mg_count_dccs (void) +{ + GSList *list; + struct DCC *dcc; + int dccs = 0; + + list = dcc_list; + while (list) + { + dcc = list->data; + if ((dcc->type == TYPE_SEND || dcc->type == TYPE_RECV) && + dcc->dccstat == STAT_ACTIVE) + dccs++; + list = list->next; + } + + return dccs; +} + +void +mg_open_quit_dialog (gboolean minimize_button) +{ + static GtkWidget *dialog = NULL; + GtkWidget *dialog_vbox1; + GtkWidget *table1; + GtkWidget *image; + GtkWidget *checkbutton1; + GtkWidget *label; + GtkWidget *dialog_action_area1; + GtkWidget *button; + char *text, *connecttext; + int cons; + int dccs; + + if (dialog) + { + gtk_window_present (GTK_WINDOW (dialog)); + return; + } + + dccs = mg_count_dccs (); + cons = mg_count_networks (); + if (dccs + cons == 0 || !prefs.gui_quit_dialog) + { + xchat_exit (); + return; + } + + dialog = gtk_dialog_new (); + gtk_container_set_border_width (GTK_CONTAINER (dialog), 6); + gtk_window_set_title (GTK_WINDOW (dialog), _("Quit XChat?")); + gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window)); + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + gtk_dialog_set_has_separator (GTK_DIALOG (dialog), FALSE); + + dialog_vbox1 = GTK_DIALOG (dialog)->vbox; + gtk_widget_show (dialog_vbox1); + + table1 = gtk_table_new (2, 2, FALSE); + gtk_widget_show (table1); + gtk_box_pack_start (GTK_BOX (dialog_vbox1), table1, TRUE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (table1), 6); + gtk_table_set_row_spacings (GTK_TABLE (table1), 12); + gtk_table_set_col_spacings (GTK_TABLE (table1), 12); + + image = gtk_image_new_from_stock ("gtk-dialog-warning", GTK_ICON_SIZE_DIALOG); + gtk_widget_show (image); + gtk_table_attach (GTK_TABLE (table1), image, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + + checkbutton1 = gtk_check_button_new_with_mnemonic (_("Don't ask next time.")); + gtk_widget_show (checkbutton1); + gtk_table_attach (GTK_TABLE (table1), checkbutton1, 0, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 4); + + connecttext = g_strdup_printf (_("You are connected to %i IRC networks."), cons); + text = g_strdup_printf ("<span weight=\"bold\" size=\"larger\">%s</span>\n\n%s\n%s", + _("Are you sure you want to quit?"), + cons ? connecttext : "", + dccs ? _("Some file transfers are still active.") : ""); + g_free (connecttext); + label = gtk_label_new (text); + g_free (text); + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table1), label, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_SHRINK | GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND | GTK_SHRINK), 0, 0); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + + dialog_action_area1 = GTK_DIALOG (dialog)->action_area; + gtk_widget_show (dialog_action_area1); + gtk_button_box_set_layout (GTK_BUTTON_BOX (dialog_action_area1), + GTK_BUTTONBOX_END); + + if (minimize_button) + { + button = gtk_button_new_with_mnemonic (_("_Minimize to Tray")); + gtk_widget_show (button); + gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, 1); + } + + button = gtk_button_new_from_stock ("gtk-cancel"); + gtk_widget_show (button); + gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, + GTK_RESPONSE_CANCEL); + gtk_widget_grab_focus (button); + + button = gtk_button_new_from_stock ("gtk-quit"); + gtk_widget_show (button); + gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, 0); + + gtk_widget_show (dialog); + + switch (gtk_dialog_run (GTK_DIALOG (dialog))) + { + case 0: + if (GTK_TOGGLE_BUTTON (checkbutton1)->active) + prefs.gui_quit_dialog = 0; + xchat_exit (); + break; + case 1: /* minimize to tray */ + if (GTK_TOGGLE_BUTTON (checkbutton1)->active) + { + prefs.gui_tray_flags |= 1; + /*prefs.gui_quit_dialog = 0;*/ + } + /* force tray icon ON, if not already */ + if (!prefs.gui_tray) + { + prefs.gui_tray = 1; + tray_apply_setup (); + } + tray_toggle_visibility (TRUE); + break; + } + + gtk_widget_destroy (dialog); + dialog = NULL; +} + +void +mg_close_sess (session *sess) +{ + if (sess_list->next == NULL) + { + mg_open_quit_dialog (FALSE); + return; + } + + fe_close_window (sess); +} + +static int +mg_chan_remove (chan *ch) +{ + /* remove the tab from chanview */ + chan_remove (ch, TRUE); + /* any tabs left? */ + if (chanview_get_size (mg_gui->chanview) < 1) + { + /* if not, destroy the main tab window */ + gtk_widget_destroy (mg_gui->window); + current_tab = NULL; + active_tab = NULL; + mg_gui = NULL; + parent_window = NULL; + return TRUE; + } + return FALSE; +} + +/* destroy non-irc tab/window */ + +static void +mg_close_gen (chan *ch, GtkWidget *box) +{ + char *title = g_object_get_data (G_OBJECT (box), "title"); + + if (title) + free (title); + if (!ch) + ch = g_object_get_data (G_OBJECT (box), "ch"); + if (ch) + { + /* remove from notebook */ + gtk_widget_destroy (box); + /* remove the tab from chanview */ + mg_chan_remove (ch); + } else + { + gtk_widget_destroy (gtk_widget_get_toplevel (box)); + } +} + +/* the "X" close button has been pressed (tab-view) */ + +static void +mg_xbutton_cb (chanview *cv, chan *ch, int tag, gpointer userdata) +{ + if (tag == TAG_IRC) /* irc tab */ + mg_close_sess (userdata); + else /* non-irc utility tab */ + mg_close_gen (ch, userdata); +} + +static void +mg_link_gentab (chan *ch, GtkWidget *box) +{ + int num; + GtkWidget *win; + + g_object_ref (box); + + num = gtk_notebook_page_num (GTK_NOTEBOOK (mg_gui->note_book), box); + gtk_notebook_remove_page (GTK_NOTEBOOK (mg_gui->note_book), num); + mg_chan_remove (ch); + + win = gtkutil_window_new (g_object_get_data (G_OBJECT (box), "title"), "", + GPOINTER_TO_INT (g_object_get_data (G_OBJECT (box), "w")), + GPOINTER_TO_INT (g_object_get_data (G_OBJECT (box), "h")), + 3); + /* so it doesn't try to chan_remove (there's no tab anymore) */ + g_object_steal_data (G_OBJECT (box), "ch"); + gtk_container_set_border_width (GTK_CONTAINER (box), 0); + gtk_container_add (GTK_CONTAINER (win), box); + gtk_widget_show (win); + + g_object_unref (box); +} + +static void +mg_detach_tab_cb (GtkWidget *item, chan *ch) +{ + if (chan_get_tag (ch) == TAG_IRC) /* IRC tab */ + { + /* userdata is session * */ + mg_link_irctab (chan_get_userdata (ch), 1); + return; + } + + /* userdata is GtkWidget * */ + mg_link_gentab (ch, chan_get_userdata (ch)); /* non-IRC tab */ +} + +static void +mg_destroy_tab_cb (GtkWidget *item, chan *ch) +{ + /* treat it just like the X button press */ + mg_xbutton_cb (mg_gui->chanview, ch, chan_get_tag (ch), chan_get_userdata (ch)); +} + +static void +mg_color_insert (GtkWidget *item, gpointer userdata) +{ + char buf[32]; + char *text; + int num = GPOINTER_TO_INT (userdata); + + if (num > 99) + { + switch (num) + { + case 100: + text = "\002"; break; + case 101: + text = "\037"; break; + case 102: + text = "\035"; break; + default: + text = "\017"; break; + } + key_action_insert (current_sess->gui->input_box, 0, text, 0, 0); + } else + { + sprintf (buf, "\003%02d", num); + key_action_insert (current_sess->gui->input_box, 0, buf, 0, 0); + } +} + +static void +mg_markup_item (GtkWidget *menu, char *text, int arg) +{ + GtkWidget *item; + + item = gtk_menu_item_new_with_label (""); + gtk_label_set_markup (GTK_LABEL (GTK_BIN (item)->child), text); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (mg_color_insert), GINT_TO_POINTER (arg)); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show (item); +} + +GtkWidget * +mg_submenu (GtkWidget *menu, char *text) +{ + GtkWidget *submenu, *item; + + item = gtk_menu_item_new_with_mnemonic (text); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show (item); + + submenu = gtk_menu_new (); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu); + gtk_widget_show (submenu); + + return submenu; +} + +static void +mg_create_color_menu (GtkWidget *menu, session *sess) +{ + GtkWidget *submenu; + GtkWidget *subsubmenu; + char buf[256]; + int i; + + submenu = mg_submenu (menu, _("Insert Attribute or Color Code")); + + mg_markup_item (submenu, _("<b>Bold</b>"), 100); + mg_markup_item (submenu, _("<u>Underline</u>"), 101); + /*mg_markup_item (submenu, _("<i>Italic</i>"), 102);*/ + mg_markup_item (submenu, _("Normal"), 103); + + subsubmenu = mg_submenu (submenu, _("Colors 0-7")); + + for (i = 0; i < 8; i++) + { + sprintf (buf, "<tt><sup>%02d</sup> <span background=\"#%02x%02x%02x\">" + " </span></tt>", + i, colors[i].red >> 8, colors[i].green >> 8, colors[i].blue >> 8); + mg_markup_item (subsubmenu, buf, i); + } + + subsubmenu = mg_submenu (submenu, _("Colors 8-15")); + + for (i = 8; i < 16; i++) + { + sprintf (buf, "<tt><sup>%02d</sup> <span background=\"#%02x%02x%02x\">" + " </span></tt>", + i, colors[i].red >> 8, colors[i].green >> 8, colors[i].blue >> 8); + mg_markup_item (subsubmenu, buf, i); + } +} + +static void +mg_set_guint8 (GtkCheckMenuItem *item, guint8 *setting) +{ + session *sess = current_sess; + guint8 logging = sess->text_logging; + + *setting = SET_OFF; + if (item->active) + *setting = SET_ON; + + /* has the logging setting changed? */ + if (logging != sess->text_logging) + log_open_or_close (sess); +} + +static void +mg_perchan_menu_item (char *label, GtkWidget *menu, guint8 *setting, guint global) +{ + guint8 initial_value = *setting; + + /* if it's using global value, use that as initial state */ + if (initial_value == SET_DEFAULT) + initial_value = global; + + menu_toggle_item (label, menu, mg_set_guint8, setting, initial_value); +} + +static void +mg_create_perchannelmenu (session *sess, GtkWidget *menu) +{ + GtkWidget *submenu; + + submenu = menu_quick_sub (_("_Settings"), menu, NULL, XCMENU_MNEMONIC, -1); + + mg_perchan_menu_item (_("_Log to Disk"), submenu, &sess->text_logging, prefs.logging); + mg_perchan_menu_item (_("_Reload Scrollback"), submenu, &sess->text_scrollback, prefs.text_replay); + if (sess->type == SESS_CHANNEL) + mg_perchan_menu_item (_("_Hide Join/Part Messages"), submenu, &sess->text_hidejoinpart, prefs.confmode); +} + +static void +mg_create_alertmenu (session *sess, GtkWidget *menu) +{ + GtkWidget *submenu; + + submenu = menu_quick_sub (_("_Extra Alerts"), menu, NULL, XCMENU_MNEMONIC, -1); + + mg_perchan_menu_item (_("Beep on _Message"), submenu, &sess->alert_beep, prefs.input_beep_chans); + mg_perchan_menu_item (_("Blink Tray _Icon"), submenu, &sess->alert_tray, prefs.input_tray_chans); + mg_perchan_menu_item (_("Blink Task _Bar"), submenu, &sess->alert_taskbar, prefs.input_flash_chans); +} + +static void +mg_create_tabmenu (session *sess, GdkEventButton *event, chan *ch) +{ + GtkWidget *menu, *item; + char buf[256]; + + menu = gtk_menu_new (); + + if (sess) + { + char *name = g_markup_escape_text (sess->channel[0] ? sess->channel : _("<none>"), -1); + snprintf (buf, sizeof (buf), "<span foreground=\"#3344cc\"><b>%s</b></span>", name); + g_free (name); + + item = gtk_menu_item_new_with_label (""); + gtk_label_set_markup (GTK_LABEL (GTK_BIN (item)->child), buf); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show (item); + + /* separator */ + menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0); + + /* per-channel alerts */ + mg_create_alertmenu (sess, menu); + + /* per-channel settings */ + mg_create_perchannelmenu (sess, menu); + + /* separator */ + menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0); + + if (sess->type == SESS_CHANNEL) + menu_addfavoritemenu (sess->server, menu, sess->channel); + } + + mg_create_icon_item (_("_Detach"), GTK_STOCK_REDO, menu, + mg_detach_tab_cb, ch); + mg_create_icon_item (_("_Close"), GTK_STOCK_CLOSE, menu, + mg_destroy_tab_cb, ch); + if (sess && tabmenu_list) + menu_create (menu, tabmenu_list, sess->channel, FALSE); + menu_add_plugin_items (menu, "\x4$TAB", sess->channel); + + if (event->window) + gtk_menu_set_screen (GTK_MENU (menu), gdk_drawable_get_screen (event->window)); + g_object_ref (menu); + g_object_ref_sink (menu); + g_object_unref (menu); + g_signal_connect (G_OBJECT (menu), "selection-done", + G_CALLBACK (mg_menu_destroy), NULL); + gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 0, event->time); +} + +static gboolean +mg_tab_contextmenu_cb (chanview *cv, chan *ch, int tag, gpointer ud, GdkEventButton *event) +{ + /* shift-click to close a tab */ + if ((event->state & GDK_SHIFT_MASK) && event->type == GDK_BUTTON_PRESS) + { + mg_xbutton_cb (cv, ch, tag, ud); + return FALSE; + } + + if (event->button != 3) + return FALSE; + + if (tag == TAG_IRC) + mg_create_tabmenu (ud, event, ch); + else + mg_create_tabmenu (NULL, event, ch); + + return TRUE; +} + +void +mg_dnd_drop_file (session *sess, char *target, char *uri) +{ + char *p, *data, *next, *fname; + + p = data = strdup (uri); + while (*p) + { + next = strchr (p, '\r'); + if (strncasecmp ("file:", p, 5) == 0) + { + if (next) + *next = 0; + fname = g_filename_from_uri (p, NULL, NULL); + if (fname) + { + /* dcc_send() expects utf-8 */ + p = xchat_filename_to_utf8 (fname, -1, 0, 0, 0); + if (p) + { + dcc_send (sess, target, p, prefs.dcc_max_send_cps, 0); + g_free (p); + } + g_free (fname); + } + } + if (!next) + break; + p = next + 1; + if (*p == '\n') + p++; + } + free (data); + +} + +static void +mg_dialog_dnd_drop (GtkWidget * widget, GdkDragContext * context, gint x, + gint y, GtkSelectionData * selection_data, guint info, + guint32 time, gpointer ud) +{ + if (current_sess->type == SESS_DIALOG) + /* sess->channel is really the nickname of dialogs */ + mg_dnd_drop_file (current_sess, current_sess->channel, selection_data->data); +} + +/* add a tabbed channel */ + +static void +mg_add_chan (session *sess) +{ + GdkPixbuf *icon; + char *name = _("<none>"); + + if (sess->channel[0]) + name = sess->channel; + + switch (sess->type) + { + case SESS_CHANNEL: + icon = pix_channel; + break; + case SESS_SERVER: + icon = pix_server; + break; + default: + icon = pix_dialog; + } + + sess->res->tab = chanview_add (sess->gui->chanview, name, sess->server, sess, + sess->type == SESS_SERVER ? FALSE : TRUE, + TAG_IRC, icon); + if (plain_list == NULL) + mg_create_tab_colors (); + + chan_set_color (sess->res->tab, plain_list); + + if (sess->res->buffer == NULL) + { + sess->res->buffer = gtk_xtext_buffer_new (GTK_XTEXT (sess->gui->xtext)); + gtk_xtext_set_time_stamp (sess->res->buffer, prefs.timestamp); + sess->res->user_model = userlist_create_model (); + } +} + +static void +mg_userlist_button (GtkWidget * box, char *label, char *cmd, + int a, int b, int c, int d) +{ + GtkWidget *wid = gtk_button_new_with_label (label); + g_signal_connect (G_OBJECT (wid), "clicked", + G_CALLBACK (userlist_button_cb), cmd); + gtk_table_attach_defaults (GTK_TABLE (box), wid, a, b, c, d); + show_and_unfocus (wid); +} + +static GtkWidget * +mg_create_userlistbuttons (GtkWidget *box) +{ + struct popup *pop; + GSList *list = button_list; + int a = 0, b = 0; + GtkWidget *tab; + + tab = gtk_table_new (5, 2, FALSE); + gtk_box_pack_end (GTK_BOX (box), tab, FALSE, FALSE, 0); + + while (list) + { + pop = list->data; + if (pop->cmd[0]) + { + mg_userlist_button (tab, pop->name, pop->cmd, a, a + 1, b, b + 1); + a++; + if (a == 2) + { + a = 0; + b++; + } + } + list = list->next; + } + + return tab; +} + +static void +mg_topic_cb (GtkWidget *entry, gpointer userdata) +{ + session *sess = current_sess; + char *text; + + if (sess->channel[0] && sess->server->connected && sess->type == SESS_CHANNEL) + { + text = GTK_ENTRY (entry)->text; + if (text[0] == 0) + text = NULL; + sess->server->p_topic (sess->server, sess->channel, text); + } else + gtk_entry_set_text (GTK_ENTRY (entry), ""); + /* restore focus to the input widget, where the next input will most +likely be */ + gtk_widget_grab_focus (sess->gui->input_box); +} + +static void +mg_tabwindow_kill_cb (GtkWidget *win, gpointer userdata) +{ + GSList *list, *next; + session *sess; + +/* puts("enter mg_tabwindow_kill_cb");*/ + xchat_is_quitting = TRUE; + + /* see if there's any non-tab windows left */ + list = sess_list; + while (list) + { + sess = list->data; + next = list->next; + if (!sess->gui->is_tab) + { + xchat_is_quitting = FALSE; +/* puts("-> will not exit, some toplevel windows left");*/ + } else + { + mg_ircdestroy (sess); + } + list = next; + } + + current_tab = NULL; + active_tab = NULL; + mg_gui = NULL; + parent_window = NULL; +} + +static GtkWidget * +mg_changui_destroy (session *sess) +{ + GtkWidget *ret = NULL; + + if (sess->gui->is_tab) + { + /* avoid calling the "destroy" callback */ + g_signal_handlers_disconnect_by_func (G_OBJECT (sess->gui->window), + mg_tabwindow_kill_cb, 0); + /* remove the tab from the chanview */ + if (!mg_chan_remove (sess->res->tab)) + /* if the window still exists, restore the signal handler */ + g_signal_connect (G_OBJECT (sess->gui->window), "destroy", + G_CALLBACK (mg_tabwindow_kill_cb), 0); + } else + { + /* avoid calling the "destroy" callback */ + g_signal_handlers_disconnect_by_func (G_OBJECT (sess->gui->window), + mg_topdestroy_cb, sess); + /*gtk_widget_destroy (sess->gui->window);*/ + /* don't destroy until the new one is created. Not sure why, but */ + /* it fixes: Gdk-CRITICAL **: gdk_colormap_get_screen: */ + /* assertion `GDK_IS_COLORMAP (cmap)' failed */ + ret = sess->gui->window; + free (sess->gui); + sess->gui = NULL; + } + return ret; +} + +static void +mg_link_irctab (session *sess, int focus) +{ + GtkWidget *win; + + if (sess->gui->is_tab) + { + win = mg_changui_destroy (sess); + mg_changui_new (sess, sess->res, 0, focus); + mg_populate (sess); + xchat_is_quitting = FALSE; + if (win) + gtk_widget_destroy (win); + return; + } + + mg_unpopulate (sess); + win = mg_changui_destroy (sess); + mg_changui_new (sess, sess->res, 1, focus); + /* the buffer is now attached to a different widget */ + ((xtext_buffer *)sess->res->buffer)->xtext = (GtkXText *)sess->gui->xtext; + if (win) + gtk_widget_destroy (win); +} + +void +mg_detach (session *sess, int mode) +{ + switch (mode) + { + /* detach only */ + case 1: + if (sess->gui->is_tab) + mg_link_irctab (sess, 1); + break; + /* attach only */ + case 2: + if (!sess->gui->is_tab) + mg_link_irctab (sess, 1); + break; + /* toggle */ + default: + mg_link_irctab (sess, 1); + } +} + +static int +check_is_number (char *t) +{ + while (*t) + { + if (*t < '0' || *t > '9') + return FALSE; + t++; + } + return TRUE; +} + +static void +mg_change_flag (GtkWidget * wid, session *sess, char flag) +{ + server *serv = sess->server; + char mode[3]; + + mode[1] = flag; + mode[2] = '\0'; + if (serv->connected && sess->channel[0]) + { + if (GTK_TOGGLE_BUTTON (wid)->active) + mode[0] = '+'; + else + mode[0] = '-'; + serv->p_mode (serv, sess->channel, mode); + serv->p_join_info (serv, sess->channel); + sess->ignore_mode = TRUE; + sess->ignore_date = TRUE; + } +} + +static void +flagl_hit (GtkWidget * wid, struct session *sess) +{ + char modes[512]; + const char *limit_str; + server *serv = sess->server; + + if (GTK_TOGGLE_BUTTON (wid)->active) + { + if (serv->connected && sess->channel[0]) + { + limit_str = gtk_entry_get_text (GTK_ENTRY (sess->gui->limit_entry)); + if (check_is_number ((char *)limit_str) == FALSE) + { + fe_message (_("User limit must be a number!\n"), FE_MSG_ERROR); + gtk_entry_set_text (GTK_ENTRY (sess->gui->limit_entry), ""); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), FALSE); + return; + } + snprintf (modes, sizeof (modes), "+l %d", atoi (limit_str)); + serv->p_mode (serv, sess->channel, modes); + serv->p_join_info (serv, sess->channel); + } + } else + mg_change_flag (wid, sess, 'l'); +} + +static void +flagk_hit (GtkWidget * wid, struct session *sess) +{ + char modes[512]; + server *serv = sess->server; + + if (serv->connected && sess->channel[0]) + { + snprintf (modes, sizeof (modes), "-k %s", + gtk_entry_get_text (GTK_ENTRY (sess->gui->key_entry))); + + if (GTK_TOGGLE_BUTTON (wid)->active) + modes[0] = '+'; + + serv->p_mode (serv, sess->channel, modes); + } +} + +static void +mg_flagbutton_cb (GtkWidget *but, char *flag) +{ + session *sess; + char mode; + + if (ignore_chanmode) + return; + + sess = current_sess; + mode = tolower ((unsigned char) flag[0]); + + switch (mode) + { + case 'l': + flagl_hit (but, sess); + break; + case 'k': + flagk_hit (but, sess); + break; + case 'b': + ignore_chanmode = TRUE; + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sess->gui->flag_b), FALSE); + ignore_chanmode = FALSE; + banlist_opengui (sess); + break; + default: + mg_change_flag (but, sess, mode); + } +} + +static GtkWidget * +mg_create_flagbutton (char *tip, GtkWidget *box, char *face) +{ + GtkWidget *wid; + + wid = gtk_toggle_button_new_with_label (face); + gtk_widget_set_size_request (wid, 18, 0); + add_tip (wid, tip); + gtk_box_pack_start (GTK_BOX (box), wid, 0, 0, 0); + g_signal_connect (G_OBJECT (wid), "toggled", + G_CALLBACK (mg_flagbutton_cb), face); + show_and_unfocus (wid); + + return wid; +} + +static void +mg_key_entry_cb (GtkWidget * igad, gpointer userdata) +{ + char modes[512]; + session *sess = current_sess; + server *serv = sess->server; + + if (serv->connected && sess->channel[0]) + { + snprintf (modes, sizeof (modes), "+k %s", + gtk_entry_get_text (GTK_ENTRY (igad))); + serv->p_mode (serv, sess->channel, modes); + serv->p_join_info (serv, sess->channel); + } +} + +static void +mg_limit_entry_cb (GtkWidget * igad, gpointer userdata) +{ + char modes[512]; + session *sess = current_sess; + server *serv = sess->server; + + if (serv->connected && sess->channel[0]) + { + if (check_is_number ((char *)gtk_entry_get_text (GTK_ENTRY (igad))) == FALSE) + { + gtk_entry_set_text (GTK_ENTRY (igad), ""); + fe_message (_("User limit must be a number!\n"), FE_MSG_ERROR); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (sess->gui->flag_l), FALSE); + return; + } + snprintf (modes, sizeof(modes), "+l %d", + atoi (gtk_entry_get_text (GTK_ENTRY (igad)))); + serv->p_mode (serv, sess->channel, modes); + serv->p_join_info (serv, sess->channel); + } +} + +static void +mg_apply_entry_style (GtkWidget *entry) +{ + gtk_widget_modify_base (entry, GTK_STATE_NORMAL, &colors[COL_BG]); + gtk_widget_modify_text (entry, GTK_STATE_NORMAL, &colors[COL_FG]); + gtk_widget_modify_font (entry, input_style->font_desc); +} + +static void +mg_create_chanmodebuttons (session_gui *gui, GtkWidget *box) +{ + gui->flag_t = mg_create_flagbutton (_("Topic Protection"), box, "T"); + gui->flag_n = mg_create_flagbutton (_("No outside messages"), box, "N"); + gui->flag_s = mg_create_flagbutton (_("Secret"), box, "S"); + gui->flag_i = mg_create_flagbutton (_("Invite Only"), box, "I"); + gui->flag_p = mg_create_flagbutton (_("Private"), box, "P"); + gui->flag_m = mg_create_flagbutton (_("Moderated"), box, "M"); + gui->flag_b = mg_create_flagbutton (_("Ban List"), box, "B"); + + gui->flag_k = mg_create_flagbutton (_("Keyword"), box, "K"); + gui->key_entry = gtk_entry_new (); + gtk_widget_set_name (gui->key_entry, "xchat-inputbox"); + gtk_entry_set_max_length (GTK_ENTRY (gui->key_entry), 16); + gtk_widget_set_size_request (gui->key_entry, 30, -1); + gtk_box_pack_start (GTK_BOX (box), gui->key_entry, 0, 0, 0); + g_signal_connect (G_OBJECT (gui->key_entry), "activate", + G_CALLBACK (mg_key_entry_cb), NULL); + + if (prefs.style_inputbox) + mg_apply_entry_style (gui->key_entry); + + gui->flag_l = mg_create_flagbutton (_("User Limit"), box, "L"); + gui->limit_entry = gtk_entry_new (); + gtk_widget_set_name (gui->limit_entry, "xchat-inputbox"); + gtk_entry_set_max_length (GTK_ENTRY (gui->limit_entry), 10); + gtk_widget_set_size_request (gui->limit_entry, 30, -1); + gtk_box_pack_start (GTK_BOX (box), gui->limit_entry, 0, 0, 0); + g_signal_connect (G_OBJECT (gui->limit_entry), "activate", + G_CALLBACK (mg_limit_entry_cb), NULL); + + if (prefs.style_inputbox) + mg_apply_entry_style (gui->limit_entry); +} + +/*static void +mg_create_link_buttons (GtkWidget *box, gpointer userdata) +{ + gtkutil_button (box, GTK_STOCK_CLOSE, _("Close this tab/window"), + mg_x_click_cb, userdata, 0); + + if (!userdata) + gtkutil_button (box, GTK_STOCK_REDO, _("Attach/Detach this tab"), + mg_link_cb, userdata, 0); +}*/ + +static void +mg_dialog_button_cb (GtkWidget *wid, char *cmd) +{ + /* the longest cmd is 12, and the longest nickname is 64 */ + char buf[128]; + char *host = ""; + char *topic; + + if (!current_sess) + return; + + topic = (char *)(GTK_ENTRY (current_sess->gui->topic_entry)->text); + topic = strrchr (topic, '@'); + if (topic) + host = topic + 1; + + auto_insert (buf, sizeof (buf), cmd, 0, 0, "", "", "", + server_get_network (current_sess->server, TRUE), host, "", + current_sess->channel); + + handle_command (current_sess, buf, TRUE); + + /* dirty trick to avoid auto-selection */ + SPELL_ENTRY_SET_EDITABLE (current_sess->gui->input_box, FALSE); + gtk_widget_grab_focus (current_sess->gui->input_box); + SPELL_ENTRY_SET_EDITABLE (current_sess->gui->input_box, TRUE); +} + +static void +mg_dialog_button (GtkWidget *box, char *name, char *cmd) +{ + GtkWidget *wid; + + wid = gtk_button_new_with_label (name); + gtk_box_pack_start (GTK_BOX (box), wid, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (wid), "clicked", + G_CALLBACK (mg_dialog_button_cb), cmd); + gtk_widget_set_size_request (wid, -1, 0); +} + +static void +mg_create_dialogbuttons (GtkWidget *box) +{ + struct popup *pop; + GSList *list = dlgbutton_list; + + while (list) + { + pop = list->data; + if (pop->cmd[0]) + mg_dialog_button (box, pop->name, pop->cmd); + list = list->next; + } +} + +static void +mg_create_topicbar (session *sess, GtkWidget *box) +{ + GtkWidget *hbox, *topic, *bbox; + session_gui *gui = sess->gui; + + gui->topic_bar = hbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (box), hbox, 0, 0, 0); + + if (!gui->is_tab) + sess->res->tab = NULL; + + gui->topic_entry = topic = gtk_entry_new (); + gtk_widget_set_name (topic, "xchat-inputbox"); + gtk_container_add (GTK_CONTAINER (hbox), topic); + g_signal_connect (G_OBJECT (topic), "activate", + G_CALLBACK (mg_topic_cb), 0); + + if (prefs.style_inputbox) + mg_apply_entry_style (topic); + + gui->topicbutton_box = bbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (hbox), bbox, 0, 0, 0); + mg_create_chanmodebuttons (gui, bbox); + + gui->dialogbutton_box = bbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (hbox), bbox, 0, 0, 0); + mg_create_dialogbuttons (bbox); + + if (!prefs.paned_userlist) + gtkutil_button (hbox, GTK_STOCK_GOTO_LAST, _("Show/Hide userlist"), + mg_userlist_toggle_cb, 0, 0); +} + +/* check if a word is clickable */ + +static int +mg_word_check (GtkWidget * xtext, char *word, int len) +{ + session *sess = current_sess; + int ret; + + ret = url_check_word (word, len); /* common/url.c */ + if (ret == 0) + { + if (( (word[0]=='@' || word[0]=='+' || word[0]=='%') && userlist_find (sess, word+1)) || userlist_find (sess, word)) + return WORD_NICK; + + if (sess->type == SESS_DIALOG) + return WORD_DIALOG; + } + + return ret; +} + +/* mouse click inside text area */ + +static void +mg_word_clicked (GtkWidget *xtext, char *word, GdkEventButton *even) +{ + session *sess = current_sess; + + if (even->button == 1) /* left button */ + { + if (word == NULL) + { + mg_focus (sess); + return; + } + + if ((even->state & 13) == prefs.gui_url_mod) + { + switch (mg_word_check (xtext, word, strlen (word))) + { + case WORD_URL: + case WORD_HOST: + fe_open_url (word); + } + } + return; + } + + if (even->button == 2) + { + if (sess->type == SESS_DIALOG) + menu_middlemenu (sess, even); + else if (even->type == GDK_2BUTTON_PRESS) + userlist_select (sess, word); + return; + } + + switch (mg_word_check (xtext, word, strlen (word))) + { + case 0: + menu_middlemenu (sess, even); + break; + case WORD_URL: + case WORD_HOST: + menu_urlmenu (even, word); + break; + case WORD_NICK: + menu_nickmenu (sess, even, (word[0]=='@' || word[0]=='+' || word[0]=='%') ? + word+1 : word, FALSE); + break; + case WORD_CHANNEL: + if (*word == '@' || *word == '+' || *word=='^' || *word=='%' || *word=='*') + word++; + menu_chanmenu (sess, even, word); + break; + case WORD_EMAIL: + { + char *newword = malloc (strlen (word) + 10); + if (*word == '~') + word++; + sprintf (newword, "mailto:%s", word); + menu_urlmenu (even, newword); + free (newword); + } + break; + case WORD_DIALOG: + menu_nickmenu (sess, even, sess->channel, FALSE); + break; + } +} + +void +mg_update_xtext (GtkWidget *wid) +{ + GtkXText *xtext = GTK_XTEXT (wid); + + gtk_xtext_set_palette (xtext, colors); + gtk_xtext_set_max_lines (xtext, prefs.max_lines); + gtk_xtext_set_tint (xtext, prefs.tint_red, prefs.tint_green, prefs.tint_blue); + gtk_xtext_set_background (xtext, channelwin_pix, prefs.transparent); + gtk_xtext_set_wordwrap (xtext, prefs.wordwrap); + gtk_xtext_set_show_marker (xtext, prefs.show_marker); + gtk_xtext_set_show_separator (xtext, prefs.indent_nicks ? prefs.show_separator : 0); + gtk_xtext_set_indent (xtext, prefs.indent_nicks); + if (!gtk_xtext_set_font (xtext, prefs.font_normal)) + { + fe_message ("Failed to open any font. I'm out of here!", FE_MSG_WAIT | FE_MSG_ERROR); + exit (1); + } + + gtk_xtext_refresh (xtext, FALSE); +} + +/* handle errors reported by xtext */ + +static void +mg_xtext_error (int type) +{ + switch (type) + { + case 0: + fe_message (_("Unable to set transparent background!\n\n" + "You may be using a non-compliant window\n" + "manager that is not currently supported.\n"), FE_MSG_WARN); + prefs.transparent = 0; + /* no others exist yet */ + } +} + +static void +mg_create_textarea (session *sess, GtkWidget *box) +{ + GtkWidget *inbox, *vbox, *frame; + GtkXText *xtext; + session_gui *gui = sess->gui; + static const GtkTargetEntry dnd_targets[] = + { + {"text/uri-list", 0, 1} + }; + static const GtkTargetEntry dnd_dest_targets[] = + { + {"XCHAT_CHANVIEW", GTK_TARGET_SAME_APP, 75 }, + {"XCHAT_USERLIST", GTK_TARGET_SAME_APP, 75 } + }; + + vbox = gtk_vbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (box), vbox); + + inbox = gtk_hbox_new (FALSE, SCROLLBAR_SPACING); + gtk_container_add (GTK_CONTAINER (vbox), inbox); + + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (inbox), frame); + + gui->xtext = gtk_xtext_new (colors, TRUE); + xtext = GTK_XTEXT (gui->xtext); + gtk_xtext_set_max_indent (xtext, prefs.max_auto_indent); + gtk_xtext_set_thin_separator (xtext, prefs.thin_separator); + gtk_xtext_set_error_function (xtext, mg_xtext_error); + gtk_xtext_set_urlcheck_function (xtext, mg_word_check); + gtk_xtext_set_max_lines (xtext, prefs.max_lines); + gtk_container_add (GTK_CONTAINER (frame), GTK_WIDGET (xtext)); + mg_update_xtext (GTK_WIDGET (xtext)); + + g_signal_connect (G_OBJECT (xtext), "word_click", + G_CALLBACK (mg_word_clicked), NULL); + + gui->vscrollbar = gtk_vscrollbar_new (GTK_XTEXT (xtext)->adj); + gtk_box_pack_start (GTK_BOX (inbox), gui->vscrollbar, FALSE, TRUE, 0); +#ifndef WIN32 /* needs more work */ + gtk_drag_dest_set (gui->vscrollbar, 5, dnd_dest_targets, 2, + GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK); + g_signal_connect (G_OBJECT (gui->vscrollbar), "drag_begin", + G_CALLBACK (mg_drag_begin_cb), NULL); + g_signal_connect (G_OBJECT (gui->vscrollbar), "drag_drop", + G_CALLBACK (mg_drag_drop_cb), NULL); + g_signal_connect (G_OBJECT (gui->vscrollbar), "drag_motion", + G_CALLBACK (mg_drag_motion_cb), gui->vscrollbar); + g_signal_connect (G_OBJECT (gui->vscrollbar), "drag_end", + G_CALLBACK (mg_drag_end_cb), NULL); + + gtk_drag_dest_set (gui->xtext, GTK_DEST_DEFAULT_ALL, dnd_targets, 1, + GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK); + g_signal_connect (G_OBJECT (gui->xtext), "drag_data_received", + G_CALLBACK (mg_dialog_dnd_drop), NULL); +#endif +} + +static GtkWidget * +mg_create_infoframe (GtkWidget *box) +{ + GtkWidget *frame, *label, *hbox; + + frame = gtk_frame_new (0); + gtk_frame_set_shadow_type ((GtkFrame*)frame, GTK_SHADOW_OUT); + gtk_container_add (GTK_CONTAINER (box), frame); + + hbox = gtk_hbox_new (0, 0); + gtk_container_add (GTK_CONTAINER (frame), hbox); + + label = gtk_label_new (NULL); + gtk_container_add (GTK_CONTAINER (hbox), label); + + return label; +} + +static void +mg_create_meters (session_gui *gui, GtkWidget *parent_box) +{ + GtkWidget *infbox, *wid, *box; + + gui->meter_box = infbox = box = gtk_vbox_new (0, 1); + gtk_box_pack_start (GTK_BOX (parent_box), box, 0, 0, 0); + + if ((prefs.lagometer & 2) || (prefs.throttlemeter & 2)) + { + infbox = gtk_hbox_new (0, 0); + gtk_box_pack_start (GTK_BOX (box), infbox, 0, 0, 0); + } + + if (prefs.lagometer & 1) + { + gui->lagometer = wid = gtk_progress_bar_new (); +#ifdef WIN32 + gtk_widget_set_size_request (wid, 1, 10); +#else + gtk_widget_set_size_request (wid, 1, 8); +#endif + + wid = gtk_event_box_new (); + gtk_container_add (GTK_CONTAINER (wid), gui->lagometer); + gtk_box_pack_start (GTK_BOX (box), wid, 0, 0, 0); + } + if (prefs.lagometer & 2) + { + gui->laginfo = wid = mg_create_infoframe (infbox); + gtk_label_set_text ((GtkLabel *) wid, "Lag"); + } + + if (prefs.throttlemeter & 1) + { + gui->throttlemeter = wid = gtk_progress_bar_new (); +#ifdef WIN32 + gtk_widget_set_size_request (wid, 1, 10); +#else + gtk_widget_set_size_request (wid, 1, 8); +#endif + + wid = gtk_event_box_new (); + gtk_container_add (GTK_CONTAINER (wid), gui->throttlemeter); + gtk_box_pack_start (GTK_BOX (box), wid, 0, 0, 0); + } + if (prefs.throttlemeter & 2) + { + gui->throttleinfo = wid = mg_create_infoframe (infbox); + gtk_label_set_text ((GtkLabel *) wid, "Throttle"); + } +} + +void +mg_update_meters (session_gui *gui) +{ + gtk_widget_destroy (gui->meter_box); + gui->lagometer = NULL; + gui->laginfo = NULL; + gui->throttlemeter = NULL; + gui->throttleinfo = NULL; + + mg_create_meters (gui, gui->button_box_parent); + gtk_widget_show_all (gui->meter_box); +} + +static void +mg_create_userlist (session_gui *gui, GtkWidget *box) +{ + GtkWidget *frame, *ulist, *vbox; + + vbox = gtk_vbox_new (0, 1); + gtk_container_add (GTK_CONTAINER (box), vbox); + + frame = gtk_frame_new (NULL); + if (!(prefs.gui_tweaks & 1)) + gtk_box_pack_start (GTK_BOX (vbox), frame, 0, 0, GUI_SPACING); + + gui->namelistinfo = gtk_label_new (NULL); + gtk_container_add (GTK_CONTAINER (frame), gui->namelistinfo); + + gui->user_tree = ulist = userlist_create (vbox); + + if (prefs.style_namelistgad) + { + gtk_widget_set_style (ulist, input_style); + gtk_widget_modify_base (ulist, GTK_STATE_NORMAL, &colors[COL_BG]); + } + + mg_create_meters (gui, vbox); + + gui->button_box_parent = vbox; + gui->button_box = mg_create_userlistbuttons (vbox); +} + +static void +mg_leftpane_cb (GtkPaned *pane, GParamSpec *param, session_gui *gui) +{ + prefs.gui_pane_left_size = gtk_paned_get_position (pane); +} + +static void +mg_rightpane_cb (GtkPaned *pane, GParamSpec *param, session_gui *gui) +{ + int handle_size; + +/* if (pane->child1 == NULL || (!GTK_WIDGET_VISIBLE (pane->child1))) + return; + if (pane->child2 == NULL || (!GTK_WIDGET_VISIBLE (pane->child2))) + return;*/ + + gtk_widget_style_get (GTK_WIDGET (pane), "handle-size", &handle_size, NULL); + /* record the position from the RIGHT side */ + prefs.gui_pane_right_size = GTK_WIDGET (pane)->allocation.width - gtk_paned_get_position (pane) - handle_size; +} + +static gboolean +mg_add_pane_signals (session_gui *gui) +{ + g_signal_connect (G_OBJECT (gui->hpane_right), "notify::position", + G_CALLBACK (mg_rightpane_cb), gui); + g_signal_connect (G_OBJECT (gui->hpane_left), "notify::position", + G_CALLBACK (mg_leftpane_cb), gui); + return FALSE; +} + +static void +mg_create_center (session *sess, session_gui *gui, GtkWidget *box) +{ + GtkWidget *vbox, *hbox, *book; + + /* sep between top and bottom of left side */ + gui->vpane_left = gtk_vpaned_new (); + + /* sep between top and bottom of right side */ + gui->vpane_right = gtk_vpaned_new (); + + /* sep between left and xtext */ + gui->hpane_left = gtk_hpaned_new (); + gtk_paned_set_position (GTK_PANED (gui->hpane_left), prefs.gui_pane_left_size); + + /* sep between xtext and right side */ + gui->hpane_right = gtk_hpaned_new (); + + if (prefs.gui_tweaks & 4) + { + gtk_paned_pack2 (GTK_PANED (gui->hpane_left), gui->vpane_left, FALSE, TRUE); + gtk_paned_pack1 (GTK_PANED (gui->hpane_left), gui->hpane_right, TRUE, TRUE); + } + else + { + gtk_paned_pack1 (GTK_PANED (gui->hpane_left), gui->vpane_left, FALSE, TRUE); + gtk_paned_pack2 (GTK_PANED (gui->hpane_left), gui->hpane_right, TRUE, TRUE); + } + gtk_paned_pack2 (GTK_PANED (gui->hpane_right), gui->vpane_right, FALSE, TRUE); + + gtk_container_add (GTK_CONTAINER (box), gui->hpane_left); + + gui->note_book = book = gtk_notebook_new (); + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (book), FALSE); + gtk_notebook_set_show_border (GTK_NOTEBOOK (book), FALSE); + gtk_paned_pack1 (GTK_PANED (gui->hpane_right), book, TRUE, TRUE); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_paned_pack1 (GTK_PANED (gui->vpane_right), hbox, FALSE, TRUE); + mg_create_userlist (gui, hbox); + + gui->user_box = hbox; + + vbox = gtk_vbox_new (FALSE, 3); + gtk_notebook_append_page (GTK_NOTEBOOK (book), vbox, NULL); + mg_create_topicbar (sess, vbox); + mg_create_textarea (sess, vbox); + mg_create_entry (sess, vbox); + + g_idle_add ((GSourceFunc)mg_add_pane_signals, gui); +} + +static void +mg_change_nick (int cancel, char *text, gpointer userdata) +{ + char buf[256]; + + if (!cancel) + { + snprintf (buf, sizeof (buf), "nick %s", text); + handle_command (current_sess, buf, FALSE); + } +} + +static void +mg_nickclick_cb (GtkWidget *button, gpointer userdata) +{ + fe_get_str (_("Enter new nickname:"), current_sess->server->nick, + mg_change_nick, NULL); +} + +/* make sure chanview and userlist positions are sane */ + +static void +mg_sanitize_positions (int *cv, int *ul) +{ + if (prefs.tab_layout == 2) + { + /* treeview can't be on TOP or BOTTOM */ + if (*cv == POS_TOP || *cv == POS_BOTTOM) + *cv = POS_TOPLEFT; + } + + /* userlist can't be on TOP or BOTTOM */ + if (*ul == POS_TOP || *ul == POS_BOTTOM) + *ul = POS_TOPRIGHT; + + /* can't have both in the same place */ + if (*cv == *ul) + { + *cv = POS_TOPRIGHT; + if (*ul == POS_TOPRIGHT) + *cv = POS_BOTTOMRIGHT; + } +} + +static void +mg_place_userlist_and_chanview_real (session_gui *gui, GtkWidget *userlist, GtkWidget *chanview) +{ + int unref_userlist = FALSE; + int unref_chanview = FALSE; + + /* first, remove userlist/treeview from their containers */ + if (userlist && userlist->parent) + { + g_object_ref (userlist); + gtk_container_remove (GTK_CONTAINER (userlist->parent), userlist); + unref_userlist = TRUE; + } + + if (chanview && chanview->parent) + { + g_object_ref (chanview); + gtk_container_remove (GTK_CONTAINER (chanview->parent), chanview); + unref_chanview = TRUE; + } + + if (chanview) + { + /* incase the previous pos was POS_HIDDEN */ + gtk_widget_show (chanview); + + gtk_table_set_row_spacing (GTK_TABLE (gui->main_table), 1, 0); + gtk_table_set_row_spacing (GTK_TABLE (gui->main_table), 2, 2); + + /* then place them back in their new positions */ + switch (prefs.tab_pos) + { + case POS_TOPLEFT: + gtk_paned_pack1 (GTK_PANED (gui->vpane_left), chanview, FALSE, TRUE); + break; + case POS_BOTTOMLEFT: + gtk_paned_pack2 (GTK_PANED (gui->vpane_left), chanview, FALSE, TRUE); + break; + case POS_TOPRIGHT: + gtk_paned_pack1 (GTK_PANED (gui->vpane_right), chanview, FALSE, TRUE); + break; + case POS_BOTTOMRIGHT: + gtk_paned_pack2 (GTK_PANED (gui->vpane_right), chanview, FALSE, TRUE); + break; + case POS_TOP: + gtk_table_set_row_spacing (GTK_TABLE (gui->main_table), 1, GUI_SPACING-1); + gtk_table_attach (GTK_TABLE (gui->main_table), chanview, + 1, 2, 1, 2, GTK_FILL, GTK_FILL, 0, 0); + break; + case POS_HIDDEN: + gtk_widget_hide (chanview); + /* always attach it to something to avoid ref_count=0 */ + if (prefs.gui_ulist_pos == POS_TOP) + gtk_table_attach (GTK_TABLE (gui->main_table), chanview, + 1, 2, 3, 4, GTK_FILL, GTK_FILL, 0, 0); + + else + gtk_table_attach (GTK_TABLE (gui->main_table), chanview, + 1, 2, 1, 2, GTK_FILL, GTK_FILL, 0, 0); + break; + default:/* POS_BOTTOM */ + gtk_table_set_row_spacing (GTK_TABLE (gui->main_table), 2, 3); + gtk_table_attach (GTK_TABLE (gui->main_table), chanview, + 1, 2, 3, 4, GTK_FILL, GTK_FILL, 0, 0); + } + } + + if (userlist) + { + switch (prefs.gui_ulist_pos) + { + case POS_TOPLEFT: + gtk_paned_pack1 (GTK_PANED (gui->vpane_left), userlist, FALSE, TRUE); + break; + case POS_BOTTOMLEFT: + gtk_paned_pack2 (GTK_PANED (gui->vpane_left), userlist, FALSE, TRUE); + break; + case POS_BOTTOMRIGHT: + gtk_paned_pack2 (GTK_PANED (gui->vpane_right), userlist, FALSE, TRUE); + break; + /*case POS_HIDDEN: + break;*/ /* Hide using the VIEW menu instead */ + default:/* POS_TOPRIGHT */ + gtk_paned_pack1 (GTK_PANED (gui->vpane_right), userlist, FALSE, TRUE); + } + } + + if (unref_chanview) + g_object_unref (chanview); + if (unref_userlist) + g_object_unref (userlist); + + mg_hide_empty_boxes (gui); +} + +static void +mg_place_userlist_and_chanview (session_gui *gui) +{ + GtkOrientation orientation; + GtkWidget *chanviewbox = NULL; + int pos; + + mg_sanitize_positions (&prefs.tab_pos, &prefs.gui_ulist_pos); + + if (gui->chanview) + { + pos = prefs.tab_pos; + + orientation = chanview_get_orientation (gui->chanview); + if ((pos == POS_BOTTOM || pos == POS_TOP) && orientation == GTK_ORIENTATION_VERTICAL) + chanview_set_orientation (gui->chanview, FALSE); + else if ((pos == POS_TOPLEFT || pos == POS_BOTTOMLEFT || pos == POS_TOPRIGHT || pos == POS_BOTTOMRIGHT) && orientation == GTK_ORIENTATION_HORIZONTAL) + chanview_set_orientation (gui->chanview, TRUE); + chanviewbox = chanview_get_box (gui->chanview); + } + + mg_place_userlist_and_chanview_real (gui, gui->user_box, chanviewbox); +} + +void +mg_change_layout (int type) +{ + if (mg_gui) + { + /* put tabs at the bottom */ + if (type == 0 && prefs.tab_pos != POS_BOTTOM && prefs.tab_pos != POS_TOP) + prefs.tab_pos = POS_BOTTOM; + + mg_place_userlist_and_chanview (mg_gui); + chanview_set_impl (mg_gui->chanview, type); + } +} + +static void +mg_inputbox_rightclick (GtkEntry *entry, GtkWidget *menu) +{ + mg_create_color_menu (menu, NULL); +} + +static void +mg_create_entry (session *sess, GtkWidget *box) +{ + GtkWidget *sw, *hbox, *but, *entry; + session_gui *gui = sess->gui; + + hbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (box), hbox, 0, 0, 0); + + gui->nick_box = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (hbox), gui->nick_box, 0, 0, 0); + + gui->nick_label = but = gtk_button_new_with_label (sess->server->nick); + gtk_button_set_relief (GTK_BUTTON (but), GTK_RELIEF_NONE); + GTK_WIDGET_UNSET_FLAGS (but, GTK_CAN_FOCUS); + gtk_box_pack_end (GTK_BOX (gui->nick_box), but, 0, 0, 0); + g_signal_connect (G_OBJECT (but), "clicked", + G_CALLBACK (mg_nickclick_cb), NULL); + +#ifdef USE_GTKSPELL + gui->input_box = entry = gtk_text_view_new (); + gtk_widget_set_size_request (entry, 0, 1); + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (entry), GTK_WRAP_NONE); + gtk_text_view_set_accepts_tab (GTK_TEXT_VIEW (entry), FALSE); + if (prefs.gui_input_spell) + gtkspell_new_attach (GTK_TEXT_VIEW (entry), NULL, NULL); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_NEVER, + GTK_POLICY_NEVER); + gtk_container_add (GTK_CONTAINER (sw), entry); + gtk_container_add (GTK_CONTAINER (hbox), sw); +#else +#ifdef USE_LIBSEXY + gui->input_box = entry = sexy_spell_entry_new (); + sexy_spell_entry_set_checked ((SexySpellEntry *)entry, prefs.gui_input_spell); +#else + gui->input_box = entry = gtk_entry_new (); +#endif + gtk_entry_set_max_length (GTK_ENTRY (gui->input_box), 2048); + g_signal_connect (G_OBJECT (entry), "activate", + G_CALLBACK (mg_inputbox_cb), gui); + gtk_container_add (GTK_CONTAINER (hbox), entry); +#endif + + gtk_widget_set_name (entry, "xchat-inputbox"); + g_signal_connect (G_OBJECT (entry), "key_press_event", + G_CALLBACK (key_handle_key_press), NULL); + g_signal_connect (G_OBJECT (entry), "focus_in_event", + G_CALLBACK (mg_inputbox_focus), gui); + g_signal_connect (G_OBJECT (entry), "populate_popup", + G_CALLBACK (mg_inputbox_rightclick), NULL); + gtk_widget_grab_focus (entry); + + if (prefs.style_inputbox) + mg_apply_entry_style (entry); +} + +static void +mg_switch_tab_cb (chanview *cv, chan *ch, int tag, gpointer ud) +{ + chan *old; + session *sess = ud; + + old = active_tab; + active_tab = ch; + + if (tag == TAG_IRC) + { + if (active_tab != old) + { + if (old && current_tab) + mg_unpopulate (current_tab); + mg_populate (sess); + } + } else if (old != active_tab) + { + /* userdata for non-irc tabs is actually the GtkBox */ + mg_show_generic_tab (ud); + if (!mg_is_userlist_and_tree_combined ()) + mg_userlist_showhide (current_sess, FALSE); /* hide */ + } +} + +/* compare two tabs (for tab sorting function) */ + +static int +mg_tabs_compare (session *a, session *b) +{ + /* server tabs always go first */ + if (a->type == SESS_SERVER) + return -1; + + /* then channels */ + if (a->type == SESS_CHANNEL && b->type != SESS_CHANNEL) + return -1; + if (a->type != SESS_CHANNEL && b->type == SESS_CHANNEL) + return 1; + + return strcasecmp (a->channel, b->channel); +} + +static void +mg_create_tabs (session_gui *gui) +{ + gboolean use_icons = FALSE; + + /* if any one of these PNGs exist, the chanview will create + * the extra column for icons. */ + if (pix_channel || pix_dialog || pix_server || pix_util) + use_icons = TRUE; + + gui->chanview = chanview_new (prefs.tab_layout, prefs.truncchans, + prefs.tab_sort, use_icons, + prefs.style_namelistgad ? input_style : NULL); + chanview_set_callbacks (gui->chanview, mg_switch_tab_cb, mg_xbutton_cb, + mg_tab_contextmenu_cb, (void *)mg_tabs_compare); + mg_place_userlist_and_chanview (gui); +} + +static gboolean +mg_tabwin_focus_cb (GtkWindow * win, GdkEventFocus *event, gpointer userdata) +{ + current_sess = current_tab; + if (current_sess) + { + gtk_xtext_check_marker_visibility (GTK_XTEXT (current_sess->gui->xtext)); + plugin_emit_dummy_print (current_sess, "Focus Window"); + } +#ifndef WIN32 +#ifdef USE_XLIB + unflash_window (GTK_WIDGET (win)); +#endif +#endif + return FALSE; +} + +static gboolean +mg_topwin_focus_cb (GtkWindow * win, GdkEventFocus *event, session *sess) +{ + current_sess = sess; + if (!sess->server->server_session) + sess->server->server_session = sess; + gtk_xtext_check_marker_visibility(GTK_XTEXT (current_sess->gui->xtext)); +#ifndef WIN32 +#ifdef USE_XLIB + unflash_window (GTK_WIDGET (win)); +#endif +#endif + plugin_emit_dummy_print (sess, "Focus Window"); + return FALSE; +} + +static void +mg_create_menu (session_gui *gui, GtkWidget *table, int away_state) +{ + GtkAccelGroup *accel_group; + + accel_group = gtk_accel_group_new (); + gtk_window_add_accel_group (GTK_WINDOW (gtk_widget_get_toplevel (table)), + accel_group); + g_object_unref (accel_group); + + gui->menu = menu_create_main (accel_group, TRUE, away_state, !gui->is_tab, + gui->menu_item); + gtk_table_attach (GTK_TABLE (table), gui->menu, 0, 3, 0, 1, + GTK_EXPAND | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); +} + +static void +mg_create_irctab (session *sess, GtkWidget *table) +{ + GtkWidget *vbox; + session_gui *gui = sess->gui; + + vbox = gtk_vbox_new (FALSE, 0); + gtk_table_attach (GTK_TABLE (table), vbox, 1, 2, 2, 3, + GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0); + mg_create_center (sess, gui, vbox); +} + +static void +mg_create_topwindow (session *sess) +{ + GtkWidget *win; + GtkWidget *table; + + if (sess->type == SESS_DIALOG) + win = gtkutil_window_new ("XChat", NULL, + prefs.dialog_width, prefs.dialog_height, 0); + else + win = gtkutil_window_new ("XChat", NULL, + prefs.mainwindow_width, + prefs.mainwindow_height, 0); + sess->gui->window = win; + gtk_container_set_border_width (GTK_CONTAINER (win), GUI_BORDER); + + g_signal_connect (G_OBJECT (win), "focus_in_event", + G_CALLBACK (mg_topwin_focus_cb), sess); + g_signal_connect (G_OBJECT (win), "destroy", + G_CALLBACK (mg_topdestroy_cb), sess); + g_signal_connect (G_OBJECT (win), "configure_event", + G_CALLBACK (mg_configure_cb), sess); + + palette_alloc (win); + + table = gtk_table_new (4, 3, FALSE); + /* spacing under the menubar */ + gtk_table_set_row_spacing (GTK_TABLE (table), 0, GUI_SPACING); + /* left and right borders */ + gtk_table_set_col_spacing (GTK_TABLE (table), 0, 1); + gtk_table_set_col_spacing (GTK_TABLE (table), 1, 1); + gtk_container_add (GTK_CONTAINER (win), table); + + mg_create_irctab (sess, table); + mg_create_menu (sess->gui, table, sess->server->is_away); + + if (sess->res->buffer == NULL) + { + sess->res->buffer = gtk_xtext_buffer_new (GTK_XTEXT (sess->gui->xtext)); + gtk_xtext_buffer_show (GTK_XTEXT (sess->gui->xtext), sess->res->buffer, TRUE); + gtk_xtext_set_time_stamp (sess->res->buffer, prefs.timestamp); + sess->res->user_model = userlist_create_model (); + } + + userlist_show (sess); + + gtk_widget_show_all (table); + + if (prefs.hidemenu) + gtk_widget_hide (sess->gui->menu); + + if (!prefs.topicbar) + gtk_widget_hide (sess->gui->topic_bar); + + if (!prefs.userlistbuttons) + gtk_widget_hide (sess->gui->button_box); + + if (prefs.gui_tweaks & 2) + gtk_widget_hide (sess->gui->nick_box); + + mg_decide_userlist (sess, FALSE); + + if (sess->type == SESS_DIALOG) + { + /* hide the chan-mode buttons */ + gtk_widget_hide (sess->gui->topicbutton_box); + } else + { + gtk_widget_hide (sess->gui->dialogbutton_box); + + if (!prefs.chanmodebuttons) + gtk_widget_hide (sess->gui->topicbutton_box); + } + + mg_place_userlist_and_chanview (sess->gui); + + gtk_widget_show (win); +} + +static gboolean +mg_tabwindow_de_cb (GtkWidget *widget, GdkEvent *event, gpointer user_data) +{ + GSList *list; + session *sess; + + if ((prefs.gui_tray_flags & 1) && tray_toggle_visibility (FALSE)) + return TRUE; + + /* check for remaining toplevel windows */ + list = sess_list; + while (list) + { + sess = list->data; + if (!sess->gui->is_tab) + return FALSE; + list = list->next; + } + + mg_open_quit_dialog (TRUE); + return TRUE; +} + +static void +mg_create_tabwindow (session *sess) +{ + GtkWidget *win; + GtkWidget *table; + + win = gtkutil_window_new ("XChat", NULL, prefs.mainwindow_width, + prefs.mainwindow_height, 0); + sess->gui->window = win; + gtk_window_move (GTK_WINDOW (win), prefs.mainwindow_left, + prefs.mainwindow_top); + if (prefs.gui_win_state) + gtk_window_maximize (GTK_WINDOW (win)); + gtk_container_set_border_width (GTK_CONTAINER (win), GUI_BORDER); + + g_signal_connect (G_OBJECT (win), "delete_event", + G_CALLBACK (mg_tabwindow_de_cb), 0); + g_signal_connect (G_OBJECT (win), "destroy", + G_CALLBACK (mg_tabwindow_kill_cb), 0); + g_signal_connect (G_OBJECT (win), "focus_in_event", + G_CALLBACK (mg_tabwin_focus_cb), NULL); + g_signal_connect (G_OBJECT (win), "configure_event", + G_CALLBACK (mg_configure_cb), NULL); + g_signal_connect (G_OBJECT (win), "window_state_event", + G_CALLBACK (mg_windowstate_cb), NULL); + + palette_alloc (win); + + sess->gui->main_table = table = gtk_table_new (4, 3, FALSE); + /* spacing under the menubar */ + gtk_table_set_row_spacing (GTK_TABLE (table), 0, GUI_SPACING); + /* left and right borders */ + gtk_table_set_col_spacing (GTK_TABLE (table), 0, 1); + gtk_table_set_col_spacing (GTK_TABLE (table), 1, 1); + gtk_container_add (GTK_CONTAINER (win), table); + + mg_create_irctab (sess, table); + mg_create_tabs (sess->gui); + mg_create_menu (sess->gui, table, sess->server->is_away); + + mg_focus (sess); + + gtk_widget_show_all (table); + + if (prefs.hidemenu) + gtk_widget_hide (sess->gui->menu); + + mg_decide_userlist (sess, FALSE); + + if (!prefs.topicbar) + gtk_widget_hide (sess->gui->topic_bar); + + if (!prefs.chanmodebuttons) + gtk_widget_hide (sess->gui->topicbutton_box); + + if (!prefs.userlistbuttons) + gtk_widget_hide (sess->gui->button_box); + + if (prefs.gui_tweaks & 2) + gtk_widget_hide (sess->gui->nick_box); + + mg_place_userlist_and_chanview (sess->gui); + + gtk_widget_show (win); +} + +void +mg_apply_setup (void) +{ + GSList *list = sess_list; + session *sess; + int done_main = FALSE; + + mg_create_tab_colors (); + + while (list) + { + sess = list->data; + gtk_xtext_set_time_stamp (sess->res->buffer, prefs.timestamp); + ((xtext_buffer *)sess->res->buffer)->needs_recalc = TRUE; + if (!sess->gui->is_tab || !done_main) + mg_place_userlist_and_chanview (sess->gui); + if (sess->gui->is_tab) + done_main = TRUE; + list = list->next; + } +} + +static chan * +mg_add_generic_tab (char *name, char *title, void *family, GtkWidget *box) +{ + chan *ch; + + gtk_notebook_append_page (GTK_NOTEBOOK (mg_gui->note_book), box, NULL); + gtk_widget_show (box); + + ch = chanview_add (mg_gui->chanview, name, NULL, box, TRUE, TAG_UTIL, pix_util); + chan_set_color (ch, plain_list); + /* FIXME: memory leak */ + g_object_set_data (G_OBJECT (box), "title", strdup (title)); + g_object_set_data (G_OBJECT (box), "ch", ch); + + if (prefs.newtabstofront) + chan_focus (ch); + + return ch; +} + +void +fe_buttons_update (session *sess) +{ + session_gui *gui = sess->gui; + + gtk_widget_destroy (gui->button_box); + gui->button_box = mg_create_userlistbuttons (gui->button_box_parent); + + if (prefs.userlistbuttons) + gtk_widget_show (sess->gui->button_box); + else + gtk_widget_hide (sess->gui->button_box); +} + +void +fe_clear_channel (session *sess) +{ + char tbuf[CHANLEN+6]; + session_gui *gui = sess->gui; + + if (sess->gui->is_tab) + { + if (sess->waitchannel[0]) + { + if (prefs.truncchans > 2 && g_utf8_strlen (sess->waitchannel, -1) > prefs.truncchans) + { + /* truncate long channel names */ + tbuf[0] = '('; + strcpy (tbuf + 1, sess->waitchannel); + g_utf8_offset_to_pointer(tbuf, prefs.truncchans)[0] = 0; + strcat (tbuf, "..)"); + } else + { + sprintf (tbuf, "(%s)", sess->waitchannel); + } + } + else + strcpy (tbuf, _("<none>")); + chan_rename (sess->res->tab, tbuf, prefs.truncchans); + } + + if (!sess->gui->is_tab || sess == current_tab) + { + gtk_entry_set_text (GTK_ENTRY (gui->topic_entry), ""); + + if (gui->op_xpm) + { + gtk_widget_destroy (gui->op_xpm); + gui->op_xpm = 0; + } + } else + { + if (sess->res->topic_text) + { + free (sess->res->topic_text); + sess->res->topic_text = NULL; + } + } +} + +void +fe_set_nonchannel (session *sess, int state) +{ +} + +void +fe_dlgbuttons_update (session *sess) +{ + GtkWidget *box; + session_gui *gui = sess->gui; + + gtk_widget_destroy (gui->dialogbutton_box); + + gui->dialogbutton_box = box = gtk_hbox_new (0, 0); + gtk_box_pack_start (GTK_BOX (gui->topic_bar), box, 0, 0, 0); + gtk_box_reorder_child (GTK_BOX (gui->topic_bar), box, 3); + mg_create_dialogbuttons (box); + + gtk_widget_show_all (box); + + if (current_tab && current_tab->type != SESS_DIALOG) + gtk_widget_hide (current_tab->gui->dialogbutton_box); +} + +void +fe_update_mode_buttons (session *sess, char mode, char sign) +{ + int state, i; + + if (sign == '+') + state = TRUE; + else + state = FALSE; + + for (i = 0; i < NUM_FLAG_WIDS - 1; i++) + { + if (chan_flags[i] == mode) + { + if (!sess->gui->is_tab || sess == current_tab) + { + ignore_chanmode = TRUE; + if (GTK_TOGGLE_BUTTON (sess->gui->flag_wid[i])->active != state) + gtk_toggle_button_set_active ( + GTK_TOGGLE_BUTTON (sess->gui->flag_wid[i]), state); + ignore_chanmode = FALSE; + } else + { + sess->res->flag_wid_state[i] = state; + } + return; + } + } +} + +void +fe_set_nick (server *serv, char *newnick) +{ + GSList *list = sess_list; + session *sess; + + while (list) + { + sess = list->data; + if (sess->server == serv) + { + if (current_tab == sess || !sess->gui->is_tab) + gtk_button_set_label (GTK_BUTTON (sess->gui->nick_label), newnick); + } + list = list->next; + } +} + +void +fe_set_away (server *serv) +{ + GSList *list = sess_list; + session *sess; + + while (list) + { + sess = list->data; + if (sess->server == serv) + { + if (!sess->gui->is_tab || sess == current_tab) + { + GTK_CHECK_MENU_ITEM (sess->gui->menu_item[MENU_ID_AWAY])->active = serv->is_away; + /* gray out my nickname */ + mg_set_myself_away (sess->gui, serv->is_away); + } + } + list = list->next; + } +} + +void +fe_set_channel (session *sess) +{ + if (sess->res->tab != NULL) + chan_rename (sess->res->tab, sess->channel, prefs.truncchans); +} + +void +mg_changui_new (session *sess, restore_gui *res, int tab, int focus) +{ + int first_run = FALSE; + session_gui *gui; + struct User *user = NULL; + + if (!res) + { + res = malloc (sizeof (restore_gui)); + memset (res, 0, sizeof (restore_gui)); + } + + sess->res = res; + + if (!sess->server->front_session) + sess->server->front_session = sess; + + if (!is_channel (sess->server, sess->channel)) + user = userlist_find_global (sess->server, sess->channel); + + if (!tab) + { + gui = malloc (sizeof (session_gui)); + memset (gui, 0, sizeof (session_gui)); + gui->is_tab = FALSE; + sess->gui = gui; + mg_create_topwindow (sess); + fe_set_title (sess); + if (user && user->hostname) + set_topic (sess, user->hostname, user->hostname); + return; + } + + if (mg_gui == NULL) + { + first_run = TRUE; + gui = &static_mg_gui; + memset (gui, 0, sizeof (session_gui)); + gui->is_tab = TRUE; + sess->gui = gui; + mg_create_tabwindow (sess); + mg_gui = gui; + parent_window = gui->window; + } else + { + sess->gui = gui = mg_gui; + gui->is_tab = TRUE; + } + + if (user && user->hostname) + set_topic (sess, user->hostname, user->hostname); + + mg_add_chan (sess); + + if (first_run || (prefs.newtabstofront == FOCUS_NEW_ONLY_ASKED && focus) + || prefs.newtabstofront == FOCUS_NEW_ALL ) + chan_focus (res->tab); +} + +GtkWidget * +mg_create_generic_tab (char *name, char *title, int force_toplevel, + int link_buttons, + void *close_callback, void *userdata, + int width, int height, GtkWidget **vbox_ret, + void *family) +{ + GtkWidget *vbox, *win; + + if (prefs.tab_pos == POS_HIDDEN && prefs.windows_as_tabs) + prefs.windows_as_tabs = 0; + + if (force_toplevel || !prefs.windows_as_tabs) + { + win = gtkutil_window_new (title, name, width, height, 3); + vbox = gtk_vbox_new (0, 0); + *vbox_ret = vbox; + gtk_container_add (GTK_CONTAINER (win), vbox); + gtk_widget_show (vbox); + if (close_callback) + g_signal_connect (G_OBJECT (win), "destroy", + G_CALLBACK (close_callback), userdata); + return win; + } + + vbox = gtk_vbox_new (0, 2); + g_object_set_data (G_OBJECT (vbox), "w", GINT_TO_POINTER (width)); + g_object_set_data (G_OBJECT (vbox), "h", GINT_TO_POINTER (height)); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 3); + *vbox_ret = vbox; + + if (close_callback) + g_signal_connect (G_OBJECT (vbox), "destroy", + G_CALLBACK (close_callback), userdata); + + mg_add_generic_tab (name, title, family, vbox); + +/* if (link_buttons) + { + hbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), hbox, 0, 0, 0); + mg_create_link_buttons (hbox, ch); + gtk_widget_show (hbox); + }*/ + + return vbox; +} + +void +mg_move_tab (session *sess, int delta) +{ + if (sess->gui->is_tab) + chan_move (sess->res->tab, delta); +} + +void +mg_move_tab_family (session *sess, int delta) +{ + if (sess->gui->is_tab) + chan_move_family (sess->res->tab, delta); +} + +void +mg_set_title (GtkWidget *vbox, char *title) /* for non-irc tab/window only */ +{ + char *old; + + old = g_object_get_data (G_OBJECT (vbox), "title"); + if (old) + { + g_object_set_data (G_OBJECT (vbox), "title", strdup (title)); + free (old); + } else + { + gtk_window_set_title (GTK_WINDOW (vbox), title); + } +} + +void +fe_server_callback (server *serv) +{ + joind_close (serv); + + if (serv->gui->chanlist_window) + mg_close_gen (NULL, serv->gui->chanlist_window); + + if (serv->gui->rawlog_window) + mg_close_gen (NULL, serv->gui->rawlog_window); + + free (serv->gui); +} + +/* called when a session is being killed */ + +void +fe_session_callback (session *sess) +{ + if (sess->res->banlist_window) + mg_close_gen (NULL, sess->res->banlist_window); + + if (sess->res->input_text) + free (sess->res->input_text); + + if (sess->res->topic_text) + free (sess->res->topic_text); + + if (sess->res->limit_text) + free (sess->res->limit_text); + + if (sess->res->key_text) + free (sess->res->key_text); + + if (sess->res->queue_text) + free (sess->res->queue_text); + if (sess->res->queue_tip) + free (sess->res->queue_tip); + + if (sess->res->lag_text) + free (sess->res->lag_text); + if (sess->res->lag_tip) + free (sess->res->lag_tip); + + if (sess->gui->bartag) + fe_timeout_remove (sess->gui->bartag); + + if (sess->gui != &static_mg_gui) + free (sess->gui); + free (sess->res); +} + +/* ===== DRAG AND DROP STUFF ===== */ + +static gboolean +is_child_of (GtkWidget *widget, GtkWidget *parent) +{ + while (widget) + { + if (widget->parent == parent) + return TRUE; + widget = widget->parent; + } + return FALSE; +} + +static void +mg_handle_drop (GtkWidget *widget, int y, int *pos, int *other_pos) +{ + int height; + session_gui *gui = current_sess->gui; + + gdk_drawable_get_size (widget->window, NULL, &height); + + if (y < height / 2) + { + if (is_child_of (widget, gui->vpane_left)) + *pos = 1; /* top left */ + else + *pos = 3; /* top right */ + } + else + { + if (is_child_of (widget, gui->vpane_left)) + *pos = 2; /* bottom left */ + else + *pos = 4; /* bottom right */ + } + + /* both in the same pos? must move one */ + if (*pos == *other_pos) + { + switch (*other_pos) + { + case 1: + *other_pos = 2; + break; + case 2: + *other_pos = 1; + break; + case 3: + *other_pos = 4; + break; + case 4: + *other_pos = 3; + break; + } + } + + mg_place_userlist_and_chanview (gui); +} + +static gboolean +mg_is_gui_target (GdkDragContext *context) +{ + char *target_name; + + if (!context || !context->targets || !context->targets->data) + return FALSE; + + target_name = gdk_atom_name (context->targets->data); + if (target_name) + { + /* if it's not XCHAT_CHANVIEW or XCHAT_USERLIST */ + /* we should ignore it. */ + if (target_name[0] != 'X') + { + g_free (target_name); + return FALSE; + } + g_free (target_name); + } + + return TRUE; +} + +/* this begin callback just creates an nice of the source */ + +gboolean +mg_drag_begin_cb (GtkWidget *widget, GdkDragContext *context, gpointer userdata) +{ +#ifndef WIN32 /* leaks GDI pool memory - don't use on win32 */ + int width, height; + GdkColormap *cmap; + GdkPixbuf *pix, *pix2; + + /* ignore file drops */ + if (!mg_is_gui_target (context)) + return FALSE; + + cmap = gtk_widget_get_colormap (widget); + gdk_drawable_get_size (widget->window, &width, &height); + + pix = gdk_pixbuf_get_from_drawable (NULL, widget->window, cmap, 0, 0, 0, 0, width, height); + pix2 = gdk_pixbuf_scale_simple (pix, width * 4 / 5, height / 2, GDK_INTERP_HYPER); + g_object_unref (pix); + + gtk_drag_set_icon_pixbuf (context, pix2, 0, 0); + g_object_set_data (G_OBJECT (widget), "ico", pix2); +#endif + + return TRUE; +} + +void +mg_drag_end_cb (GtkWidget *widget, GdkDragContext *context, gpointer userdata) +{ + /* ignore file drops */ + if (!mg_is_gui_target (context)) + return; + +#ifndef WIN32 + g_object_unref (g_object_get_data (G_OBJECT (widget), "ico")); +#endif +} + +/* drop complete */ + +gboolean +mg_drag_drop_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, guint time, gpointer user_data) +{ + /* ignore file drops */ + if (!mg_is_gui_target (context)) + return FALSE; + + switch (context->action) + { + case GDK_ACTION_MOVE: + /* from userlist */ + mg_handle_drop (widget, y, &prefs.gui_ulist_pos, &prefs.tab_pos); + break; + case GDK_ACTION_COPY: + /* from tree - we use GDK_ACTION_COPY for the tree */ + mg_handle_drop (widget, y, &prefs.tab_pos, &prefs.gui_ulist_pos); + break; + default: + return FALSE; + } + + return TRUE; +} + +/* draw highlight rectangle in the destination */ + +gboolean +mg_drag_motion_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, guint time, gpointer scbar) +{ + GdkGC *gc; + GdkColor col; + GdkGCValues val; + int half, width, height; + int ox, oy; + GtkPaned *paned; + GdkDrawable *draw; + + /* ignore file drops */ + if (!mg_is_gui_target (context)) + return FALSE; + + if (scbar) /* scrollbar */ + { + ox = widget->allocation.x; + oy = widget->allocation.y; + width = widget->allocation.width; + height = widget->allocation.height; + draw = widget->window; + } + else + { + ox = oy = 0; + gdk_drawable_get_size (widget->window, &width, &height); + draw = widget->window; + } + + val.subwindow_mode = GDK_INCLUDE_INFERIORS; + val.graphics_exposures = 0; + val.function = GDK_XOR; + + gc = gdk_gc_new_with_values (widget->window, &val, GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW | GDK_GC_FUNCTION); + col.red = rand() % 0xffff; + col.green = rand() % 0xffff; + col.blue = rand() % 0xffff; + gdk_colormap_alloc_color (gtk_widget_get_colormap (widget), &col, FALSE, TRUE); + gdk_gc_set_foreground (gc, &col); + + half = height / 2; + +#if 0 + /* are both tree/userlist on the same side? */ + paned = (GtkPaned *)widget->parent->parent; + if (paned->child1 != NULL && paned->child2 != NULL) + { + gdk_draw_rectangle (draw, gc, 0, 1, 2, width - 3, height - 4); + gdk_draw_rectangle (draw, gc, 0, 0, 1, width - 1, height - 2); + g_object_unref (gc); + return TRUE; + } +#endif + + if (y < half) + { + gdk_draw_rectangle (draw, gc, FALSE, 1 + ox, 2 + oy, width - 3, half - 4); + gdk_draw_rectangle (draw, gc, FALSE, 0 + ox, 1 + oy, width - 1, half - 2); + gtk_widget_queue_draw_area (widget, ox, half + oy, width, height - half); + } + else + { + gdk_draw_rectangle (draw, gc, FALSE, 0 + ox, half + 1 + oy, width - 1, half - 2); + gdk_draw_rectangle (draw, gc, FALSE, 1 + ox, half + 2 + oy, width - 3, half - 4); + gtk_widget_queue_draw_area (widget, ox, oy, width, half); + } + + g_object_unref (gc); + + return TRUE; +} |