diff options
author | berkeviktor@aol.com <berkeviktor@aol.com> | 2011-02-24 04:14:30 +0100 |
---|---|---|
committer | berkeviktor@aol.com <berkeviktor@aol.com> | 2011-02-24 04:14:30 +0100 |
commit | 4a6ceffb98a0b785494f680d3776c4bfc4052f9e (patch) | |
tree | 850703c1c841ccd99f58d0b06084615aaebe782c /src/fe-gtk | |
parent | f16af8be941b596dedac3bf4e371ee2d21f4b598 (diff) |
add xchat r1489
Diffstat (limited to 'src/fe-gtk')
59 files changed, 31943 insertions, 0 deletions
diff --git a/src/fe-gtk/Makefile.am b/src/fe-gtk/Makefile.am new file mode 100644 index 00000000..7db38096 --- /dev/null +++ b/src/fe-gtk/Makefile.am @@ -0,0 +1,32 @@ +localedir = $(datadir)/locale + +bin_PROGRAMS = xchat + +INCLUDES = $(GUI_CFLAGS) -DG_DISABLE_CAST_CHECKS -DLOCALEDIR=\"$(localedir)\" + +xchat_LDADD = ../common/libxchatcommon.a $(GUI_LIBS) + +EXTRA_DIST = \ + about.h ascii.h banlist.h chanlist.h chanview.h chanview-tabs.c \ + chanview-tree.c custom-list.h editlist.h fe-gtk.h fkeys.h gtkutil.h joind.h \ + maingui.h menu.h mmx_cmod.S mmx_cmod.h notifygui.h palette.h pixmaps.h \ + plugin-tray.h plugingui.c plugingui.h rawlog.h search.h sexy-spell-entry.h \ + textgui.h urlgrab.h userlistgui.h xtext.h + +if USE_MMX +mmx_cmod_S = mmx_cmod.S +endif + +if DO_PLUGIN +plugingui_c = plugingui.c +endif + +if USE_LIBSEXY +sexy_spell_entry_c = sexy-spell-entry.c +endif + +xchat_SOURCES = about.c ascii.c banlist.c chanlist.c chanview.c custom-list.c \ + dccgui.c editlist.c fe-gtk.c fkeys.c gtkutil.c ignoregui.c joind.c menu.c \ + maingui.c $(mmx_cmod_S) notifygui.c palette.c pixmaps.c plugin-tray.c $(plugingui_c) \ + rawlog.c search.c servlistgui.c setup.c $(sexy_spell_entry_c) textgui.c \ + urlgrab.c userlistgui.c xtext.c diff --git a/src/fe-gtk/about.c b/src/fe-gtk/about.c new file mode 100644 index 00000000..60700aec --- /dev/null +++ b/src/fe-gtk/about.c @@ -0,0 +1,161 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "fe-gtk.h" + +#include <gtk/gtkmain.h> +#include <gtk/gtkcontainer.h> +#include <gtk/gtkdialog.h> +#include <gtk/gtkvbox.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkimage.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkbutton.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkwindow.h> + +#ifdef USE_XLIB +#include <gdk/gdkx.h> +#endif + +#include "../common/xchat.h" +#include "../common/util.h" +#include "palette.h" +#include "pixmaps.h" +#include "gtkutil.h" +#include "about.h" + + +#if 0 /*def USE_GNOME*/ + +void +menu_about (GtkWidget * wid, gpointer sess) +{ + char buf[512]; + const gchar *author[] = { "Peter Zelezny <zed@xchat.org>", 0 }; + + (snprintf) (buf, sizeof (buf), + "An IRC Client for UNIX.\n\n" + "This binary was compiled on "__DATE__"\n" + "Using GTK %d.%d.%d X %d\n" + "Running on %s", + gtk_major_version, gtk_minor_version, gtk_micro_version, +#ifdef USE_XLIB + VendorRelease (GDK_DISPLAY ()), get_cpu_str()); +#else + 666, get_cpu_str()); +#endif + + gtk_widget_show (gnome_about_new ("X-Chat", PACKAGE_VERSION, + "(C) 1998-2005 Peter Zelezny", author, buf, 0)); +} + +#else + +static GtkWidget *about = 0; + +static int +about_close (void) +{ + about = 0; + return 0; +} + +void +menu_about (GtkWidget * wid, gpointer sess) +{ + GtkWidget *vbox, *label, *hbox; + char buf[512]; + const char *locale = NULL; + extern GtkWindow *parent_window; /* maingui.c */ + + if (about) + { + gtk_window_present (GTK_WINDOW (about)); + return; + } + + about = gtk_dialog_new (); + gtk_window_set_position (GTK_WINDOW (about), GTK_WIN_POS_CENTER); + gtk_window_set_resizable (GTK_WINDOW (about), FALSE); + gtk_window_set_title (GTK_WINDOW (about), _("About "DISPLAY_NAME)); + if (parent_window) + gtk_window_set_transient_for (GTK_WINDOW (about), parent_window); + g_signal_connect (G_OBJECT (about), "destroy", + G_CALLBACK (about_close), 0); + + vbox = GTK_DIALOG (about)->vbox; + + wid = gtk_image_new_from_pixbuf (pix_xchat); + gtk_container_add (GTK_CONTAINER (vbox), wid); + + label = gtk_label_new (NULL); + gtk_label_set_selectable (GTK_LABEL (label), TRUE); + gtk_container_add (GTK_CONTAINER (vbox), label); + g_get_charset (&locale); + (snprintf) (buf, sizeof (buf), + "<span size=\"x-large\"><b>"DISPLAY_NAME" "PACKAGE_VERSION"</b></span>\n\n" + "%s\n\n" +#ifdef WIN32 + /* leave this message to avoid time wasting bug reports! */ + "This version is unofficial and comes with no support.\n\n" +#endif + "%s\n" + "<b>Charset</b>: %s " +#ifdef WIN32 + "<b>GTK+</b>: %i.%i.%i\n" +#else + "<b>Renderer</b>: %s\n" +#endif + "<b>Compiled</b>: "__DATE__"\n\n" + "<small>\302\251 1998-2010 Peter \305\275elezn\303\275 <zed@xchat.org></small>", + _("A multiplatform IRC Client"), + get_cpu_str(), + locale, +#ifdef WIN32 + gtk_major_version, + gtk_minor_version, + gtk_micro_version +#else +#ifdef USE_XFT + "Xft" +#else + "Pango" +#endif +#endif + ); + gtk_label_set_markup (GTK_LABEL (label), buf); + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER); + + hbox = gtk_hbox_new (0, 2); + gtk_container_add (GTK_CONTAINER (vbox), hbox); + + wid = gtk_button_new_from_stock (GTK_STOCK_CLOSE); + GTK_WIDGET_SET_FLAGS (GTK_WIDGET (wid), GTK_CAN_DEFAULT); + gtk_box_pack_end (GTK_BOX (GTK_DIALOG (about)->action_area), wid, 0, 0, 0); + gtk_widget_grab_default (wid); + g_signal_connect (G_OBJECT (wid), "clicked", + G_CALLBACK (gtkutil_destroy), about); + + gtk_widget_show_all (about); +} +#endif diff --git a/src/fe-gtk/about.h b/src/fe-gtk/about.h new file mode 100644 index 00000000..2bad159c --- /dev/null +++ b/src/fe-gtk/about.h @@ -0,0 +1 @@ +void menu_about (GtkWidget * wid, gpointer sess); diff --git a/src/fe-gtk/ascii.c b/src/fe-gtk/ascii.c new file mode 100644 index 00000000..f1adbdfc --- /dev/null +++ b/src/fe-gtk/ascii.c @@ -0,0 +1,177 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "fe-gtk.h" + +#include <gtk/gtkeditable.h> +#include <gtk/gtkframe.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkbutton.h> + +#include "../common/xchat.h" +#include "../common/xchatc.h" +#include "gtkutil.h" +#include "ascii.h" +#include "maingui.h" + +static const unsigned char table[]= +{ +/* Line 1 */ '\n', +0xc2,0xa1,0xc2,0xbf,0xc2,0xa2,0xc2,0xa3,0xe2,0x82,0xac,0xc2,0xa5,0xc2,0xa6,0xc2, +0xa7,0xc2,0xa8,0xc2,0xa9,0xc2,0xae,0xc2,0xaa,0xc2,0xab,0xc2,0xbb,0xc2,0xac,0xc2, +0xad,0xc2,0xaf,0xe2,0x99,0xaa, +/* Line 2 */ '\n', +0xc2,0xba,0xc2,0xb9,0xc2,0xb2,0xc2,0xb3,0xc2,0xb4,0xc2,0xb5,0xc3,0x9e,0xc3,0xbe, +0xc2,0xb6,0xc2,0xb7,0xc2,0xb8,0xc2,0xb0,0xc2,0xbc,0xc2,0xbd,0xc2,0xbe,0xc3,0x97, +0xc2,0xb1,0xc3,0xb7, +/* Line 3 */ '\n', +0xc3,0x80,0xc3,0x81,0xc3,0x82,0xc3,0x83,0xc3,0x84,0xc3,0x85,0xc3,0x86,0xc4,0x82, +0xc4,0x84,0x20,0xc3,0x87,0xc4,0x86,0xc4,0x8c,0xc5,0x92,0x20,0xc4,0x8e,0xc4,0x90, +0x20, +/* Line 4 */ '\n', +0xc3,0xa0,0xc3,0xa1,0xc3,0xa2,0xc3,0xa3,0xc3,0xa4,0xc3,0xa5,0xc3,0xa6,0xc4,0x83, +0xc4,0x85,0x20,0xc3,0xa7,0xc4,0x87,0xc4,0x8d,0xc5,0x93,0x20,0xc4,0x8f,0xc4,0x91, +0x20, +/* Line 5 */ '\n', +0xc3,0x88,0xc3,0x89,0xc3,0x8a,0xc3,0x8b,0xc4,0x98,0xc4,0x9a,0x20,0xc4,0x9e,0x20, +0xc3,0x8c,0xc3,0x8d,0xc3,0x8e,0xc3,0x8f,0xc4,0xb0,0x20,0xc4,0xb9,0xc4,0xbd,0xc5, +0x81, +/* Line 6 */ '\n', +0xc3,0xa8,0xc3,0xa9,0xc3,0xaa,0xc3,0xab,0xc4,0x99,0xc4,0x9b,0x20,0xc4,0x9f,0x20, +0xc3,0xac,0xc3,0xad,0xc3,0xae,0xc3,0xaf,0xc4,0xb1,0x20,0xc4,0xba,0xc4,0xbe,0xc5, +0x82, +/* Line 7 */ '\n', +0xc3,0x91,0xc5,0x83,0xc5,0x87,0x20,0xc3,0x92,0xc3,0x93,0xc3,0x94,0xc3,0x95,0xc3, +0x96,0xc3,0x98,0xc5,0x90,0x20,0xc5,0x94,0xc5,0x98,0x20,0xc5,0x9a,0xc5,0x9e,0xc5, +0xa0, +/* Line 8 */ '\n', +0xc3,0xb1,0xc5,0x84,0xc5,0x88,0x20,0xc3,0xb2,0xc3,0xb3,0xc3,0xb4,0xc3,0xb5,0xc3, +0xb6,0xc3,0xb8,0xc5,0x91,0x20,0xc5,0x95,0xc5,0x99,0x20,0xc5,0x9b,0xc5,0x9f,0xc5, +0xa1, +/* Line 9 */ '\n', +0x20,0xc5,0xa2,0xc5,0xa4,0x20,0xc3,0x99,0xc3,0x9a,0xc3,0x9b,0xc5,0xb2,0xc3,0x9c, +0xc5,0xae,0xc5,0xb0,0x20,0xc3,0x9d,0xc3,0x9f,0x20,0xc5,0xb9,0xc5,0xbb,0xc5,0xbd, +/* Line 10 */ '\n', +0x20,0xc5,0xa3,0xc5,0xa5,0x20,0xc3,0xb9,0xc3,0xba,0xc3,0xbb,0xc5,0xb3,0xc3,0xbc, +0xc5,0xaf,0xc5,0xb1,0x20,0xc3,0xbd,0xc3,0xbf,0x20,0xc5,0xba,0xc5,0xbc,0xc5,0xbe, +/* Line 11 */ '\n', +0xd0,0x90,0xd0,0x91,0xd0,0x92,0xd0,0x93,0xd0,0x94,0xd0,0x95,0xd0,0x81,0xd0,0x96, +0xd0,0x97,0xd0,0x98,0xd0,0x99,0xd0,0x9a,0xd0,0x9b,0xd0,0x9c,0xd0,0x9d,0xd0,0x9e, +0xd0,0x9f,0xd0,0xa0, +/* Line 12 */ '\n', +0xd0,0xb0,0xd0,0xb1,0xd0,0xb2,0xd0,0xb3,0xd0,0xb4,0xd0,0xb5,0xd1,0x91,0xd0,0xb6, +0xd0,0xb7,0xd0,0xb8,0xd0,0xb9,0xd0,0xba,0xd0,0xbb,0xd0,0xbc,0xd0,0xbd,0xd0,0xbe, +0xd0,0xbf,0xd1,0x80, +/* Line 13 */ '\n', +0xd0,0xa1,0xd0,0xa2,0xd0,0xa3,0xd0,0xa4,0xd0,0xa5,0xd0,0xa6,0xd0,0xa7,0xd0,0xa8, +0xd0,0xa9,0xd0,0xaa,0xd0,0xab,0xd0,0xac,0xd0,0xad,0xd0,0xae,0xd0,0xaf, +/* Line 14 */ '\n', +0xd1,0x81,0xd1,0x82,0xd1,0x83,0xd1,0x84,0xd1,0x85,0xd1,0x86,0xd1,0x87,0xd1,0x88, +0xd1,0x89,0xd1,0x8a,0xd1,0x8b,0xd1,0x8c,0xd1,0x8d,0xd1,0x8e,0xd1,0x8f,0 +}; + + +static gboolean +ascii_enter (GtkWidget * wid, GdkEventCrossing *event, GtkWidget *label) +{ + char buf[64]; + const char *text; + gunichar u; + + text = gtk_button_get_label (GTK_BUTTON (wid)); + u = g_utf8_get_char (text); + sprintf (buf, "%s U+%04X", text, u); + gtk_label_set_text (GTK_LABEL (label), buf); + + return FALSE; +} + +static void +ascii_click (GtkWidget * wid, gpointer userdata) +{ + int tmp_pos; + const char *text; + + if (current_sess) + { + text = gtk_button_get_label (GTK_BUTTON (wid)); + wid = current_sess->gui->input_box; + tmp_pos = SPELL_ENTRY_GET_POS (wid); + SPELL_ENTRY_INSERT (wid, text, -1, &tmp_pos); + SPELL_ENTRY_SET_POS (wid, tmp_pos); + } +} + +void +ascii_open (void) +{ + int i, len; + const unsigned char *table_pos; + char name[8]; + GtkWidget *frame, *label, *but, *hbox = NULL, *vbox, *win; + + win = mg_create_generic_tab ("charmap", _("Character Chart"), TRUE, TRUE, + NULL, NULL, 0, 0, &vbox, NULL); + gtk_container_set_border_width (GTK_CONTAINER (win), 5); + + label = gtk_label_new (NULL); + + table_pos = table; + i = 0; + while (table_pos[0] != 0) + { + if (table_pos[0] == '\n' || i == 0) + { + table_pos++; + hbox = gtk_hbox_new (0, 0); + gtk_container_add (GTK_CONTAINER (vbox), hbox); + gtk_widget_show (hbox); + i++; + continue; + } + + i++; + len = g_utf8_skip[table_pos[0]]; + memcpy (name, table_pos, len); + name[len] = 0; + + but = gtk_button_new_with_label (name); + gtk_widget_set_size_request (but, 28, -1); + g_signal_connect (G_OBJECT (but), "clicked", + G_CALLBACK (ascii_click), NULL); + g_signal_connect (G_OBJECT (but), "enter_notify_event", + G_CALLBACK (ascii_enter), label); + gtk_box_pack_start (GTK_BOX (hbox), but, 0, 0, 0); + gtk_widget_show (but); + + table_pos += len; + } + + frame = gtk_frame_new (""); + gtk_container_add (GTK_CONTAINER (hbox), frame); + gtk_container_add (GTK_CONTAINER (frame), label); + gtk_widget_show (label); + gtk_widget_show (frame); + + gtk_widget_show (win); +} diff --git a/src/fe-gtk/ascii.h b/src/fe-gtk/ascii.h new file mode 100644 index 00000000..afd3bd4f --- /dev/null +++ b/src/fe-gtk/ascii.h @@ -0,0 +1 @@ +void ascii_open (void); diff --git a/src/fe-gtk/banlist.c b/src/fe-gtk/banlist.c new file mode 100644 index 00000000..afaa7eb4 --- /dev/null +++ b/src/fe-gtk/banlist.c @@ -0,0 +1,418 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <time.h> + +#include "fe-gtk.h" + +#include <gtk/gtkhbox.h> +#include <gtk/gtkvbox.h> +#include <gtk/gtkhbbox.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkliststore.h> +#include <gtk/gtkmessagedialog.h> +#include <gtk/gtktreeview.h> +#include <gtk/gtktreeselection.h> + +#include "../common/xchat.h" +#include "../common/fe.h" +#include "../common/modes.h" +#include "../common/outbound.h" +#include "../common/xchatc.h" +#include "gtkutil.h" +#include "maingui.h" +#include "banlist.h" + +/* model for the banlist tree */ +enum +{ + MASK_COLUMN, + FROM_COLUMN, + DATE_COLUMN, + N_COLUMNS +}; + +static GtkTreeView * +get_view (struct session *sess) +{ + return GTK_TREE_VIEW (sess->res->banlist_treeview); +} + +static GtkListStore * +get_store (struct session *sess) +{ + return GTK_LIST_STORE (gtk_tree_view_get_model (get_view (sess))); +} + +static gboolean +supports_exempt (server *serv) +{ + char *cm = serv->chanmodes; + + if (serv->have_except) + return TRUE; + + if (!cm) + return FALSE; + + while (*cm) + { + if (*cm == ',') + break; + if (*cm == 'e') + return TRUE; + cm++; + } + + return FALSE; +} + +void +fe_add_ban_list (struct session *sess, char *mask, char *who, char *when, int is_exempt) +{ + GtkListStore *store; + GtkTreeIter iter; + char buf[512]; + + store = get_store (sess); + gtk_list_store_append (store, &iter); + + if (is_exempt) + { + snprintf (buf, sizeof (buf), "(EX) %s", mask); + gtk_list_store_set (store, &iter, 0, buf, 1, who, 2, when, -1); + } else + { + gtk_list_store_set (store, &iter, 0, mask, 1, who, 2, when, -1); + } +} + +void +fe_ban_list_end (struct session *sess, int is_exemption) +{ + gtk_widget_set_sensitive (sess->res->banlist_butRefresh, TRUE); +} + +/** + * * Performs the actual refresh operations. + * */ +static void +banlist_do_refresh (struct session *sess) +{ + char tbuf[256]; + if (sess->server->connected) + { + GtkListStore *store; + + gtk_widget_set_sensitive (sess->res->banlist_butRefresh, FALSE); + + snprintf (tbuf, sizeof tbuf, "XChat: Ban List (%s, %s)", + sess->channel, sess->server->servername); + mg_set_title (sess->res->banlist_window, tbuf); + + store = get_store (sess); + gtk_list_store_clear (store); + + handle_command (sess, "ban", FALSE); +#ifdef WIN32 + if (0) +#else + if (supports_exempt (sess->server)) +#endif + { + snprintf (tbuf, sizeof (tbuf), "quote mode %s +e", sess->channel); + handle_command (sess, tbuf, FALSE); + } + + } else + { + fe_message (_("Not connected."), FE_MSG_ERROR); + } +} + +static void +banlist_refresh (GtkWidget * wid, struct session *sess) +{ + /* JG NOTE: Didn't see actual use of wid here, so just forwarding + * * this to chanlist_do_refresh because I use it without any widget + * * param in chanlist_build_gui_list when the user presses enter + * * or apply for the first time if the list has not yet been + * * received. + * */ + banlist_do_refresh (sess); +} + +static int +banlist_unban_inner (gpointer none, struct session *sess, int do_exempts) +{ + GtkTreeModel *model; + GtkTreeSelection *sel; + GtkTreeIter iter; + char tbuf[2048]; + char **masks, *tmp, *space; + int num_sel, i; + + /* grab the list of selected items */ + model = GTK_TREE_MODEL (get_store (sess)); + sel = gtk_tree_view_get_selection (get_view (sess)); + num_sel = 0; + if (gtk_tree_model_get_iter_first (model, &iter)) + { + do + { + if (gtk_tree_selection_iter_is_selected (sel, &iter)) + num_sel++; + } + while (gtk_tree_model_iter_next (model, &iter)); + } + + if (num_sel < 1) + return 0; + + /* create an array of all the masks */ + masks = calloc (1, num_sel * sizeof (char *)); + + i = 0; + gtk_tree_model_get_iter_first (model, &iter); + do + { + if (gtk_tree_selection_iter_is_selected (sel, &iter)) + { + gtk_tree_model_get (model, &iter, MASK_COLUMN, &masks[i], -1); + space = strchr (masks[i], ' '); + + if (do_exempts) + { + if (space) + { + /* remove the "(EX) " */ + tmp = masks[i]; + masks[i] = g_strdup (space + 1); + g_free (tmp); + i++; + } + } else + { + if (!space) + i++; + } + } + } + while (gtk_tree_model_iter_next (model, &iter)); + + /* and send to server */ + if (do_exempts) + send_channel_modes (sess, tbuf, masks, 0, i, '-', 'e', 0); + else + send_channel_modes (sess, tbuf, masks, 0, i, '-', 'b', 0); + + /* now free everything, and refresh banlist */ + for (i=0; i < num_sel; i++) + g_free (masks[i]); + free (masks); + + return num_sel; +} + +static void +banlist_unban (GtkWidget * wid, struct session *sess) +{ + int num = 0; + + num += banlist_unban_inner (wid, sess, FALSE); + num += banlist_unban_inner (wid, sess, TRUE); + + if (num < 1) + { + fe_message (_("You must select some bans."), FE_MSG_ERROR); + return; + } + + banlist_do_refresh (sess); +} + +static void +banlist_clear_cb (GtkDialog *dialog, gint response, gpointer sess) +{ + GtkTreeSelection *sel; + + gtk_widget_destroy (GTK_WIDGET (dialog)); + + if (response == GTK_RESPONSE_OK) + { + sel = gtk_tree_view_get_selection (get_view (sess)); + gtk_tree_selection_select_all (sel); + banlist_unban (NULL, sess); + } +} + +static void +banlist_clear (GtkWidget * wid, struct session *sess) +{ + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (NULL, 0, + GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL, + _("Are you sure you want to remove all bans in %s?"), sess->channel); + g_signal_connect (G_OBJECT (dialog), "response", + G_CALLBACK (banlist_clear_cb), sess); + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + gtk_widget_show (dialog); +} + +static void +banlist_add_selected_cb (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter, gpointer data) +{ + GSList **lp = data; + GSList *list = NULL; + GtkTreeIter *copy; + + if (!lp) return; + list = *lp; + copy = g_malloc (sizeof (GtkTreeIter)); + g_return_if_fail (copy != NULL); + *copy = *iter; + + list = g_slist_append (list, copy); + *(GSList **)data = list; +} + +static void +banlist_crop (GtkWidget * wid, struct session *sess) +{ + GtkTreeSelection *select; + GSList *list = NULL, *node; + int num_sel; + + /* remember which bans are selected */ + select = gtk_tree_view_get_selection (get_view (sess)); + /* gtk_tree_selected_get_selected_rows() isn't present in gtk 2.0.x */ + gtk_tree_selection_selected_foreach (select, banlist_add_selected_cb, + &list); + + num_sel = g_slist_length (list); + /* select all, then unselect those that we remembered */ + if (num_sel) + { + gtk_tree_selection_select_all (select); + + for (node = list; node; node = node->next) + gtk_tree_selection_unselect_iter (select, node->data); + + g_slist_foreach (list, (GFunc)g_free, NULL); + g_slist_free (list); + + banlist_unban (NULL, sess); + } else + fe_message (_("You must select some bans."), FE_MSG_ERROR); +} + +static GtkWidget * +banlist_treeview_new (GtkWidget *box) +{ + GtkListStore *store; + GtkWidget *view; + GtkTreeSelection *select; + GtkTreeViewColumn *col; + + store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_STRING); + g_return_val_if_fail (store != NULL, NULL); + view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store), NULL, + MASK_COLUMN, _("Mask"), + FROM_COLUMN, _("From"), + DATE_COLUMN, _("Date"), -1); + + col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), MASK_COLUMN); + gtk_tree_view_column_set_alignment (col, 0.5); + gtk_tree_view_column_set_min_width (col, 300); + gtk_tree_view_column_set_sort_column_id (col, MASK_COLUMN); + + col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), FROM_COLUMN); + gtk_tree_view_column_set_alignment (col, 0.5); + gtk_tree_view_column_set_sort_column_id (col, FROM_COLUMN); + + col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), DATE_COLUMN); + gtk_tree_view_column_set_alignment (col, 0.5); + + select = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); + gtk_tree_selection_set_mode (select, GTK_SELECTION_MULTIPLE); + + gtk_widget_show (view); + return view; +} + +static void +banlist_closegui (GtkWidget *wid, session *sess) +{ + if (is_session (sess)) + sess->res->banlist_window = 0; +} + +void +banlist_opengui (struct session *sess) +{ + GtkWidget *vbox1; + GtkWidget *bbox; + char tbuf[256]; + + if (sess->res->banlist_window) + { + mg_bring_tofront (sess->res->banlist_window); + return; + } + + if (sess->type != SESS_CHANNEL) + { + fe_message (_("You can only open the Ban List window while in a channel tab."), FE_MSG_ERROR); + return; + } + + snprintf (tbuf, sizeof tbuf, _("XChat: Ban List (%s)"), + sess->server->servername); + + sess->res->banlist_window = mg_create_generic_tab ("BanList", tbuf, FALSE, + TRUE, banlist_closegui, sess, 550, 200, &vbox1, sess->server); + + /* create banlist view */ + sess->res->banlist_treeview = banlist_treeview_new (vbox1); + + bbox = gtk_hbutton_box_new (); + gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD); + gtk_container_set_border_width (GTK_CONTAINER (bbox), 5); + gtk_box_pack_end (GTK_BOX (vbox1), bbox, 0, 0, 0); + gtk_widget_show (bbox); + + gtkutil_button (bbox, GTK_STOCK_REMOVE, 0, banlist_unban, sess, + _("Remove")); + gtkutil_button (bbox, GTK_STOCK_REMOVE, 0, banlist_crop, sess, + _("Crop")); + gtkutil_button (bbox, GTK_STOCK_CLEAR, 0, banlist_clear, sess, + _("Clear")); + + sess->res->banlist_butRefresh = gtkutil_button (bbox, GTK_STOCK_REFRESH, 0, banlist_refresh, sess, _("Refresh")); + + banlist_do_refresh (sess); + + gtk_widget_show (sess->res->banlist_window); +} diff --git a/src/fe-gtk/banlist.h b/src/fe-gtk/banlist.h new file mode 100644 index 00000000..7ceccd00 --- /dev/null +++ b/src/fe-gtk/banlist.h @@ -0,0 +1 @@ +void banlist_opengui (session *sess); diff --git a/src/fe-gtk/chanlist.c b/src/fe-gtk/chanlist.c new file mode 100644 index 00000000..2f65b518 --- /dev/null +++ b/src/fe-gtk/chanlist.c @@ -0,0 +1,943 @@ +/* X-Chat + * Copyright (C) 1998-2006 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <time.h> + +#include "fe-gtk.h" + +#include <gtk/gtkalignment.h> +#include <gtk/gtkcellrenderertext.h> +#include <gtk/gtkcheckbutton.h> +#include <gtk/gtkcombobox.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkliststore.h> +#include <gtk/gtkscrolledwindow.h> +#include <gtk/gtkspinbutton.h> +#include <gtk/gtkstock.h> +#include <gtk/gtktable.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtkvbox.h> +#include <gtk/gtkvseparator.h> +#include <gdk/gdkkeysyms.h> + +#include "../common/xchat.h" +#include "../common/xchatc.h" +#include "../common/cfgfiles.h" +#include "../common/outbound.h" +#include "../common/util.h" +#include "../common/fe.h" +#include "../common/server.h" +#include "gtkutil.h" +#include "maingui.h" + + +#include "custom-list.h" + + +enum +{ + COL_CHANNEL, + COL_USERS, + COL_TOPIC, + N_COLUMNS +}; + +#ifndef CUSTOM_LIST +typedef struct /* this is now in custom-list.h */ +{ + char *topic; + char *collation_key; + guint32 pos; + guint32 users; + /* channel string lives beyond "users" */ +#define GET_CHAN(row) (((char *)row)+sizeof(chanlistrow)) +} +chanlistrow; +#endif + +#define GET_MODEL(xserv) (gtk_tree_view_get_model(GTK_TREE_VIEW(xserv->gui->chanlist_list))) + + +static gboolean +chanlist_match (server *serv, const char *str) +{ + switch (serv->gui->chanlist_search_type) + { + case 1: + return match (GTK_ENTRY (serv->gui->chanlist_wild)->text, str); +#ifndef WIN32 + case 2: + if (!serv->gui->have_regex) + return 0; + /* regex returns 0 if it's a match: */ + return !regexec (&serv->gui->chanlist_match_regex, str, 1, NULL, REG_NOTBOL); +#endif + default: /* case 0: */ + return nocasestrstr (str, GTK_ENTRY (serv->gui->chanlist_wild)->text) ? 1 : 0; + } +} + +/** + * Updates the caption to reflect the number of users and channels + */ +static void +chanlist_update_caption (server *serv) +{ + gchar tbuf[256]; + + snprintf (tbuf, sizeof tbuf, + _("Displaying %d/%d users on %d/%d channels."), + serv->gui->chanlist_users_shown_count, + serv->gui->chanlist_users_found_count, + serv->gui->chanlist_channels_shown_count, + serv->gui->chanlist_channels_found_count); + + gtk_label_set_text (GTK_LABEL (serv->gui->chanlist_label), tbuf); + serv->gui->chanlist_caption_is_stale = FALSE; +} + +static void +chanlist_update_buttons (server *serv) +{ + if (serv->gui->chanlist_channels_shown_count) + { + gtk_widget_set_sensitive (serv->gui->chanlist_join, TRUE); + gtk_widget_set_sensitive (serv->gui->chanlist_savelist, TRUE); + } + else + { + gtk_widget_set_sensitive (serv->gui->chanlist_join, FALSE); + gtk_widget_set_sensitive (serv->gui->chanlist_savelist, FALSE); + } +} + +static void +chanlist_reset_counters (server *serv) +{ + serv->gui->chanlist_users_found_count = 0; + serv->gui->chanlist_users_shown_count = 0; + serv->gui->chanlist_channels_found_count = 0; + serv->gui->chanlist_channels_shown_count = 0; + + chanlist_update_caption (serv); + chanlist_update_buttons (serv); +} + +/* free up our entire linked list and all the nodes */ + +static void +chanlist_data_free (server *serv) +{ + GSList *rows; + chanlistrow *data; + + if (serv->gui->chanlist_data_stored_rows) + { + for (rows = serv->gui->chanlist_data_stored_rows; rows != NULL; + rows = rows->next) + { + data = rows->data; + g_free (data->topic); + g_free (data->collation_key); + free (data); + } + + g_slist_free (serv->gui->chanlist_data_stored_rows); + serv->gui->chanlist_data_stored_rows = NULL; + } + + g_slist_free (serv->gui->chanlist_pending_rows); + serv->gui->chanlist_pending_rows = NULL; +} + +/* add any rows we received from the server in the last 0.25s to the GUI */ + +static void +chanlist_flush_pending (server *serv) +{ + GSList *list = serv->gui->chanlist_pending_rows; + GtkTreeModel *model; + chanlistrow *row; + + if (!list) + { + if (serv->gui->chanlist_caption_is_stale) + chanlist_update_caption (serv); + return; + } + model = GET_MODEL (serv); + + while (list) + { + row = list->data; + custom_list_append (CUSTOM_LIST (model), row); + list = list->next; + } + + g_slist_free (serv->gui->chanlist_pending_rows); + serv->gui->chanlist_pending_rows = NULL; + chanlist_update_caption (serv); +} + +static gboolean +chanlist_timeout (server *serv) +{ + chanlist_flush_pending (serv); + return TRUE; +} + +/** + * Places a data row into the gui GtkTreeView, if and only if the row matches + * the user and regex/search requirements. + */ +static void +chanlist_place_row_in_gui (server *serv, chanlistrow *next_row, gboolean force) +{ + GtkTreeModel *model; + + /* First, update the 'found' counter values */ + serv->gui->chanlist_users_found_count += next_row->users; + serv->gui->chanlist_channels_found_count++; + + if (serv->gui->chanlist_channels_shown_count == 1) + /* join & save buttons become live */ + chanlist_update_buttons (serv); + + if (next_row->users < serv->gui->chanlist_minusers) + { + serv->gui->chanlist_caption_is_stale = TRUE; + return; + } + + if (next_row->users > serv->gui->chanlist_maxusers + && serv->gui->chanlist_maxusers > 0) + { + serv->gui->chanlist_caption_is_stale = TRUE; + return; + } + + if (GTK_ENTRY (serv->gui->chanlist_wild)->text[0]) + { + /* Check what the user wants to match. If both buttons or _neither_ + * button is checked, look for match in both by default. + */ + if (serv->gui->chanlist_match_wants_channel == + serv->gui->chanlist_match_wants_topic) + { + if (!chanlist_match (serv, GET_CHAN (next_row)) + && !chanlist_match (serv, next_row->topic)) + { + serv->gui->chanlist_caption_is_stale = TRUE; + return; + } + } + + else if (serv->gui->chanlist_match_wants_channel) + { + if (!chanlist_match (serv, GET_CHAN (next_row))) + { + serv->gui->chanlist_caption_is_stale = TRUE; + return; + } + } + + else if (serv->gui->chanlist_match_wants_topic) + { + if (!chanlist_match (serv, next_row->topic)) + { + serv->gui->chanlist_caption_is_stale = TRUE; + return; + } + } + } + + if (force || serv->gui->chanlist_channels_shown_count < 20) + { + model = GET_MODEL (serv); + /* makes it appear fast :) */ + custom_list_append (CUSTOM_LIST (model), next_row); + chanlist_update_caption (serv); + } + else + /* add it to GUI at the next update interval */ + serv->gui->chanlist_pending_rows = g_slist_prepend (serv->gui->chanlist_pending_rows, next_row); + + /* Update the 'shown' counter values */ + serv->gui->chanlist_users_shown_count += next_row->users; + serv->gui->chanlist_channels_shown_count++; +} + +/* Performs the LIST download from the IRC server. */ + +static void +chanlist_do_refresh (server *serv) +{ + if (serv->gui->chanlist_flash_tag) + { + g_source_remove (serv->gui->chanlist_flash_tag); + serv->gui->chanlist_flash_tag = 0; + } + + if (!serv->connected) + { + fe_message (_("Not connected."), FE_MSG_ERROR); + return; + } + + custom_list_clear ((CustomList *)GET_MODEL (serv)); + gtk_widget_set_sensitive (serv->gui->chanlist_refresh, FALSE); + + chanlist_data_free (serv); + chanlist_reset_counters (serv); + + /* can we request a list with minusers arg? */ + if (serv->use_listargs) + { + /* yes - it will download faster */ + serv->p_list_channels (serv, "", serv->gui->chanlist_minusers); + /* don't allow the spin button below this value from now on */ + serv->gui->chanlist_minusers_downloaded = serv->gui->chanlist_minusers; + } + else + { + /* download all, filter minusers locally only */ + serv->p_list_channels (serv, "", 1); + serv->gui->chanlist_minusers_downloaded = 1; + } + +/* gtk_spin_button_set_range ((GtkSpinButton *)serv->gui->chanlist_min_spin, + serv->gui->chanlist_minusers_downloaded, 999999);*/ +} + +static void +chanlist_refresh (GtkWidget * wid, server *serv) +{ + chanlist_do_refresh (serv); +} + +/** + * Fills the gui GtkTreeView with stored items from the GSList. + */ +static void +chanlist_build_gui_list (server *serv) +{ + GSList *rows; + + /* first check if the list is present */ + if (serv->gui->chanlist_data_stored_rows == NULL) + { + /* start a download */ + chanlist_do_refresh (serv); + return; + } + + custom_list_clear ((CustomList *)GET_MODEL (serv)); + + /* discard pending rows FIXME: free the structs? */ + g_slist_free (serv->gui->chanlist_pending_rows); + serv->gui->chanlist_pending_rows = NULL; + + /* Reset the counters */ + chanlist_reset_counters (serv); + + /* Refill the list */ + for (rows = serv->gui->chanlist_data_stored_rows; rows != NULL; + rows = rows->next) + { + chanlist_place_row_in_gui (serv, rows->data, TRUE); + } + + custom_list_resort ((CustomList *)GET_MODEL (serv)); +} + +/** + * Accepts incoming channel data from inbound.c, allocates new space for a + * chanlistrow, adds it to our linked list and calls chanlist_place_row_in_gui. + */ +void +fe_add_chan_list (server *serv, char *chan, char *users, char *topic) +{ + chanlistrow *next_row; + int len = strlen (chan) + 1; + + /* we allocate the struct and channel string in one go */ + next_row = malloc (sizeof (chanlistrow) + len); + memcpy (((char *)next_row) + sizeof (chanlistrow), chan, len); + next_row->topic = strip_color (topic, -1, STRIP_ALL); + next_row->collation_key = g_utf8_collate_key (chan, len-1); + if (!(next_row->collation_key)) + next_row->collation_key = g_strdup (chan); + next_row->users = atoi (users); + + /* add this row to the data */ + serv->gui->chanlist_data_stored_rows = + g_slist_prepend (serv->gui->chanlist_data_stored_rows, next_row); + + /* _possibly_ add the row to the gui */ + chanlist_place_row_in_gui (serv, next_row, FALSE); +} + +void +fe_chan_list_end (server *serv) +{ + /* download complete */ + chanlist_flush_pending (serv); + gtk_widget_set_sensitive (serv->gui->chanlist_refresh, TRUE); + custom_list_resort ((CustomList *)GET_MODEL (serv)); +} + +static void +chanlist_search_pressed (GtkButton * button, server *serv) +{ + chanlist_build_gui_list (serv); +} + +static void +chanlist_find_cb (GtkWidget * wid, server *serv) +{ +#ifndef WIN32 + /* recompile the regular expression. */ + if (serv->gui->have_regex) + { + serv->gui->have_regex = 0; + regfree (&serv->gui->chanlist_match_regex); + } + + if (regcomp (&serv->gui->chanlist_match_regex, GTK_ENTRY (wid)->text, + REG_ICASE | REG_EXTENDED | REG_NOSUB) == 0) + serv->gui->have_regex = 1; +#endif +} + +static void +chanlist_match_channel_button_toggled (GtkWidget * wid, server *serv) +{ + serv->gui->chanlist_match_wants_channel = GTK_TOGGLE_BUTTON (wid)->active; +} + +static void +chanlist_match_topic_button_toggled (GtkWidget * wid, server *serv) +{ + serv->gui->chanlist_match_wants_topic = GTK_TOGGLE_BUTTON (wid)->active; +} + +static char * +chanlist_get_selected (server *serv, gboolean get_topic) +{ + char *chan; + GtkTreeSelection *sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (serv->gui->chanlist_list)); + GtkTreeModel *model; + GtkTreeIter iter; + + if (!gtk_tree_selection_get_selected (sel, &model, &iter)) + return NULL; + + gtk_tree_model_get (model, &iter, get_topic ? COL_TOPIC : COL_CHANNEL, &chan, -1); + return chan; +} + +static void +chanlist_join (GtkWidget * wid, server *serv) +{ + char tbuf[CHANLEN + 6]; + char *chan = chanlist_get_selected (serv, FALSE); + if (chan) + { + if (serv->connected && (strcmp (chan, "*") != 0)) + { + snprintf (tbuf, sizeof (tbuf), "join %s", chan); + handle_command (serv->server_session, tbuf, FALSE); + } else + gdk_beep (); + g_free (chan); + } +} + +static void +chanlist_filereq_done (server *serv, char *file) +{ + time_t t = time (0); + int fh, users; + char *chan, *topic; + char buf[1024]; + GtkTreeModel *model = GET_MODEL (serv); + GtkTreeIter iter; + + if (!file) + return; + + fh = xchat_open_file (file, O_TRUNC | O_WRONLY | O_CREAT, 0600, + XOF_DOMODE | XOF_FULLPATH); + if (fh == -1) + return; + + snprintf (buf, sizeof buf, "XChat Channel List: %s - %s\n", + serv->servername, ctime (&t)); + write (fh, buf, strlen (buf)); + + if (gtk_tree_model_get_iter_first (model, &iter)) + { + do + { + gtk_tree_model_get (model, &iter, + COL_CHANNEL, &chan, + COL_USERS, &users, + COL_TOPIC, &topic, -1); + snprintf (buf, sizeof buf, "%-16s %-5d%s\n", chan, users, topic); + g_free (chan); + g_free (topic); + write (fh, buf, strlen (buf)); + } + while (gtk_tree_model_iter_next (model, &iter)); + } + + close (fh); +} + +static void +chanlist_save (GtkWidget * wid, server *serv) +{ + GtkTreeIter iter; + GtkTreeModel *model = GET_MODEL (serv); + + if (gtk_tree_model_get_iter_first (model, &iter)) + gtkutil_file_req (_("Select an output filename"), chanlist_filereq_done, + serv, NULL, FRF_WRITE); +} + +static gboolean +chanlist_flash (server *serv) +{ + if (serv->gui->chanlist_refresh->state != GTK_STATE_ACTIVE) + gtk_widget_set_state (serv->gui->chanlist_refresh, GTK_STATE_ACTIVE); + else + gtk_widget_set_state (serv->gui->chanlist_refresh, GTK_STATE_PRELIGHT); + + return TRUE; +} + +static void +chanlist_minusers (GtkSpinButton *wid, server *serv) +{ + serv->gui->chanlist_minusers = gtk_spin_button_get_value_as_int (wid); + + if (serv->gui->chanlist_minusers < serv->gui->chanlist_minusers_downloaded) + { + if (serv->gui->chanlist_flash_tag == 0) + serv->gui->chanlist_flash_tag = g_timeout_add (500, (GSourceFunc)chanlist_flash, serv); + } + else + { + if (serv->gui->chanlist_flash_tag) + { + g_source_remove (serv->gui->chanlist_flash_tag); + serv->gui->chanlist_flash_tag = 0; + } + } +} + +static void +chanlist_maxusers (GtkSpinButton *wid, server *serv) +{ + serv->gui->chanlist_maxusers = gtk_spin_button_get_value_as_int (wid); +} + +static void +chanlist_dclick_cb (GtkTreeView *view, GtkTreePath *path, + GtkTreeViewColumn *column, gpointer data) +{ + chanlist_join (0, (server *) data); /* double clicked a row */ +} + +static void +chanlist_menu_destroy (GtkWidget *menu, gpointer userdata) +{ + gtk_widget_destroy (menu); + g_object_unref (menu); +} + +static void +chanlist_copychannel (GtkWidget *item, server *serv) +{ + char *chan = chanlist_get_selected (serv, FALSE); + if (chan) + { + gtkutil_copy_to_clipboard (item, NULL, chan); + g_free (chan); + } +} + +static void +chanlist_copytopic (GtkWidget *item, server *serv) +{ + char *topic = chanlist_get_selected (serv, TRUE); + if (topic) + { + gtkutil_copy_to_clipboard (item, NULL, topic); + g_free (topic); + } +} + +static gboolean +chanlist_button_cb (GtkTreeView *tree, GdkEventButton *event, server *serv) +{ + GtkWidget *menu; + GtkTreeSelection *sel; + GtkTreePath *path; + char *chan; + + if (event->button != 3) + return FALSE; + + if (!gtk_tree_view_get_path_at_pos (tree, event->x, event->y, &path, 0, 0, 0)) + return FALSE; + + /* select what they right-clicked on */ + sel = gtk_tree_view_get_selection (tree); + gtk_tree_selection_unselect_all (sel); + gtk_tree_selection_select_path (sel, path); + gtk_tree_path_free (path); + + menu = gtk_menu_new (); + 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 (chanlist_menu_destroy), NULL); + mg_create_icon_item (_("_Join Channel"), GTK_STOCK_JUMP_TO, menu, + chanlist_join, serv); + mg_create_icon_item (_("_Copy Channel Name"), GTK_STOCK_COPY, menu, + chanlist_copychannel, serv); + mg_create_icon_item (_("Copy _Topic Text"), GTK_STOCK_COPY, menu, + chanlist_copytopic, serv); + + chan = chanlist_get_selected (serv, FALSE); + menu_addfavoritemenu (serv, menu, chan); + g_free (chan); + + gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, 0, event->time); + + return TRUE; +} + +static void +chanlist_destroy_widget (GtkWidget *wid, server *serv) +{ + custom_list_clear ((CustomList *)GET_MODEL (serv)); + chanlist_data_free (serv); + + if (serv->gui->chanlist_flash_tag) + { + g_source_remove (serv->gui->chanlist_flash_tag); + serv->gui->chanlist_flash_tag = 0; + } + + if (serv->gui->chanlist_tag) + { + g_source_remove (serv->gui->chanlist_tag); + serv->gui->chanlist_tag = 0; + } + +#ifndef WIN32 + if (serv->gui->have_regex) + { + regfree (&serv->gui->chanlist_match_regex); + serv->gui->have_regex = 0; + } +#endif +} + +static void +chanlist_closegui (GtkWidget *wid, server *serv) +{ + if (is_server (serv)) + serv->gui->chanlist_window = NULL; +} + +static void +chanlist_add_column (GtkWidget *tree, int textcol, int size, char *title, gboolean right_justified) +{ + GtkCellRenderer *renderer; + GtkTreeViewColumn *col; + + renderer = gtk_cell_renderer_text_new (); + if (right_justified) + g_object_set (G_OBJECT (renderer), "xalign", (gfloat) 1.0, NULL); + g_object_set (G_OBJECT (renderer), "ypad", (gint) 0, NULL); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree), -1, title, + renderer, "text", textcol, NULL); + gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer), 1); + + col = gtk_tree_view_get_column (GTK_TREE_VIEW (tree), textcol); + gtk_tree_view_column_set_sort_column_id (col, textcol); + gtk_tree_view_column_set_resizable (col, TRUE); + if (textcol != COL_TOPIC) + { + gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_FIXED); + gtk_tree_view_column_set_fixed_width (col, size); + } +} + +static void +chanlist_combo_cb (GtkWidget *combo, server *serv) +{ + serv->gui->chanlist_search_type = gtk_combo_box_get_active (GTK_COMBO_BOX (combo)); +} + +void +chanlist_opengui (server *serv, int do_refresh) +{ + GtkWidget *vbox, *hbox, *table, *wid, *view; + char tbuf[256]; + GtkListStore *store; + + if (serv->gui->chanlist_window) + { + mg_bring_tofront (serv->gui->chanlist_window); + return; + } + + snprintf (tbuf, sizeof tbuf, _("XChat: Channel List (%s)"), + server_get_network (serv, TRUE)); + + serv->gui->chanlist_pending_rows = NULL; + serv->gui->chanlist_tag = 0; + serv->gui->chanlist_flash_tag = 0; + serv->gui->chanlist_data_stored_rows = NULL; + + if (!serv->gui->chanlist_minusers) + serv->gui->chanlist_minusers = 5; + + if (!serv->gui->chanlist_maxusers) + serv->gui->chanlist_maxusers = 9999; + + serv->gui->chanlist_window = + mg_create_generic_tab ("ChanList", tbuf, FALSE, TRUE, chanlist_closegui, + serv, 640, 480, &vbox, serv); + + gtk_container_set_border_width (GTK_CONTAINER (vbox), 6); + gtk_box_set_spacing (GTK_BOX (vbox), 12); + + /* make a label to store the user/channel info */ + wid = gtk_label_new (NULL); + gtk_box_pack_start (GTK_BOX (vbox), wid, 0, 0, 0); + gtk_widget_show (wid); + serv->gui->chanlist_label = wid; + + /* ============================================================= */ + + store = (GtkListStore *) custom_list_new(); + view = gtkutil_treeview_new (vbox, GTK_TREE_MODEL (store), NULL, -1); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (view->parent), + GTK_SHADOW_IN); + serv->gui->chanlist_list = view; + + g_signal_connect (G_OBJECT (view), "row_activated", + G_CALLBACK (chanlist_dclick_cb), serv); + g_signal_connect (G_OBJECT (view), "button-press-event", + G_CALLBACK (chanlist_button_cb), serv); + + chanlist_add_column (view, COL_CHANNEL, 96, _("Channel"), FALSE); + chanlist_add_column (view, COL_USERS, 50, _("Users"), TRUE); + chanlist_add_column (view, COL_TOPIC, 50, _("Topic"), FALSE); + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE); + /* this is a speed up, but no horizontal scrollbar :( */ + /*gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (view), TRUE);*/ + gtk_widget_show (view); + + /* ============================================================= */ + + table = gtk_table_new (4, 4, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 12); + gtk_table_set_row_spacings (GTK_TABLE (table), 3); + gtk_box_pack_start (GTK_BOX (vbox), table, 0, 1, 0); + gtk_widget_show (table); + + wid = gtkutil_button (NULL, GTK_STOCK_FIND, 0, chanlist_search_pressed, serv, + _("_Search")); + serv->gui->chanlist_search = wid; + gtk_table_attach (GTK_TABLE (table), wid, 3, 4, 3, 4, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + + wid = gtkutil_button (NULL, GTK_STOCK_REFRESH, 0, chanlist_refresh, serv, + _("_Download List")); + serv->gui->chanlist_refresh = wid; + gtk_table_attach (GTK_TABLE (table), wid, 3, 4, 2, 3, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + + wid = gtkutil_button (NULL, GTK_STOCK_SAVE_AS, 0, chanlist_save, serv, + _("Save _List...")); + serv->gui->chanlist_savelist = wid; + gtk_table_attach (GTK_TABLE (table), wid, 3, 4, 1, 2, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + + wid = gtkutil_button (NULL, GTK_STOCK_JUMP_TO, 0, chanlist_join, serv, + _("_Join Channel")); + serv->gui->chanlist_join = wid; + gtk_table_attach (GTK_TABLE (table), wid, 3, 4, 0, 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + + /* ============================================================= */ + + wid = gtk_label_new (_("Show only:")); + gtk_misc_set_alignment (GTK_MISC (wid), 0, 0.5); + gtk_table_attach (GTK_TABLE (table), wid, 0, 1, 3, 4, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (wid); + + hbox = gtk_hbox_new (0, 0); + gtk_box_set_spacing (GTK_BOX (hbox), 9); + gtk_table_attach (GTK_TABLE (table), hbox, 1, 2, 3, 4, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (hbox); + + wid = gtk_label_new (_("channels with")); + gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0); + gtk_widget_show (wid); + + wid = gtk_spin_button_new_with_range (1, 999999, 1); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (wid), + serv->gui->chanlist_minusers); + g_signal_connect (G_OBJECT (wid), "value_changed", + G_CALLBACK (chanlist_minusers), serv); + gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0); + gtk_widget_show (wid); + serv->gui->chanlist_min_spin = wid; + + wid = gtk_label_new (_("to")); + gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0); + gtk_widget_show (wid); + + wid = gtk_spin_button_new_with_range (1, 999999, 1); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (wid), + serv->gui->chanlist_maxusers); + g_signal_connect (G_OBJECT (wid), "value_changed", + G_CALLBACK (chanlist_maxusers), serv); + gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0); + gtk_widget_show (wid); + + wid = gtk_label_new (_("users.")); + gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0); + gtk_widget_show (wid); + + /* ============================================================= */ + + wid = gtk_label_new (_("Look in:")); + gtk_misc_set_alignment (GTK_MISC (wid), 0, 0.5); + gtk_table_attach (GTK_TABLE (table), wid, 0, 1, 2, 3, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (wid); + + hbox = gtk_hbox_new (0, 0); + gtk_box_set_spacing (GTK_BOX (hbox), 12); + gtk_table_attach (GTK_TABLE (table), hbox, 1, 2, 2, 3, + GTK_FILL, GTK_FILL, 0, 0); + gtk_widget_show (hbox); + + wid = gtk_check_button_new_with_label (_("Channel name")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), TRUE); + gtk_signal_connect (GTK_OBJECT (wid), "toggled", + GTK_SIGNAL_FUNC + (chanlist_match_channel_button_toggled), serv); + gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0); + gtk_widget_show (wid); + + wid = gtk_check_button_new_with_label (_("Topic")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), TRUE); + gtk_signal_connect (GTK_OBJECT (wid), "toggled", + GTK_SIGNAL_FUNC (chanlist_match_topic_button_toggled), + serv); + gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0); + gtk_widget_show (wid); + + serv->gui->chanlist_match_wants_channel = 1; + serv->gui->chanlist_match_wants_topic = 1; + + /* ============================================================= */ + + wid = gtk_label_new (_("Search type:")); + gtk_misc_set_alignment (GTK_MISC (wid), 0, 0.5); + gtk_table_attach (GTK_TABLE (table), wid, 0, 1, 1, 2, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (wid); + + wid = gtk_combo_box_new_text (); + gtk_combo_box_append_text (GTK_COMBO_BOX (wid), _("Simple Search")); + gtk_combo_box_append_text (GTK_COMBO_BOX (wid), _("Pattern Match (Wildcards)")); +#ifndef WIN32 + gtk_combo_box_append_text (GTK_COMBO_BOX (wid), _("Regular Expression")); +#endif + gtk_combo_box_set_active (GTK_COMBO_BOX (wid), serv->gui->chanlist_search_type); + gtk_table_attach (GTK_TABLE (table), wid, 1, 2, 1, 2, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + g_signal_connect (G_OBJECT (wid), "changed", + G_CALLBACK (chanlist_combo_cb), serv); + gtk_widget_show (wid); + + /* ============================================================= */ + + wid = gtk_label_new (_("Find:")); + gtk_misc_set_alignment (GTK_MISC (wid), 0, 0.5); + gtk_table_attach (GTK_TABLE (table), wid, 0, 1, 0, 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (wid); + + wid = gtk_entry_new_with_max_length (255); + gtk_signal_connect (GTK_OBJECT (wid), "changed", + GTK_SIGNAL_FUNC (chanlist_find_cb), serv); + gtk_signal_connect (GTK_OBJECT (wid), "activate", + GTK_SIGNAL_FUNC (chanlist_search_pressed), + (gpointer) serv); + gtk_table_attach (GTK_TABLE (table), wid, 1, 2, 0, 1, + GTK_EXPAND | GTK_FILL, 0, 0, 0); + gtk_widget_show (wid); + serv->gui->chanlist_wild = wid; + + chanlist_find_cb (wid, serv); + + /* ============================================================= */ + + wid = gtk_vseparator_new (); + gtk_table_attach (GTK_TABLE (table), wid, 2, 3, 0, 5, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + gtk_widget_show (wid); + + g_signal_connect (G_OBJECT (serv->gui->chanlist_window), "destroy", + G_CALLBACK (chanlist_destroy_widget), serv); + + /* reset the counters. */ + chanlist_reset_counters (serv); + + serv->gui->chanlist_tag = g_timeout_add (250, (GSourceFunc)chanlist_timeout, serv); + + if (do_refresh) + chanlist_do_refresh (serv); + + chanlist_update_buttons (serv); + gtk_widget_show (serv->gui->chanlist_window); + gtk_widget_grab_focus (serv->gui->chanlist_refresh); +} diff --git a/src/fe-gtk/chanlist.h b/src/fe-gtk/chanlist.h new file mode 100644 index 00000000..19a8b25e --- /dev/null +++ b/src/fe-gtk/chanlist.h @@ -0,0 +1 @@ +void chanlist_opengui (server *serv, int do_refresh); diff --git a/src/fe-gtk/chanview-tabs.c b/src/fe-gtk/chanview-tabs.c new file mode 100644 index 00000000..8e3da8e6 --- /dev/null +++ b/src/fe-gtk/chanview-tabs.c @@ -0,0 +1,779 @@ +/* file included in chanview.c */ + +typedef struct +{ + GtkWidget *outer; /* outer box */ + GtkWidget *inner; /* inner box */ + GtkWidget *b1; /* button1 */ + GtkWidget *b2; /* button2 */ +} tabview; + +static void chanview_populate (chanview *cv); + +/* ignore "toggled" signal? */ +static int ignore_toggle = FALSE; +static int tab_left_is_moving = 0; +static int tab_right_is_moving = 0; + +/* userdata for gobjects used here: + * + * tab (togglebuttons inside boxes): + * "u" userdata passed to tab-focus callback function (sess) + * "c" the tab's (chan *) + * + * box (family box) + * "f" family + * + */ + +/* + * GtkViewports request at least as much space as their children do. + * If we don't intervene here, the GtkViewport will be granted its + * request, even at the expense of resizing the top-level window. + */ +static void +cv_tabs_sizerequest (GtkWidget *viewport, GtkRequisition *requisition, chanview *cv) +{ + if (!cv->vertical) + requisition->width = 1; + else + requisition->height = 1; +} + +static void +cv_tabs_sizealloc (GtkWidget *widget, GtkAllocation *allocation, chanview *cv) +{ + GtkAdjustment *adj; + GtkWidget *inner; + gint viewport_size; + + inner = ((tabview *)cv)->inner; + + if (cv->vertical) + { + adj = gtk_viewport_get_vadjustment (GTK_VIEWPORT (inner->parent)); + gdk_window_get_geometry (inner->parent->window, 0, 0, 0, &viewport_size, 0); + } else + { + adj = gtk_viewport_get_hadjustment (GTK_VIEWPORT (inner->parent)); + gdk_window_get_geometry (inner->parent->window, 0, 0, &viewport_size, 0, 0); + } + + if (adj->upper <= viewport_size) + { + gtk_widget_hide (((tabview *)cv)->b1); + gtk_widget_hide (((tabview *)cv)->b2); + } else + { + gtk_widget_show (((tabview *)cv)->b1); + gtk_widget_show (((tabview *)cv)->b2); + } +} + +static gint +tab_search_offset (GtkWidget *inner, gint start_offset, + gboolean forward, gboolean vertical) +{ + GList *boxes; + GList *tabs; + GtkWidget *box; + GtkWidget *button; + gint found; + + boxes = GTK_BOX (inner)->children; + if (!forward && boxes) + boxes = g_list_last (boxes); + + while (boxes) + { + box = ((GtkBoxChild *)boxes->data)->widget; + boxes = (forward ? boxes->next : boxes->prev); + + tabs = GTK_BOX (box)->children; + if (!forward && tabs) + tabs = g_list_last (tabs); + + while (tabs) + { + button = ((GtkBoxChild *)tabs->data)->widget; + tabs = (forward ? tabs->next : tabs->prev); + + if (!GTK_IS_TOGGLE_BUTTON (button)) + continue; + + found = (vertical ? button->allocation.y : button->allocation.x); + if ((forward && found > start_offset) || + (!forward && found < start_offset)) + return found; + } + } + + return 0; +} + +static void +tab_scroll_left_up_clicked (GtkWidget *widget, chanview *cv) +{ + GtkAdjustment *adj; + gint viewport_size; + gfloat new_value; + GtkWidget *inner; + gfloat i; + + inner = ((tabview *)cv)->inner; + + if (cv->vertical) + { + adj = gtk_viewport_get_vadjustment (GTK_VIEWPORT (inner->parent)); + gdk_window_get_geometry (inner->parent->window, 0, 0, 0, &viewport_size, 0); + } else + { + adj = gtk_viewport_get_hadjustment (GTK_VIEWPORT (inner->parent)); + gdk_window_get_geometry (inner->parent->window, 0, 0, &viewport_size, 0, 0); + } + + new_value = tab_search_offset (inner, adj->value, 0, cv->vertical); + + if (new_value + viewport_size > adj->upper) + new_value = adj->upper - viewport_size; + + if (!tab_left_is_moving) + { + tab_left_is_moving = 1; + + for (i = adj->value; ((i > new_value) && (tab_left_is_moving)); i -= 0.1) + { + gtk_adjustment_set_value (adj, i); + while (g_main_pending ()) + g_main_iteration (TRUE); + } + + gtk_adjustment_set_value (adj, new_value); + + tab_left_is_moving = 0; /* hSP: set to false in case we didnt get stopped (the normal case) */ + } + else + { + tab_left_is_moving = 0; /* hSP: jump directly to next element if user is clicking faster than we can scroll.. */ + } +} + +static void +tab_scroll_right_down_clicked (GtkWidget *widget, chanview *cv) +{ + GtkAdjustment *adj; + gint viewport_size; + gfloat new_value; + GtkWidget *inner; + gfloat i; + + inner = ((tabview *)cv)->inner; + + if (cv->vertical) + { + adj = gtk_viewport_get_vadjustment (GTK_VIEWPORT (inner->parent)); + gdk_window_get_geometry (inner->parent->window, 0, 0, 0, &viewport_size, 0); + } else + { + adj = gtk_viewport_get_hadjustment (GTK_VIEWPORT (inner->parent)); + gdk_window_get_geometry (inner->parent->window, 0, 0, &viewport_size, 0, 0); + } + + new_value = tab_search_offset (inner, adj->value, 1, cv->vertical); + + if (new_value == 0 || new_value + viewport_size > adj->upper) + new_value = adj->upper - viewport_size; + + if (!tab_right_is_moving) + { + tab_right_is_moving = 1; + + for (i = adj->value; ((i < new_value) && (tab_right_is_moving)); i += 0.1) + { + gtk_adjustment_set_value (adj, i); + while (g_main_pending ()) + g_main_iteration (TRUE); + } + + gtk_adjustment_set_value (adj, new_value); + + tab_right_is_moving = 0; /* hSP: set to false in case we didnt get stopped (the normal case) */ + } + else + { + tab_right_is_moving = 0; /* hSP: jump directly to next element if user is clicking faster than we can scroll.. */ + } +} + +static gboolean +tab_scroll_cb (GtkWidget *widget, GdkEventScroll *event, gpointer cv) +{ + /* mouse wheel scrolling */ + if (event->direction == GDK_SCROLL_UP) + tab_scroll_left_up_clicked (widget, cv); + else if (event->direction == GDK_SCROLL_DOWN) + tab_scroll_right_down_clicked (widget, cv); + + return FALSE; +} + +static void +cv_tabs_xclick_cb (GtkWidget *button, chanview *cv) +{ + cv->cb_xbutton (cv, cv->focused, cv->focused->tag, cv->focused->userdata); +} + +/* make a Scroll (arrow) button */ + +static GtkWidget * +make_sbutton (GtkArrowType type, void *click_cb, void *userdata) +{ + GtkWidget *button, *arrow; + + button = gtk_button_new (); + arrow = gtk_arrow_new (type, GTK_SHADOW_NONE); + gtk_container_add (GTK_CONTAINER (button), arrow); + gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); + g_signal_connect (G_OBJECT (button), "clicked", + G_CALLBACK (click_cb), userdata); + g_signal_connect (G_OBJECT (button), "scroll_event", + G_CALLBACK (tab_scroll_cb), userdata); + gtk_widget_show (arrow); + + return button; +} + +static void +cv_tabs_init (chanview *cv) +{ + GtkWidget *box, *hbox = NULL; + GtkWidget *viewport; + GtkWidget *outer; + GtkWidget *button; + + if (cv->vertical) + outer = gtk_vbox_new (0, 0); + else + outer = gtk_hbox_new (0, 0); + ((tabview *)cv)->outer = outer; + g_signal_connect (G_OBJECT (outer), "size_allocate", + G_CALLBACK (cv_tabs_sizealloc), cv); +/* gtk_container_set_border_width (GTK_CONTAINER (outer), 2);*/ + gtk_widget_show (outer); + + viewport = gtk_viewport_new (0, 0); + gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE); + g_signal_connect (G_OBJECT (viewport), "size_request", + G_CALLBACK (cv_tabs_sizerequest), cv); + g_signal_connect (G_OBJECT (viewport), "scroll_event", + G_CALLBACK (tab_scroll_cb), cv); + gtk_box_pack_start (GTK_BOX (outer), viewport, 1, 1, 0); + gtk_widget_show (viewport); + + if (cv->vertical) + box = gtk_vbox_new (FALSE, 0); + else + box = gtk_hbox_new (FALSE, 0); + ((tabview *)cv)->inner = box; + gtk_container_add (GTK_CONTAINER (viewport), box); + gtk_widget_show (box); + + /* if vertical, the buttons can be side by side */ + if (cv->vertical) + { + hbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (outer), hbox, 0, 0, 0); + gtk_widget_show (hbox); + } + + /* make the Scroll buttons */ + ((tabview *)cv)->b2 = make_sbutton (cv->vertical ? + GTK_ARROW_UP : GTK_ARROW_LEFT, + tab_scroll_left_up_clicked, + cv); + + ((tabview *)cv)->b1 = make_sbutton (cv->vertical ? + GTK_ARROW_DOWN : GTK_ARROW_RIGHT, + tab_scroll_right_down_clicked, + cv); + + if (hbox) + { + gtk_container_add (GTK_CONTAINER (hbox), ((tabview *)cv)->b2); + gtk_container_add (GTK_CONTAINER (hbox), ((tabview *)cv)->b1); + } else + { + gtk_box_pack_start (GTK_BOX (outer), ((tabview *)cv)->b2, 0, 0, 0); + gtk_box_pack_start (GTK_BOX (outer), ((tabview *)cv)->b1, 0, 0, 0); + } + + button = gtkutil_button (outer, GTK_STOCK_CLOSE, NULL, cv_tabs_xclick_cb, + cv, 0); + gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); + GTK_WIDGET_UNSET_FLAGS (button, GTK_CAN_FOCUS); + + gtk_container_add (GTK_CONTAINER (cv->box), outer); +} + +static void +cv_tabs_postinit (chanview *cv) +{ +} + +static void +tab_add_sorted (chanview *cv, GtkWidget *box, GtkWidget *tab, chan *ch) +{ + GList *list; + GtkBoxChild *child; + int i = 0; + void *b; + + if (!cv->sorted) + { + gtk_box_pack_start (GTK_BOX (box), tab, 0, 0, 0); + gtk_widget_show (tab); + return; + } + + /* sorting TODO: + * - move tab if renamed (dialogs) */ + + /* userdata, passed to mg_tabs_compare() */ + b = ch->userdata; + + list = GTK_BOX (box)->children; + while (list) + { + child = list->data; + if (!GTK_IS_SEPARATOR (child->widget)) + { + void *a = g_object_get_data (G_OBJECT (child->widget), "u"); + + if (ch->tag == 0 && cv->cb_compare (a, b) > 0) + { + gtk_box_pack_start (GTK_BOX (box), tab, 0, 0, 0); + gtk_box_reorder_child (GTK_BOX (box), tab, i); + gtk_widget_show (tab); + return; + } + } + i++; + list = list->next; + } + + /* append */ + gtk_box_pack_start (GTK_BOX (box), tab, 0, 0, 0); + gtk_box_reorder_child (GTK_BOX (box), tab, i); + gtk_widget_show (tab); +} + +/* remove empty boxes and separators */ + +static void +cv_tabs_prune (chanview *cv) +{ + GList *boxes, *children; + GtkWidget *box, *inner; + GtkBoxChild *child; + int empty; + + inner = ((tabview *)cv)->inner; + boxes = GTK_BOX (inner)->children; + while (boxes) + { + child = boxes->data; + box = child->widget; + boxes = boxes->next; + + /* check if the box is empty (except a vseperator) */ + empty = TRUE; + children = GTK_BOX (box)->children; + while (children) + { + if (!GTK_IS_SEPARATOR (((GtkBoxChild *)children->data)->widget)) + { + empty = FALSE; + break; + } + children = children->next; + } + + if (empty) + gtk_widget_destroy (box); + } +} + +static void +tab_add_real (chanview *cv, GtkWidget *tab, chan *ch) +{ + GList *boxes, *children; + GtkWidget *sep, *box, *inner; + GtkBoxChild *child; + int empty; + + inner = ((tabview *)cv)->inner; + /* see if a family for this tab already exists */ + boxes = GTK_BOX (inner)->children; + while (boxes) + { + child = boxes->data; + box = child->widget; + + if (g_object_get_data (G_OBJECT (box), "f") == ch->family) + { + tab_add_sorted (cv, box, tab, ch); + gtk_widget_queue_resize (inner->parent); + return; + } + + boxes = boxes->next; + + /* check if the box is empty (except a vseperator) */ + empty = TRUE; + children = GTK_BOX (box)->children; + while (children) + { + if (!GTK_IS_SEPARATOR (((GtkBoxChild *)children->data)->widget)) + { + empty = FALSE; + break; + } + children = children->next; + } + + if (empty) + gtk_widget_destroy (box); + } + + /* create a new family box */ + if (cv->vertical) + { + /* vertical */ + box = gtk_vbox_new (FALSE, 0); + sep = gtk_hseparator_new (); + } else + { + /* horiz */ + box = gtk_hbox_new (FALSE, 0); + sep = gtk_vseparator_new (); + } + + gtk_box_pack_end (GTK_BOX (box), sep, 0, 0, 4); + gtk_widget_show (sep); + gtk_box_pack_start (GTK_BOX (inner), box, 0, 0, 0); + g_object_set_data (G_OBJECT (box), "f", ch->family); + gtk_box_pack_start (GTK_BOX (box), tab, 0, 0, 0); + gtk_widget_show (tab); + gtk_widget_show (box); + gtk_widget_queue_resize (inner->parent); +} + +static gboolean +tab_ignore_cb (GtkWidget *widget, GdkEventCrossing *event, gpointer user_data) +{ + return TRUE; +} + +/* called when a tab is clicked (button down) */ + +static void +tab_pressed_cb (GtkToggleButton *tab, chan *ch) +{ + chan *old_tab; + int is_switching = TRUE; + chanview *cv = ch->cv; + + ignore_toggle = TRUE; + /* de-activate the old tab */ + old_tab = cv->focused; + if (old_tab && old_tab->impl) + { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (old_tab->impl), FALSE); + if (old_tab == ch) + is_switching = FALSE; + } + gtk_toggle_button_set_active (tab, TRUE); + ignore_toggle = FALSE; + cv->focused = ch; + + if (/*tab->active*/is_switching) + /* call the focus callback */ + cv->cb_focus (cv, ch, ch->tag, ch->userdata); +} + +/* called for keyboard tab toggles only */ +static void +tab_toggled_cb (GtkToggleButton *tab, chan *ch) +{ + if (ignore_toggle) + return; + + /* activated a tab via keyboard */ + tab_pressed_cb (tab, ch); +} + +static gboolean +tab_click_cb (GtkWidget *wid, GdkEventButton *event, chan *ch) +{ + return ch->cv->cb_contextmenu (ch->cv, ch, ch->tag, ch->userdata, event); +} + +static void * +cv_tabs_add (chanview *cv, chan *ch, char *name, GtkTreeIter *parent) +{ + GtkWidget *but; + + but = gtk_toggle_button_new_with_label (name); + gtk_widget_set_name (but, "xchat-tab"); + g_object_set_data (G_OBJECT (but), "c", ch); + /* used to trap right-clicks */ + g_signal_connect (G_OBJECT (but), "button_press_event", + G_CALLBACK (tab_click_cb), ch); + /* avoid prelights */ + g_signal_connect (G_OBJECT (but), "enter_notify_event", + G_CALLBACK (tab_ignore_cb), NULL); + g_signal_connect (G_OBJECT (but), "leave_notify_event", + G_CALLBACK (tab_ignore_cb), NULL); + g_signal_connect (G_OBJECT (but), "pressed", + G_CALLBACK (tab_pressed_cb), ch); + /* for keyboard */ + g_signal_connect (G_OBJECT (but), "toggled", + G_CALLBACK (tab_toggled_cb), ch); + g_object_set_data (G_OBJECT (but), "u", ch->userdata); + + tab_add_real (cv, but, ch); + + return but; +} + +/* traverse all the family boxes of tabs + * + * A "group" is basically: + * GtkV/HBox + * `-GtkViewPort + * `-GtkV/HBox (inner box) + * `- GtkBox (family box) + * `- GtkToggleButton + * `- GtkToggleButton + * `- ... + * `- GtkBox + * `- GtkToggleButton + * `- GtkToggleButton + * `- ... + * `- ... + * + * */ + +static int +tab_group_for_each_tab (chanview *cv, + int (*callback) (GtkWidget *tab, int num, int usernum), + int usernum) +{ + GList *tabs; + GList *boxes; + GtkBoxChild *child; + GtkBox *innerbox; + int i; + + innerbox = (GtkBox *) ((tabview *)cv)->inner; + boxes = innerbox->children; + i = 0; + while (boxes) + { + child = boxes->data; + tabs = GTK_BOX (child->widget)->children; + + while (tabs) + { + child = tabs->data; + + if (!GTK_IS_SEPARATOR (child->widget)) + { + if (callback (child->widget, i, usernum) != -1) + return i; + i++; + } + tabs = tabs->next; + } + + boxes = boxes->next; + } + + return i; +} + +static int +tab_check_focus_cb (GtkWidget *tab, int num, int unused) +{ + if (GTK_TOGGLE_BUTTON (tab)->active) + return num; + + return -1; +} + +/* returns the currently focused tab number */ + +static int +tab_group_get_cur_page (chanview *cv) +{ + return tab_group_for_each_tab (cv, tab_check_focus_cb, 0); +} + +static void +cv_tabs_focus (chan *ch) +{ + if (ch->impl) + /* focus the new one (tab_pressed_cb defocuses the old one) */ + tab_pressed_cb (GTK_TOGGLE_BUTTON (ch->impl), ch); +} + +static int +tab_focus_num_cb (GtkWidget *tab, int num, int want) +{ + if (num == want) + { + cv_tabs_focus (g_object_get_data (G_OBJECT (tab), "c")); + return 1; + } + + return -1; +} + +static void +cv_tabs_change_orientation (chanview *cv) +{ + /* cleanup the old one */ + if (cv->func_cleanup) + cv->func_cleanup (cv); + + /* now rebuild a new tabbar or tree */ + cv->func_init (cv); + chanview_populate (cv); +} + +/* switch to the tab number specified */ + +static void +cv_tabs_move_focus (chanview *cv, gboolean relative, int num) +{ + int i, max; + + if (relative) + { + max = cv->size; + i = tab_group_get_cur_page (cv) + num; + /* make it wrap around at both ends */ + if (i < 0) + i = max - 1; + if (i >= max) + i = 0; + tab_group_for_each_tab (cv, tab_focus_num_cb, i); + return; + } + + tab_group_for_each_tab (cv, tab_focus_num_cb, num); +} + +static void +cv_tabs_remove (chan *ch) +{ + gtk_widget_destroy (ch->impl); + ch->impl = NULL; + + cv_tabs_prune (ch->cv); +} + +static void +cv_tabs_move (chan *ch, int delta) +{ + int i, pos = 0; + GList *list; + GtkWidget *parent = ((GtkWidget *)ch->impl)->parent; + + i = 0; + for (list = GTK_BOX (parent)->children; list; list = list->next) + { + GtkBoxChild *child_entry; + + child_entry = list->data; + if (child_entry->widget == ch->impl) + pos = i; + i++; + } + + pos = (pos - delta) % i; + gtk_box_reorder_child (GTK_BOX (parent), ch->impl, pos); +} + +static void +cv_tabs_move_family (chan *ch, int delta) +{ + int i, pos = 0; + GList *list; + GtkWidget *box = NULL; + + /* find position of tab's family */ + i = 0; + for (list = GTK_BOX (((tabview *)ch->cv)->inner)->children; list; list = list->next) + { + GtkBoxChild *child_entry; + void *fam; + + child_entry = list->data; + fam = g_object_get_data (G_OBJECT (child_entry->widget), "f"); + if (fam == ch->family) + { + box = child_entry->widget; + pos = i; + } + i++; + } + + pos = (pos - delta) % i; + gtk_box_reorder_child (GTK_BOX (box->parent), box, pos); +} + +static void +cv_tabs_cleanup (chanview *cv) +{ + if (cv->box) + gtk_widget_destroy (((tabview *)cv)->outer); +} + +static void +cv_tabs_set_color (chan *ch, PangoAttrList *list) +{ + gtk_label_set_attributes (GTK_LABEL (GTK_BIN (ch->impl)->child), list); +} + +static void +cv_tabs_rename (chan *ch, char *name) +{ + PangoAttrList *attr; + GtkWidget *tab = ch->impl; + + attr = gtk_label_get_attributes (GTK_LABEL (GTK_BIN (tab)->child)); + if (attr) + pango_attr_list_ref (attr); + + gtk_button_set_label (GTK_BUTTON (tab), name); + gtk_widget_queue_resize (tab->parent->parent->parent); + + if (attr) + { + gtk_label_set_attributes (GTK_LABEL (GTK_BIN (tab)->child), attr); + pango_attr_list_unref (attr); + } +} + +static gboolean +cv_tabs_is_collapsed (chan *ch) +{ + return FALSE; +} + +static chan * +cv_tabs_get_parent (chan *ch) +{ + return NULL; +} diff --git a/src/fe-gtk/chanview-tree.c b/src/fe-gtk/chanview-tree.c new file mode 100644 index 00000000..5373f21b --- /dev/null +++ b/src/fe-gtk/chanview-tree.c @@ -0,0 +1,364 @@ +/* file included in chanview.c */ + +typedef struct +{ + GtkTreeView *tree; + GtkWidget *scrollw; /* scrolledWindow */ +} treeview; + +#include "../common/xchat.h" +#include "../common/xchatc.h" +#include "fe-gtk.h" +#include "maingui.h" + +#include <gdk/gdk.h> +#include <gtk/gtktreeview.h> + +static void /* row-activated, when a row is double clicked */ +cv_tree_activated_cb (GtkTreeView *view, GtkTreePath *path, + GtkTreeViewColumn *column, gpointer data) +{ + if (gtk_tree_view_row_expanded (view, path)) + gtk_tree_view_collapse_row (view, path); + else + gtk_tree_view_expand_row (view, path, FALSE); +} + +static void /* row selected callback */ +cv_tree_sel_cb (GtkTreeSelection *sel, chanview *cv) +{ + GtkTreeModel *model; + GtkTreeIter iter; + chan *ch; + + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + { + gtk_tree_model_get (model, &iter, COL_CHAN, &ch, -1); + + cv->focused = ch; + cv->cb_focus (cv, ch, ch->tag, ch->userdata); + } +} + +static gboolean +cv_tree_click_cb (GtkTreeView *tree, GdkEventButton *event, chanview *cv) +{ + chan *ch; + GtkTreeSelection *sel; + GtkTreePath *path; + GtkTreeIter iter; + int ret = FALSE; + + if (event->button != 3 && event->state == 0) + return FALSE; + + sel = gtk_tree_view_get_selection (tree); + if (gtk_tree_view_get_path_at_pos (tree, event->x, event->y, &path, 0, 0, 0)) + { + if (event->button == 2) + { + gtk_tree_selection_unselect_all (sel); + gtk_tree_selection_select_path (sel, path); + } + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (cv->store), &iter, path)) + { + gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &iter, COL_CHAN, &ch, -1); + ret = cv->cb_contextmenu (cv, ch, ch->tag, ch->userdata, event); + } + gtk_tree_path_free (path); + } + return ret; +} + +static void +cv_tree_init (chanview *cv) +{ + GtkWidget *view, *win; + GtkCellRenderer *renderer; + static const GtkTargetEntry dnd_src_target[] = + { + {"XCHAT_CHANVIEW", GTK_TARGET_SAME_APP, 75 } + }; + static const GtkTargetEntry dnd_dest_target[] = + { + {"XCHAT_USERLIST", GTK_TARGET_SAME_APP, 75 } + }; + + win = gtk_scrolled_window_new (0, 0); + /*gtk_container_set_border_width (GTK_CONTAINER (win), 1);*/ + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (win), + GTK_SHADOW_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (win), + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_container_add (GTK_CONTAINER (cv->box), win); + gtk_widget_show (win); + + view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (cv->store)); + gtk_widget_set_name (view, "xchat-tree"); + if (cv->style) + gtk_widget_set_style (view, cv->style); + /*gtk_widget_modify_base (view, GTK_STATE_NORMAL, &colors[COL_BG]);*/ + GTK_WIDGET_UNSET_FLAGS (view, GTK_CAN_FOCUS); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view), FALSE); +#if GTK_CHECK_VERSION(2,10,0) + if (!(prefs.gui_tweaks & 8)) + gtk_tree_view_set_enable_tree_lines (GTK_TREE_VIEW (view), TRUE); +#endif + gtk_container_add (GTK_CONTAINER (win), view); + + /* icon column */ + if (cv->use_icons) + { + renderer = gtk_cell_renderer_pixbuf_new (); + if (prefs.gui_tweaks & 32) + g_object_set (G_OBJECT (renderer), "ypad", 0, NULL); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), + -1, NULL, renderer, + "pixbuf", COL_PIXBUF, NULL); + } + + /* main column */ + renderer = gtk_cell_renderer_text_new (); + if (prefs.gui_tweaks & 32) + g_object_set (G_OBJECT (renderer), "ypad", 0, NULL); + gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer), 1); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), + -1, NULL, renderer, + "text", COL_NAME, "attributes", COL_ATTR, NULL); + + g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (view))), + "changed", G_CALLBACK (cv_tree_sel_cb), cv); + g_signal_connect (G_OBJECT (view), "button-press-event", + G_CALLBACK (cv_tree_click_cb), cv); + g_signal_connect (G_OBJECT (view), "row-activated", + G_CALLBACK (cv_tree_activated_cb), NULL); + + gtk_drag_dest_set (view, GTK_DEST_DEFAULT_ALL, dnd_dest_target, 1, + GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK); + gtk_drag_source_set (view, GDK_BUTTON1_MASK, dnd_src_target, 1, GDK_ACTION_COPY); + +#ifndef WIN32 + g_signal_connect (G_OBJECT (view), "drag_begin", + G_CALLBACK (mg_drag_begin_cb), NULL); + g_signal_connect (G_OBJECT (view), "drag_drop", + G_CALLBACK (mg_drag_drop_cb), NULL); + g_signal_connect (G_OBJECT (view), "drag_motion", + G_CALLBACK (mg_drag_motion_cb), NULL); + g_signal_connect (G_OBJECT (view), "drag_end", + G_CALLBACK (mg_drag_end_cb), NULL); +#endif + + ((treeview *)cv)->tree = GTK_TREE_VIEW (view); + ((treeview *)cv)->scrollw = win; + gtk_widget_show (view); +} + +static void +cv_tree_postinit (chanview *cv) +{ + gtk_tree_view_expand_all (((treeview *)cv)->tree); +} + +static void * +cv_tree_add (chanview *cv, chan *ch, char *name, GtkTreeIter *parent) +{ + GtkTreePath *path; + + if (parent) + { + /* expand the parent node */ + path = gtk_tree_model_get_path (GTK_TREE_MODEL (cv->store), parent); + if (path) + { + gtk_tree_view_expand_row (((treeview *)cv)->tree, path, FALSE); + gtk_tree_path_free (path); + } + } + + return NULL; +} + +static void +cv_tree_change_orientation (chanview *cv) +{ +} + +static void +cv_tree_focus (chan *ch) +{ + GtkTreeView *tree = ((treeview *)ch->cv)->tree; + GtkTreeModel *model = gtk_tree_view_get_model (tree); + GtkTreePath *path; + GtkTreeIter parent; + GdkRectangle cell_rect; + GdkRectangle vis_rect; + gint dest_y; + + /* expand the parent node */ + if (gtk_tree_model_iter_parent (model, &parent, &ch->iter)) + { + path = gtk_tree_model_get_path (model, &parent); + if (path) + { + /*if (!gtk_tree_view_row_expanded (tree, path)) + { + gtk_tree_path_free (path); + return; + }*/ + gtk_tree_view_expand_row (tree, path, FALSE); + gtk_tree_path_free (path); + } + } + + path = gtk_tree_model_get_path (model, &ch->iter); + if (path) + { + /* This full section does what + * gtk_tree_view_scroll_to_cell (tree, path, NULL, TRUE, 0.5, 0.5); + * does, except it only scrolls the window if the provided cell is + * not visible. Basic algorithm taken from gtktreeview.c */ + + /* obtain information to see if the cell is visible */ + gtk_tree_view_get_background_area (tree, path, NULL, &cell_rect); + gtk_tree_view_get_visible_rect (tree, &vis_rect); + + /* The cordinates aren't offset correctly */ + gtk_tree_view_widget_to_tree_coords( tree, cell_rect.x, cell_rect.y, NULL, &cell_rect.y ); + + /* only need to scroll if out of bounds */ + if (cell_rect.y < vis_rect.y || + cell_rect.y + cell_rect.height > vis_rect.y + vis_rect.height) + { + dest_y = cell_rect.y - ((vis_rect.height - cell_rect.height) * 0.5); + if (dest_y < 0) + dest_y = 0; + gtk_tree_view_scroll_to_point (tree, -1, dest_y); + } + /* theft done, now make it focused like */ + gtk_tree_view_set_cursor (tree, path, NULL, FALSE); + gtk_tree_path_free (path); + } +} + +static void +cv_tree_move_focus (chanview *cv, gboolean relative, int num) +{ + chan *ch; + + if (relative) + { + num += cv_find_number_of_chan (cv, cv->focused); + num %= cv->size; + /* make it wrap around at both ends */ + if (num < 0) + num = cv->size - 1; + } + + ch = cv_find_chan_by_number (cv, num); + if (ch) + cv_tree_focus (ch); +} + +static void +cv_tree_remove (chan *ch) +{ +} + +static void +move_row (chan *ch, int delta, GtkTreeIter *parent) +{ + GtkTreeStore *store = ch->cv->store; + GtkTreeIter *src = &ch->iter; + GtkTreeIter dest = ch->iter; + GtkTreePath *dest_path; + + if (delta < 0) /* down */ + { + if (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &dest)) + gtk_tree_store_swap (store, src, &dest); + else /* move to top */ + gtk_tree_store_move_after (store, src, NULL); + + } else + { + dest_path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &dest); + if (gtk_tree_path_prev (dest_path)) + { + gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &dest, dest_path); + gtk_tree_store_swap (store, src, &dest); + } else + { /* move to bottom */ + gtk_tree_store_move_before (store, src, NULL); + } + + gtk_tree_path_free (dest_path); + } +} + +static void +cv_tree_move (chan *ch, int delta) +{ + GtkTreeIter parent; + + /* do nothing if this is a server row */ + if (gtk_tree_model_iter_parent (GTK_TREE_MODEL (ch->cv->store), &parent, &ch->iter)) + move_row (ch, delta, &parent); +} + +static void +cv_tree_move_family (chan *ch, int delta) +{ + move_row (ch, delta, NULL); +} + +static void +cv_tree_cleanup (chanview *cv) +{ + if (cv->box) + /* kill the scrolled window */ + gtk_widget_destroy (((treeview *)cv)->scrollw); +} + +static void +cv_tree_set_color (chan *ch, PangoAttrList *list) +{ + /* nothing to do, it's already set in the store */ +} + +static void +cv_tree_rename (chan *ch, char *name) +{ + /* nothing to do, it's already renamed in the store */ +} + +static chan * +cv_tree_get_parent (chan *ch) +{ + chan *parent_ch = NULL; + GtkTreeIter parent; + + if (gtk_tree_model_iter_parent (GTK_TREE_MODEL (ch->cv->store), &parent, &ch->iter)) + { + gtk_tree_model_get (GTK_TREE_MODEL (ch->cv->store), &parent, COL_CHAN, &parent_ch, -1); + } + + return parent_ch; +} + +static gboolean +cv_tree_is_collapsed (chan *ch) +{ + chan *parent = cv_tree_get_parent (ch); + GtkTreePath *path = NULL; + gboolean ret; + + if (parent == NULL) + return FALSE; + + path = gtk_tree_model_get_path (GTK_TREE_MODEL (parent->cv->store), + &parent->iter); + ret = !gtk_tree_view_row_expanded (((treeview *)parent->cv)->tree, path); + gtk_tree_path_free (path); + + return ret; +} diff --git a/src/fe-gtk/chanview.c b/src/fe-gtk/chanview.c new file mode 100644 index 00000000..e90c4df8 --- /dev/null +++ b/src/fe-gtk/chanview.c @@ -0,0 +1,643 @@ +/* abstract channel view: tabs or tree or anything you like */ + +#include <stdlib.h> +#include <string.h> + +#include <gtk/gtk.h> + +#include "chanview.h" +#include "gtkutil.h" + + +/* treeStore columns */ + +#define COL_NAME 0 /* (char *) */ +#define COL_CHAN 1 /* (chan *) */ +#define COL_ATTR 2 /* (PangoAttrList *) */ +#define COL_PIXBUF 3 /* (GdkPixbuf *) */ + +struct _chanview +{ + /* impl scratch area */ + char implscratch[sizeof (void *) * 8]; + + GtkTreeStore *store; + int size; /* number of channels in view */ + + GtkWidget *box; /* the box we destroy when changing implementations */ + GtkStyle *style; /* style used for tree */ + chan *focused; /* currently focused channel */ + int trunc_len; + + /* callbacks */ + void (*cb_focus) (chanview *, chan *, int tag, void *userdata); + void (*cb_xbutton) (chanview *, chan *, int tag, void *userdata); + gboolean (*cb_contextmenu) (chanview *, chan *, int tag, void *userdata, GdkEventButton *); + int (*cb_compare) (void *a, void *b); + + /* impl */ + void (*func_init) (chanview *); + void (*func_postinit) (chanview *); + void *(*func_add) (chanview *, chan *, char *, GtkTreeIter *); + void (*func_move_focus) (chanview *, gboolean, int); + void (*func_change_orientation) (chanview *); + void (*func_remove) (chan *); + void (*func_move) (chan *, int delta); + void (*func_move_family) (chan *, int delta); + void (*func_focus) (chan *); + void (*func_set_color) (chan *, PangoAttrList *); + void (*func_rename) (chan *, char *); + gboolean (*func_is_collapsed) (chan *); + chan *(*func_get_parent) (chan *); + void (*func_cleanup) (chanview *); + + unsigned int sorted:1; + unsigned int vertical:1; + unsigned int use_icons:1; +}; + +struct _chan +{ + chanview *cv; /* our owner */ + GtkTreeIter iter; + void *userdata; /* session * */ + void *family; /* server * or null */ + void *impl; /* togglebutton or null */ + GdkPixbuf *icon; + short allow_closure; /* allow it to be closed when it still has children? */ + short tag; +}; + +static chan *cv_find_chan_by_number (chanview *cv, int num); +static int cv_find_number_of_chan (chanview *cv, chan *find_ch); + + +/* ======= TABS ======= */ + +#include "chanview-tabs.c" + + +/* ======= TREE ======= */ + +#include "chanview-tree.c" + + +/* ==== ABSTRACT CHANVIEW ==== */ + +static char * +truncate_tab_name (char *name, int max) +{ + char *buf; + + if (max > 2 && g_utf8_strlen (name, -1) > max) + { + /* truncate long channel names */ + buf = malloc (strlen (name) + 4); + strcpy (buf, name); + g_utf8_offset_to_pointer (buf, max)[0] = 0; + strcat (buf, ".."); + return buf; + } + + return name; +} + +/* iterate through a model, into 1 depth of children */ + +static void +model_foreach_1 (GtkTreeModel *model, void (*func)(void *, GtkTreeIter *), + void *userdata) +{ + GtkTreeIter iter, inner; + + if (gtk_tree_model_get_iter_first (model, &iter)) + { + do + { + func (userdata, &iter); + if (gtk_tree_model_iter_children (model, &inner, &iter)) + { + do + func (userdata, &inner); + while (gtk_tree_model_iter_next (model, &inner)); + } + } + while (gtk_tree_model_iter_next (model, &iter)); + } +} + +static void +chanview_pop_cb (chanview *cv, GtkTreeIter *iter) +{ + chan *ch; + char *name; + PangoAttrList *attr; + + gtk_tree_model_get (GTK_TREE_MODEL (cv->store), iter, + COL_NAME, &name, COL_CHAN, &ch, COL_ATTR, &attr, -1); + ch->impl = cv->func_add (cv, ch, name, NULL); + if (attr) + { + cv->func_set_color (ch, attr); + pango_attr_list_unref (attr); + } + g_free (name); +} + +static void +chanview_populate (chanview *cv) +{ + model_foreach_1 (GTK_TREE_MODEL (cv->store), (void *)chanview_pop_cb, cv); +} + +void +chanview_set_impl (chanview *cv, int type) +{ + /* cleanup the old one */ + if (cv->func_cleanup) + cv->func_cleanup (cv); + + switch (type) + { + case 0: + cv->func_init = cv_tabs_init; + cv->func_postinit = cv_tabs_postinit; + cv->func_add = cv_tabs_add; + cv->func_move_focus = cv_tabs_move_focus; + cv->func_change_orientation = cv_tabs_change_orientation; + cv->func_remove = cv_tabs_remove; + cv->func_move = cv_tabs_move; + cv->func_move_family = cv_tabs_move_family; + cv->func_focus = cv_tabs_focus; + cv->func_set_color = cv_tabs_set_color; + cv->func_rename = cv_tabs_rename; + cv->func_is_collapsed = cv_tabs_is_collapsed; + cv->func_get_parent = cv_tabs_get_parent; + cv->func_cleanup = cv_tabs_cleanup; + break; + + default: + cv->func_init = cv_tree_init; + cv->func_postinit = cv_tree_postinit; + cv->func_add = cv_tree_add; + cv->func_move_focus = cv_tree_move_focus; + cv->func_change_orientation = cv_tree_change_orientation; + cv->func_remove = cv_tree_remove; + cv->func_move = cv_tree_move; + cv->func_move_family = cv_tree_move_family; + cv->func_focus = cv_tree_focus; + cv->func_set_color = cv_tree_set_color; + cv->func_rename = cv_tree_rename; + cv->func_is_collapsed = cv_tree_is_collapsed; + cv->func_get_parent = cv_tree_get_parent; + cv->func_cleanup = cv_tree_cleanup; + break; + } + + /* now rebuild a new tabbar or tree */ + cv->func_init (cv); + + chanview_populate (cv); + + cv->func_postinit (cv); + + /* force re-focus */ + if (cv->focused) + cv->func_focus (cv->focused); +} + +static void +chanview_free_ch (chanview *cv, GtkTreeIter *iter) +{ + chan *ch; + + gtk_tree_model_get (GTK_TREE_MODEL (cv->store), iter, COL_CHAN, &ch, -1); + free (ch); +} + +static void +chanview_destroy_store (chanview *cv) /* free every (chan *) in the store */ +{ + model_foreach_1 (GTK_TREE_MODEL (cv->store), (void *)chanview_free_ch, cv); + g_object_unref (cv->store); +} + +static void +chanview_destroy (chanview *cv) +{ + if (cv->func_cleanup) + cv->func_cleanup (cv); + + if (cv->box) + gtk_widget_destroy (cv->box); + + chanview_destroy_store (cv); + free (cv); +} + +static void +chanview_box_destroy_cb (GtkWidget *box, chanview *cv) +{ + cv->box = NULL; + chanview_destroy (cv); +} + +chanview * +chanview_new (int type, int trunc_len, gboolean sort, gboolean use_icons, + GtkStyle *style) +{ + chanview *cv; + + cv = calloc (1, sizeof (chanview)); + cv->store = gtk_tree_store_new (4, G_TYPE_STRING, G_TYPE_POINTER, + PANGO_TYPE_ATTR_LIST, GDK_TYPE_PIXBUF); + cv->style = style; + cv->box = gtk_hbox_new (0, 0); + cv->trunc_len = trunc_len; + cv->sorted = sort; + cv->use_icons = use_icons; + gtk_widget_show (cv->box); + chanview_set_impl (cv, type); + + g_signal_connect (G_OBJECT (cv->box), "destroy", + G_CALLBACK (chanview_box_destroy_cb), cv); + + return cv; +} + +/* too lazy for signals */ + +void +chanview_set_callbacks (chanview *cv, + void (*cb_focus) (chanview *, chan *, int tag, void *userdata), + void (*cb_xbutton) (chanview *, chan *, int tag, void *userdata), + gboolean (*cb_contextmenu) (chanview *, chan *, int tag, void *userdata, GdkEventButton *), + int (*cb_compare) (void *a, void *b)) +{ + cv->cb_focus = cb_focus; + cv->cb_xbutton = cb_xbutton; + cv->cb_contextmenu = cb_contextmenu; + cv->cb_compare = cb_compare; +} + +/* find a place to insert this new entry, based on the compare function */ + +static void +chanview_insert_sorted (chanview *cv, GtkTreeIter *add_iter, GtkTreeIter *parent, void *ud) +{ + GtkTreeIter iter; + chan *ch; + + if (cv->sorted && gtk_tree_model_iter_children (GTK_TREE_MODEL (cv->store), &iter, parent)) + { + do + { + gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &iter, COL_CHAN, &ch, -1); + if (ch->tag == 0 && cv->cb_compare (ch->userdata, ud) > 0) + { + gtk_tree_store_insert_before (cv->store, add_iter, parent, &iter); + return; + } + } + while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &iter)); + } + + gtk_tree_store_append (cv->store, add_iter, parent); +} + +/* find a parent node with the same "family" pointer (i.e. the Server tab) */ + +static int +chanview_find_parent (chanview *cv, void *family, GtkTreeIter *search_iter, chan *avoid) +{ + chan *search_ch; + + /* find this new row's parent, if any */ + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (cv->store), search_iter)) + { + do + { + gtk_tree_model_get (GTK_TREE_MODEL (cv->store), search_iter, + COL_CHAN, &search_ch, -1); + if (family == search_ch->family && search_ch != avoid /*&& + gtk_tree_store_iter_depth (cv->store, search_iter) == 0*/) + return TRUE; + } + while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), search_iter)); + } + + return FALSE; +} + +static chan * +chanview_add_real (chanview *cv, char *name, void *family, void *userdata, + gboolean allow_closure, int tag, GdkPixbuf *icon, + chan *ch, chan *avoid) +{ + GtkTreeIter parent_iter; + GtkTreeIter iter; + gboolean has_parent = FALSE; + + if (chanview_find_parent (cv, family, &parent_iter, avoid)) + { + chanview_insert_sorted (cv, &iter, &parent_iter, userdata); + has_parent = TRUE; + } else + { + gtk_tree_store_append (cv->store, &iter, NULL); + } + + if (!ch) + { + ch = calloc (1, sizeof (chan)); + ch->userdata = userdata; + ch->family = family; + ch->cv = cv; + ch->allow_closure = allow_closure; + ch->tag = tag; + ch->icon = icon; + } + memcpy (&(ch->iter), &iter, sizeof (iter)); + + gtk_tree_store_set (cv->store, &iter, COL_NAME, name, COL_CHAN, ch, + COL_PIXBUF, icon, -1); + + cv->size++; + if (!has_parent) + ch->impl = cv->func_add (cv, ch, name, NULL); + else + ch->impl = cv->func_add (cv, ch, name, &parent_iter); + + return ch; +} + +chan * +chanview_add (chanview *cv, char *name, void *family, void *userdata, gboolean allow_closure, int tag, GdkPixbuf *icon) +{ + char *new_name; + chan *ret; + + new_name = truncate_tab_name (name, cv->trunc_len); + + ret = chanview_add_real (cv, new_name, family, userdata, allow_closure, tag, icon, NULL, NULL); + + if (new_name != name) + free (new_name); + + return ret; +} + +int +chanview_get_size (chanview *cv) +{ + return cv->size; +} + +GtkWidget * +chanview_get_box (chanview *cv) +{ + return cv->box; +} + +void +chanview_move_focus (chanview *cv, gboolean relative, int num) +{ + cv->func_move_focus (cv, relative, num); +} + +GtkOrientation +chanview_get_orientation (chanview *cv) +{ + return (cv->vertical ? GTK_ORIENTATION_VERTICAL : GTK_ORIENTATION_HORIZONTAL); +} + +void +chanview_set_orientation (chanview *cv, gboolean vertical) +{ + if (vertical != cv->vertical) + { + cv->vertical = vertical; + cv->func_change_orientation (cv); + } +} + +int +chan_get_tag (chan *ch) +{ + return ch->tag; +} + +void * +chan_get_userdata (chan *ch) +{ + return ch->userdata; +} + +void +chan_focus (chan *ch) +{ + if (ch->cv->focused == ch) + return; + + ch->cv->func_focus (ch); +} + +void +chan_move (chan *ch, int delta) +{ + ch->cv->func_move (ch, delta); +} + +void +chan_move_family (chan *ch, int delta) +{ + ch->cv->func_move_family (ch, delta); +} + +void +chan_set_color (chan *ch, PangoAttrList *list) +{ + gtk_tree_store_set (ch->cv->store, &ch->iter, COL_ATTR, list, -1); + ch->cv->func_set_color (ch, list); +} + +void +chan_rename (chan *ch, char *name, int trunc_len) +{ + char *new_name; + + new_name = truncate_tab_name (name, trunc_len); + + gtk_tree_store_set (ch->cv->store, &ch->iter, COL_NAME, new_name, -1); + ch->cv->func_rename (ch, new_name); + ch->cv->trunc_len = trunc_len; + + if (new_name != name) + free (new_name); +} + +/* this thing is overly complicated */ + +static int +cv_find_number_of_chan (chanview *cv, chan *find_ch) +{ + GtkTreeIter iter, inner; + chan *ch; + int i = 0; + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (cv->store), &iter)) + { + do + { + gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &iter, COL_CHAN, &ch, -1); + if (ch == find_ch) + return i; + i++; + + if (gtk_tree_model_iter_children (GTK_TREE_MODEL (cv->store), &inner, &iter)) + { + do + { + gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &inner, COL_CHAN, &ch, -1); + if (ch == find_ch) + return i; + i++; + } + while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &inner)); + } + } + while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &iter)); + } + + return 0; /* WARNING */ +} + +/* this thing is overly complicated too */ + +static chan * +cv_find_chan_by_number (chanview *cv, int num) +{ + GtkTreeIter iter, inner; + chan *ch; + int i = 0; + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (cv->store), &iter)) + { + do + { + if (i == num) + { + gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &iter, COL_CHAN, &ch, -1); + return ch; + } + i++; + + if (gtk_tree_model_iter_children (GTK_TREE_MODEL (cv->store), &inner, &iter)) + { + do + { + if (i == num) + { + gtk_tree_model_get (GTK_TREE_MODEL (cv->store), &inner, COL_CHAN, &ch, -1); + return ch; + } + i++; + } + while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &inner)); + } + } + while (gtk_tree_model_iter_next (GTK_TREE_MODEL (cv->store), &iter)); + } + + return NULL; +} + +static void +chan_emancipate_children (chan *ch) +{ + char *name; + chan *childch; + GtkTreeIter childiter; + PangoAttrList *attr; + + while (gtk_tree_model_iter_children (GTK_TREE_MODEL (ch->cv->store), &childiter, &ch->iter)) + { + /* remove and re-add all the children, but avoid using "ch" as parent */ + gtk_tree_model_get (GTK_TREE_MODEL (ch->cv->store), &childiter, + COL_NAME, &name, COL_CHAN, &childch, COL_ATTR, &attr, -1); + ch->cv->func_remove (childch); + gtk_tree_store_remove (ch->cv->store, &childiter); + ch->cv->size--; + chanview_add_real (childch->cv, name, childch->family, childch->userdata, childch->allow_closure, childch->tag, childch->icon, childch, ch); + if (attr) + { + childch->cv->func_set_color (childch, attr); + pango_attr_list_unref (attr); + } + g_free (name); + } +} + +gboolean +chan_remove (chan *ch, gboolean force) +{ + chan *new_ch; + int i, num; + extern int xchat_is_quitting; + + if (xchat_is_quitting) /* avoid lots of looping on exit */ + return TRUE; + + /* is this ch allowed to be closed while still having children? */ + if (!force && + gtk_tree_model_iter_has_child (GTK_TREE_MODEL (ch->cv->store), &ch->iter) && + !ch->allow_closure) + return FALSE; + + chan_emancipate_children (ch); + ch->cv->func_remove (ch); + + /* is it the focused one? */ + if (ch->cv->focused == ch) + { + ch->cv->focused = NULL; + + /* try to move the focus to some other valid channel */ + num = cv_find_number_of_chan (ch->cv, ch); + /* move to the one left of the closing tab */ + new_ch = cv_find_chan_by_number (ch->cv, num - 1); + if (new_ch && new_ch != ch) + { + chan_focus (new_ch); /* this'll will set ch->cv->focused for us too */ + } else + { + /* if it fails, try focus from tab 0 and up */ + for (i = 0; i < ch->cv->size; i++) + { + new_ch = cv_find_chan_by_number (ch->cv, i); + if (new_ch && new_ch != ch) + { + chan_focus (new_ch); /* this'll will set ch->cv->focused for us too */ + break; + } + } + } + } + + ch->cv->size--; + gtk_tree_store_remove (ch->cv->store, &ch->iter); + free (ch); + return TRUE; +} + +gboolean +chan_is_collapsed (chan *ch) +{ + return ch->cv->func_is_collapsed (ch); +} + +chan * +chan_get_parent (chan *ch) +{ + return ch->cv->func_get_parent (ch); +} diff --git a/src/fe-gtk/chanview.h b/src/fe-gtk/chanview.h new file mode 100644 index 00000000..75b5ef1f --- /dev/null +++ b/src/fe-gtk/chanview.h @@ -0,0 +1,31 @@ +typedef struct _chanview chanview; +typedef struct _chan chan; + +chanview *chanview_new (int type, int trunc_len, gboolean sort, gboolean use_icons, GtkStyle *style); +void chanview_set_callbacks (chanview *cv, + void (*cb_focus) (chanview *, chan *, int tag, void *userdata), + void (*cb_xbutton) (chanview *, chan *, int tag, void *userdata), + gboolean (*cb_contextmenu) (chanview *, chan *, int tag, void *userdata, GdkEventButton *), + int (*cb_compare) (void *a, void *b)); +void chanview_set_impl (chanview *cv, int type); +chan *chanview_add (chanview *cv, char *name, void *family, void *userdata, gboolean allow_closure, int tag, GdkPixbuf *icon); +int chanview_get_size (chanview *cv); +GtkWidget *chanview_get_box (chanview *cv); +void chanview_move_focus (chanview *cv, gboolean relative, int num); +GtkOrientation chanview_get_orientation (chanview *cv); +void chanview_set_orientation (chanview *cv, gboolean vertical); + +int chan_get_tag (chan *ch); +void *chan_get_userdata (chan *ch); +void chan_focus (chan *ch); +void chan_move (chan *ch, int delta); +void chan_move_family (chan *ch, int delta); +void chan_set_color (chan *ch, PangoAttrList *list); +void chan_rename (chan *ch, char *new_name, int trunc_len); +gboolean chan_remove (chan *ch, gboolean force); +gboolean chan_is_collapsed (chan *ch); +chan * chan_get_parent (chan *ch); + +#define FOCUS_NEW_ALL 1 +#define FOCUS_NEW_ONLY_ASKED 2 +#define FOCUS_NEW_NONE 0 diff --git a/src/fe-gtk/custom-list.c b/src/fe-gtk/custom-list.c new file mode 100644 index 00000000..ac20e0ff --- /dev/null +++ b/src/fe-gtk/custom-list.c @@ -0,0 +1,753 @@ +#include <string.h> +#include <stdlib.h> +#include "custom-list.h" + +/* indent -i3 -ci3 -ut -ts3 -bli0 -c0 custom-list.c */ + +/* boring declarations of local functions */ + +static void custom_list_init (CustomList * pkg_tree); + +static void custom_list_class_init (CustomListClass * klass); + +static void custom_list_tree_model_init (GtkTreeModelIface * iface); + +static void custom_list_finalize (GObject * object); + +static GtkTreeModelFlags custom_list_get_flags (GtkTreeModel * tree_model); + +static gint custom_list_get_n_columns (GtkTreeModel * tree_model); + +static GType custom_list_get_column_type (GtkTreeModel * tree_model, + gint index); + +static gboolean custom_list_get_iter (GtkTreeModel * tree_model, + GtkTreeIter * iter, GtkTreePath * path); + +static GtkTreePath *custom_list_get_path (GtkTreeModel * tree_model, + GtkTreeIter * iter); + +static void custom_list_get_value (GtkTreeModel * tree_model, + GtkTreeIter * iter, + gint column, GValue * value); + +static gboolean custom_list_iter_next (GtkTreeModel * tree_model, + GtkTreeIter * iter); + +static gboolean custom_list_iter_children (GtkTreeModel * tree_model, + GtkTreeIter * iter, + GtkTreeIter * parent); + +static gboolean custom_list_iter_has_child (GtkTreeModel * tree_model, + GtkTreeIter * iter); + +static gint custom_list_iter_n_children (GtkTreeModel * tree_model, + GtkTreeIter * iter); + +static gboolean custom_list_iter_nth_child (GtkTreeModel * tree_model, + GtkTreeIter * iter, + GtkTreeIter * parent, gint n); + +static gboolean custom_list_iter_parent (GtkTreeModel * tree_model, + GtkTreeIter * iter, + GtkTreeIter * child); + + /* -- GtkTreeSortable interface functions -- */ + +static gboolean custom_list_sortable_get_sort_column_id (GtkTreeSortable * + sortable, + gint * sort_col_id, + GtkSortType * order); + +static void custom_list_sortable_set_sort_column_id (GtkTreeSortable * + sortable, + gint sort_col_id, + GtkSortType order); + +static void custom_list_sortable_set_sort_func (GtkTreeSortable * sortable, + gint sort_col_id, + GtkTreeIterCompareFunc + sort_func, gpointer user_data, + GtkDestroyNotify + destroy_func); + +static void custom_list_sortable_set_default_sort_func (GtkTreeSortable * + sortable, + GtkTreeIterCompareFunc + sort_func, + gpointer user_data, + GtkDestroyNotify + destroy_func); + +static gboolean custom_list_sortable_has_default_sort_func (GtkTreeSortable * + sortable); + + + +static GObjectClass *parent_class = NULL; /* GObject stuff - nothing to worry about */ + + +static void +custom_list_sortable_init (GtkTreeSortableIface * iface) +{ + iface->get_sort_column_id = custom_list_sortable_get_sort_column_id; + iface->set_sort_column_id = custom_list_sortable_set_sort_column_id; + iface->set_sort_func = custom_list_sortable_set_sort_func; /* NOT SUPPORTED */ + iface->set_default_sort_func = custom_list_sortable_set_default_sort_func; /* NOT SUPPORTED */ + iface->has_default_sort_func = custom_list_sortable_has_default_sort_func; /* NOT SUPPORTED */ +} + +/***************************************************************************** + * + * custom_list_get_type: here we register our new type and its interfaces + * with the type system. If you want to implement + * additional interfaces like GtkTreeSortable, you + * will need to do it here. + * + *****************************************************************************/ + +static GType +custom_list_get_type (void) +{ + static GType custom_list_type = 0; + + if (custom_list_type) + return custom_list_type; + + /* Some boilerplate type registration stuff */ + if (1) + { + static const GTypeInfo custom_list_info = { + sizeof (CustomListClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) custom_list_class_init, + NULL, /* class finalize */ + NULL, /* class_data */ + sizeof (CustomList), + 0, /* n_preallocs */ + (GInstanceInitFunc) custom_list_init + }; + + custom_list_type = + g_type_register_static (G_TYPE_OBJECT, "CustomList", + &custom_list_info, (GTypeFlags) 0); + } + + /* Here we register our GtkTreeModel interface with the type system */ + if (1) + { + static const GInterfaceInfo tree_model_info = { + (GInterfaceInitFunc) custom_list_tree_model_init, + NULL, + NULL + }; + + g_type_add_interface_static (custom_list_type, GTK_TYPE_TREE_MODEL, + &tree_model_info); + } + + /* Add GtkTreeSortable interface */ + if (1) + { + static const GInterfaceInfo tree_sortable_info = { + (GInterfaceInitFunc) custom_list_sortable_init, + NULL, + NULL + }; + + g_type_add_interface_static (custom_list_type, + GTK_TYPE_TREE_SORTABLE, + &tree_sortable_info); + } + + return custom_list_type; +} + +/***************************************************************************** + * + * custom_list_class_init: more boilerplate GObject/GType stuff. + * Init callback for the type system, + * called once when our new class is created. + * + *****************************************************************************/ + +static void +custom_list_class_init (CustomListClass * klass) +{ + GObjectClass *object_class; + + parent_class = (GObjectClass *) g_type_class_peek_parent (klass); + object_class = (GObjectClass *) klass; + + object_class->finalize = custom_list_finalize; +} + +/***************************************************************************** + * + * custom_list_tree_model_init: init callback for the interface registration + * in custom_list_get_type. Here we override + * the GtkTreeModel interface functions that + * we implement. + * + *****************************************************************************/ + +static void +custom_list_tree_model_init (GtkTreeModelIface * iface) +{ + iface->get_flags = custom_list_get_flags; + iface->get_n_columns = custom_list_get_n_columns; + iface->get_column_type = custom_list_get_column_type; + iface->get_iter = custom_list_get_iter; + iface->get_path = custom_list_get_path; + iface->get_value = custom_list_get_value; + iface->iter_next = custom_list_iter_next; + iface->iter_children = custom_list_iter_children; + iface->iter_has_child = custom_list_iter_has_child; + iface->iter_n_children = custom_list_iter_n_children; + iface->iter_nth_child = custom_list_iter_nth_child; + iface->iter_parent = custom_list_iter_parent; +} + + +/***************************************************************************** + * + * custom_list_init: this is called everytime a new custom list object + * instance is created (we do that in custom_list_new). + * Initialise the list structure's fields here. + * + *****************************************************************************/ + +static void +custom_list_init (CustomList * custom_list) +{ + custom_list->n_columns = CUSTOM_LIST_N_COLUMNS; + + custom_list->column_types[0] = G_TYPE_STRING; /* CUSTOM_LIST_COL_NAME */ + custom_list->column_types[1] = G_TYPE_UINT; /* CUSTOM_LIST_COL_USERS */ + custom_list->column_types[2] = G_TYPE_STRING; /* CUSTOM_LIST_COL_TOPIC */ + + custom_list->num_rows = 0; + custom_list->num_alloc = 0; + custom_list->rows = NULL; + + custom_list->sort_id = SORT_ID_CHANNEL; + custom_list->sort_order = GTK_SORT_ASCENDING; +} + + +/***************************************************************************** + * + * custom_list_finalize: this is called just before a custom list is + * destroyed. Free dynamically allocated memory here. + * + *****************************************************************************/ + +static void +custom_list_finalize (GObject * object) +{ + custom_list_clear (CUSTOM_LIST (object)); + + /* must chain up - finalize parent */ + (*parent_class->finalize) (object); +} + + +/***************************************************************************** + * + * custom_list_get_flags: tells the rest of the world whether our tree model + * has any special characteristics. In our case, + * we have a list model (instead of a tree), and each + * tree iter is valid as long as the row in question + * exists, as it only contains a pointer to our struct. + * + *****************************************************************************/ + +static GtkTreeModelFlags +custom_list_get_flags (GtkTreeModel * tree_model) +{ + return (GTK_TREE_MODEL_LIST_ONLY /*| GTK_TREE_MODEL_ITERS_PERSIST */ ); +} + + +/***************************************************************************** + * + * custom_list_get_n_columns: tells the rest of the world how many data + * columns we export via the tree model interface + * + *****************************************************************************/ + +static gint +custom_list_get_n_columns (GtkTreeModel * tree_model) +{ + return 3;/*CUSTOM_LIST (tree_model)->n_columns;*/ +} + + +/***************************************************************************** + * + * custom_list_get_column_type: tells the rest of the world which type of + * data an exported model column contains + * + *****************************************************************************/ + +static GType +custom_list_get_column_type (GtkTreeModel * tree_model, gint index) +{ + return CUSTOM_LIST (tree_model)->column_types[index]; +} + + +/***************************************************************************** + * + * custom_list_get_iter: converts a tree path (physical position) into a + * tree iter structure (the content of the iter + * fields will only be used internally by our model). + * We simply store a pointer to our chanlistrow + * structure that represents that row in the tree iter. + * + *****************************************************************************/ + +static gboolean +custom_list_get_iter (GtkTreeModel * tree_model, + GtkTreeIter * iter, GtkTreePath * path) +{ + CustomList *custom_list = CUSTOM_LIST (tree_model); + chanlistrow *record; + gint n; + + n = gtk_tree_path_get_indices (path)[0]; + if (n >= custom_list->num_rows || n < 0) + return FALSE; + + record = custom_list->rows[n]; + + /* We simply store a pointer to our custom record in the iter */ + iter->user_data = record; + + return TRUE; +} + + +/***************************************************************************** + * + * custom_list_get_path: converts a tree iter into a tree path (ie. the + * physical position of that row in the list). + * + *****************************************************************************/ + +static GtkTreePath * +custom_list_get_path (GtkTreeModel * tree_model, GtkTreeIter * iter) +{ + GtkTreePath *path; + chanlistrow *record; + + record = (chanlistrow *) iter->user_data; + + path = gtk_tree_path_new (); + gtk_tree_path_append_index (path, record->pos); + + return path; +} + + +/***************************************************************************** + * + * custom_list_get_value: Returns a row's exported data columns + * (_get_value is what gtk_tree_model_get uses) + * + *****************************************************************************/ + +static void +custom_list_get_value (GtkTreeModel * tree_model, + GtkTreeIter * iter, gint column, GValue * value) +{ + chanlistrow *record; + CustomList *custom_list = CUSTOM_LIST (tree_model); + + if (custom_list->num_rows == 0) + return; + + g_value_init (value, custom_list->column_types[column]); + + record = (chanlistrow *) iter->user_data; + + switch (column) + { + case CUSTOM_LIST_COL_NAME: + g_value_set_static_string (value, GET_CHAN (record)); + break; + + case CUSTOM_LIST_COL_USERS: + g_value_set_uint (value, record->users); + break; + + case CUSTOM_LIST_COL_TOPIC: + g_value_set_static_string (value, record->topic); + break; + } +} + + +/***************************************************************************** + * + * custom_list_iter_next: Takes an iter structure and sets it to point + * to the next row. + * + *****************************************************************************/ + +static gboolean +custom_list_iter_next (GtkTreeModel * tree_model, GtkTreeIter * iter) +{ + chanlistrow *record, *nextrecord; + CustomList *custom_list = CUSTOM_LIST (tree_model); + + record = (chanlistrow *) iter->user_data; + + /* Is this the last record in the list? */ + if ((record->pos + 1) >= custom_list->num_rows) + return FALSE; + + nextrecord = custom_list->rows[(record->pos + 1)]; + + g_assert (nextrecord != NULL); + g_assert (nextrecord->pos == (record->pos + 1)); + + iter->user_data = nextrecord; + + return TRUE; +} + + +/***************************************************************************** + * + * custom_list_iter_children: Returns TRUE or FALSE depending on whether + * the row specified by 'parent' has any children. + * If it has children, then 'iter' is set to + * point to the first child. Special case: if + * 'parent' is NULL, then the first top-level + * row should be returned if it exists. + * + *****************************************************************************/ + +static gboolean +custom_list_iter_children (GtkTreeModel * tree_model, + GtkTreeIter * iter, GtkTreeIter * parent) +{ + CustomList *custom_list = CUSTOM_LIST (tree_model); + + /* this is a list, nodes have no children */ + if (parent) + return FALSE; + + /* parent == NULL is a special case; we need to return the first top-level row */ + /* No rows => no first row */ + if (custom_list->num_rows == 0) + return FALSE; + + /* Set iter to first item in list */ + iter->user_data = custom_list->rows[0]; + + return TRUE; +} + + +/***************************************************************************** + * + * custom_list_iter_has_child: Returns TRUE or FALSE depending on whether + * the row specified by 'iter' has any children. + * We only have a list and thus no children. + * + *****************************************************************************/ + +static gboolean +custom_list_iter_has_child (GtkTreeModel * tree_model, GtkTreeIter * iter) +{ + return FALSE; +} + + +/***************************************************************************** + * + * custom_list_iter_n_children: Returns the number of children the row + * specified by 'iter' has. This is usually 0, + * as we only have a list and thus do not have + * any children to any rows. A special case is + * when 'iter' is NULL, in which case we need + * to return the number of top-level nodes, + * ie. the number of rows in our list. + * + *****************************************************************************/ + +static gint +custom_list_iter_n_children (GtkTreeModel * tree_model, GtkTreeIter * iter) +{ + CustomList *custom_list = CUSTOM_LIST (tree_model); + + /* special case: if iter == NULL, return number of top-level rows */ + if (!iter) + return custom_list->num_rows; + + return 0; /* otherwise, this is easy again for a list */ +} + + +/***************************************************************************** + * + * custom_list_iter_nth_child: If the row specified by 'parent' has any + * children, set 'iter' to the n-th child and + * return TRUE if it exists, otherwise FALSE. + * A special case is when 'parent' is NULL, in + * which case we need to set 'iter' to the n-th + * row if it exists. + * + *****************************************************************************/ + +static gboolean +custom_list_iter_nth_child (GtkTreeModel * tree_model, + GtkTreeIter * iter, GtkTreeIter * parent, gint n) +{ + CustomList *custom_list = CUSTOM_LIST (tree_model); + + /* a list has only top-level rows */ + if (parent) + return FALSE; + + /* special case: if parent == NULL, set iter to n-th top-level row */ + if (n >= custom_list->num_rows) + return FALSE; + + iter->user_data = custom_list->rows[n]; + return TRUE; +} + + +/***************************************************************************** + * + * custom_list_iter_parent: Point 'iter' to the parent node of 'child'. As + * we have a list and thus no children and no + * parents of children, we can just return FALSE. + * + *****************************************************************************/ + +static gboolean +custom_list_iter_parent (GtkTreeModel * tree_model, + GtkTreeIter * iter, GtkTreeIter * child) +{ + return FALSE; +} + +static gboolean +custom_list_sortable_get_sort_column_id (GtkTreeSortable * sortable, + gint * sort_col_id, + GtkSortType * order) +{ + CustomList *custom_list = CUSTOM_LIST (sortable); + + if (sort_col_id) + *sort_col_id = custom_list->sort_id; + + if (order) + *order = custom_list->sort_order; + + return TRUE; +} + + +static void +custom_list_sortable_set_sort_column_id (GtkTreeSortable * sortable, + gint sort_col_id, GtkSortType order) +{ + CustomList *custom_list = CUSTOM_LIST (sortable); + + if (custom_list->sort_id == sort_col_id + && custom_list->sort_order == order) + return; + + custom_list->sort_id = sort_col_id; + custom_list->sort_order = order; + + custom_list_resort (custom_list); + + /* emit "sort-column-changed" signal to tell any tree views + * that the sort column has changed (so the little arrow + * in the column header of the sort column is drawn + * in the right column) */ + + gtk_tree_sortable_sort_column_changed (sortable); +} + +static void +custom_list_sortable_set_sort_func (GtkTreeSortable * sortable, + gint sort_col_id, + GtkTreeIterCompareFunc sort_func, + gpointer user_data, + GtkDestroyNotify destroy_func) +{ +} + +static void +custom_list_sortable_set_default_sort_func (GtkTreeSortable * sortable, + GtkTreeIterCompareFunc sort_func, + gpointer user_data, + GtkDestroyNotify destroy_func) +{ +} + +static gboolean +custom_list_sortable_has_default_sort_func (GtkTreeSortable * sortable) +{ + return FALSE; +} + +/* fast as possible compare func for sorting. + TODO: If fast enough, use a unicode collation key and strcmp. */ + +#define TOSML(c) (((c) >= 'A' && (c) <= 'Z') ? (c) - 'A' + 'a' : (c)) + +static inline int +fast_ascii_stricmp (const char *s1, const char *s2) +{ + int c1, c2; + + while (*s1 && *s2) + { + c1 = (int) (unsigned char) TOSML (*s1); + c2 = (int) (unsigned char) TOSML (*s2); + if (c1 != c2) + return (c1 - c2); + s1++; + s2++; + } + + return (((int) (unsigned char) *s1) - ((int) (unsigned char) *s2)); +} + +static gint +custom_list_qsort_compare_func (chanlistrow ** a, chanlistrow ** b, + CustomList * custom_list) +{ + if (custom_list->sort_order == GTK_SORT_DESCENDING) + { + chanlistrow **tmp = a; + a = b; + b = tmp; + } + + if (custom_list->sort_id == SORT_ID_USERS) + { + return (*a)->users - (*b)->users; + } + + if (custom_list->sort_id == SORT_ID_TOPIC) + { + return fast_ascii_stricmp ((*a)->topic, (*b)->topic); + } + + return strcmp ((*a)->collation_key, (*b)->collation_key); +} + +/***************************************************************************** + * + * custom_list_new: This is what you use in your own code to create a + * new custom list tree model for you to use. + * + *****************************************************************************/ + +CustomList * +custom_list_new (void) +{ + return (CustomList *) g_object_new (CUSTOM_TYPE_LIST, NULL); +} + +void +custom_list_append (CustomList * custom_list, chanlistrow * newrecord) +{ + GtkTreeIter iter; + GtkTreePath *path; + gulong newsize; + guint pos; + + if (custom_list->num_rows >= custom_list->num_alloc) + { + custom_list->num_alloc += 64; + newsize = custom_list->num_alloc * sizeof (chanlistrow *); + custom_list->rows = g_realloc (custom_list->rows, newsize); + } + + /* TODO: Binary search insert? */ + + pos = custom_list->num_rows; + custom_list->rows[pos] = newrecord; + custom_list->num_rows++; + newrecord->pos = pos; + + /* inform the tree view and other interested objects + * (e.g. tree row references) that we have inserted + * a new row, and where it was inserted */ + + path = gtk_tree_path_new (); + gtk_tree_path_append_index (path, newrecord->pos); +/* custom_list_get_iter(GTK_TREE_MODEL(custom_list), &iter, path);*/ + iter.user_data = newrecord; + gtk_tree_model_row_inserted (GTK_TREE_MODEL (custom_list), path, &iter); + gtk_tree_path_free (path); +} + +void +custom_list_resort (CustomList * custom_list) +{ + GtkTreePath *path; + gint *neworder, i; + + if (custom_list->num_rows < 2) + return; + + /* resort */ + g_qsort_with_data (custom_list->rows, + custom_list->num_rows, + sizeof (chanlistrow *), + (GCompareDataFunc) custom_list_qsort_compare_func, + custom_list); + + /* let other objects know about the new order */ + neworder = malloc (sizeof (gint) * custom_list->num_rows); + + for (i = custom_list->num_rows - 1; i >= 0; i--) + { + /* Note that the API reference might be wrong about + * this, see bug number 124790 on bugs.gnome.org. + * Both will work, but one will give you 'jumpy' + * selections after row reordering. */ + /* neworder[(custom_list->rows[i])->pos] = i; */ + neworder[i] = (custom_list->rows[i])->pos; + (custom_list->rows[i])->pos = i; + } + + path = gtk_tree_path_new (); + gtk_tree_model_rows_reordered (GTK_TREE_MODEL (custom_list), path, NULL, + neworder); + gtk_tree_path_free (path); + free (neworder); +} + +void +custom_list_clear (CustomList * custom_list) +{ + int i, max = custom_list->num_rows - 1; + GtkTreePath *path; + + for (i = max; i >= 0; i--) + { + path = gtk_tree_path_new (); + gtk_tree_path_append_index (path, custom_list->rows[i]->pos); + gtk_tree_model_row_deleted (GTK_TREE_MODEL (custom_list), path); + gtk_tree_path_free (path); + } + + custom_list->num_rows = 0; + custom_list->num_alloc = 0; + + g_free (custom_list->rows); + custom_list->rows = NULL; +} diff --git a/src/fe-gtk/custom-list.h b/src/fe-gtk/custom-list.h new file mode 100644 index 00000000..d9e4f09e --- /dev/null +++ b/src/fe-gtk/custom-list.h @@ -0,0 +1,85 @@ +#ifndef _custom_list_h_included_ +#define _custom_list_h_included_ + +#include <gtk/gtk.h> + +/* Some boilerplate GObject defines. 'klass' is used + * instead of 'class', because 'class' is a C++ keyword */ + +#define CUSTOM_TYPE_LIST (custom_list_get_type ()) +#define CUSTOM_LIST(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), CUSTOM_TYPE_LIST, CustomList)) +#define CUSTOM_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), CUSTOM_TYPE_LIST, CustomListClass)) +#define CUSTOM_IS_LIST(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), CUSTOM_TYPE_LIST)) +#define CUSTOM_IS_LIST_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), CUSTOM_TYPE_LIST)) +#define CUSTOM_LIST_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), CUSTOM_TYPE_LIST, CustomListClass)) + +/* The data columns that we export via the tree model interface */ + +enum +{ + CUSTOM_LIST_COL_NAME, + CUSTOM_LIST_COL_USERS, + CUSTOM_LIST_COL_TOPIC, + CUSTOM_LIST_N_COLUMNS +}; + +enum +{ + SORT_ID_CHANNEL, + SORT_ID_USERS, + SORT_ID_TOPIC +}; + +typedef struct +{ + char *topic; + char *collation_key; + guint32 pos; /* pos within the array */ + guint32 users; + /* channel string lives beyond "users" */ +#define GET_CHAN(row) (((char *)row)+sizeof(chanlistrow)) +} +chanlistrow; + +typedef struct _CustomList CustomList; +typedef struct _CustomListClass CustomListClass; + + + +/* CustomList: this structure contains everything we need for our + * model implementation. You can add extra fields to + * this structure, e.g. hashtables to quickly lookup + * rows or whatever else you might need, but it is + * crucial that 'parent' is the first member of the + * structure. */ +struct _CustomList +{ + GObject parent; + + guint num_rows; /* number of rows that we have used */ + guint num_alloc; /* number of rows allocated */ + chanlistrow **rows; /* a dynamically allocated array of pointers to the + * CustomRecord structure for each row */ + + gint n_columns; + GType column_types[CUSTOM_LIST_N_COLUMNS]; + + gint sort_id; + GtkSortType sort_order; +}; + + +/* CustomListClass: more boilerplate GObject stuff */ + +struct _CustomListClass +{ + GObjectClass parent_class; +}; + + +CustomList *custom_list_new (void); +void custom_list_append (CustomList *, chanlistrow *); +void custom_list_resort (CustomList *); +void custom_list_clear (CustomList *); + +#endif /* _custom_list_h_included_ */ diff --git a/src/fe-gtk/dccgui.c b/src/fe-gtk/dccgui.c new file mode 100644 index 00000000..61f6d502 --- /dev/null +++ b/src/fe-gtk/dccgui.c @@ -0,0 +1,1098 @@ +/* X-Chat + * Copyright (C) 1998-2006 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 <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <time.h> + +#define WANTSOCKET +#define WANTARPA +#include "../common/inet.h" +#include "fe-gtk.h" + +#include <gtk/gtkhbbox.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkmessagedialog.h> +#include <gtk/gtktable.h> +#include <gtk/gtktreeview.h> +#include <gtk/gtkexpander.h> +#include <gtk/gtkliststore.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtkcellrendererpixbuf.h> +#include <gtk/gtkcellrenderertext.h> +#include <gtk/gtkcheckmenuitem.h> +#include <gtk/gtkradiobutton.h> +#include <gtk/gtkversion.h> + +#include "../common/xchat.h" +#include "../common/xchatc.h" +#include "../common/fe.h" +#include "../common/util.h" +#include "../common/network.h" +#include "gtkutil.h" +#include "palette.h" +#include "maingui.h" + + +enum /* DCC SEND/RECV */ +{ + COL_TYPE, + COL_STATUS, + COL_FILE, + COL_SIZE, + COL_POS, + COL_PERC, + COL_SPEED, + COL_ETA, + COL_NICK, + COL_DCC, /* struct DCC * */ + COL_COLOR, /* GdkColor */ + N_COLUMNS +}; + +enum /* DCC CHAT */ +{ + CCOL_STATUS, + CCOL_NICK, + CCOL_RECV, + CCOL_SENT, + CCOL_START, + CCOL_DCC, /* struct DCC * */ + CCOL_COLOR, /* GdkColor * */ + CN_COLUMNS +}; + +struct dccwindow +{ + GtkWidget *window; + + GtkWidget *list; + GtkListStore *store; + GtkTreeSelection *sel; + + GtkWidget *abort_button; + GtkWidget *accept_button; + GtkWidget *resume_button; + GtkWidget *open_button; + + GtkWidget *file_label; + GtkWidget *address_label; +}; + +struct my_dcc_send +{ + struct session *sess; + char *nick; + int maxcps; + int passive; +}; + +static struct dccwindow dccfwin = {NULL, }; /* file */ +static struct dccwindow dcccwin = {NULL, }; /* chat */ +static GdkPixbuf *pix_up = NULL; /* down arrow */ +static GdkPixbuf *pix_dn = NULL; /* up arrow */ +static int win_width = 600; +static int win_height = 256; +static short view_mode; /* 1=download 2=upload 3=both */ +#define VIEW_DOWNLOAD 1 +#define VIEW_UPLOAD 2 +#define VIEW_BOTH 3 + +#define KILOBYTE 1024 +#define MEGABYTE (KILOBYTE * 1024) +#define GIGABYTE (MEGABYTE * 1024) + + +static void +proper_unit (DCC_SIZE size, char *buf, int buf_len) +{ + if (size <= KILOBYTE) + { + snprintf (buf, buf_len, "%"DCC_SFMT"B", size); + } + else if (size > KILOBYTE && size <= MEGABYTE) + { + snprintf (buf, buf_len, "%"DCC_SFMT"kB", size / KILOBYTE); + } + else + { + snprintf (buf, buf_len, "%.2fMB", (float)size / MEGABYTE); + } +} + +static void +dcc_send_filereq_file (struct my_dcc_send *mdc, char *file) +{ + if (file) + dcc_send (mdc->sess, mdc->nick, file, mdc->maxcps, mdc->passive); + else + { + free (mdc->nick); + free (mdc); + } +} + +void +fe_dcc_send_filereq (struct session *sess, char *nick, int maxcps, int passive) +{ + char tbuf[128]; + struct my_dcc_send *mdc; + + mdc = malloc (sizeof (*mdc)); + mdc->sess = sess; + mdc->nick = strdup (nick); + mdc->maxcps = maxcps; + mdc->passive = passive; + + snprintf (tbuf, sizeof tbuf, _("Send file to %s"), nick); + gtkutil_file_req (tbuf, dcc_send_filereq_file, mdc, NULL, FRF_MULTIPLE); +} + +static void +dcc_prepare_row_chat (struct DCC *dcc, GtkListStore *store, GtkTreeIter *iter, + gboolean update_only) +{ + static char pos[16], siz[16]; + char *date; + + date = ctime (&dcc->starttime); + date[strlen (date) - 1] = 0; /* remove the \n */ + + proper_unit (dcc->pos, pos, sizeof (pos)); + proper_unit (dcc->size, siz, sizeof (siz)); + + gtk_list_store_set (store, iter, + CCOL_STATUS, _(dccstat[dcc->dccstat].name), + CCOL_NICK, dcc->nick, + CCOL_RECV, pos, + CCOL_SENT, siz, + CCOL_START, date, + CCOL_DCC, dcc, + CCOL_COLOR, + dccstat[dcc->dccstat].color == 1 ? + NULL : + colors + dccstat[dcc->dccstat].color, + -1); +} + +static void +dcc_prepare_row_send (struct DCC *dcc, GtkListStore *store, GtkTreeIter *iter, + gboolean update_only) +{ + static char pos[16], size[16], kbs[14], perc[14], eta[14]; + int to_go; + float per; + + if (!pix_up) + pix_up = gtk_widget_render_icon (dccfwin.window, "gtk-go-up", + GTK_ICON_SIZE_MENU, NULL); + + /* percentage ack'ed */ + per = (float) ((dcc->ack * 100.00) / dcc->size); + proper_unit (dcc->size, size, sizeof (size)); + proper_unit (dcc->pos, pos, sizeof (pos)); + snprintf (kbs, sizeof (kbs), "%.1f", ((float)dcc->cps) / 1024); +/* proper_unit (dcc->ack, ack, sizeof (ack));*/ + snprintf (perc, sizeof (perc), "%.0f%%", per); + if (dcc->cps != 0) + { + to_go = (dcc->size - dcc->ack) / dcc->cps; + snprintf (eta, sizeof (eta), "%.2d:%.2d:%.2d", + to_go / 3600, (to_go / 60) % 60, to_go % 60); + } else + strcpy (eta, "--:--:--"); + + if (update_only) + gtk_list_store_set (store, iter, + COL_STATUS, _(dccstat[dcc->dccstat].name), + COL_POS, pos, + COL_PERC, perc, + COL_SPEED, kbs, + COL_ETA, eta, + COL_COLOR, + dccstat[dcc->dccstat].color == 1 ? + NULL : + colors + dccstat[dcc->dccstat].color, + -1); + else + gtk_list_store_set (store, iter, + COL_TYPE, pix_up, + COL_STATUS, _(dccstat[dcc->dccstat].name), + COL_FILE, file_part (dcc->file), + COL_SIZE, size, + COL_POS, pos, + COL_PERC, perc, + COL_SPEED, kbs, + COL_ETA, eta, + COL_NICK, dcc->nick, + COL_DCC, dcc, + COL_COLOR, + dccstat[dcc->dccstat].color == 1 ? + NULL : + colors + dccstat[dcc->dccstat].color, + -1); +} + +static void +dcc_prepare_row_recv (struct DCC *dcc, GtkListStore *store, GtkTreeIter *iter, + gboolean update_only) +{ + static char size[16], pos[16], kbs[16], perc[14], eta[16]; + float per; + int to_go; + + if (!pix_dn) + pix_dn = gtk_widget_render_icon (dccfwin.window, "gtk-go-down", + GTK_ICON_SIZE_MENU, NULL); + + proper_unit (dcc->size, size, sizeof (size)); + if (dcc->dccstat == STAT_QUEUED) + proper_unit (dcc->resumable, pos, sizeof (pos)); + else + proper_unit (dcc->pos, pos, sizeof (pos)); + snprintf (kbs, sizeof (kbs), "%.1f", ((float)dcc->cps) / 1024); + /* percentage recv'ed */ + per = (float) ((dcc->pos * 100.00) / dcc->size); + snprintf (perc, sizeof (perc), "%.0f%%", per); + if (dcc->cps != 0) + { + to_go = (dcc->size - dcc->pos) / dcc->cps; + snprintf (eta, sizeof (eta), "%.2d:%.2d:%.2d", + to_go / 3600, (to_go / 60) % 60, to_go % 60); + } else + strcpy (eta, "--:--:--"); + + if (update_only) + gtk_list_store_set (store, iter, + COL_STATUS, _(dccstat[dcc->dccstat].name), + COL_POS, pos, + COL_PERC, perc, + COL_SPEED, kbs, + COL_ETA, eta, + COL_COLOR, + dccstat[dcc->dccstat].color == 1 ? + NULL : + colors + dccstat[dcc->dccstat].color, + -1); + else + gtk_list_store_set (store, iter, + COL_TYPE, pix_dn, + COL_STATUS, _(dccstat[dcc->dccstat].name), + COL_FILE, file_part (dcc->file), + COL_SIZE, size, + COL_POS, pos, + COL_PERC, perc, + COL_SPEED, kbs, + COL_ETA, eta, + COL_NICK, dcc->nick, + COL_DCC, dcc, + COL_COLOR, + dccstat[dcc->dccstat].color == 1 ? + NULL : + colors + dccstat[dcc->dccstat].color, + -1); +} + +static gboolean +dcc_find_row (struct DCC *find_dcc, GtkTreeModel *model, GtkTreeIter *iter, int col) +{ + struct DCC *dcc; + + if (gtk_tree_model_get_iter_first (model, iter)) + { + do + { + gtk_tree_model_get (model, iter, col, &dcc, -1); + if (dcc == find_dcc) + return TRUE; + } + while (gtk_tree_model_iter_next (model, iter)); + } + + return FALSE; +} + +static void +dcc_update_recv (struct DCC *dcc) +{ + GtkTreeIter iter; + + if (!dccfwin.window) + return; + + if (!dcc_find_row (dcc, GTK_TREE_MODEL (dccfwin.store), &iter, COL_DCC)) + return; + + dcc_prepare_row_recv (dcc, dccfwin.store, &iter, TRUE); +} + +static void +dcc_update_chat (struct DCC *dcc) +{ + GtkTreeIter iter; + + if (!dcccwin.window) + return; + + if (!dcc_find_row (dcc, GTK_TREE_MODEL (dcccwin.store), &iter, CCOL_DCC)) + return; + + dcc_prepare_row_chat (dcc, dcccwin.store, &iter, TRUE); +} + +static void +dcc_update_send (struct DCC *dcc) +{ + GtkTreeIter iter; + + if (!dccfwin.window) + return; + + if (!dcc_find_row (dcc, GTK_TREE_MODEL (dccfwin.store), &iter, COL_DCC)) + return; + + dcc_prepare_row_send (dcc, dccfwin.store, &iter, TRUE); +} + +static void +close_dcc_file_window (GtkWindow *win, gpointer data) +{ + dccfwin.window = NULL; +} + +static void +dcc_append (struct DCC *dcc, GtkListStore *store, gboolean prepend) +{ + GtkTreeIter iter; + + if (prepend) + gtk_list_store_prepend (store, &iter); + else + gtk_list_store_append (store, &iter); + + if (dcc->type == TYPE_RECV) + dcc_prepare_row_recv (dcc, store, &iter, FALSE); + else + dcc_prepare_row_send (dcc, store, &iter, FALSE); +} + +static void +dcc_fill_window (int flags) +{ + struct DCC *dcc; + GSList *list; + GtkTreeIter iter; + int i = 0; + + gtk_list_store_clear (GTK_LIST_STORE (dccfwin.store)); + + if (flags & VIEW_UPLOAD) + { + list = dcc_list; + while (list) + { + dcc = list->data; + if (dcc->type == TYPE_SEND) + { + dcc_append (dcc, dccfwin.store, FALSE); + i++; + } + list = list->next; + } + } + + if (flags & VIEW_DOWNLOAD) + { + list = dcc_list; + while (list) + { + dcc = list->data; + if (dcc->type == TYPE_RECV) + { + dcc_append (dcc, dccfwin.store, FALSE); + i++; + } + list = list->next; + } + } + + /* if only one entry, select it (so Accept button can work) */ + if (i == 1) + { + gtk_tree_model_get_iter_first (GTK_TREE_MODEL (dccfwin.store), &iter); + gtk_tree_selection_select_iter (dccfwin.sel, &iter); + } +} + +/* return list of selected DCCs */ + +static GSList * +treeview_get_selected (GtkTreeModel *model, GtkTreeSelection *sel, int column) +{ + GtkTreeIter iter; + GSList *list = NULL; + void *ptr; + + if (gtk_tree_model_get_iter_first (model, &iter)) + { + do + { + if (gtk_tree_selection_iter_is_selected (sel, &iter)) + { + gtk_tree_model_get (model, &iter, column, &ptr, -1); + list = g_slist_prepend (list, ptr); + } + } + while (gtk_tree_model_iter_next (model, &iter)); + } + + return g_slist_reverse (list); +} + +static GSList * +dcc_get_selected (void) +{ + return treeview_get_selected (GTK_TREE_MODEL (dccfwin.store), + dccfwin.sel, COL_DCC); +} + +static void +resume_clicked (GtkWidget * wid, gpointer none) +{ + struct DCC *dcc; + char buf[512]; + GSList *list; + + list = dcc_get_selected (); + if (!list) + return; + dcc = list->data; + g_slist_free (list); + + if (dcc->type == TYPE_RECV && !dcc_resume (dcc)) + { + switch (dcc->resume_error) + { + case 0: /* unknown error */ + fe_message (_("That file is not resumable."), FE_MSG_ERROR); + break; + case 1: + snprintf (buf, sizeof (buf), + _( "Cannot access file: %s\n" + "%s.\n" + "Resuming not possible."), dcc->destfile, + errorstring (dcc->resume_errno)); + fe_message (buf, FE_MSG_ERROR); + break; + case 2: + fe_message (_("File in download directory is larger " + "than file offered. Resuming not possible."), FE_MSG_ERROR); + break; + case 3: + fe_message (_("Cannot resume the same file from two people."), FE_MSG_ERROR); + } + } +} + +static void +abort_clicked (GtkWidget * wid, gpointer none) +{ + struct DCC *dcc; + GSList *start, *list; + + start = list = dcc_get_selected (); + for (; list; list = list->next) + { + dcc = list->data; + dcc_abort (dcc->serv->front_session, dcc); + } + g_slist_free (start); +} + +static void +accept_clicked (GtkWidget * wid, gpointer none) +{ + struct DCC *dcc; + GSList *start, *list; + + start = list = dcc_get_selected (); + for (; list; list = list->next) + { + dcc = list->data; + if (dcc->type != TYPE_SEND) + dcc_get (dcc); + } + g_slist_free (start); +} + +static void +browse_folder (char *dir) +{ +#ifdef WIN32 + /* no need for file:// in ShellExecute() */ + fe_open_url (dir); +#else + char buf[512]; + + snprintf (buf, sizeof (buf), "file://%s", dir); + fe_open_url (buf); +#endif +} + +static void +browse_dcc_folder (void) +{ + if (prefs.dcc_completed_dir[0]) + browse_folder (prefs.dcc_completed_dir); + else + browse_folder (prefs.dccdir); +} + +static void +dcc_details_populate (struct DCC *dcc) +{ + char buf[128]; + + if (!dcc) + { + gtk_label_set_text (GTK_LABEL (dccfwin.file_label), NULL); + gtk_label_set_text (GTK_LABEL (dccfwin.address_label), NULL); + return; + } + + /* full path */ + if (dcc->type == TYPE_RECV) + gtk_label_set_text (GTK_LABEL (dccfwin.file_label), dcc->destfile); + else + gtk_label_set_text (GTK_LABEL (dccfwin.file_label), dcc->file); + + /* address and port */ + snprintf (buf, sizeof (buf), "%s : %d", net_ip (dcc->addr), dcc->port); + gtk_label_set_text (GTK_LABEL (dccfwin.address_label), buf); +} + +static void +dcc_row_cb (GtkTreeSelection *sel, gpointer user_data) +{ + struct DCC *dcc; + GSList *list; + + list = dcc_get_selected (); + if (!list) + { + gtk_widget_set_sensitive (dccfwin.accept_button, FALSE); + gtk_widget_set_sensitive (dccfwin.resume_button, FALSE); + gtk_widget_set_sensitive (dccfwin.abort_button, FALSE); + dcc_details_populate (NULL); + return; + } + + gtk_widget_set_sensitive (dccfwin.abort_button, TRUE); + + if (list->next) /* multi selection */ + { + gtk_widget_set_sensitive (dccfwin.accept_button, TRUE); + gtk_widget_set_sensitive (dccfwin.resume_button, TRUE); + dcc_details_populate (list->data); + } + else + { + /* turn OFF/ON appropriate buttons */ + dcc = list->data; + if (dcc->dccstat == STAT_QUEUED && dcc->type == TYPE_RECV) + { + gtk_widget_set_sensitive (dccfwin.accept_button, TRUE); + gtk_widget_set_sensitive (dccfwin.resume_button, TRUE); + } + else + { + gtk_widget_set_sensitive (dccfwin.accept_button, FALSE); + gtk_widget_set_sensitive (dccfwin.resume_button, FALSE); + } + + dcc_details_populate (dcc); + } + + g_slist_free (list); +} + +static void +dcc_dclick_cb (GtkTreeView *view, GtkTreePath *path, + GtkTreeViewColumn *column, gpointer data) +{ + struct DCC *dcc; + GSList *list; + + list = dcc_get_selected (); + if (!list) + return; + dcc = list->data; + g_slist_free (list); + + if (dcc->type == TYPE_RECV) + { + accept_clicked (0, 0); + return; + } + + switch (dcc->dccstat) + { + case STAT_FAILED: + case STAT_ABORTED: + case STAT_DONE: + dcc_abort (dcc->serv->front_session, dcc); + } +} + +static void +dcc_add_column (GtkWidget *tree, int textcol, int colorcol, char *title, gboolean right_justified) +{ + GtkCellRenderer *renderer; + + renderer = gtk_cell_renderer_text_new (); + if (right_justified) + g_object_set (G_OBJECT (renderer), "xalign", (float) 1.0, NULL); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree), -1, title, renderer, + "text", textcol, "foreground-gdk", colorcol, + NULL); + gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer), 1); +} + +static GtkWidget * +dcc_detail_label (char *text, GtkWidget *box, int num) +{ + GtkWidget *label; + char buf[64]; + + label = gtk_label_new (NULL); + snprintf (buf, sizeof (buf), "<b>%s</b>", text); + gtk_label_set_markup (GTK_LABEL (label), buf); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_table_attach (GTK_TABLE (box), label, 0, 1, 0 + num, 1 + num, GTK_FILL, GTK_FILL, 0, 0); + + label = gtk_label_new (NULL); + gtk_label_set_selectable (GTK_LABEL (label), TRUE); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0); + gtk_table_attach (GTK_TABLE (box), label, 1, 2, 0 + num, 1 + num, GTK_FILL, GTK_FILL, 0, 0); + + return label; +} + +static void +dcc_exp_cb (GtkWidget *exp, GtkWidget *box) +{ +#if GTK_CHECK_VERSION(2,20,0) + if (gtk_widget_get_visible (box)) +#else + if (GTK_WIDGET_VISIBLE (box)) +#endif + gtk_widget_hide (box); + else + gtk_widget_show (box); +} + +static void +dcc_toggle (GtkWidget *item, gpointer data) +{ + if (GTK_TOGGLE_BUTTON (item)->active) + { + view_mode = GPOINTER_TO_INT (data); + dcc_fill_window (GPOINTER_TO_INT (data)); + } +} + +static gboolean +dcc_configure_cb (GtkWindow *win, GdkEventConfigure *event, gpointer data) +{ + /* remember the window size */ + gtk_window_get_size (win, &win_width, &win_height); + return FALSE; +} + +int +fe_dcc_open_recv_win (int passive) +{ + GtkWidget *radio, *table, *vbox, *bbox, *view, *exp, *detailbox; + GtkListStore *store; + GSList *group; + + if (dccfwin.window) + { + if (!passive) + mg_bring_tofront (dccfwin.window); + return TRUE; + } + dccfwin.window = mg_create_generic_tab ("Transfers", _("XChat: Uploads and Downloads"), + FALSE, TRUE, close_dcc_file_window, NULL, + win_width, win_height, &vbox, 0); + gtk_container_set_border_width (GTK_CONTAINER (dccfwin.window), 3); + gtk_box_set_spacing (GTK_BOX (vbox), 3); + + store = gtk_list_store_new (N_COLUMNS, GDK_TYPE_PIXBUF, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_POINTER, GDK_TYPE_COLOR); + view = gtkutil_treeview_new (vbox, GTK_TREE_MODEL (store), NULL, -1); + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE); + /* Up/Down Icon column */ + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), -1, NULL, + gtk_cell_renderer_pixbuf_new (), + "pixbuf", COL_TYPE, NULL); + dcc_add_column (view, COL_STATUS, COL_COLOR, _("Status"), FALSE); + dcc_add_column (view, COL_FILE, COL_COLOR, _("File"), FALSE); + dcc_add_column (view, COL_SIZE, COL_COLOR, _("Size"), TRUE); + dcc_add_column (view, COL_POS, COL_COLOR, _("Position"), TRUE); + dcc_add_column (view, COL_PERC, COL_COLOR, "%", TRUE); + dcc_add_column (view, COL_SPEED, COL_COLOR, "KB/s", TRUE); + dcc_add_column (view, COL_ETA, COL_COLOR, _("ETA"), FALSE); + dcc_add_column (view, COL_NICK, COL_COLOR, _("Nick"), FALSE); + + gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), COL_FILE), TRUE); + gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), COL_NICK), TRUE); + + dccfwin.list = view; + dccfwin.store = store; + dccfwin.sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); + view_mode = VIEW_BOTH; + gtk_tree_selection_set_mode (dccfwin.sel, GTK_SELECTION_MULTIPLE); + + if (!prefs.windows_as_tabs) + g_signal_connect (G_OBJECT (dccfwin.window), "configure_event", + G_CALLBACK (dcc_configure_cb), 0); + g_signal_connect (G_OBJECT (dccfwin.sel), "changed", + G_CALLBACK (dcc_row_cb), NULL); + /* double click */ + g_signal_connect (G_OBJECT (view), "row-activated", + G_CALLBACK (dcc_dclick_cb), NULL); + + table = gtk_table_new (1, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (table), 16); + gtk_box_pack_start (GTK_BOX (vbox), table, 0, 0, 0); + + radio = gtk_radio_button_new_with_mnemonic (NULL, _("Both")); + g_signal_connect (G_OBJECT (radio), "toggled", + G_CALLBACK (dcc_toggle), GINT_TO_POINTER (VIEW_BOTH)); + gtk_table_attach (GTK_TABLE (table), radio, 3, 4, 0, 1, GTK_FILL, GTK_FILL, 0, 0); + group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio)); + + radio = gtk_radio_button_new_with_mnemonic (group, _("Uploads")); + g_signal_connect (G_OBJECT (radio), "toggled", + G_CALLBACK (dcc_toggle), GINT_TO_POINTER (VIEW_UPLOAD)); + gtk_table_attach (GTK_TABLE (table), radio, 1, 2, 0, 1, GTK_FILL, GTK_FILL, 0, 0); + group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio)); + + radio = gtk_radio_button_new_with_mnemonic (group, _("Downloads")); + g_signal_connect (G_OBJECT (radio), "toggled", + G_CALLBACK (dcc_toggle), GINT_TO_POINTER (VIEW_DOWNLOAD)); + gtk_table_attach (GTK_TABLE (table), radio, 2, 3, 0, 1, GTK_FILL, GTK_FILL, 0, 0); + + exp = gtk_expander_new (_("Details")); + gtk_table_attach (GTK_TABLE (table), exp, 0, 1, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); + + detailbox = gtk_table_new (3, 3, FALSE); + gtk_table_set_col_spacings (GTK_TABLE (detailbox), 6); + gtk_table_set_row_spacings (GTK_TABLE (detailbox), 2); + gtk_container_set_border_width (GTK_CONTAINER (detailbox), 6); + g_signal_connect (G_OBJECT (exp), "activate", + G_CALLBACK (dcc_exp_cb), detailbox); + gtk_table_attach (GTK_TABLE (table), detailbox, 0, 4, 1, 2, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); + + dccfwin.file_label = dcc_detail_label (_("File:"), detailbox, 0); + dccfwin.address_label = dcc_detail_label (_("Address:"), detailbox, 1); + + bbox = gtk_hbutton_box_new (); + gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD); + gtk_box_pack_end (GTK_BOX (vbox), bbox, FALSE, FALSE, 2); + + dccfwin.abort_button = gtkutil_button (bbox, GTK_STOCK_CANCEL, 0, abort_clicked, 0, _("Abort")); + dccfwin.accept_button = gtkutil_button (bbox, GTK_STOCK_APPLY, 0, accept_clicked, 0, _("Accept")); + dccfwin.resume_button = gtkutil_button (bbox, GTK_STOCK_REFRESH, 0, resume_clicked, 0, _("Resume")); + dccfwin.open_button = gtkutil_button (bbox, 0, 0, browse_dcc_folder, 0, _("Open Folder...")); + gtk_widget_set_sensitive (dccfwin.accept_button, FALSE); + gtk_widget_set_sensitive (dccfwin.resume_button, FALSE); + gtk_widget_set_sensitive (dccfwin.abort_button, FALSE); + + dcc_fill_window (3); + gtk_widget_show_all (dccfwin.window); + gtk_widget_hide (detailbox); + + return FALSE; +} + +int +fe_dcc_open_send_win (int passive) +{ + /* combined send/recv GUI */ + return fe_dcc_open_recv_win (passive); +} + + +/* DCC CHAT GUIs BELOW */ + +static GSList * +dcc_chat_get_selected (void) +{ + return treeview_get_selected (GTK_TREE_MODEL (dcccwin.store), + dcccwin.sel, CCOL_DCC); +} + +static void +accept_chat_clicked (GtkWidget * wid, gpointer none) +{ + struct DCC *dcc; + GSList *start, *list; + + start = list = dcc_chat_get_selected (); + for (; list; list = list->next) + { + dcc = list->data; + dcc_get (dcc); + } + g_slist_free (start); +} + +static void +abort_chat_clicked (GtkWidget * wid, gpointer none) +{ + struct DCC *dcc; + GSList *start, *list; + + start = list = dcc_chat_get_selected (); + for (; list; list = list->next) + { + dcc = list->data; + dcc_abort (dcc->serv->front_session, dcc); + } + g_slist_free (start); +} + +static void +dcc_chat_close_cb (void) +{ + dcccwin.window = NULL; +} + +static void +dcc_chat_append (struct DCC *dcc, GtkListStore *store, gboolean prepend) +{ + GtkTreeIter iter; + + if (prepend) + gtk_list_store_prepend (store, &iter); + else + gtk_list_store_append (store, &iter); + + dcc_prepare_row_chat (dcc, store, &iter, FALSE); +} + +static void +dcc_chat_fill_win (void) +{ + struct DCC *dcc; + GSList *list; + GtkTreeIter iter; + int i = 0; + + gtk_list_store_clear (GTK_LIST_STORE (dcccwin.store)); + + list = dcc_list; + while (list) + { + dcc = list->data; + if (dcc->type == TYPE_CHATSEND || dcc->type == TYPE_CHATRECV) + { + dcc_chat_append (dcc, dcccwin.store, FALSE); + i++; + } + list = list->next; + } + + /* if only one entry, select it (so Accept button can work) */ + if (i == 1) + { + gtk_tree_model_get_iter_first (GTK_TREE_MODEL (dcccwin.store), &iter); + gtk_tree_selection_select_iter (dcccwin.sel, &iter); + } +} + +static void +dcc_chat_row_cb (GtkTreeSelection *sel, gpointer user_data) +{ + struct DCC *dcc; + GSList *list; + + list = dcc_chat_get_selected (); + if (!list) + { + gtk_widget_set_sensitive (dcccwin.accept_button, FALSE); + gtk_widget_set_sensitive (dcccwin.abort_button, FALSE); + return; + } + + gtk_widget_set_sensitive (dcccwin.abort_button, TRUE); + + if (list->next) /* multi selection */ + gtk_widget_set_sensitive (dcccwin.accept_button, TRUE); + else + { + /* turn OFF/ON appropriate buttons */ + dcc = list->data; + if (dcc->dccstat == STAT_QUEUED && dcc->type == TYPE_CHATRECV) + gtk_widget_set_sensitive (dcccwin.accept_button, TRUE); + else + gtk_widget_set_sensitive (dcccwin.accept_button, FALSE); + } + + g_slist_free (list); +} + +static void +dcc_chat_dclick_cb (GtkTreeView *view, GtkTreePath *path, + GtkTreeViewColumn *column, gpointer data) +{ + accept_chat_clicked (0, 0); +} + +int +fe_dcc_open_chat_win (int passive) +{ + GtkWidget *view, *vbox, *bbox; + GtkListStore *store; + + if (dcccwin.window) + { + if (!passive) + mg_bring_tofront (dcccwin.window); + return TRUE; + } + + dcccwin.window = + mg_create_generic_tab ("DCCChat", _("XChat: DCC Chat List"), + FALSE, TRUE, dcc_chat_close_cb, NULL, 550, 180, &vbox, 0); + gtk_container_set_border_width (GTK_CONTAINER (dcccwin.window), 3); + gtk_box_set_spacing (GTK_BOX (vbox), 3); + + store = gtk_list_store_new (CN_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_POINTER, GDK_TYPE_COLOR); + view = gtkutil_treeview_new (vbox, GTK_TREE_MODEL (store), NULL, -1); + + dcc_add_column (view, CCOL_STATUS, CCOL_COLOR, _("Status"), FALSE); + dcc_add_column (view, CCOL_NICK, CCOL_COLOR, _("Nick"), FALSE); + dcc_add_column (view, CCOL_RECV, CCOL_COLOR, _("Recv"), TRUE); + dcc_add_column (view, CCOL_SENT, CCOL_COLOR, _("Sent"), TRUE); + dcc_add_column (view, CCOL_START, CCOL_COLOR, _("Start Time"), FALSE); + + gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), 1), TRUE); + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE); + + dcccwin.list = view; + dcccwin.store = store; + dcccwin.sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (view)); + gtk_tree_selection_set_mode (dcccwin.sel, GTK_SELECTION_MULTIPLE); + + g_signal_connect (G_OBJECT (dcccwin.sel), "changed", + G_CALLBACK (dcc_chat_row_cb), NULL); + /* double click */ + g_signal_connect (G_OBJECT (view), "row-activated", + G_CALLBACK (dcc_chat_dclick_cb), NULL); + + bbox = gtk_hbutton_box_new (); + gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD); + gtk_box_pack_end (GTK_BOX (vbox), bbox, FALSE, FALSE, 2); + + dcccwin.abort_button = gtkutil_button (bbox, GTK_STOCK_CANCEL, 0, abort_chat_clicked, 0, _("Abort")); + dcccwin.accept_button = gtkutil_button (bbox, GTK_STOCK_APPLY, 0, accept_chat_clicked, 0, _("Accept")); + gtk_widget_set_sensitive (dcccwin.accept_button, FALSE); + gtk_widget_set_sensitive (dcccwin.abort_button, FALSE); + + dcc_chat_fill_win (); + gtk_widget_show_all (dcccwin.window); + + return FALSE; +} + +void +fe_dcc_add (struct DCC *dcc) +{ + switch (dcc->type) + { + case TYPE_RECV: + if (dccfwin.window && (view_mode & VIEW_DOWNLOAD)) + dcc_append (dcc, dccfwin.store, TRUE); + break; + + case TYPE_SEND: + if (dccfwin.window && (view_mode & VIEW_UPLOAD)) + dcc_append (dcc, dccfwin.store, TRUE); + break; + + default: /* chat */ + if (dcccwin.window) + dcc_chat_append (dcc, dcccwin.store, TRUE); + } +} + +void +fe_dcc_update (struct DCC *dcc) +{ + switch (dcc->type) + { + case TYPE_SEND: + dcc_update_send (dcc); + break; + + case TYPE_RECV: + dcc_update_recv (dcc); + break; + + default: + dcc_update_chat (dcc); + } +} + +void +fe_dcc_remove (struct DCC *dcc) +{ + GtkTreeIter iter; + + switch (dcc->type) + { + case TYPE_SEND: + case TYPE_RECV: + if (dccfwin.window) + { + if (dcc_find_row (dcc, GTK_TREE_MODEL (dccfwin.store), &iter, COL_DCC)) + gtk_list_store_remove (dccfwin.store, &iter); + } + break; + + default: /* chat */ + if (dcccwin.window) + { + if (dcc_find_row (dcc, GTK_TREE_MODEL (dcccwin.store), &iter, CCOL_DCC)) + gtk_list_store_remove (dcccwin.store, &iter); + } + break; + } +} diff --git a/src/fe-gtk/editlist.c b/src/fe-gtk/editlist.c new file mode 100644 index 00000000..5af67e32 --- /dev/null +++ b/src/fe-gtk/editlist.c @@ -0,0 +1,409 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include "fe-gtk.h" + +#include <gtk/gtkstock.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkclist.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkvseparator.h> + +#include "../common/xchat.h" +#include "../common/cfgfiles.h" +#include "../common/xchatc.h" +#include "../common/fe.h" +#include "menu.h" +#include "gtkutil.h" +#include "maingui.h" +#include "editlist.h" + + +static GtkWidget *editlist_gui_entry_name; +static GtkWidget *editlist_gui_entry_cmd; +static GtkWidget *editlist_gui_window; +static GtkWidget *editlist_gui_list; +static GSList *editlist_list; +static char *editlist_file; +static char *editlist_help; + + + +static void +editlist_gui_load (GtkWidget * listgad) +{ + gchar *nnew[2]; + GSList *list = editlist_list; + struct popup *pop; + + while (list) + { + pop = (struct popup *) list->data; + nnew[0] = pop->name; + nnew[1] = pop->cmd; + gtk_clist_append (GTK_CLIST (listgad), nnew); + list = list->next; + } +} + +static void +editlist_gui_row_unselected (GtkWidget * clist, gint row, gint column, + GdkEventButton * even, gpointer none) +{ + gtk_entry_set_text (GTK_ENTRY (editlist_gui_entry_name), ""); + gtk_entry_set_text (GTK_ENTRY (editlist_gui_entry_cmd), ""); +} + +static void +editlist_gui_row_selected (GtkWidget * clist, gint row, gint column, + GdkEventButton * even, gpointer none) +{ + char *name, *cmd; + + row = gtkutil_clist_selection (editlist_gui_list); + if (row != -1) + { + gtk_clist_get_text (GTK_CLIST (clist), row, 0, &name); + gtk_clist_get_text (GTK_CLIST (clist), row, 1, &cmd); + + name = strdup (name); + cmd = strdup (cmd); + + gtk_entry_set_text (GTK_ENTRY (editlist_gui_entry_name), name); + gtk_entry_set_text (GTK_ENTRY (editlist_gui_entry_cmd), cmd); + + free (name); + free (cmd); + } else + { + editlist_gui_row_unselected (0, 0, 0, 0, 0); + } +} + +static void +editlist_gui_handle_cmd (GtkWidget * igad) +{ + int row; + const char *reply; + + row = gtkutil_clist_selection (editlist_gui_list); + if (row != -1) + { + reply = gtk_entry_get_text (GTK_ENTRY (igad)); + gtk_clist_set_text (GTK_CLIST (editlist_gui_list), row, 1, reply); + } +} + +static void +editlist_gui_handle_name (GtkWidget * igad) +{ + int row; + const char *ctcp; + + row = gtkutil_clist_selection (editlist_gui_list); + if (row != -1) + { + ctcp = gtk_entry_get_text (GTK_ENTRY (igad)); + gtk_clist_set_text (GTK_CLIST (editlist_gui_list), row, 0, ctcp); + } +} + +static void +editlist_gui_addnew (GtkWidget * igad) +{ + int i; + gchar *nnew[2]; + + nnew[0] = _("*NEW*"); + nnew[1] = _("EDIT ME"); + + i = gtk_clist_append (GTK_CLIST (editlist_gui_list), nnew); + gtk_clist_select_row (GTK_CLIST (editlist_gui_list), i, 0); + gtk_clist_moveto (GTK_CLIST (editlist_gui_list), i, 0, 0.5, 0); +} + +static void +editlist_gui_delete (GtkWidget * igad) +{ + int row; + + row = gtkutil_clist_selection (editlist_gui_list); + if (row != -1) + { + gtk_clist_unselect_all (GTK_CLIST (editlist_gui_list)); + gtk_clist_remove (GTK_CLIST (editlist_gui_list), row); + } +} + +static void +editlist_gui_save (GtkWidget * igad) +{ + int fh, i = 0; + char buf[512]; + char *a, *b; + + fh = xchat_open_file (editlist_file, O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); + if (fh != -1) + { + while (1) + { + if (!gtk_clist_get_text (GTK_CLIST (editlist_gui_list), i, 0, &a)) + break; + gtk_clist_get_text (GTK_CLIST (editlist_gui_list), i, 1, &b); + snprintf (buf, sizeof (buf), "NAME %s\nCMD %s\n\n", a, b); + write (fh, buf, strlen (buf)); + i++; + } + close (fh); + gtk_widget_destroy (editlist_gui_window); + if (editlist_list == replace_list) + { + list_free (&replace_list); + list_loadconf (editlist_file, &replace_list, 0); + } else if (editlist_list == popup_list) + { + list_free (&popup_list); + list_loadconf (editlist_file, &popup_list, 0); + } else if (editlist_list == button_list) + { + GSList *list = sess_list; + struct session *sess; + list_free (&button_list); + list_loadconf (editlist_file, &button_list, 0); + while (list) + { + sess = (struct session *) list->data; + fe_buttons_update (sess); + list = list->next; + } + } else if (editlist_list == dlgbutton_list) + { + GSList *list = sess_list; + struct session *sess; + list_free (&dlgbutton_list); + list_loadconf (editlist_file, &dlgbutton_list, 0); + while (list) + { + sess = (struct session *) list->data; + fe_dlgbuttons_update (sess); + list = list->next; + } + } else if (editlist_list == ctcp_list) + { + list_free (&ctcp_list); + list_loadconf (editlist_file, &ctcp_list, 0); + } else if (editlist_list == command_list) + { + list_free (&command_list); + list_loadconf (editlist_file, &command_list, 0); + } else if (editlist_list == usermenu_list) + { + list_free (&usermenu_list); + list_loadconf (editlist_file, &usermenu_list, 0); + usermenu_update (); + } else + { + list_free (&urlhandler_list); + list_loadconf (editlist_file, &urlhandler_list, 0); + } + } +} + +static void +editlist_gui_help (GtkWidget * igad) +{ +/* if (editlist_help)*/ + fe_message (editlist_help, FE_MSG_INFO); +} + +static void +editlist_gui_sort (GtkWidget * igad) +{ + int row; + + row = gtkutil_clist_selection (editlist_gui_list); + if (row != -1) + gtk_clist_unselect_row (GTK_CLIST (editlist_gui_list), row, 0); + gtk_clist_sort (GTK_CLIST (editlist_gui_list)); +} + +static void +editlist_gui_movedown (GtkWidget * igad) +{ + int row; + char *temp; + + row = gtkutil_clist_selection (editlist_gui_list); + if (row != -1) + { + if (!gtk_clist_get_text (GTK_CLIST (editlist_gui_list), row + 1, 0, &temp)) + return; + gtk_clist_freeze (GTK_CLIST (editlist_gui_list)); + gtk_clist_swap_rows (GTK_CLIST (editlist_gui_list), row, row + 1); + gtk_clist_thaw (GTK_CLIST (editlist_gui_list)); + row++; + if (!gtk_clist_row_is_visible (GTK_CLIST (editlist_gui_list), row) != + GTK_VISIBILITY_FULL) + gtk_clist_moveto (GTK_CLIST (editlist_gui_list), row, 0, 0.9, 0); + } +} + +static void +editlist_gui_moveup (GtkWidget * igad) +{ + int row; + + row = gtkutil_clist_selection (editlist_gui_list); + if (row != -1 && row > 0) + { + gtk_clist_freeze (GTK_CLIST (editlist_gui_list)); + gtk_clist_swap_rows (GTK_CLIST (editlist_gui_list), row - 1, row); + gtk_clist_thaw (GTK_CLIST (editlist_gui_list)); + row--; + if (gtk_clist_row_is_visible (GTK_CLIST (editlist_gui_list), row) != + GTK_VISIBILITY_FULL) + gtk_clist_moveto (GTK_CLIST (editlist_gui_list), row, 0, 0.1, 0); + } +} + +static void +editlist_gui_close (void) +{ + editlist_gui_window = 0; +} + +void +editlist_gui_open (char *title1, char *title2, GSList * list, char *title, char *wmclass, + char *file, char *help) +{ + gchar *titles[2]; + GtkWidget *vbox, *hbox, *button; + + if (title1) + { + titles[0] = title1; + titles[1] = title2; + } else + { + titles[0] = _("Name"); + titles[1] = _("Command"); + } + + if (editlist_gui_window) + { + mg_bring_tofront (editlist_gui_window); + return; + } + + editlist_list = list; + editlist_file = file; + editlist_help = help; + + editlist_gui_window = + mg_create_generic_tab (wmclass, title, TRUE, FALSE, + editlist_gui_close, NULL, 450, 250, &vbox, 0); + + editlist_gui_list = gtkutil_clist_new (2, titles, vbox, GTK_POLICY_ALWAYS, + editlist_gui_row_selected, 0, + editlist_gui_row_unselected, 0, + GTK_SELECTION_BROWSE); + gtk_clist_set_column_width (GTK_CLIST (editlist_gui_list), 0, 90); + + hbox = gtk_hbox_new (0, 2); + gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 0); + gtk_widget_show (hbox); + + button = gtkutil_button (hbox, GTK_STOCK_GO_UP, 0, editlist_gui_moveup, + 0, _("Move Up")); + gtk_widget_set_usize (button, 100, 0); + + button = gtkutil_button (hbox, GTK_STOCK_GO_DOWN, 0, editlist_gui_movedown, + 0, _("Move Dn")); + gtk_widget_set_usize (button, 100, 0); + + button = gtk_vseparator_new (); + gtk_container_add (GTK_CONTAINER (hbox), button); + gtk_widget_show (button); + + button = gtkutil_button (hbox, GTK_STOCK_CANCEL, 0, gtkutil_destroy, + editlist_gui_window, _("Cancel")); + gtk_widget_set_usize (button, 100, 0); + + button = gtkutil_button (hbox, GTK_STOCK_SAVE, 0, editlist_gui_save, + 0, _("Save")); + gtk_widget_set_usize (button, 100, 0); + + hbox = gtk_hbox_new (0, 2); + gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 0); + gtk_widget_show (hbox); + + button = gtkutil_button (hbox, GTK_STOCK_ADD, 0, editlist_gui_addnew, + 0, _("Add New")); + gtk_widget_set_usize (button, 100, 0); + + button = gtkutil_button (hbox, GTK_STOCK_REMOVE, 0, editlist_gui_delete, + 0, _("Delete")); + gtk_widget_set_usize (button, 100, 0); + + button = gtk_vseparator_new (); + gtk_container_add (GTK_CONTAINER (hbox), button); + gtk_widget_show (button); + + button = gtkutil_button (hbox, GTK_STOCK_SORT_ASCENDING, 0, editlist_gui_sort, + 0, _("Sort")); + gtk_widget_set_usize (button, 100, 0); + + button = gtkutil_button (hbox, GTK_STOCK_HELP, 0, editlist_gui_help, + 0, _("Help")); + gtk_widget_set_usize (button, 100, 0); + + if (!help) + gtk_widget_set_sensitive (GTK_WIDGET (button), FALSE); + + hbox = gtk_hbox_new (0, 2); + gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 0); + gtk_widget_show (hbox); + + editlist_gui_entry_name = gtk_entry_new_with_max_length (82); + gtk_widget_set_usize (editlist_gui_entry_name, 96, 0); + gtk_signal_connect (GTK_OBJECT (editlist_gui_entry_name), "changed", + GTK_SIGNAL_FUNC (editlist_gui_handle_name), 0); + gtk_box_pack_start (GTK_BOX (hbox), editlist_gui_entry_name, 0, 0, 0); + gtk_widget_show (editlist_gui_entry_name); + + editlist_gui_entry_cmd = gtk_entry_new_with_max_length (255); + gtk_signal_connect (GTK_OBJECT (editlist_gui_entry_cmd), "changed", + GTK_SIGNAL_FUNC (editlist_gui_handle_cmd), 0); + gtk_container_add (GTK_CONTAINER (hbox), editlist_gui_entry_cmd); + gtk_widget_show (editlist_gui_entry_cmd); + + hbox = gtk_hbox_new (0, 2); + gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 0); + gtk_widget_show (hbox); + + editlist_gui_load (editlist_gui_list); + + gtk_widget_show (editlist_gui_window); +} diff --git a/src/fe-gtk/editlist.h b/src/fe-gtk/editlist.h new file mode 100644 index 00000000..f17cc2e0 --- /dev/null +++ b/src/fe-gtk/editlist.h @@ -0,0 +1 @@ +void editlist_gui_open (char *title1, char *title2, GSList * list, char *title, char *wmclass, char *file, char *help); diff --git a/src/fe-gtk/fe-gtk.c b/src/fe-gtk/fe-gtk.c new file mode 100644 index 00000000..5efcaeec --- /dev/null +++ b/src/fe-gtk/fe-gtk.c @@ -0,0 +1,1063 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> + +#include "fe-gtk.h" + +#include <gtk/gtkmain.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkprogressbar.h> +#include <gtk/gtkbox.h> +#include <gtk/gtklabel.h> +#include <gtk/gtktogglebutton.h> +#include <gtk/gtkmessagedialog.h> +#include <gtk/gtkversion.h> + +#include "../common/xchat.h" +#include "../common/fe.h" +#include "../common/util.h" +#include "../common/text.h" +#include "../common/cfgfiles.h" +#include "../common/xchatc.h" +#include "../common/plugin.h" +#include "gtkutil.h" +#include "maingui.h" +#include "pixmaps.h" +#include "joind.h" +#include "xtext.h" +#include "palette.h" +#include "menu.h" +#include "notifygui.h" +#include "textgui.h" +#include "fkeys.h" +#include "plugin-tray.h" +#include "urlgrab.h" + +#ifdef USE_XLIB +#include <gdk/gdkx.h> +#include <gtk/gtkinvisible.h> +#endif + +#ifdef USE_GTKSPELL +#include <gtk/gtktextview.h> +#endif + +#ifdef WIN32 +#include <windows.h> +#endif + +GdkPixmap *channelwin_pix; + + +#ifdef USE_XLIB + +static void +redraw_trans_xtexts (void) +{ + GSList *list = sess_list; + session *sess; + int done_main = FALSE; + + while (list) + { + sess = list->data; + if (GTK_XTEXT (sess->gui->xtext)->transparent) + { + if (!sess->gui->is_tab || !done_main) + gtk_xtext_refresh (GTK_XTEXT (sess->gui->xtext), 1); + if (sess->gui->is_tab) + done_main = TRUE; + } + list = list->next; + } +} + +static GdkFilterReturn +root_event_cb (GdkXEvent *xev, GdkEventProperty *event, gpointer data) +{ + static Atom at = None; + XEvent *xevent = (XEvent *)xev; + + if (xevent->type == PropertyNotify) + { + if (at == None) + at = XInternAtom (xevent->xproperty.display, "_XROOTPMAP_ID", True); + + if (at == xevent->xproperty.atom) + redraw_trans_xtexts (); + } + + return GDK_FILTER_CONTINUE; +} + +#endif + +/* === command-line parameter parsing : requires glib 2.6 === */ + +static char *arg_cfgdir = NULL; +static gint arg_show_autoload = 0; +static gint arg_show_config = 0; +static gint arg_show_version = 0; +static gint arg_minimize = 0; + +static const GOptionEntry gopt_entries[] = +{ + {"no-auto", 'a', 0, G_OPTION_ARG_NONE, &arg_dont_autoconnect, N_("Don't auto connect to servers"), NULL}, + {"cfgdir", 'd', 0, G_OPTION_ARG_STRING, &arg_cfgdir, N_("Use a different config directory"), "PATH"}, + {"no-plugins", 'n', 0, G_OPTION_ARG_NONE, &arg_skip_plugins, N_("Don't auto load any plugins"), NULL}, + {"plugindir", 'p', 0, G_OPTION_ARG_NONE, &arg_show_autoload, N_("Show plugin auto-load directory"), NULL}, + {"configdir", 'u', 0, G_OPTION_ARG_NONE, &arg_show_config, N_("Show user config directory"), NULL}, + {"url", 0, 0, G_OPTION_ARG_STRING, &arg_url, N_("Open an irc://server:port/channel URL"), "URL"}, +#ifndef WIN32 /* uses DBUS */ + {"command", 'c', 0, G_OPTION_ARG_STRING, &arg_command, N_("Execute command:"), "COMMAND"}, + {"existing", 'e', 0, G_OPTION_ARG_NONE, &arg_existing, N_("Open URL or execute command in an existing XChat"), NULL}, +#endif + {"minimize", 0, 0, G_OPTION_ARG_INT, &arg_minimize, N_("Begin minimized. Level 0=Normal 1=Iconified 2=Tray"), N_("level")}, + {"version", 'v', 0, G_OPTION_ARG_NONE, &arg_show_version, N_("Show version information"), NULL}, + {NULL} +}; + +int +fe_args (int argc, char *argv[]) +{ + GError *error = NULL; + GOptionContext *context; + +#ifdef ENABLE_NLS + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); +#endif + + context = g_option_context_new (NULL); + g_option_context_add_main_entries (context, gopt_entries, GETTEXT_PACKAGE); + g_option_context_add_group (context, gtk_get_option_group (FALSE)); + g_option_context_parse (context, &argc, &argv, &error); + + if (error) + { + if (error->message) + printf ("%s\n", error->message); + return 1; + } + + g_option_context_free (context); + + if (arg_show_version) + { + printf (PACKAGE_TARNAME" "PACKAGE_VERSION"\n"); + return 0; + } + + if (arg_show_autoload) + { +#ifdef WIN32 + /* see the chdir() below */ + char *sl, *exe = strdup (argv[0]); + sl = strrchr (exe, '\\'); + if (sl) + { + *sl = 0; + printf ("%s\\plugins\n", exe); + } +#else + printf ("%s\n", XCHATLIBDIR"/plugins"); +#endif + return 0; + } + + if (arg_show_config) + { + printf ("%s\n", get_xdir_fs ()); + return 0; + } + +#ifdef WIN32 + /* this is mainly for irc:// URL handling. When windows calls us from */ + /* I.E, it doesn't give an option of "Start in" directory, like short */ + /* cuts can. So we have to set the current dir manually, to the path */ + /* of the exe. */ + { + char *tmp = strdup (argv[0]); + char *sl; + + sl = strrchr (tmp, '\\'); + if (sl) + { + *sl = 0; + chdir (tmp); + } + free (tmp); + } +#endif + + if (arg_cfgdir) /* we want filesystem encoding */ + { + xdir_fs = strdup (arg_cfgdir); + if (xdir_fs[strlen (xdir_fs) - 1] == '/') + xdir_fs[strlen (xdir_fs) - 1] = 0; + g_free (arg_cfgdir); + } + + gtk_init (&argc, &argv); + +#ifdef USE_XLIB + gdk_window_set_events (gdk_get_default_root_window (), GDK_PROPERTY_CHANGE_MASK); + gdk_window_add_filter (gdk_get_default_root_window (), + (GdkFilterFunc)root_event_cb, NULL); +#endif + + return -1; +} + +const char cursor_color_rc[] = + "style \"xc-ib-st\"" + "{" +#ifdef USE_GTKSPELL + "GtkTextView::cursor-color=\"#%02x%02x%02x\"" +#else + "GtkEntry::cursor-color=\"#%02x%02x%02x\"" +#endif + "}" + "widget \"*.xchat-inputbox\" style : application \"xc-ib-st\""; + +GtkStyle * +create_input_style (GtkStyle *style) +{ + char buf[256]; + static int done_rc = FALSE; + + pango_font_description_free (style->font_desc); + style->font_desc = pango_font_description_from_string (prefs.font_normal); + + /* fall back */ + if (pango_font_description_get_size (style->font_desc) == 0) + { + snprintf (buf, sizeof (buf), _("Failed to open font:\n\n%s"), prefs.font_normal); + fe_message (buf, FE_MSG_ERROR); + pango_font_description_free (style->font_desc); + style->font_desc = pango_font_description_from_string ("sans 11"); + } + + if (prefs.style_inputbox && !done_rc) + { + done_rc = TRUE; + sprintf (buf, cursor_color_rc, (colors[COL_FG].red >> 8), + (colors[COL_FG].green >> 8), (colors[COL_FG].blue >> 8)); + gtk_rc_parse_string (buf); + } + + style->bg[GTK_STATE_NORMAL] = colors[COL_FG]; + style->base[GTK_STATE_NORMAL] = colors[COL_BG]; + style->text[GTK_STATE_NORMAL] = colors[COL_FG]; + + return style; +} + +void +fe_init (void) +{ + palette_load (); + key_init (); + pixmaps_init (); + + channelwin_pix = pixmap_load_from_file (prefs.background); + input_style = create_input_style (gtk_style_new ()); +} + +void +fe_main (void) +{ + gtk_main (); + + /* sleep for 3 seconds so any QUIT messages are not lost. The */ + /* GUI is closed at this point, so the user doesn't even know! */ + if (prefs.wait_on_exit) + sleep (3); +} + +void +fe_cleanup (void) +{ + /* it's saved when pressing OK in setup.c */ + /*palette_save ();*/ +} + +void +fe_exit (void) +{ + gtk_main_quit (); +} + +int +fe_timeout_add (int interval, void *callback, void *userdata) +{ + return g_timeout_add (interval, (GSourceFunc) callback, userdata); +} + +void +fe_timeout_remove (int tag) +{ + g_source_remove (tag); +} + +#ifdef WIN32 + +static void +log_handler (const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer unused_data) +{ + session *sess; + + if (getenv ("XCHAT_WARNING_IGNORE")) + return; + + sess = find_dialog (serv_list->data, "(warnings)"); + if (!sess) + sess = new_ircwindow (serv_list->data, "(warnings)", SESS_DIALOG, 0); + + PrintTextf (sess, "%s\t%s\n", log_domain, message); + if (getenv ("XCHAT_WARNING_ABORT")) + abort (); +} + +#endif + +/* install tray stuff */ + +static int +fe_idle (gpointer data) +{ + session *sess = sess_list->data; + + plugin_add (sess, NULL, NULL, tray_plugin_init, tray_plugin_deinit, NULL, FALSE); + + if (arg_minimize == 1) + gtk_window_iconify (GTK_WINDOW (sess->gui->window)); + else if (arg_minimize == 2) + tray_toggle_visibility (FALSE); + + return 0; +} + +void +fe_new_window (session *sess, int focus) +{ + int tab = FALSE; + + if (sess->type == SESS_DIALOG) + { + if (prefs.privmsgtab) + tab = TRUE; + } else + { + if (prefs.tabchannels) + tab = TRUE; + } + + mg_changui_new (sess, NULL, tab, focus); + +#ifdef WIN32 + g_log_set_handler ("GLib", G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING, (GLogFunc)log_handler, 0); + g_log_set_handler ("GLib-GObject", G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING, (GLogFunc)log_handler, 0); + g_log_set_handler ("Gdk", G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING, (GLogFunc)log_handler, 0); + g_log_set_handler ("Gtk", G_LOG_LEVEL_CRITICAL|G_LOG_LEVEL_WARNING, (GLogFunc)log_handler, 0); +#endif + + if (!sess_list->next) + g_idle_add (fe_idle, NULL); +} + +void +fe_new_server (struct server *serv) +{ + serv->gui = malloc (sizeof (struct server_gui)); + memset (serv->gui, 0, sizeof (struct server_gui)); +} + +void +fe_message (char *msg, int flags) +{ + GtkWidget *dialog; + int type = GTK_MESSAGE_WARNING; + + if (flags & FE_MSG_ERROR) + type = GTK_MESSAGE_ERROR; + if (flags & FE_MSG_INFO) + type = GTK_MESSAGE_INFO; + + dialog = gtk_message_dialog_new (GTK_WINDOW (parent_window), 0, type, + GTK_BUTTONS_OK, "%s", msg); + if (flags & FE_MSG_MARKUP) + gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (dialog), msg); + g_signal_connect (G_OBJECT (dialog), "response", + G_CALLBACK (gtk_widget_destroy), 0); + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + gtk_widget_show (dialog); + + if (flags & FE_MSG_WAIT) + gtk_dialog_run (GTK_DIALOG (dialog)); +} + +void +fe_idle_add (void *func, void *data) +{ + g_idle_add (func, data); +} + +void +fe_input_remove (int tag) +{ + g_source_remove (tag); +} + +int +fe_input_add (int sok, int flags, void *func, void *data) +{ + int tag, type = 0; + GIOChannel *channel; + +#ifdef WIN32 + if (flags & FIA_FD) + channel = g_io_channel_win32_new_fd (sok); + else + channel = g_io_channel_win32_new_socket (sok); +#else + channel = g_io_channel_unix_new (sok); +#endif + + if (flags & FIA_READ) + type |= G_IO_IN | G_IO_HUP | G_IO_ERR; + if (flags & FIA_WRITE) + type |= G_IO_OUT | G_IO_ERR; + if (flags & FIA_EX) + type |= G_IO_PRI; + + tag = g_io_add_watch (channel, type, (GIOFunc) func, data); + g_io_channel_unref (channel); + + return tag; +} + +void +fe_set_topic (session *sess, char *topic, char *stripped_topic) +{ + if (!sess->gui->is_tab || sess == current_tab) + { + gtk_entry_set_text (GTK_ENTRY (sess->gui->topic_entry), stripped_topic); + mg_set_topic_tip (sess); + } else + { + if (sess->res->topic_text) + free (sess->res->topic_text); + sess->res->topic_text = strdup (stripped_topic); + } +} + +void +fe_set_hilight (struct session *sess) +{ + if (sess->gui->is_tab) + fe_set_tab_color (sess, 3); /* set tab to blue */ + + if (prefs.input_flash_hilight) + fe_flash_window (sess); /* taskbar flash */ +} + +static void +fe_update_mode_entry (session *sess, GtkWidget *entry, char **text, char *new_text) +{ + if (!sess->gui->is_tab || sess == current_tab) + { + if (sess->gui->flag_wid[0]) /* channel mode buttons enabled? */ + gtk_entry_set_text (GTK_ENTRY (entry), new_text); + } else + { + if (sess->gui->is_tab) + { + if (*text) + free (*text); + *text = strdup (new_text); + } + } +} + +void +fe_update_channel_key (struct session *sess) +{ + fe_update_mode_entry (sess, sess->gui->key_entry, + &sess->res->key_text, sess->channelkey); + fe_set_title (sess); +} + +void +fe_update_channel_limit (struct session *sess) +{ + char tmp[16]; + + sprintf (tmp, "%d", sess->limit); + fe_update_mode_entry (sess, sess->gui->limit_entry, + &sess->res->limit_text, tmp); + fe_set_title (sess); +} + +int +fe_is_chanwindow (struct server *serv) +{ + if (!serv->gui->chanlist_window) + return 0; + return 1; +} + +int +fe_is_banwindow (struct session *sess) +{ + if (!sess->res->banlist_window) + return 0; + return 1; +} + +void +fe_notify_update (char *name) +{ + if (!name) + notify_gui_update (); +} + +void +fe_text_clear (struct session *sess, int lines) +{ + gtk_xtext_clear (sess->res->buffer, lines); +} + +void +fe_close_window (struct session *sess) +{ + if (sess->gui->is_tab) + mg_tab_close (sess); + else + gtk_widget_destroy (sess->gui->window); +} + +void +fe_progressbar_start (session *sess) +{ + if (!sess->gui->is_tab || current_tab == sess) + /* if it's the focused tab, create it for real! */ + mg_progressbar_create (sess->gui); + else + /* otherwise just remember to create on when it gets focused */ + sess->res->c_graph = TRUE; +} + +void +fe_progressbar_end (server *serv) +{ + GSList *list = sess_list; + session *sess; + + while (list) /* check all windows that use this server and * + * remove the connecting graph, if it has one. */ + { + sess = list->data; + if (sess->server == serv) + { + if (sess->gui->bar) + mg_progressbar_destroy (sess->gui); + sess->res->c_graph = FALSE; + } + list = list->next; + } +} + +void +fe_print_text (struct session *sess, char *text, time_t stamp) +{ + PrintTextRaw (sess->res->buffer, (unsigned char *)text, prefs.indent_nicks, stamp); + + if (!sess->new_data && sess != current_tab && + sess->gui->is_tab && !sess->nick_said && stamp == 0) + { + sess->new_data = TRUE; + if (sess->msg_said) + fe_set_tab_color (sess, 2); + else + fe_set_tab_color (sess, 1); + } +} + +void +fe_beep (void) +{ + gdk_beep (); +} + +#ifndef WIN32 +static int +lastlog_regex_cmp (char *a, regex_t *reg) +{ + return !regexec (reg, a, 1, NULL, REG_NOTBOL); +} +#endif + +void +fe_lastlog (session *sess, session *lastlog_sess, char *sstr, gboolean regexp) +{ +#ifndef WIN32 + regex_t reg; +#endif + + if (gtk_xtext_is_empty (sess->res->buffer)) + { + PrintText (lastlog_sess, _("Search buffer is empty.\n")); + return; + } + + if (!regexp) + { + gtk_xtext_lastlog (lastlog_sess->res->buffer, sess->res->buffer, + (void *) nocasestrstr, sstr); + return; + } + +#ifndef WIN32 + if (regcomp (®, sstr, REG_ICASE | REG_EXTENDED | REG_NOSUB) == 0) + { + gtk_xtext_lastlog (lastlog_sess->res->buffer, sess->res->buffer, + (void *) lastlog_regex_cmp, ®); + regfree (®); + } +#endif +} + +void +fe_set_lag (server *serv, int lag) +{ + GSList *list = sess_list; + session *sess; + gdouble per; + char lagtext[64]; + char lagtip[128]; + unsigned long nowtim; + + if (lag == -1) + { + if (!serv->lag_sent) + return; + nowtim = make_ping_time (); + lag = (nowtim - serv->lag_sent) / 100000; + } + + per = (double)((double)lag / (double)10); + if (per > 1.0) + per = 1.0; + + snprintf (lagtext, sizeof (lagtext) - 1, "%s%d.%ds", + serv->lag_sent ? "+" : "", lag / 10, lag % 10); + snprintf (lagtip, sizeof (lagtip) - 1, "Lag: %s%d.%d seconds", + serv->lag_sent ? "+" : "", lag / 10, lag % 10); + + while (list) + { + sess = list->data; + if (sess->server == serv) + { + if (sess->res->lag_tip) + free (sess->res->lag_tip); + sess->res->lag_tip = strdup (lagtip); + + if (!sess->gui->is_tab || current_tab == sess) + { + if (sess->gui->lagometer) + { + gtk_progress_bar_set_fraction ((GtkProgressBar *) sess->gui->lagometer, per); + add_tip (sess->gui->lagometer->parent, lagtip); + } + if (sess->gui->laginfo) + gtk_label_set_text ((GtkLabel *) sess->gui->laginfo, lagtext); + } else + { + sess->res->lag_value = per; + if (sess->res->lag_text) + free (sess->res->lag_text); + sess->res->lag_text = strdup (lagtext); + } + } + list = list->next; + } +} + +void +fe_set_throttle (server *serv) +{ + GSList *list = sess_list; + struct session *sess; + float per; + char tbuf[96]; + char tip[160]; + + per = (float) serv->sendq_len / 1024.0; + if (per > 1.0) + per = 1.0; + + while (list) + { + sess = list->data; + if (sess->server == serv) + { + snprintf (tbuf, sizeof (tbuf) - 1, _("%d bytes"), serv->sendq_len); + snprintf (tip, sizeof (tip) - 1, _("Network send queue: %d bytes"), serv->sendq_len); + + if (sess->res->queue_tip) + free (sess->res->queue_tip); + sess->res->queue_tip = strdup (tip); + + if (!sess->gui->is_tab || current_tab == sess) + { + if (sess->gui->throttlemeter) + { + gtk_progress_bar_set_fraction ((GtkProgressBar *) sess->gui->throttlemeter, per); + add_tip (sess->gui->throttlemeter->parent, tip); + } + if (sess->gui->throttleinfo) + gtk_label_set_text ((GtkLabel *) sess->gui->throttleinfo, tbuf); + } else + { + sess->res->queue_value = per; + if (sess->res->queue_text) + free (sess->res->queue_text); + sess->res->queue_text = strdup (tbuf); + } + } + list = list->next; + } +} + +void +fe_ctrl_gui (session *sess, fe_gui_action action, int arg) +{ + switch (action) + { + case FE_GUI_HIDE: + gtk_widget_hide (sess->gui->window); break; + case FE_GUI_SHOW: + gtk_widget_show (sess->gui->window); + gtk_window_present (GTK_WINDOW (sess->gui->window)); + break; + case FE_GUI_FOCUS: + mg_bring_tofront_sess (sess); break; + case FE_GUI_FLASH: + fe_flash_window (sess); break; + case FE_GUI_COLOR: + fe_set_tab_color (sess, arg); break; + case FE_GUI_ICONIFY: + gtk_window_iconify (GTK_WINDOW (sess->gui->window)); break; + case FE_GUI_MENU: + menu_bar_toggle (); /* toggle menubar on/off */ + break; + case FE_GUI_ATTACH: + mg_detach (sess, arg); /* arg: 0=toggle 1=detach 2=attach */ + break; + case FE_GUI_APPLY: + setup_apply_real (TRUE, TRUE); + } +} + +static void +dcc_saveas_cb (struct DCC *dcc, char *file) +{ + if (is_dcc (dcc)) + { + if (dcc->dccstat == STAT_QUEUED) + { + if (file) + dcc_get_with_destfile (dcc, file); + else if (dcc->resume_sent == 0) + dcc_abort (dcc->serv->front_session, dcc); + } + } +} + +void +fe_confirm (const char *message, void (*yesproc)(void *), void (*noproc)(void *), void *ud) +{ + /* warning, assuming fe_confirm is used by DCC only! */ + struct DCC *dcc = ud; + + if (dcc->file) + gtkutil_file_req (message, dcc_saveas_cb, ud, dcc->file, + FRF_WRITE|FRF_FILTERISINITIAL|FRF_NOASKOVERWRITE); +} + +int +fe_gui_info (session *sess, int info_type) +{ + switch (info_type) + { + case 0: /* window status */ +#if GTK_CHECK_VERSION(2,20,0) + if (!gtk_widget_get_visible (GTK_WIDGET (sess->gui->window))) +#else + if (!GTK_WIDGET_VISIBLE (GTK_WIDGET (sess->gui->window))) +#endif + return 2; /* hidden (iconified or systray) */ +#if GTK_CHECK_VERSION(2,4,0) + if (gtk_window_is_active (GTK_WINDOW (sess->gui->window))) +#else +#if GTK_CHECK_VERSION(2,2,0) + if (GTK_WINDOW (sess->gui->window)->is_active) +#endif +#endif + return 1; /* active/focused */ + + return 0; /* normal (no keyboard focus or behind a window) */ + } + + return -1; +} + +void * +fe_gui_info_ptr (session *sess, int info_type) +{ + switch (info_type) + { + case 0: /* native window pointer (for plugins) */ +#ifdef WIN32 + return GDK_WINDOW_HWND (sess->gui->window->window); +#else + return sess->gui->window; +#endif + break; + + case 1: /* GtkWindow * (for plugins) */ + return sess->gui->window; + } + return NULL; +} + +char * +fe_get_inputbox_contents (session *sess) +{ + /* not the current tab */ + if (sess->res->input_text) + return sess->res->input_text; + + /* current focused tab */ + return SPELL_ENTRY_GET_TEXT (sess->gui->input_box); +} + +int +fe_get_inputbox_cursor (session *sess) +{ + /* not the current tab (we don't remember the cursor pos) */ + if (sess->res->input_text) + return 0; + + /* current focused tab */ + return SPELL_ENTRY_GET_POS (sess->gui->input_box); +} + +void +fe_set_inputbox_cursor (session *sess, int delta, int pos) +{ + if (!sess->gui->is_tab || sess == current_tab) + { + if (delta) + pos += SPELL_ENTRY_GET_POS (sess->gui->input_box); + SPELL_ENTRY_SET_POS (sess->gui->input_box, pos); + } else + { + /* we don't support changing non-front tabs yet */ + } +} + +void +fe_set_inputbox_contents (session *sess, char *text) +{ + if (!sess->gui->is_tab || sess == current_tab) + { + SPELL_ENTRY_SET_TEXT (sess->gui->input_box, text); + } else + { + if (sess->res->input_text) + free (sess->res->input_text); + sess->res->input_text = strdup (text); + } +} + +#ifndef WIN32 + +static gboolean +try_browser (const char *browser, const char *arg, const char *url) +{ + const char *argv[4]; + char *path; + + path = g_find_program_in_path (browser); + if (!path) + return 0; + + argv[0] = path; + argv[1] = url; + argv[2] = NULL; + if (arg) + { + argv[1] = arg; + argv[2] = url; + argv[3] = NULL; + } + xchat_execv (argv); + g_free (path); + return 1; +} + +#endif + +static void +fe_open_url_inner (const char *url) +{ +#ifdef WIN32 + ShellExecute (0, "open", url, NULL, NULL, SW_SHOWNORMAL); +#else + /* universal desktop URL opener (from xdg-utils). Supports gnome,kde,xfce4. */ + if (try_browser ("xdg-open", NULL, url)) + return; + + /* try to detect GNOME */ + if (g_getenv ("GNOME_DESKTOP_SESSION_ID")) + { + if (try_browser ("gnome-open", NULL, url)) /* Gnome 2.4+ has this */ + return; + } + + /* try to detect KDE */ + if (g_getenv ("KDE_FULL_SESSION")) + { + if (try_browser ("kfmclient", "exec", url)) + return; + } + + /* everything failed, what now? just try firefox */ + if (try_browser ("firefox", NULL, url)) + return; + + /* fresh out of ideas... */ + try_browser ("mozilla", NULL, url); +#endif +} + +static void +fe_open_url_locale (const char *url) +{ +#ifndef WIN32 + if (url[0] != '/' && strchr (url, ':') == NULL) + { + url = g_strdup_printf ("http://%s", url); + fe_open_url_inner (url); + g_free ((char *)url); + return; + } +#endif + fe_open_url_inner (url); +} + +void +fe_open_url (const char *url) +{ + char *loc; + + if (prefs.utf8_locale) + { + fe_open_url_locale (url); + return; + } + + /* the OS expects it in "locale" encoding. This makes it work on + unix systems that use ISO-8859-x and Win32. */ + loc = g_locale_from_utf8 (url, -1, 0, 0, 0); + if (loc) + { + fe_open_url_locale (loc); + g_free (loc); + } +} + +void +fe_server_event (server *serv, int type, int arg) +{ + GSList *list = sess_list; + session *sess; + + while (list) + { + sess = list->data; + if (sess->server == serv && (current_tab == sess || !sess->gui->is_tab)) + { + session_gui *gui = sess->gui; + + switch (type) + { + case FE_SE_CONNECTING: /* connecting in progress */ + case FE_SE_RECONDELAY: /* reconnect delay begun */ + /* enable Disconnect item */ + gtk_widget_set_sensitive (gui->menu_item[MENU_ID_DISCONNECT], 1); + break; + + case FE_SE_CONNECT: + /* enable Disconnect and Away menu items */ + gtk_widget_set_sensitive (gui->menu_item[MENU_ID_AWAY], 1); + gtk_widget_set_sensitive (gui->menu_item[MENU_ID_DISCONNECT], 1); + break; + + case FE_SE_LOGGEDIN: /* end of MOTD */ + gtk_widget_set_sensitive (gui->menu_item[MENU_ID_JOIN], 1); + /* if number of auto-join channels is zero, open joind */ + if (arg == 0) + joind_open (serv); + break; + + case FE_SE_DISCONNECT: + /* disable Disconnect and Away menu items */ + gtk_widget_set_sensitive (gui->menu_item[MENU_ID_AWAY], 0); + gtk_widget_set_sensitive (gui->menu_item[MENU_ID_DISCONNECT], 0); + gtk_widget_set_sensitive (gui->menu_item[MENU_ID_JOIN], 0); + /* close the join-dialog, if one exists */ + joind_close (serv); + } + } + list = list->next; + } +} + +void +fe_get_file (const char *title, char *initial, + void (*callback) (void *userdata, char *file), void *userdata, + int flags) + +{ + /* OK: Call callback once per file, then once more with file=NULL. */ + /* CANCEL: Call callback once with file=NULL. */ + gtkutil_file_req (title, callback, userdata, initial, flags | FRF_FILTERISINITIAL); +} diff --git a/src/fe-gtk/fe-gtk.h b/src/fe-gtk/fe-gtk.h new file mode 100644 index 00000000..12516259 --- /dev/null +++ b/src/fe-gtk/fe-gtk.h @@ -0,0 +1,197 @@ +#include "../../config.h" + +#ifdef WIN32 +/* If you're compiling this for Windows, your release is un-official + * and not condoned. Please don't use the XChat name. Make up your + * own name! */ +#define DISPLAY_NAME "XChat-Unofficial" +#else +#define DISPLAY_NAME "XChat" +#endif + +#ifndef WIN32 +#include <sys/types.h> +#include <regex.h> +#endif + +#if defined(ENABLE_NLS) && !defined(_) +# include <libintl.h> +# define _(x) gettext(x) +# ifdef gettext_noop +# define N_(String) gettext_noop (String) +# else +# define N_(String) (String) +# endif +#endif +#if !defined(ENABLE_NLS) && defined(_) +# undef _ +# define N_(String) (String) +# define _(x) (x) +#endif + +#include <gtk/gtkwidget.h> +#include <gtk/gtkcontainer.h> +#include <gtk/gtksignal.h> + +#undef gtk_signal_connect +#define gtk_signal_connect g_signal_connect + +#define flag_t flag_wid[0] +#define flag_n flag_wid[1] +#define flag_s flag_wid[2] +#define flag_i flag_wid[3] +#define flag_p flag_wid[4] +#define flag_m flag_wid[5] +#define flag_l flag_wid[6] +#define flag_k flag_wid[7] +#define flag_b flag_wid[8] +#define NUM_FLAG_WIDS 9 + +struct server_gui +{ + GtkWidget *rawlog_window; + GtkWidget *rawlog_textlist; + + /* join dialog */ + GtkWidget *joind_win; + GtkWidget *joind_entry; + GtkWidget *joind_radio1; + GtkWidget *joind_radio2; + GtkWidget *joind_check; + + /* chanlist variables */ + GtkWidget *chanlist_wild; /* GtkEntry */ + GtkWidget *chanlist_window; + GtkWidget *chanlist_list; + GtkWidget *chanlist_label; + GtkWidget *chanlist_min_spin; /* minusers GtkSpinButton */ + GtkWidget *chanlist_refresh; /* buttons */ + GtkWidget *chanlist_join; + GtkWidget *chanlist_savelist; + GtkWidget *chanlist_search; + + GSList *chanlist_data_stored_rows; /* stored list so it can be resorted */ + GSList *chanlist_pending_rows; + gint chanlist_tag; + gint chanlist_flash_tag; + + gboolean chanlist_match_wants_channel; /* match in channel name */ + gboolean chanlist_match_wants_topic; /* match in topic */ + +#ifndef WIN32 + regex_t chanlist_match_regex; /* compiled regular expression here */ + unsigned int have_regex; +#endif + + guint chanlist_users_found_count; /* users total for all channels */ + guint chanlist_users_shown_count; /* users total for displayed channels */ + guint chanlist_channels_found_count; /* channel total for /LIST operation */ + guint chanlist_channels_shown_count; /* total number of displayed + channels */ + + int chanlist_maxusers; + int chanlist_minusers; + int chanlist_minusers_downloaded; /* used by LIST IRC command */ + int chanlist_search_type; /* 0=simple 1=pattern/wildcard 2=regexp */ + gboolean chanlist_caption_is_stale; +}; + +/* this struct is persistant even when delinking/relinking */ + +typedef struct restore_gui +{ + /* banlist stuff */ + GtkWidget *banlist_window; + GtkWidget *banlist_treeview; + GtkWidget *banlist_butRefresh; + + void *tab; /* (chan *) */ + + /* information stored when this tab isn't front-most */ + void *user_model; /* for filling the GtkTreeView */ + void *buffer; /* xtext_Buffer */ + char *input_text; /* input text buffer (while not-front tab) */ + char *topic_text; /* topic GtkEntry buffer */ + char *key_text; + char *limit_text; + gfloat old_ul_value; /* old userlist value (for adj) */ + gfloat lag_value; /* lag-o-meter */ + char *lag_text; /* lag-o-meter text */ + char *lag_tip; /* lag-o-meter tooltip */ + gfloat queue_value; /* outbound queue meter */ + char *queue_text; /* outbound queue text */ + char *queue_tip; /* outbound queue tooltip */ + short flag_wid_state[NUM_FLAG_WIDS]; + unsigned int c_graph:1; /* connecting graph, is there one? */ +} restore_gui; + +typedef struct session_gui +{ + GtkWidget + *xtext, + *vscrollbar, + *window, /* toplevel */ + *topic_entry, + *note_book, + *main_table, + *user_tree, /* GtkTreeView */ + *user_box, /* userlist box */ + *button_box_parent, + *button_box, /* userlist buttons' box */ + *dialogbutton_box, + *topicbutton_box, + *meter_box, /* all the meters inside this */ + *lagometer, + *laginfo, + *throttlemeter, + *throttleinfo, + *topic_bar, + *hpane_left, + *hpane_right, + *vpane_left, + *vpane_right, + *menu, + *bar, /* connecting progress bar */ + *nick_box, /* contains label to the left of input_box */ + *nick_label, + *op_xpm, /* icon to the left of nickname */ + *namelistinfo, /* label above userlist */ + *input_box, + *flag_wid[NUM_FLAG_WIDS], /* channelmode buttons */ + *limit_entry, /* +l */ + *key_entry; /* +k */ + +#define MENU_ID_NUM 12 + GtkWidget *menu_item[MENU_ID_NUM+1]; /* some items we may change state of */ + + void *chanview; /* chanview.h */ + + int bartag; /*connecting progressbar timeout */ + + int pane_left_size; /*last position of the pane*/ + int pane_right_size; + + guint16 is_tab; /* is tab or toplevel? */ + guint16 ul_hidden; /* userlist hidden? */ + +} session_gui; + +extern GdkPixmap *channelwin_pix; +extern GdkPixmap *dialogwin_pix; + + +#ifdef USE_GTKSPELL +char *SPELL_ENTRY_GET_TEXT (GtkWidget *entry); +#define SPELL_ENTRY_SET_TEXT(e,txt) gtk_text_buffer_set_text (gtk_text_view_get_buffer(GTK_TEXT_VIEW(e)),txt,-1); +#define SPELL_ENTRY_SET_EDITABLE(e,v) gtk_text_view_set_editable(GTK_TEXT_VIEW(e), v) +int SPELL_ENTRY_GET_POS (GtkWidget *entry); +void SPELL_ENTRY_SET_POS (GtkWidget *entry, int pos); +void SPELL_ENTRY_INSERT (GtkWidget *entry, const char *text, int len, int *pos); +#else +#define SPELL_ENTRY_GET_TEXT(e) (GTK_ENTRY(e)->text) +#define SPELL_ENTRY_SET_TEXT(e,txt) gtk_entry_set_text(GTK_ENTRY(e),txt) +#define SPELL_ENTRY_SET_EDITABLE(e,v) gtk_editable_set_editable(GTK_EDITABLE(e),v) +#define SPELL_ENTRY_GET_POS(e) gtk_editable_get_position(GTK_EDITABLE(e)) +#define SPELL_ENTRY_SET_POS(e,p) gtk_editable_set_position(GTK_EDITABLE(e),p); +#define SPELL_ENTRY_INSERT(e,t,l,p) gtk_editable_insert_text(GTK_EDITABLE(e),t,l,p) +#endif diff --git a/src/fe-gtk/fkeys.c b/src/fe-gtk/fkeys.c new file mode 100644 index 00000000..014b5cc3 --- /dev/null +++ b/src/fe-gtk/fkeys.c @@ -0,0 +1,1814 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> +#include <fcntl.h> +#include <ctype.h> + +#include "fe-gtk.h" + +#include <gtk/gtklabel.h> +#include <gtk/gtkeditable.h> +#include <gtk/gtkmenu.h> +#include <gtk/gtkmenuitem.h> +#include <gtk/gtkoptionmenu.h> +#include <gtk/gtkvbox.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkclist.h> +#include <gtk/gtknotebook.h> +#include <gtk/gtkcheckbutton.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkvscrollbar.h> + +#include "../common/xchat.h" +#include "../common/xchatc.h" +#include "../common/cfgfiles.h" +#include "../common/fe.h" +#include "../common/userlist.h" +#include "../common/outbound.h" +#include "../common/util.h" +#include "../common/text.h" +#include "../common/plugin.h" +#include <gdk/gdkkeysyms.h> +#include "gtkutil.h" +#include "menu.h" +#include "xtext.h" +#include "palette.h" +#include "maingui.h" +#include "textgui.h" +#include "fkeys.h" + +#ifdef USE_GTKSPELL +#include <gtk/gtktextview.h> +#endif + +static void replace_handle (GtkWidget * wid); +void key_action_tab_clean (void); + +/***************** Key Binding Code ******************/ + +/* NOTES: + + To add a new action: + 1) inc KEY_MAX_ACTIONS + 2) write the function at the bottom of this file (with all the others) + FIXME: Write about calling and returning + 3) Add it to key_actions + + --AGL + + */ + +/* Remember that the *number* of actions is this *plus* 1 --AGL */ +#define KEY_MAX_ACTIONS 14 +/* These are cp'ed from history.c --AGL */ +#define STATE_SHIFT GDK_SHIFT_MASK +#define STATE_ALT GDK_MOD1_MASK +#define STATE_CTRL GDK_CONTROL_MASK + +struct key_binding +{ + int keyval; /* GDK keynumber */ + char *keyname; /* String with the name of the function */ + int action; /* Index into key_actions */ + int mod; /* Flags of STATE_* above */ + char *data1, *data2; /* Pointers to strings, these must be freed */ + struct key_binding *next; +}; + +struct key_action +{ + int (*handler) (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2, + struct session * sess); + char *name; + char *help; +}; + +struct gcomp_data +{ + char data[CHANLEN]; + int elen; +}; + +static int key_load_kbs (char *); +static void key_load_defaults (); +static void key_save_kbs (char *); +static int key_action_handle_command (GtkWidget * wid, GdkEventKey * evt, + char *d1, char *d2, + struct session *sess); +static int key_action_page_switch (GtkWidget * wid, GdkEventKey * evt, + char *d1, char *d2, struct session *sess); +int key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2, + struct session *sess); +static int key_action_scroll_page (GtkWidget * wid, GdkEventKey * evt, + char *d1, char *d2, struct session *sess); +static int key_action_set_buffer (GtkWidget * wid, GdkEventKey * evt, + char *d1, char *d2, struct session *sess); +static int key_action_history_up (GtkWidget * wid, GdkEventKey * evt, + char *d1, char *d2, struct session *sess); +static int key_action_history_down (GtkWidget * wid, GdkEventKey * evt, + char *d1, char *d2, struct session *sess); +static int key_action_tab_comp (GtkWidget * wid, GdkEventKey * evt, char *d1, + char *d2, struct session *sess); +static int key_action_comp_chng (GtkWidget * wid, GdkEventKey * evt, char *d1, + char *d2, struct session *sess); +static int key_action_replace (GtkWidget * wid, GdkEventKey * evt, char *d1, + char *d2, struct session *sess); +static int key_action_move_tab_left (GtkWidget * wid, GdkEventKey * evt, + char *d1, char *d2, + struct session *sess); +static int key_action_move_tab_right (GtkWidget * wid, GdkEventKey * evt, + char *d1, char *d2, + struct session *sess); +static int key_action_move_tab_family_left (GtkWidget * wid, GdkEventKey * evt, + char *d1, char *d2, + struct session *sess); +static int key_action_move_tab_family_right (GtkWidget * wid, GdkEventKey * evt, + char *d1, char *d2, + struct session *sess); +static int key_action_put_history (GtkWidget * wid, GdkEventKey * evt, + char *d1, char *d2, + struct session *sess); + +static GtkWidget *key_dialog; +static struct key_binding *keys_root = NULL; + +static const struct key_action key_actions[KEY_MAX_ACTIONS + 1] = { + + {key_action_handle_command, "Run Command", + N_("The \002Run Command\002 action runs the data in Data 1 as if it has been typed into the entry box where you pressed the key sequence. Thus it can contain text (which will be sent to the channel/person), commands or user commands. When run all \002\\n\002 characters in Data 1 are used to deliminate seperate commands so it is possible to run more than one command. If you want a \002\\\002 in the actual text run then enter \002\\\\\002")}, + {key_action_page_switch, "Change Page", + N_("The \002Change Page\002 command switches between pages in the notebook. Set Data 1 to the page you want to switch to. If Data 2 is set to anything then the switch will be relative to the current position")}, + {key_action_insert, "Insert in Buffer", + N_("The \002Insert in Buffer\002 command will insert the contents of Data 1 into the entry where the key sequence was pressed at the current cursor position")}, + {key_action_scroll_page, "Scroll Page", + N_("The \002Scroll Page\002 command scrolls the text widget up or down one page or one line. Set Data 1 to either Up, Down, +1 or -1.")}, + {key_action_set_buffer, "Set Buffer", + N_("The \002Set Buffer\002 command sets the entry where the key sequence was entered to the contents of Data 1")}, + {key_action_history_up, "Last Command", + N_("The \002Last Command\002 command sets the entry to contain the last command entered - the same as pressing up in a shell")}, + {key_action_history_down, "Next Command", + N_("The \002Next Command\002 command sets the entry to contain the next command entered - the same as pressing down in a shell")}, + {key_action_tab_comp, "Complete nick/command", + N_("This command changes the text in the entry to finish an incomplete nickname or command. If Data 1 is set then double-tabbing in a string will select the last nick, not the next")}, + {key_action_comp_chng, "Change Selected Nick", + N_("This command scrolls up and down through the list of nicks. If Data 1 is set to anything it will scroll up, else it scrolls down")}, + {key_action_replace, "Check For Replace", + N_("This command checks the last word entered in the entry against the replace list and replaces it if it finds a match")}, + {key_action_move_tab_left, "Move front tab left", + N_("This command moves the front tab left by one")}, + {key_action_move_tab_right, "Move front tab right", + N_("This command moves the front tab right by one")}, + {key_action_move_tab_family_left, "Move tab family left", + N_("This command moves the current tab family to the left")}, + {key_action_move_tab_family_right, "Move tab family right", + N_("This command moves the current tab family to the right")}, + {key_action_put_history, "Push input line into history", + N_("Push input line into history but doesn't send to server")}, +}; + +void +key_init () +{ + keys_root = NULL; + if (key_load_kbs (NULL) == 1) + { + key_load_defaults (); + if (key_load_kbs (NULL) == 1) + fe_message (_("There was an error loading key" + " bindings configuration"), FE_MSG_ERROR); + } +} + +static char * +key_get_key_name (int keyval) +{ + return gdk_keyval_name (gdk_keyval_to_lower (keyval)); +} + +/* Ok, here are the NOTES + + key_handle_key_press now handles all the key presses and history_keypress is + now defunct. It goes thru the linked list keys_root and finds a matching + key. It runs the action func and switches on these values: + 0) Return + 1) Find next + 2) stop signal and return + + * history_keypress is now dead (and gone) + * key_handle_key_press now takes its role + * All the possible actions are in a struct called key_actions (in fkeys.c) + * it is made of {function, name, desc} + * key bindings can pass 2 *text* strings to the handler. If more options are nee + ded a format can be put on one of these options + * key actions are passed { + the entry widget + the Gdk event + data 1 + data 2 + session struct + } + * key bindings are stored in a linked list of key_binding structs + * which looks like { + int keyval; GDK keynumber + char *keyname; String with the name of the function + int action; Index into key_actions + int mod; Flags of STATE_* above + char *data1, *data2; Pointers to strings, these must be freed + struct key_binding *next; + } + * remember that is (data1 || data2) != NULL then they need to be free()'ed + + --AGL + + */ + +gboolean +key_handle_key_press (GtkWidget *wid, GdkEventKey *evt, session *sess) +{ + struct key_binding *kb, *last = NULL; + int keyval = evt->keyval; + int mod, n; + GSList *list; + + /* where did this event come from? */ + list = sess_list; + while (list) + { + sess = list->data; + if (sess->gui->input_box == wid) + { + if (sess->gui->is_tab) + sess = current_tab; + break; + } + list = list->next; + } + if (!list) + return FALSE; + current_sess = sess; + + if (plugin_emit_keypress (sess, evt->state, evt->keyval, evt->length, evt->string)) + return 1; + + /* maybe the plugin closed this tab? */ + if (!is_session (sess)) + return 1; + + mod = evt->state & (STATE_CTRL | STATE_ALT | STATE_SHIFT); + + kb = keys_root; + while (kb) + { + if (kb->keyval == keyval && kb->mod == mod) + { + if (kb->action < 0 || kb->action > KEY_MAX_ACTIONS) + return 0; + + /* Bump this binding to the top of the list */ + if (last != NULL) + { + last->next = kb->next; + kb->next = keys_root; + keys_root = kb; + } + /* Run the function */ + n = key_actions[kb->action].handler (wid, evt, kb->data1, + kb->data2, sess); + switch (n) + { + case 0: + return 1; + case 2: + g_signal_stop_emission_by_name (G_OBJECT (wid), + "key_press_event"); + return 1; + } + } + last = kb; + kb = kb->next; + } + + switch (keyval) + { + case GDK_space: + key_action_tab_clean (); + break; + +#if defined(USE_GTKSPELL) && !defined(WIN32) + /* gtktextview has no 'activate' event, so we trap ENTER here */ + case GDK_Return: + case GDK_KP_Enter: + if (!(evt->state & GDK_CONTROL_MASK)) + { + g_signal_stop_emission_by_name (G_OBJECT (wid), "key_press_event"); + mg_inputbox_cb (wid, sess->gui); + } +#endif + } + + return 0; +} + +/* Walks keys_root and free()'s everything */ +/*static void +key_free_all () +{ + struct key_binding *cur, *next; + + cur = keys_root; + while (cur) + { + next = cur->next; + if (cur->data1) + free (cur->data1); + if (cur->data2) + free (cur->data2); + free (cur); + cur = next; + } + keys_root = NULL; +}*/ + +/* Turns mod flags into a C-A-S string */ +static char * +key_make_mod_str (int mod, char *buf) +{ + int i = 0; + + if (mod & STATE_CTRL) + { + if (i) + buf[i++] = '-'; + buf[i++] = 'C'; + } + if (mod & STATE_ALT) + { + if (i) + buf[i++] = '-'; + buf[i++] = 'A'; + } + if (mod & STATE_SHIFT) + { + if (i) + buf[i++] = '-'; + buf[i++] = 'S'; + } + buf[i] = 0; + return buf; +} + +/* ***** GUI code here ******************* */ + +/* NOTE: The key_dialog defin is above --AGL */ +static GtkWidget *key_dialog_act_menu, *key_dialog_kb_clist; +static GtkWidget *key_dialog_tog_c, *key_dialog_tog_s, *key_dialog_tog_a; +static GtkWidget *key_dialog_ent_key, *key_dialog_ent_d1, *key_dialog_ent_d2; +static GtkWidget *key_dialog_text; + +static void +key_load_defaults () +{ + /* This is the default config */ +#define defcfg \ + "C\nPrior\nChange Page\nD1:-1\nD2:Relative\n\n"\ + "C\nNext\nChange Page\nD1:1\nD2:Relative\n\n"\ + "A\n9\nChange Page\nD1:9\nD2!\n\n"\ + "A\n8\nChange Page\nD1:8\nD2!\n\n"\ + "A\n7\nChange Page\nD1:7\nD2!\n\n"\ + "A\n6\nChange Page\nD1:6\nD2!\n\n"\ + "A\n5\nChange Page\nD1:5\nD2!\n\n"\ + "A\n4\nChange Page\nD1:4\nD2!\n\n"\ + "A\n3\nChange Page\nD1:3\nD2!\n\n"\ + "A\n2\nChange Page\nD1:2\nD2!\n\n"\ + "A\n1\nChange Page\nD1:1\nD2!\n\n"\ + "C\no\nInsert in Buffer\nD1:\nD2!\n\n"\ + "C\nb\nInsert in Buffer\nD1:\nD2!\n\n"\ + "C\nk\nInsert in Buffer\nD1:\nD2!\n\n"\ + "S\nNext\nChange Selected Nick\nD1!\nD2!\n\n"\ + "S\nPrior\nChange Selected Nick\nD1:Up\nD2!\n\n"\ + "None\nNext\nScroll Page\nD1:Down\nD2!\n\n"\ + "None\nPrior\nScroll Page\nD1:Up\nD2!\n\n"\ + "S\nDown\nScroll Page\nD1:+1\nD2!\n\n"\ + "S\nUp\nScroll Page\nD1:-1\nD2!\n\n"\ + "None\nDown\nNext Command\nD1!\nD2!\n\n"\ + "None\nUp\nLast Command\nD1!\nD2!\n\n"\ + "None\nTab\nComplete nick/command\nD1!\nD2!\n\n"\ + "None\nspace\nCheck For Replace\nD1!\nD2!\n\n"\ + "None\nReturn\nCheck For Replace\nD1!\nD2!\n\n"\ + "None\nKP_Enter\nCheck For Replace\nD1!\nD2!\n\n"\ + "C\nTab\nComplete nick/command\nD1:Up\nD2!\n\n"\ + "A\nLeft\nMove front tab left\nD1!\nD2!\n\n"\ + "A\nRight\nMove front tab right\nD1!\nD2!\n\n"\ + "CS\nPrior\nMove tab family left\nD1!\nD2!\n\n"\ + "CS\nNext\nMove tab family right\nD1!\nD2!\n\n"\ + "None\nF9\nRun Command\nD1:/GUI MENU TOGGLE\nD2!\n\n" + int fd; + + fd = xchat_open_file ("keybindings.conf", O_CREAT | O_TRUNC | O_WRONLY, 0x180, XOF_DOMODE); + if (fd < 0) + /* ???!!! */ + return; + + write (fd, defcfg, strlen (defcfg)); + close (fd); +} + +static void +key_dialog_close () +{ + key_dialog = NULL; + key_save_kbs (NULL); +} + +static void +key_dialog_add_new (GtkWidget * button, GtkCList * list) +{ + gchar *strs[] = { "", NULL, NULL, NULL, NULL }; + struct key_binding *kb; + + strs[1] = _("<none>"); + strs[2] = _("<none>"); + strs[3] = _("<none>"); + strs[4] = _("<none>"); + + kb = malloc (sizeof (struct key_binding)); + + kb->keyval = 0; + kb->keyname = NULL; + kb->action = -1; + kb->mod = 0; + kb->data1 = kb->data2 = NULL; + kb->next = keys_root; + + keys_root = kb; + + gtk_clist_set_row_data (GTK_CLIST (list), + gtk_clist_append (GTK_CLIST (list), strs), kb); + +} + +static void +key_dialog_delete (GtkWidget * button, GtkCList * list) +{ + struct key_binding *kb, *cur, *last; + int row = gtkutil_clist_selection ((GtkWidget *) list); + + if (row != -1) + { + kb = gtk_clist_get_row_data (list, row); + cur = keys_root; + last = NULL; + while (cur) + { + if (cur == kb) + { + if (last) + last->next = kb->next; + else + keys_root = kb->next; + + if (kb->data1) + free (kb->data1); + if (kb->data2) + free (kb->data2); + free (kb); + gtk_clist_remove (list, row); + return; + } + last = cur; + cur = cur->next; + } + printf ("*** key_dialog_delete: couldn't find kb in list!\n"); + /*if (getenv ("XCHAT_DEBUG")) + abort ();*/ + } +} + +static void +key_print_text (GtkXText *xtext, char *text) +{ + unsigned int old = prefs.timestamp; + prefs.timestamp = 0; /* temporarily disable stamps */ + gtk_xtext_clear (GTK_XTEXT (xtext)->buffer, 0); + PrintTextRaw (GTK_XTEXT (xtext)->buffer, text, 0, 0); + prefs.timestamp = old; +} + +static void +key_dialog_sel_act (GtkWidget * un, int num) +{ + int row = gtkutil_clist_selection (key_dialog_kb_clist); + struct key_binding *kb; + + if (row != -1) + { + kb = gtk_clist_get_row_data (GTK_CLIST (key_dialog_kb_clist), row); + kb->action = num; + gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 2, + _(key_actions[num].name)); + if (key_actions[num].help) + { + key_print_text (GTK_XTEXT (key_dialog_text), _(key_actions[num].help)); + } + } +} + +static void +key_dialog_sel_row (GtkWidget * clist, gint row, gint column, + GdkEventButton * evt, gpointer data) +{ + struct key_binding *kb = gtk_clist_get_row_data (GTK_CLIST (clist), row); + + if (kb == NULL) + { + printf ("*** key_dialog_sel_row: kb == NULL\n"); + abort (); + } + if (kb->action > -1 && kb->action <= KEY_MAX_ACTIONS) + { + gtk_option_menu_set_history (GTK_OPTION_MENU (key_dialog_act_menu), + kb->action); + if (key_actions[kb->action].help) + { + key_print_text (GTK_XTEXT (key_dialog_text), _(key_actions[kb->action].help)); + } + } + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (key_dialog_tog_c), + (kb->mod & STATE_CTRL) == STATE_CTRL); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (key_dialog_tog_s), + (kb->mod & STATE_SHIFT) == STATE_SHIFT); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (key_dialog_tog_a), + (kb->mod & STATE_ALT) == STATE_ALT); + + if (kb->data1) + gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_d1), kb->data1); + else + gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_d1), ""); + + if (kb->data2) + gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_d2), kb->data2); + else + gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_d2), ""); + + if (kb->keyname) + gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_key), kb->keyname); + else + gtk_entry_set_text (GTK_ENTRY (key_dialog_ent_key), ""); +} + +static void +key_dialog_tog_key (GtkWidget * tog, int kstate) +{ + int state = GTK_TOGGLE_BUTTON (tog)->active; + int row = gtkutil_clist_selection (key_dialog_kb_clist); + struct key_binding *kb; + char buf[32]; + + if (row == -1) + return; + + kb = gtk_clist_get_row_data (GTK_CLIST (key_dialog_kb_clist), row); + if (state) + kb->mod |= kstate; + else + kb->mod &= ~kstate; + + gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 0, + key_make_mod_str (kb->mod, buf)); +} + +static GtkWidget * +key_dialog_make_toggle (char *label, void *callback, void *option, + GtkWidget * box) +{ + GtkWidget *wid; + + wid = gtk_check_button_new_with_label (label); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), 0); + gtk_signal_connect (GTK_OBJECT (wid), "toggled", + GTK_SIGNAL_FUNC (callback), option); + gtk_box_pack_end (GTK_BOX (box), wid, 0, 0, 0); + gtk_widget_show (wid); + + return wid; +} + +static GtkWidget * +key_dialog_make_entry (char *label, char *act, void *callback, void *option, + GtkWidget * box) +{ + GtkWidget *wid, *hbox;; + + hbox = gtk_hbox_new (0, 2); + if (label) + { + wid = gtk_label_new (label); + gtk_widget_show (wid); + gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0); + } + wid = gtk_entry_new (); + if (act) + { + gtk_signal_connect (GTK_OBJECT (wid), act, GTK_SIGNAL_FUNC (callback), + option); + } + gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0); + gtk_widget_show (wid); + gtk_widget_show (hbox); + + gtk_box_pack_start (GTK_BOX (box), hbox, 0, 0, 0); + + return wid; +} + +static void +key_dialog_set_key (GtkWidget * entry, GdkEventKey * evt, void *none) +{ + int row = gtkutil_clist_selection (key_dialog_kb_clist); + struct key_binding *kb; + + gtk_entry_set_text (GTK_ENTRY (entry), ""); + + if (row == -1) + return; + + kb = gtk_clist_get_row_data (GTK_CLIST (key_dialog_kb_clist), row); + kb->keyval = evt->keyval; + kb->keyname = key_get_key_name (kb->keyval); + gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 1, kb->keyname); + gtk_entry_set_text (GTK_ENTRY (entry), kb->keyname); + g_signal_stop_emission_by_name (G_OBJECT (entry), "key_press_event"); +} + +static void +key_dialog_set_data (GtkWidget * entry, int d) +{ + const char *text = gtk_entry_get_text (GTK_ENTRY (entry)); + int row = gtkutil_clist_selection (key_dialog_kb_clist); + struct key_binding *kb; + char *buf; + int len = strlen (text); + + len++; + + if (row == -1) + return; + + kb = gtk_clist_get_row_data (GTK_CLIST (key_dialog_kb_clist), row); + if (d == 0) + { /* using data1 */ + if (kb->data1) + free (kb->data1); + buf = (char *) malloc (len); + memcpy (buf, text, len); + kb->data1 = buf; + gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 3, text); + } else + { + if (kb->data2) + free (kb->data2); + buf = (char *) malloc (len); + memcpy (buf, text, len); + kb->data2 = buf; + gtk_clist_set_text (GTK_CLIST (key_dialog_kb_clist), row, 4, text); + } +} + +void +key_dialog_show () +{ + GtkWidget *vbox, *hbox, *list, *vbox2, *wid, *wid2, *wid3, *hbox2; + struct key_binding *kb; + gchar *titles[] = { NULL, NULL, NULL, "1", "2" }; + char temp[32]; + int i; + + titles[0] = _("Mod"); + titles[1] = _("Key"); + titles[2] = _("Action"); + + if (key_dialog) + { + mg_bring_tofront (key_dialog); + return; + } + + key_dialog = + mg_create_generic_tab ("editkeys", _("XChat: Keyboard Shortcuts"), + TRUE, FALSE, key_dialog_close, NULL, 560, 330, &vbox, 0); + + hbox = gtk_hbox_new (0, 2); + gtk_box_pack_start (GTK_BOX (vbox), hbox, 1, 1, 0); + + list = gtkutil_clist_new (5, titles, hbox, 0, key_dialog_sel_row, 0, NULL, + 0, GTK_SELECTION_SINGLE); + gtk_widget_set_usize (list, 400, 0); + key_dialog_kb_clist = list; + + gtk_widget_show (hbox); + + kb = keys_root; + + gtk_clist_set_column_width (GTK_CLIST (list), 1, 50); + gtk_clist_set_column_width (GTK_CLIST (list), 2, 120); + gtk_clist_set_column_width (GTK_CLIST (list), 3, 50); + gtk_clist_set_column_width (GTK_CLIST (list), 4, 50); + + while (kb) + { + titles[0] = key_make_mod_str (kb->mod, temp); + titles[1] = kb->keyname; + if (kb->action < 0 || kb->action > KEY_MAX_ACTIONS) + titles[2] = _("<none>"); + else + titles[2] = key_actions[kb->action].name; + if (kb->data1) + titles[3] = kb->data1; + else + titles[3] = _("<none>"); + + if (kb->data2) + titles[4] = kb->data2; + else + titles[4] = _("<none>"); + + gtk_clist_set_row_data (GTK_CLIST (list), + gtk_clist_append (GTK_CLIST (list), titles), + kb); + + kb = kb->next; + } + + vbox2 = gtk_vbox_new (0, 2); + gtk_box_pack_end (GTK_BOX (hbox), vbox2, 1, 1, 0); + wid = gtk_button_new_with_label (_("Add New")); + gtk_box_pack_start (GTK_BOX (vbox2), wid, 0, 0, 0); + gtk_signal_connect (GTK_OBJECT (wid), "clicked", + GTK_SIGNAL_FUNC (key_dialog_add_new), list); + gtk_widget_show (wid); + wid = gtk_button_new_with_label (_("Delete")); + gtk_box_pack_start (GTK_BOX (vbox2), wid, 0, 0, 0); + gtk_signal_connect (GTK_OBJECT (wid), "clicked", + GTK_SIGNAL_FUNC (key_dialog_delete), list); + gtk_widget_show (wid); + gtk_widget_show (vbox2); + + wid = gtk_option_menu_new (); + wid2 = gtk_menu_new (); + + for (i = 0; i <= KEY_MAX_ACTIONS; i++) + { + wid3 = gtk_menu_item_new_with_label (_(key_actions[i].name)); + gtk_widget_show (wid3); + gtk_menu_shell_append (GTK_MENU_SHELL (wid2), wid3); + gtk_signal_connect (GTK_OBJECT (wid3), "activate", + GTK_SIGNAL_FUNC (key_dialog_sel_act), + GINT_TO_POINTER (i)); + } + + gtk_option_menu_set_menu (GTK_OPTION_MENU (wid), wid2); + gtk_option_menu_set_history (GTK_OPTION_MENU (wid), 0); + gtk_box_pack_end (GTK_BOX (vbox2), wid, 0, 0, 0); + gtk_widget_show (wid); + key_dialog_act_menu = wid; + + key_dialog_tog_s = key_dialog_make_toggle (_("Shift"), key_dialog_tog_key, + (void *) STATE_SHIFT, vbox2); + key_dialog_tog_a = key_dialog_make_toggle (_("Alt"), key_dialog_tog_key, + (void *) STATE_ALT, vbox2); + key_dialog_tog_c = key_dialog_make_toggle (_("Ctrl"), key_dialog_tog_key, + (void *) STATE_CTRL, vbox2); + + key_dialog_ent_key = key_dialog_make_entry (_("Key"), "key_press_event", + key_dialog_set_key, NULL, + vbox2); + + key_dialog_ent_d1 = key_dialog_make_entry (_("Data 1"), "activate", + key_dialog_set_data, NULL, + vbox2); + key_dialog_ent_d2 = key_dialog_make_entry (_("Data 2"), "activate", + key_dialog_set_data, + (void *) 1, vbox2); + + hbox2 = gtk_hbox_new (0, 2); + gtk_box_pack_end (GTK_BOX (vbox), hbox2, 0, 0, 1); + + wid = gtk_xtext_new (colors, 0); + gtk_xtext_set_tint (GTK_XTEXT (wid), prefs.tint_red, prefs.tint_green, prefs.tint_blue); + gtk_xtext_set_background (GTK_XTEXT (wid), + channelwin_pix, + prefs.transparent); + gtk_widget_set_usize (wid, 0, 75); + gtk_box_pack_start (GTK_BOX (hbox2), wid, 1, 1, 1); + gtk_xtext_set_font (GTK_XTEXT (wid), prefs.font_normal); + gtk_widget_show (wid); + + wid2 = gtk_vscrollbar_new (GTK_XTEXT (wid)->adj); + gtk_box_pack_start (GTK_BOX (hbox2), wid2, 0, 0, 0); + gtk_widget_show (wid2); + + gtk_widget_show (hbox2); + key_dialog_text = wid; + + gtk_widget_show_all (key_dialog); +} + +static void +key_save_kbs (char *fn) +{ + int fd, i; + char buf[512]; + struct key_binding *kb; + + if (!fn) + fd = xchat_open_file ("keybindings.conf", O_CREAT | O_TRUNC | O_WRONLY, + 0x180, XOF_DOMODE); + else + fd = xchat_open_file (fn, O_CREAT | O_TRUNC | O_WRONLY, + 0x180, XOF_DOMODE | XOF_FULLPATH); + if (fd < 0) + { + fe_message (_("Error opening keys config file\n"), FE_MSG_ERROR); + return; + } + write (fd, buf, + snprintf (buf, 510, "# XChat key bindings config file\n\n")); + + kb = keys_root; + i = 0; + + while (kb) + { + if (kb->keyval == -1 || kb->keyname == NULL || kb->action < 0) + { + kb = kb->next; + continue; + } + i = 0; + if (kb->mod & STATE_CTRL) + { + i++; + write (fd, "C", 1); + } + if (kb->mod & STATE_ALT) + { + i++; + write (fd, "A", 1); + } + if (kb->mod & STATE_SHIFT) + { + i++; + write (fd, "S", 1); + } + if (i == 0) + write (fd, "None\n", 5); + else + write (fd, "\n", 1); + + write (fd, buf, snprintf (buf, 510, "%s\n%s\n", kb->keyname, + key_actions[kb->action].name)); + if (kb->data1 && kb->data1[0]) + write (fd, buf, snprintf (buf, 510, "D1:%s\n", kb->data1)); + else + write (fd, "D1!\n", 4); + + if (kb->data2 && kb->data2[0]) + write (fd, buf, snprintf (buf, 510, "D2:%s\n", kb->data2)); + else + write (fd, "D2!\n", 4); + + write (fd, "\n", 1); + + kb = kb->next; + } + + close (fd); +} + +/* I just know this is going to be a nasty parse, if you think it's bugged + it almost certainly is so contact the XChat dev team --AGL */ + +static inline int +key_load_kbs_helper_mod (char *in, int *out) +{ + int n, len, mod = 0; + + /* First strip off the fluff */ + while (in[0] == ' ' || in[0] == '\t') + in++; + len = strlen (in); + while (in[len] == ' ' || in[len] == '\t') + { + in[len] = 0; + len--; + } + + if (strcmp (in, "None") == 0) + { + *out = 0; + return 0; + } + for (n = 0; n < len; n++) + { + switch (in[n]) + { + case 'C': + mod |= STATE_CTRL; + break; + case 'A': + mod |= STATE_ALT; + break; + case 'S': + mod |= STATE_SHIFT; + break; + default: + return 1; + } + } + + *out = mod; + return 0; +} + +/* These are just local defines to keep me sane --AGL */ + +#define KBSTATE_MOD 0 +#define KBSTATE_KEY 1 +#define KBSTATE_ACT 2 +#define KBSTATE_DT1 3 +#define KBSTATE_DT2 4 + +/* *** Warning, Warning! - massive function ahead! --AGL */ + +static int +key_load_kbs (char *filename) +{ + char *buf, *ibuf; + struct stat st; + struct key_binding *kb = NULL, *last = NULL; + int fd, len, pnt = 0, state = 0, n; + + if (filename == NULL) + fd = xchat_open_file ("keybindings.conf", O_RDONLY, 0, 0); + else + fd = xchat_open_file (filename, O_RDONLY, 0, XOF_FULLPATH); + if (fd < 0) + return 1; + if (fstat (fd, &st) != 0) + return 1; + ibuf = malloc (st.st_size); + read (fd, ibuf, st.st_size); + close (fd); + + while (buf_get_line (ibuf, &buf, &pnt, st.st_size)) + { + if (buf[0] == '#') + continue; + if (strlen (buf) == 0) + continue; + + switch (state) + { + case KBSTATE_MOD: + kb = (struct key_binding *) malloc (sizeof (struct key_binding)); + if (key_load_kbs_helper_mod (buf, &kb->mod)) + goto corrupt_file; + state = KBSTATE_KEY; + continue; + case KBSTATE_KEY: + /* First strip off the fluff */ + while (buf[0] == ' ' || buf[0] == '\t') + buf++; + len = strlen (buf); + while (buf[len] == ' ' || buf[len] == '\t') + { + buf[len] = 0; + len--; + } + + n = gdk_keyval_from_name (buf); + if (n == 0) + { + /* Unknown keyname, abort */ + if (last) + last->next = NULL; + free (ibuf); + ibuf = malloc (1024); + snprintf (ibuf, 1024, + _("Unknown keyname %s in key bindings config file\nLoad aborted, please fix %s/keybindings.conf\n"), + buf, get_xdir_utf8 ()); + fe_message (ibuf, FE_MSG_ERROR); + free (ibuf); + return 2; + } + kb->keyname = gdk_keyval_name (n); + kb->keyval = n; + + state = KBSTATE_ACT; + continue; + case KBSTATE_ACT: + /* First strip off the fluff */ + while (buf[0] == ' ' || buf[0] == '\t') + buf++; + len = strlen (buf); + while (buf[len] == ' ' || buf[len] == '\t') + { + buf[len] = 0; + len--; + } + + for (n = 0; n < KEY_MAX_ACTIONS + 1; n++) + { + if (strcmp (key_actions[n].name, buf) == 0) + { + kb->action = n; + break; + } + } + + if (n == KEY_MAX_ACTIONS + 1) + { + if (last) + last->next = NULL; + free (ibuf); + ibuf = malloc (1024); + snprintf (ibuf, 1024, + _("Unknown action %s in key bindings config file\nLoad aborted, Please fix %s/keybindings\n"), + buf, get_xdir_utf8 ()); + fe_message (ibuf, FE_MSG_ERROR); + free (ibuf); + return 3; + } + state = KBSTATE_DT1; + continue; + case KBSTATE_DT1: + case KBSTATE_DT2: + if (state == KBSTATE_DT1) + kb->data1 = kb->data2 = NULL; + + while (buf[0] == ' ' || buf[0] == '\t') + buf++; + + if (buf[0] != 'D') + { + free (ibuf); + ibuf = malloc (1024); + snprintf (ibuf, 1024, + _("Expecting Data line (beginning Dx{:|!}) but got:\n%s\n\nLoad aborted, Please fix %s/keybindings\n"), + buf, get_xdir_utf8 ()); + fe_message (ibuf, FE_MSG_ERROR); + free (ibuf); + return 4; + } + switch (buf[1]) + { + case '1': + if (state != KBSTATE_DT1) + goto corrupt_file; + break; + case '2': + if (state != KBSTATE_DT2) + goto corrupt_file; + break; + default: + goto corrupt_file; + } + + if (buf[2] == ':') + { + len = strlen (buf); + /* Add one for the NULL, subtract 3 for the "Dx:" */ + len++; + len -= 3; + if (state == KBSTATE_DT1) + { + kb->data1 = malloc (len); + memcpy (kb->data1, &buf[3], len); + } else + { + kb->data2 = malloc (len); + memcpy (kb->data2, &buf[3], len); + } + } else if (buf[2] == '!') + { + if (state == KBSTATE_DT1) + kb->data1 = NULL; + else + kb->data2 = NULL; + } + if (state == KBSTATE_DT1) + { + state = KBSTATE_DT2; + continue; + } else + { + if (last) + last->next = kb; + else + keys_root = kb; + last = kb; + + state = KBSTATE_MOD; + } + + continue; + } + } + if (last) + last->next = NULL; + free (ibuf); + return 0; + + corrupt_file: + /*if (getenv ("XCHAT_DEBUG")) + abort ();*/ + snprintf (ibuf, 1024, + _("Key bindings config file is corrupt, load aborted\n" + "Please fix %s/keybindings.conf\n"), + get_xdir_utf8 ()); + fe_message (ibuf, FE_MSG_ERROR); + free (ibuf); + return 5; +} + +/* ***** Key actions start here *********** */ + +/* See the NOTES above --AGL */ + +/* "Run command" */ +static int +key_action_handle_command (GtkWidget * wid, GdkEventKey * evt, char *d1, + char *d2, struct session *sess) +{ + int ii, oi, len; + char out[2048], d = 0; + + if (!d1) + return 0; + + len = strlen (d1); + + /* Replace each "\n" substring with '\n' */ + for (ii = oi = 0; ii < len; ii++) + { + d = d1[ii]; + if (d == '\\') + { + ii++; + d = d1[ii]; + if (d == 'n') + out[oi++] = '\n'; + else if (d == '\\') + out[oi++] = '\\'; + else + { + out[oi++] = '\\'; + out[oi++] = d; + } + continue; + } + out[oi++] = d; + } + out[oi] = 0; + + handle_multiline (sess, out, 0, 0); + return 0; +} + +static int +key_action_page_switch (GtkWidget * wid, GdkEventKey * evt, char *d1, + char *d2, struct session *sess) +{ + int len, i, num; + + if (!d1) + return 1; + + len = strlen (d1); + if (!len) + return 1; + + for (i = 0; i < len; i++) + { + if (d1[i] < '0' || d1[i] > '9') + { + if (i == 0 && (d1[i] == '+' || d1[i] == '-')) + continue; + else + return 1; + } + } + + num = atoi (d1); + if (!d2) + num--; + if (!d2 || d2[0] == 0) + mg_switch_page (FALSE, num); + else + mg_switch_page (TRUE, num); + return 0; +} + +int +key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2, + struct session *sess) +{ + int tmp_pos; + + if (!d1) + return 1; + + tmp_pos = SPELL_ENTRY_GET_POS (wid); + SPELL_ENTRY_INSERT (wid, d1, strlen (d1), &tmp_pos); + SPELL_ENTRY_SET_POS (wid, tmp_pos); + return 2; +} + +/* handles PageUp/Down keys */ +static int +key_action_scroll_page (GtkWidget * wid, GdkEventKey * evt, char *d1, + char *d2, struct session *sess) +{ + int value, end; + GtkAdjustment *adj; + enum scroll_type { PAGE_UP, PAGE_DOWN, LINE_UP, LINE_DOWN }; + int type = PAGE_DOWN; + + if (d1) + { + if (!strcasecmp (d1, "up")) + type = PAGE_UP; + else if (!strcasecmp (d1, "+1")) + type = LINE_DOWN; + else if (!strcasecmp (d1, "-1")) + type = LINE_UP; + } + + if (!sess) + return 0; + + adj = GTK_RANGE (sess->gui->vscrollbar)->adjustment; + end = adj->upper - adj->lower - adj->page_size; + + switch (type) + { + case LINE_UP: + value = adj->value - 1.0; + break; + + case LINE_DOWN: + value = adj->value + 1.0; + break; + + case PAGE_UP: + value = adj->value - (adj->page_size - 1); + break; + + default: /* PAGE_DOWN */ + value = adj->value + (adj->page_size - 1); + break; + } + + if (value < 0) + value = 0; + if (value > end) + value = end; + + gtk_adjustment_set_value (adj, value); + + return 0; +} + +static int +key_action_set_buffer (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2, + struct session *sess) +{ + if (!d1) + return 1; + if (d1[0] == 0) + return 1; + + SPELL_ENTRY_SET_TEXT (wid, d1); + SPELL_ENTRY_SET_POS (wid, -1); + + return 2; +} + +static int +key_action_history_up (GtkWidget * wid, GdkEventKey * ent, char *d1, char *d2, + struct session *sess) +{ + char *new_line; + + new_line = history_up (&sess->history, SPELL_ENTRY_GET_TEXT (wid)); + if (new_line) + { + SPELL_ENTRY_SET_TEXT (wid, new_line); + SPELL_ENTRY_SET_POS (wid, -1); + } + + return 2; +} + +static int +key_action_history_down (GtkWidget * wid, GdkEventKey * ent, char *d1, + char *d2, struct session *sess) +{ + char *new_line; + + new_line = history_down (&sess->history); + if (new_line) + { + SPELL_ENTRY_SET_TEXT (wid, new_line); + SPELL_ENTRY_SET_POS (wid, -1); + } + + return 2; +} + +/* old data that we reuse */ +static struct gcomp_data old_gcomp; + +/* work on the data, ie return only channels */ +static int +double_chan_cb (session *lsess, GList **list) +{ + if (lsess->type == SESS_CHANNEL) + *list = g_list_prepend(*list, lsess->channel); + return TRUE; +} + +/* convert a slist -> list. */ +static GList * +chanlist_double_list (GSList *inlist) +{ + GList *list = NULL; + g_slist_foreach(inlist, (GFunc)double_chan_cb, &list); + return list; +} + +/* handle commands */ +static int +double_cmd_cb (struct popup *pop, GList **list) +{ + *list = g_list_prepend(*list, pop->name); + return TRUE; +} + +/* convert a slist -> list. */ +static GList * +cmdlist_double_list (GSList *inlist) +{ + GList *list = NULL; + g_slist_foreach (inlist, (GFunc)double_cmd_cb, &list); + return list; +} + +static char * +gcomp_nick_func (char *data) +{ + if (data) + return ((struct User *)data)->nick; + return ""; +} + +void +key_action_tab_clean(void) +{ + if (old_gcomp.elen) + { + old_gcomp.data[0] = 0; + old_gcomp.elen = 0; + } +} + +/* Used in the followig completers */ +#define COMP_BUF 2048 + +/* For use in sorting the user list for completion */ +static int +talked_recent_cmp (struct User *a, struct User *b) +{ + if (a->lasttalk < b->lasttalk) + return -1; + + if (a->lasttalk > b->lasttalk) + return 1; + + return 0; +} + +static int +key_action_tab_comp (GtkWidget *t, GdkEventKey *entry, char *d1, char *d2, + struct session *sess) +{ + int len = 0, elen = 0, i = 0, cursor_pos, ent_start = 0, comp = 0, found = 0, + prefix_len, skip_len = 0, is_nick, is_cmd = 0; + char buf[COMP_BUF], ent[CHANLEN], *postfix = NULL, *result, *ch; + GList *list = NULL, *tmp_list = NULL; + const char *text; + GCompletion *gcomp = NULL; + + /* force the IM Context to reset */ + SPELL_ENTRY_SET_EDITABLE (t, FALSE); + SPELL_ENTRY_SET_EDITABLE (t, TRUE); + + text = SPELL_ENTRY_GET_TEXT (t); + if (text[0] == 0) + return 1; + + len = g_utf8_strlen (text, -1); /* must be null terminated */ + + cursor_pos = SPELL_ENTRY_GET_POS (t); + + buf[0] = 0; /* make sure we don't get garbage in the buffer */ + + /* handle "nick: " or "nick " or "#channel "*/ + ch = g_utf8_find_prev_char(text, g_utf8_offset_to_pointer(text,cursor_pos)); + if (ch && ch[0] == ' ') + { + skip_len++; + ch = g_utf8_find_prev_char(text, ch); + if (!ch) + return 2; + + cursor_pos = g_utf8_pointer_to_offset(text, ch); + if (cursor_pos && (g_utf8_get_char_validated(ch, -1) == ':' || + g_utf8_get_char_validated(ch, -1) == ',' || + g_utf8_get_char_validated(ch, -1) == prefs.nick_suffix[0])) + { + skip_len++; + } + else + cursor_pos = g_utf8_pointer_to_offset(text, g_utf8_offset_to_pointer(ch, 1)); + } + + comp = skip_len; + + /* store the text following the cursor for reinsertion later */ + if ((cursor_pos + skip_len) < len) + postfix = g_utf8_offset_to_pointer(text, cursor_pos + skip_len); + + for (ent_start = cursor_pos; ; --ent_start) + { + if (ent_start == 0) + break; + ch = g_utf8_offset_to_pointer(text, ent_start - 1); + if (ch && ch[0] == ' ') + break; + } + + if (ent_start == 0 && text[0] == prefs.cmdchar[0]) + { + ent_start++; + is_cmd = 1; + } + + prefix_len = ent_start; + elen = cursor_pos - ent_start; + + g_utf8_strncpy (ent, g_utf8_offset_to_pointer (text, prefix_len), elen); + + is_nick = (ent[0] == '#' || ent[0] == '&' || is_cmd) ? 0 : 1; + + if (sess->type == SESS_DIALOG && is_nick) + { + /* tab in a dialog completes the other person's name */ + if (rfc_ncasecmp (sess->channel, ent, elen) == 0) + { + result = sess->channel; + is_nick = 0; + } + else + return 2; + } + else + { + if (is_nick) + { + gcomp = g_completion_new((GCompletionFunc)gcomp_nick_func); + tmp_list = userlist_double_list(sess); /* create a temp list so we can free the memory */ + if (prefs.completion_sort == 1) /* sort in last-talk order? */ + tmp_list = g_list_sort (tmp_list, (void *)talked_recent_cmp); + } + else + { + gcomp = g_completion_new (NULL); + if (is_cmd) + { + tmp_list = cmdlist_double_list (command_list); + for(i = 0; xc_cmds[i].name != NULL ; i++) + { + tmp_list = g_list_prepend (tmp_list, xc_cmds[i].name); + } + tmp_list = plugin_command_list(tmp_list); + } + else + tmp_list = chanlist_double_list (sess_list); + } + tmp_list = g_list_reverse(tmp_list); /* make the comp entries turn up in the right order */ + g_completion_set_compare (gcomp, (GCompletionStrncmpFunc)rfc_ncasecmp); + if (tmp_list) + { + g_completion_add_items (gcomp, tmp_list); + g_list_free (tmp_list); + } + + if (comp && !(rfc_ncasecmp(old_gcomp.data, ent, old_gcomp.elen) == 0)) + { + key_action_tab_clean (); + comp = 0; + } + +#if GLIB_CHECK_VERSION(2,4,0) + list = g_completion_complete_utf8 (gcomp, comp ? old_gcomp.data : ent, &result); +#else + list = g_completion_complete (gcomp, comp ? old_gcomp.data : ent, &result); +#endif + + if (result == NULL) /* No matches found */ + { + g_completion_free(gcomp); + return 2; + } + + if (comp) /* existing completion */ + { + while(list) /* find the current entry */ + { + if(rfc_ncasecmp(list->data, ent, elen) == 0) + { + found = 1; + break; + } + list = list->next; + } + + if (found) + { + if (!(d1 && d1[0])) /* not holding down shift */ + { + if (g_list_next(list) == NULL) + list = g_list_first(list); + else + list = g_list_next(list); + } + else + { + if (g_list_previous(list) == NULL) + list = g_list_last(list); + else + list = g_list_previous(list); + } + g_free(result); + result = (char*)list->data; + } + else + { + g_free(result); + g_completion_free(gcomp); + return 2; + } + } + else + { + strcpy(old_gcomp.data, ent); + old_gcomp.elen = elen; + + /* Get the first nick and put out the data for future nickcompletes */ + if (prefs.completion_amount && g_list_length (list) <= prefs.completion_amount) + { + g_free(result); + result = (char*)list->data; + } + else + { + /* bash style completion */ + if (g_list_next(list) != NULL) + { + if (strlen (result) > elen) /* the largest common prefix is larger than nick, change the data */ + { + if (prefix_len) + g_utf8_strncpy (buf, text, prefix_len); + strncat (buf, result, COMP_BUF - prefix_len); + cursor_pos = strlen (buf); + g_free(result); +#if !GLIB_CHECK_VERSION(2,4,0) + g_utf8_validate (buf, -1, (const gchar **)&result); + (*result) = 0; +#endif + if (postfix) + { + strcat (buf, " "); + strncat (buf, postfix, COMP_BUF - cursor_pos -1); + } + SPELL_ENTRY_SET_TEXT (t, buf); + SPELL_ENTRY_SET_POS (t, g_utf8_pointer_to_offset(buf, buf + cursor_pos)); + buf[0] = 0; + } + else + g_free(result); + while (list) + { + len = strlen (buf); /* current buffer */ + elen = strlen (list->data); /* next item to add */ + if (len + elen + 2 >= COMP_BUF) /* +2 is space + null */ + { + PrintText (sess, buf); + buf[0] = 0; + len = 0; + } + strcpy (buf + len, (char *) list->data); + strcpy (buf + len + elen, " "); + list = list->next; + } + PrintText (sess, buf); + g_completion_free(gcomp); + return 2; + } + /* Only one matching entry */ + g_free(result); + result = list->data; + } + } + } + + if(result) + { + if (prefix_len) + g_utf8_strncpy(buf, text, prefix_len); + strncat (buf, result, COMP_BUF - (prefix_len + 3)); /* make sure nicksuffix and space fits */ + if(!prefix_len && is_nick) + strcat (buf, &prefs.nick_suffix[0]); + strcat (buf, " "); + cursor_pos = strlen (buf); + if (postfix) + strncat (buf, postfix, COMP_BUF - cursor_pos - 2); + SPELL_ENTRY_SET_TEXT (t, buf); + SPELL_ENTRY_SET_POS (t, g_utf8_pointer_to_offset(buf, buf + cursor_pos)); + } + if (gcomp) + g_completion_free(gcomp); + return 2; +} +#undef COMP_BUF + +static int +key_action_comp_chng (GtkWidget * wid, GdkEventKey * ent, char *d1, char *d2, + struct session *sess) +{ + key_action_tab_comp(wid, ent, d1, d2, sess); + return 2; +} + + +static int +key_action_replace (GtkWidget * wid, GdkEventKey * ent, char *d1, char *d2, + struct session *sess) +{ + replace_handle (wid); + return 1; +} + + +static int +key_action_move_tab_left (GtkWidget * wid, GdkEventKey * ent, char *d1, + char *d2, struct session *sess) +{ + mg_move_tab (sess, +1); + return 2; /* don't allow default action */ +} + +static int +key_action_move_tab_right (GtkWidget * wid, GdkEventKey * ent, char *d1, + char *d2, struct session *sess) +{ + mg_move_tab (sess, -1); + return 2; /* -''- */ +} + +static int +key_action_move_tab_family_left (GtkWidget * wid, GdkEventKey * ent, char *d1, + char *d2, struct session *sess) +{ + mg_move_tab_family (sess, +1); + return 2; /* don't allow default action */ +} + +static int +key_action_move_tab_family_right (GtkWidget * wid, GdkEventKey * ent, char *d1, + char *d2, struct session *sess) +{ + mg_move_tab_family (sess, -1); + return 2; /* -''- */ +} + +static int +key_action_put_history (GtkWidget * wid, GdkEventKey * ent, char *d1, + char *d2, struct session *sess) +{ + history_add (&sess->history, SPELL_ENTRY_GET_TEXT (wid)); + SPELL_ENTRY_SET_TEXT (wid, ""); + return 2; /* -''- */ +} + + +/* -------- */ + + +#define STATE_SHIFT GDK_SHIFT_MASK +#define STATE_ALT GDK_MOD1_MASK +#define STATE_CTRL GDK_CONTROL_MASK + +static void +replace_handle (GtkWidget *t) +{ + const char *text, *postfix_pnt; + struct popup *pop; + GSList *list = replace_list; + char word[256]; + char postfix[256]; + char outbuf[4096]; + int c, len, xlen; + + text = SPELL_ENTRY_GET_TEXT (t); + + len = strlen (text); + if (len < 1) + return; + + for (c = len - 1; c > 0; c--) + { + if (text[c] == ' ') + break; + } + if (text[c] == ' ') + c++; + xlen = c; + if (len - c >= (sizeof (word) - 12)) + return; + if (len - c < 1) + return; + memcpy (word, &text[c], len - c); + word[len - c] = 0; + len = strlen (word); + if (word[0] == '\'' && word[len] == '\'') + return; + postfix_pnt = NULL; + for (c = 0; c < len; c++) + { + if (word[c] == '\'') + { + postfix_pnt = &word[c + 1]; + word[c] = 0; + break; + } + } + + if (postfix_pnt != NULL) + { + if (strlen (postfix_pnt) > sizeof (postfix) - 12) + return; + strcpy (postfix, postfix_pnt); + } + while (list) + { + pop = (struct popup *) list->data; + if (strcmp (pop->name, word) == 0) + { + memcpy (outbuf, text, xlen); + outbuf[xlen] = 0; + if (postfix_pnt == NULL) + snprintf (word, sizeof (word), "%s", pop->cmd); + else + snprintf (word, sizeof (word), "%s%s", pop->cmd, postfix); + strcat (outbuf, word); + SPELL_ENTRY_SET_TEXT (t, outbuf); + SPELL_ENTRY_SET_POS (t, -1); + return; + } + list = list->next; + } +} + diff --git a/src/fe-gtk/fkeys.h b/src/fe-gtk/fkeys.h new file mode 100644 index 00000000..20cd4c73 --- /dev/null +++ b/src/fe-gtk/fkeys.h @@ -0,0 +1,5 @@ +void key_init (void); +void key_dialog_show (void); +int key_handle_key_press (GtkWidget * wid, GdkEventKey * evt, session *sess); +int key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2, + session *sess); diff --git a/src/fe-gtk/gtkutil.c b/src/fe-gtk/gtkutil.c new file mode 100644 index 00000000..63ab491b --- /dev/null +++ b/src/fe-gtk/gtkutil.c @@ -0,0 +1,675 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ +#define _FILE_OFFSET_BITS 64 /* allow selection of large files */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include "fe-gtk.h" + +#include <gtk/gtkbutton.h> +#include <gtk/gtkclist.h> +#include <gtk/gtkscrolledwindow.h> +#include <gtk/gtkmessagedialog.h> +#include <gtk/gtkwindow.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkimage.h> +#include <gtk/gtktooltips.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkspinbutton.h> +#include <gtk/gtkclipboard.h> +#include <gtk/gtktreeview.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtkcellrenderertext.h> +#include <gtk/gtkcellrenderertoggle.h> +#include <gtk/gtkversion.h> +#include <gtk/gtkfilechooserdialog.h> + +#include "../common/xchat.h" +#include "../common/fe.h" +#include "../common/util.h" +#include "gtkutil.h" +#include "pixmaps.h" + +/* gtkutil.c, just some gtk wrappers */ + +extern void path_part (char *file, char *path, int pathlen); + + +struct file_req +{ + GtkWidget *dialog; + void *userdata; + filereqcallback callback; + int flags; /* FRF_* flags */ +}; + +static char last_dir[256] = ""; + + +static void +gtkutil_file_req_destroy (GtkWidget * wid, struct file_req *freq) +{ + freq->callback (freq->userdata, NULL); + free (freq); +} + +static void +gtkutil_check_file (char *file, struct file_req *freq) +{ + struct stat st; + int axs = FALSE; + + path_part (file, last_dir, sizeof (last_dir)); + + /* check if the file is readable or writable */ + if (freq->flags & FRF_WRITE) + { + if (access (last_dir, W_OK) == 0) + axs = TRUE; + } else + { + if (stat (file, &st) != -1) + { + if (!S_ISDIR (st.st_mode) || (freq->flags & FRF_CHOOSEFOLDER)) + axs = TRUE; + } + } + + if (axs) + { + char *utf8_file; + /* convert to UTF8. It might be converted back to locale by + server.c's g_convert */ + utf8_file = xchat_filename_to_utf8 (file, -1, NULL, NULL, NULL); + if (utf8_file) + { + freq->callback (freq->userdata, utf8_file); + g_free (utf8_file); + } else + { + fe_message ("Filename encoding is corrupt.", FE_MSG_ERROR); + } + } else + { + if (freq->flags & FRF_WRITE) + fe_message (_("Cannot write to that file."), FE_MSG_ERROR); + else + fe_message (_("Cannot read that file."), FE_MSG_ERROR); + } +} + +static void +gtkutil_file_req_done (GtkWidget * wid, struct file_req *freq) +{ + GSList *files, *cur; + GtkFileChooser *fs = GTK_FILE_CHOOSER (freq->dialog); + + if (freq->flags & FRF_MULTIPLE) + { + files = cur = gtk_file_chooser_get_filenames (fs); + while (cur) + { + gtkutil_check_file (cur->data, freq); + g_free (cur->data); + cur = cur->next; + } + if (files) + g_slist_free (files); + } else + { + if (freq->flags & FRF_CHOOSEFOLDER) + gtkutil_check_file (gtk_file_chooser_get_current_folder (fs), freq); + else + gtkutil_check_file (gtk_file_chooser_get_filename (fs), freq); + } + + /* this should call the "destroy" cb, where we free(freq) */ + gtk_widget_destroy (freq->dialog); +} + +static void +gtkutil_file_req_response (GtkWidget *dialog, gint res, struct file_req *freq) +{ + switch (res) + { + case GTK_RESPONSE_ACCEPT: + gtkutil_file_req_done (dialog, freq); + break; + + case GTK_RESPONSE_CANCEL: + /* this should call the "destroy" cb, where we free(freq) */ + gtk_widget_destroy (freq->dialog); + } +} + +void +gtkutil_file_req (const char *title, void *callback, void *userdata, char *filter, + int flags) +{ + struct file_req *freq; + GtkWidget *dialog; + extern char *get_xdir_fs (void); + + if (flags & FRF_WRITE) + { + dialog = gtk_file_chooser_dialog_new (title, NULL, + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + if (filter && filter[0]) /* filter becomes initial name when saving */ + { + char temp[1024]; + path_part (filter, temp, sizeof (temp)); + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), temp); + gtk_file_chooser_set_current_name (GTK_FILE_CHOOSER (dialog), file_part (filter)); + } +#if GTK_CHECK_VERSION(2,8,0) + if (!(flags & FRF_NOASKOVERWRITE)) + gtk_file_chooser_set_do_overwrite_confirmation (GTK_FILE_CHOOSER (dialog), TRUE); +#endif + } + else + dialog = gtk_file_chooser_dialog_new (title, NULL, + GTK_FILE_CHOOSER_ACTION_OPEN, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + NULL); + if (flags & FRF_MULTIPLE) + gtk_file_chooser_set_select_multiple (GTK_FILE_CHOOSER (dialog), TRUE); + if (last_dir[0]) + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), last_dir); + if (flags & FRF_ADDFOLDER) + gtk_file_chooser_add_shortcut_folder (GTK_FILE_CHOOSER (dialog), + get_xdir_fs (), NULL); + if (flags & FRF_CHOOSEFOLDER) + { + gtk_file_chooser_set_action (GTK_FILE_CHOOSER (dialog), GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), filter); + } + else + { + if (filter && (flags & FRF_FILTERISINITIAL)) + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), filter); + } + + freq = malloc (sizeof (struct file_req)); + freq->dialog = dialog; + freq->flags = flags; + freq->callback = callback; + freq->userdata = userdata; + + g_signal_connect (G_OBJECT (dialog), "response", + G_CALLBACK (gtkutil_file_req_response), freq); + g_signal_connect (G_OBJECT (dialog), "destroy", + G_CALLBACK (gtkutil_file_req_destroy), (gpointer) freq); + gtk_widget_show (dialog); +} + +void +gtkutil_destroy (GtkWidget * igad, GtkWidget * dgad) +{ + gtk_widget_destroy (dgad); +} + +static void +gtkutil_get_str_response (GtkDialog *dialog, gint arg1, gpointer entry) +{ + void (*callback) (int cancel, char *text, void *user_data); + char *text; + void *user_data; + + text = (char *) gtk_entry_get_text (GTK_ENTRY (entry)); + callback = g_object_get_data (G_OBJECT (dialog), "cb"); + user_data = g_object_get_data (G_OBJECT (dialog), "ud"); + + switch (arg1) + { + case GTK_RESPONSE_REJECT: + callback (TRUE, text, user_data); + gtk_widget_destroy (GTK_WIDGET (dialog)); + break; + case GTK_RESPONSE_ACCEPT: + callback (FALSE, text, user_data); + gtk_widget_destroy (GTK_WIDGET (dialog)); + break; + } +} + +static void +gtkutil_str_enter (GtkWidget *entry, GtkWidget *dialog) +{ + gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT); +} + +void +fe_get_str (char *msg, char *def, void *callback, void *userdata) +{ + GtkWidget *dialog; + GtkWidget *entry; + GtkWidget *hbox; + GtkWidget *label; + + dialog = gtk_dialog_new_with_buttons (msg, NULL, 0, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + NULL); + gtk_box_set_homogeneous (GTK_BOX (GTK_DIALOG (dialog)->vbox), TRUE); + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + hbox = gtk_hbox_new (TRUE, 0); + + g_object_set_data (G_OBJECT (dialog), "cb", callback); + g_object_set_data (G_OBJECT (dialog), "ud", userdata); + + entry = gtk_entry_new (); + g_signal_connect (G_OBJECT (entry), "activate", + G_CALLBACK (gtkutil_str_enter), dialog); + gtk_entry_set_text (GTK_ENTRY (entry), def); + gtk_box_pack_end (GTK_BOX (hbox), entry, 0, 0, 0); + + label = gtk_label_new (msg); + gtk_box_pack_end (GTK_BOX (hbox), label, 0, 0, 0); + + g_signal_connect (G_OBJECT (dialog), "response", + G_CALLBACK (gtkutil_get_str_response), entry); + + gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), hbox); + + gtk_widget_show_all (dialog); +} + +static void +gtkutil_get_number_response (GtkDialog *dialog, gint arg1, gpointer spin) +{ + void (*callback) (int cancel, int value, void *user_data); + int num; + void *user_data; + + num = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (spin)); + callback = g_object_get_data (G_OBJECT (dialog), "cb"); + user_data = g_object_get_data (G_OBJECT (dialog), "ud"); + + switch (arg1) + { + case GTK_RESPONSE_REJECT: + callback (TRUE, num, user_data); + gtk_widget_destroy (GTK_WIDGET (dialog)); + break; + case GTK_RESPONSE_ACCEPT: + callback (FALSE, num, user_data); + gtk_widget_destroy (GTK_WIDGET (dialog)); + break; + } +} + +void +fe_get_int (char *msg, int def, void *callback, void *userdata) +{ + GtkWidget *dialog; + GtkWidget *spin; + GtkWidget *hbox; + GtkWidget *label; + GtkAdjustment *adj; + + dialog = gtk_dialog_new_with_buttons (msg, NULL, 0, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + NULL); + gtk_box_set_homogeneous (GTK_BOX (GTK_DIALOG (dialog)->vbox), TRUE); + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + hbox = gtk_hbox_new (TRUE, 0); + + g_object_set_data (G_OBJECT (dialog), "cb", callback); + g_object_set_data (G_OBJECT (dialog), "ud", userdata); + + spin = gtk_spin_button_new (NULL, 1, 0); + adj = gtk_spin_button_get_adjustment ((GtkSpinButton*)spin); + adj->lower = 0; + adj->upper = 1024; + adj->step_increment = 1; + gtk_adjustment_changed (adj); + gtk_spin_button_set_value ((GtkSpinButton*)spin, def); + gtk_box_pack_end (GTK_BOX (hbox), spin, 0, 0, 0); + + label = gtk_label_new (msg); + gtk_box_pack_end (GTK_BOX (hbox), label, 0, 0, 0); + + g_signal_connect (G_OBJECT (dialog), "response", + G_CALLBACK (gtkutil_get_number_response), spin); + + gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), hbox); + + gtk_widget_show_all (dialog); +} + +GtkWidget * +gtkutil_button (GtkWidget *box, char *stock, char *tip, void *callback, + void *userdata, char *labeltext) +{ + GtkWidget *wid, *img, *bbox; + + wid = gtk_button_new (); + + if (labeltext) + { + gtk_button_set_label (GTK_BUTTON (wid), labeltext); + gtk_button_set_image (GTK_BUTTON (wid), gtk_image_new_from_stock (stock, GTK_ICON_SIZE_MENU)); + gtk_button_set_use_underline (GTK_BUTTON (wid), TRUE); + if (box) + gtk_container_add (GTK_CONTAINER (box), wid); + } + else + { + bbox = gtk_hbox_new (0, 0); + gtk_container_add (GTK_CONTAINER (wid), bbox); + gtk_widget_show (bbox); + + img = gtk_image_new_from_stock (stock, GTK_ICON_SIZE_MENU); + if (stock == GTK_STOCK_GOTO_LAST) + gtk_widget_set_usize (img, 10, 6); + gtk_container_add (GTK_CONTAINER (bbox), img); + gtk_widget_show (img); + gtk_box_pack_start (GTK_BOX (box), wid, 0, 0, 0); + } + + g_signal_connect (G_OBJECT (wid), "clicked", + G_CALLBACK (callback), userdata); + gtk_widget_show (wid); + if (tip) + add_tip (wid, tip); + + return wid; +} + +void +gtkutil_label_new (char *text, GtkWidget * box) +{ + GtkWidget *label = gtk_label_new (text); + gtk_container_add (GTK_CONTAINER (box), label); + gtk_widget_show (label); +} + +GtkWidget * +gtkutil_entry_new (int max, GtkWidget * box, void *callback, + gpointer userdata) +{ + GtkWidget *entry = gtk_entry_new_with_max_length (max); + gtk_container_add (GTK_CONTAINER (box), entry); + if (callback) + g_signal_connect (G_OBJECT (entry), "changed", + G_CALLBACK (callback), userdata); + gtk_widget_show (entry); + return entry; +} + +GtkWidget * +gtkutil_clist_new (int columns, char *titles[], + GtkWidget * box, int policy, + void *select_callback, gpointer select_userdata, + void *unselect_callback, + gpointer unselect_userdata, int selection_mode) +{ + GtkWidget *clist, *win; + + win = gtk_scrolled_window_new (0, 0); + gtk_container_add (GTK_CONTAINER (box), win); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (win), + GTK_POLICY_AUTOMATIC, policy); + gtk_widget_show (win); + + if (titles) + clist = gtk_clist_new_with_titles (columns, titles); + else + clist = gtk_clist_new (columns); + + gtk_clist_set_selection_mode (GTK_CLIST (clist), selection_mode); + gtk_clist_column_titles_passive (GTK_CLIST (clist)); + gtk_container_add (GTK_CONTAINER (win), clist); + if (select_callback) + { + g_signal_connect (G_OBJECT (clist), "select_row", + G_CALLBACK (select_callback), select_userdata); + } + if (unselect_callback) + { + g_signal_connect (G_OBJECT (clist), "unselect_row", + G_CALLBACK (unselect_callback), unselect_userdata); + } + gtk_widget_show (clist); + + return clist; +} + +int +gtkutil_clist_selection (GtkWidget * clist) +{ + if (GTK_CLIST (clist)->selection) + return GPOINTER_TO_INT(GTK_CLIST (clist)->selection->data); + return -1; +} + +static int +int_compare (const int * elem1, const int * elem2) +{ + return (*elem1) - (*elem2); +} + +int +gtkutil_clist_multiple_selection (GtkWidget * clist, int ** rows, const int max_rows) +{ + int i = 0; + GList *tmp_clist; + *rows = malloc (sizeof (int) * max_rows ); + memset( *rows, -1, max_rows * sizeof(int) ); + + for( tmp_clist = GTK_CLIST(clist)->selection; + tmp_clist && i < max_rows; tmp_clist = tmp_clist->next, i++) + { + (*rows)[i] = GPOINTER_TO_INT( tmp_clist->data ); + } + qsort(*rows, i, sizeof(int), (void *)int_compare); + return i; + +} + +void +add_tip (GtkWidget * wid, char *text) +{ + static GtkTooltips *tip = NULL; + if (!tip) + tip = gtk_tooltips_new (); + gtk_tooltips_set_tip (tip, wid, text, 0); +} + +void +show_and_unfocus (GtkWidget * wid) +{ + GTK_WIDGET_UNSET_FLAGS (wid, GTK_CAN_FOCUS); + gtk_widget_show (wid); +} + +void +gtkutil_set_icon (GtkWidget *win) +{ + gtk_window_set_icon (GTK_WINDOW (win), pix_xchat); +} + +extern GtkWidget *parent_window; /* maingui.c */ + +GtkWidget * +gtkutil_window_new (char *title, char *role, int width, int height, int flags) +{ + GtkWidget *win; + + win = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtkutil_set_icon (win); +#ifdef WIN32 + gtk_window_set_wmclass (GTK_WINDOW (win), "XChat", "xchat"); +#endif + gtk_window_set_title (GTK_WINDOW (win), title); + gtk_window_set_default_size (GTK_WINDOW (win), width, height); + gtk_window_set_role (GTK_WINDOW (win), role); + if (flags & 1) + gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_MOUSE); + if ((flags & 2) && parent_window) + { + gtk_window_set_type_hint (GTK_WINDOW (win), GDK_WINDOW_TYPE_HINT_DIALOG); + gtk_window_set_transient_for (GTK_WINDOW (win), GTK_WINDOW (parent_window)); + } + + return win; +} + +/* pass NULL as selection to paste to both clipboard & X11 text */ +void +gtkutil_copy_to_clipboard (GtkWidget *widget, GdkAtom selection, + const gchar *str) +{ + GtkWidget *win; + GtkClipboard *clip, *clip2; + + win = gtk_widget_get_toplevel (GTK_WIDGET (widget)); + if (GTK_WIDGET_TOPLEVEL (win)) + { + int len = strlen (str); + + if (selection) + { + clip = gtk_widget_get_clipboard (win, selection); + gtk_clipboard_set_text (clip, str, len); + } else + { + /* copy to both primary X selection and clipboard */ + clip = gtk_widget_get_clipboard (win, GDK_SELECTION_PRIMARY); + clip2 = gtk_widget_get_clipboard (win, GDK_SELECTION_CLIPBOARD); + gtk_clipboard_set_text (clip, str, len); + gtk_clipboard_set_text (clip2, str, len); + } + } +} + +/* Treeview util functions */ + +GtkWidget * +gtkutil_treeview_new (GtkWidget *box, GtkTreeModel *model, + GtkTreeCellDataFunc mapper, ...) +{ + GtkWidget *win, *view; + GtkCellRenderer *renderer = NULL; + GtkTreeViewColumn *col; + va_list args; + int col_id = 0; + GType type; + char *title, *attr; + + win = gtk_scrolled_window_new (0, 0); + gtk_container_add (GTK_CONTAINER (box), win); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (win), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_widget_show (win); + + view = gtk_tree_view_new_with_model (model); + /* the view now has a ref on the model, we can unref it */ + g_object_unref (G_OBJECT (model)); + gtk_container_add (GTK_CONTAINER (win), view); + + va_start (args, mapper); + for (col_id = va_arg (args, int); col_id != -1; col_id = va_arg (args, int)) + { + type = gtk_tree_model_get_column_type (model, col_id); + switch (type) + { + case G_TYPE_BOOLEAN: + renderer = gtk_cell_renderer_toggle_new (); + attr = "active"; + break; + case G_TYPE_STRING: /* fall through */ + default: + renderer = gtk_cell_renderer_text_new (); + attr = "text"; + break; + } + + title = va_arg (args, char *); + if (mapper) /* user-specified function to set renderer attributes */ + { + col = gtk_tree_view_column_new_with_attributes (title, renderer, NULL); + gtk_tree_view_column_set_cell_data_func (col, renderer, mapper, + GINT_TO_POINTER (col_id), NULL); + } else + { + /* just set the typical attribute for this type of renderer */ + col = gtk_tree_view_column_new_with_attributes (title, renderer, + attr, col_id, NULL); + } + gtk_tree_view_append_column (GTK_TREE_VIEW (view), col); + } + + va_end (args); + + return view; +} + +gboolean +gtkutil_treemodel_string_to_iter (GtkTreeModel *model, gchar *pathstr, GtkTreeIter *iter_ret) +{ + GtkTreePath *path = gtk_tree_path_new_from_string (pathstr); + gboolean success; + + success = gtk_tree_model_get_iter (model, iter_ret, path); + gtk_tree_path_free (path); + return success; +} + +/*gboolean +gtkutil_treeview_get_selected_iter (GtkTreeView *view, GtkTreeIter *iter_ret) +{ + GtkTreeModel *store; + GtkTreeSelection *select; + + select = gtk_tree_view_get_selection (view); + return gtk_tree_selection_get_selected (select, &store, iter_ret); +}*/ + +gboolean +gtkutil_treeview_get_selected (GtkTreeView *view, GtkTreeIter *iter_ret, ...) +{ + GtkTreeModel *store; + GtkTreeSelection *select; + gboolean has_selected; + va_list args; + + select = gtk_tree_view_get_selection (view); + has_selected = gtk_tree_selection_get_selected (select, &store, iter_ret); + + if (has_selected) { + va_start (args, iter_ret); + gtk_tree_model_get_valist (store, iter_ret, args); + va_end (args); + } + + return has_selected; +} + diff --git a/src/fe-gtk/gtkutil.h b/src/fe-gtk/gtkutil.h new file mode 100644 index 00000000..9bf9e058 --- /dev/null +++ b/src/fe-gtk/gtkutil.h @@ -0,0 +1,39 @@ +#include <gtk/gtktreeview.h> +#include <gtk/gtktreemodel.h> + +typedef void (*filereqcallback) (void *, char *file); + +#define FRF_WRITE 1 +#define FRF_MULTIPLE 2 +#define FRF_ADDFOLDER 4 +#define FRF_CHOOSEFOLDER 8 +#define FRF_FILTERISINITIAL 16 +#define FRF_NOASKOVERWRITE 32 + +void gtkutil_file_req (const char *title, void *callback, void *userdata, char *filter, int flags); +void gtkutil_destroy (GtkWidget * igad, GtkWidget * dgad); +GtkWidget *gtkutil_button (GtkWidget *box, char *stock, char *tip, void *callback, + void *userdata, char *labeltext); +void gtkutil_label_new (char *text, GtkWidget * box); +GtkWidget *gtkutil_entry_new (int max, GtkWidget * box, void *callback, + gpointer userdata); +GtkWidget *gtkutil_clist_new (int columns, char *titles[], GtkWidget * box, + int policy, void *select_callback, + gpointer select_userdata, + void *unselect_callback, + gpointer unselect_userdata, int selection_mode); +int gtkutil_clist_selection (GtkWidget * clist); +int gtkutil_clist_multiple_selection (GtkWidget * clist, + int ** rows, const int max_rows); +void add_tip (GtkWidget * wid, char *text); +void show_and_unfocus (GtkWidget * wid); +void gtkutil_set_icon (GtkWidget *win); +GtkWidget *gtkutil_window_new (char *title, char *role, int width, int height, int flags); +void gtkutil_copy_to_clipboard (GtkWidget *widget, GdkAtom selection, + const gchar *str); +GtkWidget *gtkutil_treeview_new (GtkWidget *box, GtkTreeModel *model, + GtkTreeCellDataFunc mapper, ...); +gboolean gtkutil_treemodel_string_to_iter (GtkTreeModel *model, gchar *pathstr, GtkTreeIter *iter_ret); +gboolean gtkutil_treeview_get_selected_iter (GtkTreeView *view, GtkTreeIter *iter_ret); +gboolean gtkutil_treeview_get_selected (GtkTreeView *view, GtkTreeIter *iter_ret, ...); + diff --git a/src/fe-gtk/ignoregui.c b/src/fe-gtk/ignoregui.c new file mode 100644 index 00000000..dc5fce9c --- /dev/null +++ b/src/fe-gtk/ignoregui.c @@ -0,0 +1,449 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include "fe-gtk.h" + +#include <gtk/gtkcheckbutton.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkhbbox.h> +#include <gtk/gtkframe.h> +#include <gtk/gtkhseparator.h> +#include <gtk/gtkversion.h> + +#include <gtk/gtkliststore.h> +#include <gtk/gtktreeview.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtkcellrenderertext.h> +#include <gtk/gtkcellrenderertoggle.h> + +#include "../common/xchat.h" +#include "../common/ignore.h" +#include "../common/cfgfiles.h" +#include "../common/fe.h" +#include "gtkutil.h" +#include "maingui.h" + +enum +{ + MASK_COLUMN, + CHAN_COLUMN, + PRIV_COLUMN, + NOTICE_COLUMN, + CTCP_COLUMN, + DCC_COLUMN, + INVITE_COLUMN, + UNIGNORE_COLUMN, + N_COLUMNS +}; + +static GtkWidget *ignorewin = 0; + +static GtkWidget *num_ctcp; +static GtkWidget *num_priv; +static GtkWidget *num_chan; +static GtkWidget *num_noti; +static GtkWidget *num_invi; + +static GtkTreeModel * +get_store (void) +{ + return gtk_tree_view_get_model (g_object_get_data (G_OBJECT (ignorewin), "view")); +} + +static int +ignore_get_flags (GtkTreeModel *model, GtkTreeIter *iter) +{ + gboolean chan, priv, noti, ctcp, dcc, invi, unig; + int flags = 0; + + gtk_tree_model_get (model, iter, 1, &chan, 2, &priv, 3, ¬i, + 4, &ctcp, 5, &dcc, 6, &invi, 7, &unig, -1); + if (chan) + flags |= IG_CHAN; + if (priv) + flags |= IG_PRIV; + if (noti) + flags |= IG_NOTI; + if (ctcp) + flags |= IG_CTCP; + if (dcc) + flags |= IG_DCC; + if (invi) + flags |= IG_INVI; + if (unig) + flags |= IG_UNIG; + return flags; +} + +static void +mask_edited (GtkCellRendererText *render, gchar *path, gchar *new, gpointer dat) +{ + GtkListStore *store = GTK_LIST_STORE (get_store ()); + GtkTreeIter iter; + char *old; + int flags; + + gtkutil_treemodel_string_to_iter (GTK_TREE_MODEL (store), path, &iter); + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, 0, &old, -1); + + if (!strcmp (old, new)) /* no change */ + ; + else if (ignore_exists (new)) /* duplicate, ignore */ + fe_message (_("That mask already exists."), FE_MSG_ERROR); + else + { + /* delete old mask, and add new one with original flags */ + ignore_del (old, NULL); + flags = ignore_get_flags (GTK_TREE_MODEL (store), &iter); + ignore_add (new, flags); + + /* update tree */ + gtk_list_store_set (store, &iter, MASK_COLUMN, new, -1); + } + g_free (old); + +} + +static void +option_toggled (GtkCellRendererToggle *render, gchar *path, gpointer data) +{ + GtkListStore *store = GTK_LIST_STORE (get_store ()); + GtkTreeIter iter; + int col_id = GPOINTER_TO_INT (data); + gboolean active; + char *mask; + int flags; + + gtkutil_treemodel_string_to_iter (GTK_TREE_MODEL (store), path, &iter); + + /* update model */ + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, col_id, &active, -1); + gtk_list_store_set (store, &iter, col_id, !active, -1); + + /* update ignore list */ + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, 0, &mask, -1); + flags = ignore_get_flags (GTK_TREE_MODEL (store), &iter); + if (ignore_add (mask, flags) != 2) + g_warning ("ignore treeview is out of sync!\n"); + + g_free (mask); +} + +static GtkWidget * +ignore_treeview_new (GtkWidget *box) +{ + GtkListStore *store; + GtkWidget *view; + GtkTreeViewColumn *col; + GtkCellRenderer *render; + int col_id; + + store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, + G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN, G_TYPE_BOOLEAN, + G_TYPE_BOOLEAN, G_TYPE_BOOLEAN); + g_return_val_if_fail (store != NULL, NULL); + + view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store), + NULL, + MASK_COLUMN, _("Mask"), + CHAN_COLUMN, _("Channel"), + PRIV_COLUMN, _("Private"), + NOTICE_COLUMN, _("Notice"), + CTCP_COLUMN, _("CTCP"), + DCC_COLUMN, _("DCC"), + INVITE_COLUMN, _("Invite"), + UNIGNORE_COLUMN, _("Unignore"), + -1); + + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE); + gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), 0), TRUE); + + /* attach to signals and customise columns */ + for (col_id=0; (col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), col_id)); + col_id++) + { + GList *list = gtk_tree_view_column_get_cell_renderers (col); + GList *tmp; + + for (tmp = list; tmp; tmp = tmp->next) + { + render = tmp->data; + if (col_id > 0) /* it's a toggle button column */ + { + g_signal_connect (render, "toggled", G_CALLBACK (option_toggled), + GINT_TO_POINTER (col_id)); + } else /* mask column */ + { + g_object_set (G_OBJECT (render), "editable", TRUE, NULL); + g_signal_connect (render, "edited", G_CALLBACK (mask_edited), NULL); + /* make this column sortable */ + gtk_tree_view_column_set_sort_column_id (col, col_id); + gtk_tree_view_column_set_min_width (col, 272); + } + /* centre titles */ + gtk_tree_view_column_set_alignment (col, 0.5); + } + + g_list_free (list); + } + + gtk_widget_show (view); + return view; +} + +static void +ignore_delete_entry_clicked (GtkWidget * wid, struct session *sess) +{ + GtkTreeView *view = g_object_get_data (G_OBJECT (ignorewin), "view"); + GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (view)); + GtkTreeIter iter; + GtkTreePath *path; + char *mask = NULL; + + if (gtkutil_treeview_get_selected (view, &iter, 0, &mask, -1)) + { + /* delete this row, select next one */ +#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0) + gtk_list_store_remove (store, &iter); +#else + if (gtk_list_store_remove (store, &iter)) + { + path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter); + gtk_tree_view_scroll_to_cell (view, path, NULL, TRUE, 1.0, 0.0); + gtk_tree_view_set_cursor (view, path, NULL, FALSE); + gtk_tree_path_free (path); + } +#endif + + ignore_del (mask, NULL); + g_free (mask); + } +} + +static void +ignore_store_new (int cancel, char *mask, gpointer data) +{ + GtkTreeView *view = g_object_get_data (G_OBJECT (ignorewin), "view"); + GtkListStore *store = GTK_LIST_STORE (get_store ()); + GtkTreeIter iter; + GtkTreePath *path; + int flags = IG_CHAN | IG_PRIV | IG_NOTI | IG_CTCP | IG_DCC | IG_INVI; + + if (cancel) + return; + /* check if it already exists */ + if (ignore_exists (mask)) + { + fe_message (_("That mask already exists."), FE_MSG_ERROR); + return; + } + + ignore_add (mask, flags); + + gtk_list_store_append (store, &iter); + /* ignore everything by default */ + gtk_list_store_set (store, &iter, 0, mask, 1, TRUE, 2, TRUE, 3, TRUE, + 4, TRUE, 5, TRUE, 6, TRUE, 7, FALSE, -1); + /* make sure the new row is visible and selected */ + path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter); + gtk_tree_view_scroll_to_cell (view, path, NULL, TRUE, 1.0, 0.0); + gtk_tree_view_set_cursor (view, path, NULL, FALSE); + gtk_tree_path_free (path); +} + +static void +ignore_clear_entry_clicked (GtkWidget * wid, gpointer unused) +{ + GtkListStore *store = GTK_LIST_STORE (get_store ()); + GtkTreeIter iter; + char *mask; + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) + { + /* remove from ignore_list */ + do + { + mask = NULL; + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, MASK_COLUMN, &mask, -1); + ignore_del (mask, NULL); + g_free (mask); + } + while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter)); + + /* remove from GUI */ + gtk_list_store_clear (store); + } +} + +static void +ignore_new_entry_clicked (GtkWidget * wid, struct session *sess) +{ + fe_get_str (_("Enter mask to ignore:"), "nick!userid@host.com", + ignore_store_new, NULL); + +} + +static void +close_ignore_gui_callback () +{ + ignore_save (); + ignorewin = 0; +} + +static GtkWidget * +ignore_stats_entry (GtkWidget * box, char *label, int value) +{ + GtkWidget *wid; + char buf[16]; + + sprintf (buf, "%d", value); + gtkutil_label_new (label, box); + wid = gtkutil_entry_new (16, box, 0, 0); + gtk_widget_set_size_request (wid, 30, -1); + gtk_editable_set_editable (GTK_EDITABLE (wid), FALSE); + gtk_widget_set_sensitive (GTK_WIDGET (wid), FALSE); + gtk_entry_set_text (GTK_ENTRY (wid), buf); + + return wid; +} + +void +ignore_gui_open () +{ + GtkWidget *vbox, *box, *stat_box, *frame; + GtkWidget *view; + GtkListStore *store; + GtkTreeIter iter; + GSList *temp = ignore_list; + char *mask; + gboolean private, chan, notice, ctcp, dcc, invite, unignore; + + if (ignorewin) + { + mg_bring_tofront (ignorewin); + return; + } + + ignorewin = + mg_create_generic_tab ("IgnoreList", _("XChat: Ignore list"), + FALSE, TRUE, close_ignore_gui_callback, + NULL, 600, 256, &vbox, 0); + + view = ignore_treeview_new (vbox); + g_object_set_data (G_OBJECT (ignorewin), "view", view); + + frame = gtk_frame_new (_("Ignore Stats:")); + gtk_widget_show (frame); + + stat_box = gtk_hbox_new (0, 2); + gtk_container_set_border_width (GTK_CONTAINER (stat_box), 6); + gtk_container_add (GTK_CONTAINER (frame), stat_box); + gtk_widget_show (stat_box); + + num_chan = ignore_stats_entry (stat_box, _("Channel:"), ignored_chan); + num_priv = ignore_stats_entry (stat_box, _("Private:"), ignored_priv); + num_noti = ignore_stats_entry (stat_box, _("Notice:"), ignored_noti); + num_ctcp = ignore_stats_entry (stat_box, _("CTCP:"), ignored_ctcp); + num_invi = ignore_stats_entry (stat_box, _("Invite:"), ignored_invi); + + gtk_box_pack_start (GTK_BOX (vbox), frame, 0, 0, 5); + + box = gtk_hbutton_box_new (); + gtk_button_box_set_layout (GTK_BUTTON_BOX (box), GTK_BUTTONBOX_SPREAD); + gtk_box_pack_start (GTK_BOX (vbox), box, FALSE, FALSE, 2); + gtk_container_set_border_width (GTK_CONTAINER (box), 5); + gtk_widget_show (box); + + gtkutil_button (box, GTK_STOCK_NEW, 0, ignore_new_entry_clicked, 0, + _("Add...")); + gtkutil_button (box, GTK_STOCK_DELETE, 0, ignore_delete_entry_clicked, + 0, _("Delete")); + gtkutil_button (box, GTK_STOCK_CLEAR, 0, ignore_clear_entry_clicked, + 0, _("Clear")); + + store = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (view))); + + while (temp) + { + struct ignore *ignore = temp->data; + + mask = ignore->mask; + chan = (ignore->type & IG_CHAN); + private = (ignore->type & IG_PRIV); + notice = (ignore->type & IG_NOTI); + ctcp = (ignore->type & IG_CTCP); + dcc = (ignore->type & IG_DCC); + invite = (ignore->type & IG_INVI); + unignore = (ignore->type & IG_UNIG); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + MASK_COLUMN, mask, + CHAN_COLUMN, chan, + PRIV_COLUMN, private, + NOTICE_COLUMN, notice, + CTCP_COLUMN, ctcp, + DCC_COLUMN, dcc, + INVITE_COLUMN, invite, + UNIGNORE_COLUMN, unignore, + -1); + + temp = temp->next; + } + gtk_widget_show (ignorewin); +} + +void +fe_ignore_update (int level) +{ + /* some ignores have changed via /ignore, we should update + the gui now */ + /* level 1 = the list only. */ + /* level 2 = the numbers only. */ + /* for now, ignore level 1, since the ignore GUI isn't realtime, + only saved when you click OK */ + char buf[16]; + + if (level == 2 && ignorewin) + { + sprintf (buf, "%d", ignored_ctcp); + gtk_entry_set_text (GTK_ENTRY (num_ctcp), buf); + + sprintf (buf, "%d", ignored_noti); + gtk_entry_set_text (GTK_ENTRY (num_noti), buf); + + sprintf (buf, "%d", ignored_chan); + gtk_entry_set_text (GTK_ENTRY (num_chan), buf); + + sprintf (buf, "%d", ignored_invi); + gtk_entry_set_text (GTK_ENTRY (num_invi), buf); + + sprintf (buf, "%d", ignored_priv); + gtk_entry_set_text (GTK_ENTRY (num_priv), buf); + } +} diff --git a/src/fe-gtk/joind.c b/src/fe-gtk/joind.c new file mode 100644 index 00000000..ee5c56d1 --- /dev/null +++ b/src/fe-gtk/joind.c @@ -0,0 +1,257 @@ +/* Copyright (c) 2005 Peter Zelezny + All Rights Reserved. + + joind.c - The Join Dialog. + + Popups up when you connect without any autojoin channels and helps you + to find or join a channel. +*/ + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> + +#include <gtk/gtkbbox.h> +#include <gtk/gtkbutton.h> +#include <gtk/gtkdialog.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkimage.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkradiobutton.h> +#include <gtk/gtkvbox.h> +#include <gtk/gtkwindow.h> + +#include "../common/xchat.h" +#include "../common/xchatc.h" +#include "../common/server.h" +#include "../common/fe.h" +#include "fe-gtk.h" +#include "chanlist.h" + + +static void +joind_radio2_cb (GtkWidget *radio, server *serv) +{ + if (GTK_TOGGLE_BUTTON (radio)->active) + { + gtk_widget_grab_focus (serv->gui->joind_entry); + gtk_editable_set_position (GTK_EDITABLE (serv->gui->joind_entry), 999); + } +} + +static void +joind_entryenter_cb (GtkWidget *entry, GtkWidget *ok) +{ + gtk_widget_grab_focus (ok); +} + +static void +joind_entryfocus_cb (GtkWidget *entry, GdkEventFocus *event, server *serv) +{ + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (serv->gui->joind_radio2), TRUE); +} + +static void +joind_destroy_cb (GtkWidget *win, server *serv) +{ + if (is_server (serv)) + serv->gui->joind_win = NULL; +} + +static void +joind_ok_cb (GtkWidget *ok, server *serv) +{ + if (!is_server (serv)) + { + gtk_widget_destroy (gtk_widget_get_toplevel (ok)); + return; + } + + /* do nothing */ + if (GTK_TOGGLE_BUTTON (serv->gui->joind_radio1)->active) + goto xit; + + /* join specific channel */ + if (GTK_TOGGLE_BUTTON (serv->gui->joind_radio2)->active) + { + char *text = GTK_ENTRY (serv->gui->joind_entry)->text; + if (strlen (text) < 2) + { + fe_message (_("Channel name too short, try again."), FE_MSG_ERROR); + return; + } + serv->p_join (serv, text, ""); + goto xit; + } + + /* channel list */ + chanlist_opengui (serv, TRUE); + +xit: + prefs.gui_join_dialog = 0; + if (GTK_TOGGLE_BUTTON (serv->gui->joind_check)->active) + prefs.gui_join_dialog = 1; + + gtk_widget_destroy (serv->gui->joind_win); + serv->gui->joind_win = NULL; +} + +static void +joind_show_dialog (server *serv) +{ + GtkWidget *dialog1; + GtkWidget *dialog_vbox1; + GtkWidget *vbox1; + GtkWidget *hbox1; + GtkWidget *image1; + GtkWidget *vbox2; + GtkWidget *label; + GtkWidget *radiobutton1; + GtkWidget *radiobutton2; + GtkWidget *radiobutton3; + GSList *radiobutton1_group; + GtkWidget *hbox2; + GtkWidget *entry1; + GtkWidget *checkbutton1; + GtkWidget *dialog_action_area1; + GtkWidget *okbutton1; + char buf[256]; + char buf2[256]; + + serv->gui->joind_win = dialog1 = gtk_dialog_new (); + gtk_window_set_title (GTK_WINDOW (dialog1), _("XChat: Connection Complete")); + gtk_window_set_type_hint (GTK_WINDOW (dialog1), GDK_WINDOW_TYPE_HINT_DIALOG); + gtk_window_set_position (GTK_WINDOW (dialog1), GTK_WIN_POS_MOUSE); + + dialog_vbox1 = GTK_DIALOG (dialog1)->vbox; + gtk_widget_show (dialog_vbox1); + + vbox1 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox1); + gtk_box_pack_start (GTK_BOX (dialog_vbox1), vbox1, TRUE, TRUE, 0); + + hbox1 = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox1); + gtk_box_pack_start (GTK_BOX (vbox1), hbox1, TRUE, TRUE, 0); + + image1 = gtk_image_new_from_stock ("gtk-yes", GTK_ICON_SIZE_DIALOG); + gtk_widget_show (image1); + gtk_box_pack_start (GTK_BOX (hbox1), image1, FALSE, TRUE, 24); + gtk_misc_set_alignment (GTK_MISC (image1), 0.5, 0.06); + + vbox2 = gtk_vbox_new (FALSE, 10); + gtk_container_set_border_width (GTK_CONTAINER (vbox2), 6); + gtk_widget_show (vbox2); + gtk_box_pack_start (GTK_BOX (hbox1), vbox2, TRUE, TRUE, 0); + + snprintf (buf2, sizeof (buf2), _("Connection to %s complete."), + server_get_network (serv, TRUE)); + snprintf (buf, sizeof (buf), "\n<b>%s</b>", buf2); + label = gtk_label_new (buf); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, FALSE, 0); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + + label = gtk_label_new (_("In the Server-List window, no channel (chat room) has been entered to be automatically joined for this network.")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, FALSE, 0); + GTK_LABEL (label)->wrap = TRUE; + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + + label = gtk_label_new (_("What would you like to do next?")); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, FALSE, 0); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + + serv->gui->joind_radio1 = radiobutton1 = gtk_radio_button_new_with_mnemonic (NULL, _("_Nothing, I'll join a channel later.")); + gtk_widget_show (radiobutton1); + gtk_box_pack_start (GTK_BOX (vbox2), radiobutton1, FALSE, FALSE, 0); + radiobutton1_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radiobutton1)); + + hbox2 = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox2); + gtk_box_pack_start (GTK_BOX (vbox2), hbox2, FALSE, FALSE, 0); + + serv->gui->joind_radio2 = radiobutton2 = gtk_radio_button_new_with_mnemonic (NULL, _("_Join this channel:")); + gtk_widget_show (radiobutton2); + gtk_box_pack_start (GTK_BOX (hbox2), radiobutton2, FALSE, FALSE, 0); + gtk_radio_button_set_group (GTK_RADIO_BUTTON (radiobutton2), radiobutton1_group); + radiobutton1_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radiobutton2)); + + serv->gui->joind_entry = entry1 = gtk_entry_new (); + gtk_entry_set_text (GTK_ENTRY (entry1), "#"); + gtk_widget_show (entry1); + gtk_box_pack_start (GTK_BOX (hbox2), entry1, TRUE, TRUE, 8); + + snprintf (buf, sizeof (buf), "<small> %s</small>", + _("If you know the name of the channel you want to join, enter it here.")); + label = gtk_label_new (buf); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, FALSE, 0); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + + radiobutton3 = gtk_radio_button_new_with_mnemonic (NULL, _("O_pen the Channel-List window.")); + gtk_widget_show (radiobutton3); + gtk_box_pack_start (GTK_BOX (vbox2), radiobutton3, FALSE, FALSE, 0); + gtk_radio_button_set_group (GTK_RADIO_BUTTON (radiobutton3), radiobutton1_group); + radiobutton1_group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (radiobutton3)); + + snprintf (buf, sizeof (buf), "<small> %s</small>", + _("Retrieving the Channel-List may take a minute or two.")); + label = gtk_label_new (buf); + gtk_widget_show (label); + gtk_box_pack_start (GTK_BOX (vbox2), label, FALSE, FALSE, 0); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + + serv->gui->joind_check = checkbutton1 = gtk_check_button_new_with_mnemonic (_("_Always show this dialog after connecting.")); + if (prefs.gui_join_dialog) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbutton1), TRUE); + gtk_widget_show (checkbutton1); + gtk_box_pack_start (GTK_BOX (vbox1), checkbutton1, FALSE, FALSE, 0); + + dialog_action_area1 = GTK_DIALOG (dialog1)->action_area; + gtk_widget_show (dialog_action_area1); + gtk_button_box_set_layout (GTK_BUTTON_BOX (dialog_action_area1), GTK_BUTTONBOX_END); + + okbutton1 = gtk_button_new_from_stock ("gtk-ok"); + gtk_widget_show (okbutton1); + gtk_box_pack_end (GTK_BOX (GTK_DIALOG (dialog1)->action_area), okbutton1, FALSE, TRUE, 0); + GTK_WIDGET_SET_FLAGS (okbutton1, GTK_CAN_DEFAULT); + + g_signal_connect (G_OBJECT (dialog1), "destroy", + G_CALLBACK (joind_destroy_cb), serv); + g_signal_connect (G_OBJECT (entry1), "focus_in_event", + G_CALLBACK (joind_entryfocus_cb), serv); + g_signal_connect (G_OBJECT (entry1), "activate", + G_CALLBACK (joind_entryenter_cb), okbutton1); + g_signal_connect (G_OBJECT (radiobutton2), "toggled", + G_CALLBACK (joind_radio2_cb), serv); + g_signal_connect (G_OBJECT (okbutton1), "clicked", + G_CALLBACK (joind_ok_cb), serv); + + gtk_widget_grab_focus (okbutton1); + gtk_widget_show_all (dialog1); +} + +void +joind_open (server *serv) +{ + if (prefs.gui_join_dialog) + joind_show_dialog (serv); +} + +void +joind_close (server *serv) +{ + if (serv->gui->joind_win) + { + gtk_widget_destroy (serv->gui->joind_win); + serv->gui->joind_win = NULL; + } +} diff --git a/src/fe-gtk/joind.h b/src/fe-gtk/joind.h new file mode 100644 index 00000000..aa0fd0ad --- /dev/null +++ b/src/fe-gtk/joind.h @@ -0,0 +1,2 @@ +void joind_open (server *serv); +void joind_close (server *serv); 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; +} diff --git a/src/fe-gtk/maingui.h b/src/fe-gtk/maingui.h new file mode 100644 index 00000000..bc9aaefd --- /dev/null +++ b/src/fe-gtk/maingui.h @@ -0,0 +1,33 @@ +extern GtkStyle *input_style; +extern GtkWidget *parent_window; + +void mg_changui_new (session *sess, restore_gui *res, int tab, int focus); +void mg_update_xtext (GtkWidget *wid); +void mg_open_quit_dialog (gboolean minimize_button); +void mg_switch_page (int relative, int num); +void mg_move_tab (session *, int delta); +void mg_move_tab_family (session *, int delta); +void mg_bring_tofront (GtkWidget *vbox); +void mg_bring_tofront_sess (session *sess); +void mg_decide_userlist (session *sess, gboolean switch_to_current); +void mg_set_topic_tip (session *sess); +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); +void mg_set_title (GtkWidget *button, char *title); +void mg_set_access_icon (session_gui *gui, GdkPixbuf *pix, gboolean away); +void mg_apply_setup (void); +void mg_close_sess (session *); +void mg_tab_close (session *sess); +void mg_detach (session *sess, int mode); +void mg_progressbar_create (session_gui *gui); +void mg_progressbar_destroy (session_gui *gui); +void mg_dnd_drop_file (session *sess, char *target, char *uri); +void mg_change_layout (int type); +void mg_update_meters (session_gui *); +void mg_inputbox_cb (GtkWidget *igad, session_gui *gui); +void mg_create_icon_item (char *label, char *stock, GtkWidget *menu, void *callback, void *userdata); +GtkWidget *mg_submenu (GtkWidget *menu, char *text); +/* DND */ +gboolean mg_drag_begin_cb (GtkWidget *widget, GdkDragContext *context, gpointer userdata); +void mg_drag_end_cb (GtkWidget *widget, GdkDragContext *context, gpointer userdata); +gboolean mg_drag_drop_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, guint time, gpointer user_data); +gboolean mg_drag_motion_cb (GtkWidget *widget, GdkDragContext *context, int x, int y, guint time, gpointer user_data); diff --git a/src/fe-gtk/menu.c b/src/fe-gtk/menu.c new file mode 100644 index 00000000..d04be222 --- /dev/null +++ b/src/fe-gtk/menu.c @@ -0,0 +1,2270 @@ +/* X-Chat + * Copyright (C) 1998-2007 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 <stdio.h> +#include <stdlib.h> +#include <fcntl.h> +#include <string.h> +#include <unistd.h> + +#ifdef WIN32 +#include <windows.h> +#endif + +#include "fe-gtk.h" + +#include <gtk/gtkhbox.h> +#include <gtk/gtkcheckmenuitem.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkimage.h> +#include <gtk/gtkimagemenuitem.h> +#include <gtk/gtkradiomenuitem.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkmessagedialog.h> +#include <gtk/gtkmenu.h> +#include <gtk/gtkmenubar.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkversion.h> +#include <gdk/gdkkeysyms.h> + +#include "../common/xchat.h" +#include "../common/xchatc.h" +#include "../common/cfgfiles.h" +#include "../common/outbound.h" +#include "../common/ignore.h" +#include "../common/fe.h" +#include "../common/server.h" +#include "../common/servlist.h" +#include "../common/notify.h" +#include "../common/util.h" +#include "xtext.h" +#include "about.h" +#include "ascii.h" +#include "banlist.h" +#include "chanlist.h" +#include "editlist.h" +#include "fkeys.h" +#include "gtkutil.h" +#include "maingui.h" +#include "notifygui.h" +#include "pixmaps.h" +#include "rawlog.h" +#include "palette.h" +#include "plugingui.h" +#include "search.h" +#include "textgui.h" +#include "urlgrab.h" +#include "userlistgui.h" +#include "menu.h" + +static GSList *submenu_list; + +enum +{ + M_MENUITEM, + M_NEWMENU, + M_END, + M_SEP, + M_MENUTOG, + M_MENURADIO, + M_MENUSTOCK, + M_MENUPIX, + M_MENUSUB +}; + +struct mymenu +{ + char *text; + void *callback; + char *image; + unsigned char type; /* M_XXX */ + unsigned char id; /* MENU_ID_XXX (menu.h) */ + unsigned char state; /* ticked or not? */ + unsigned char sensitive; /* shaded out? */ + guint key; /* GDK_x */ +}; + +#define XCMENU_DOLIST 1 +#define XCMENU_SHADED 1 +#define XCMENU_MARKUP 2 +#define XCMENU_MNEMONIC 4 + +/* execute a userlistbutton/popupmenu command */ + +static void +nick_command (session * sess, char *cmd) +{ + if (*cmd == '!') + xchat_exec (cmd + 1); + else + handle_command (sess, cmd, TRUE); +} + +/* fill in the %a %s %n etc and execute the command */ + +void +nick_command_parse (session *sess, char *cmd, char *nick, char *allnick) +{ + char *buf; + char *host = _("Host unknown"); + struct User *user; + int len; + +/* if (sess->type == SESS_DIALOG) + { + buf = (char *)(GTK_ENTRY (sess->gui->topic_entry)->text); + buf = strrchr (buf, '@'); + if (buf) + host = buf + 1; + } else*/ + { + user = userlist_find (sess, nick); + if (user && user->hostname) + host = strchr (user->hostname, '@') + 1; + } + + /* this can't overflow, since popup->cmd is only 256 */ + len = strlen (cmd) + strlen (nick) + strlen (allnick) + 512; + buf = malloc (len); + + auto_insert (buf, len, cmd, 0, 0, allnick, sess->channel, "", + server_get_network (sess->server, TRUE), host, + sess->server->nick, nick); + + nick_command (sess, buf); + + free (buf); +} + +/* userlist button has been clicked */ + +void +userlist_button_cb (GtkWidget * button, char *cmd) +{ + int i, num_sel, using_allnicks = FALSE; + char **nicks, *allnicks; + char *nick = NULL; + session *sess; + + sess = current_sess; + + if (strstr (cmd, "%a")) + using_allnicks = TRUE; + + if (sess->type == SESS_DIALOG) + { + /* fake a selection */ + nicks = malloc (sizeof (char *) * 2); + nicks[0] = g_strdup (sess->channel); + nicks[1] = NULL; + num_sel = 1; + } else + { + /* find number of selected rows */ + nicks = userlist_selection_list (sess->gui->user_tree, &num_sel); + if (num_sel < 1) + { + nick_command_parse (sess, cmd, "", ""); + return; + } + } + + /* create "allnicks" string */ + allnicks = malloc (((NICKLEN + 1) * num_sel) + 1); + *allnicks = 0; + + i = 0; + while (nicks[i]) + { + if (i > 0) + strcat (allnicks, " "); + strcat (allnicks, nicks[i]); + + if (!nick) + nick = nicks[0]; + + /* if not using "%a", execute the command once for each nickname */ + if (!using_allnicks) + nick_command_parse (sess, cmd, nicks[i], ""); + + i++; + } + + if (using_allnicks) + { + if (!nick) + nick = ""; + nick_command_parse (sess, cmd, nick, allnicks); + } + + while (num_sel) + { + num_sel--; + g_free (nicks[num_sel]); + } + + free (nicks); + free (allnicks); +} + +/* a popup-menu-item has been selected */ + +static void +popup_menu_cb (GtkWidget * item, char *cmd) +{ + char *nick; + + /* the userdata is set in menu_quick_item() */ + nick = g_object_get_data (G_OBJECT (item), "u"); + + if (!nick) /* userlist popup menu */ + { + /* treat it just like a userlist button */ + userlist_button_cb (NULL, cmd); + return; + } + + if (!current_sess) /* for url grabber window */ + nick_command_parse (sess_list->data, cmd, nick, nick); + else + nick_command_parse (current_sess, cmd, nick, nick); +} + +GtkWidget * +menu_toggle_item (char *label, GtkWidget *menu, void *callback, void *userdata, + int state) +{ + GtkWidget *item; + + item = gtk_check_menu_item_new_with_mnemonic (label); + gtk_check_menu_item_set_active ((GtkCheckMenuItem*)item, state); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (callback), userdata); + gtk_widget_show (item); + + return item; +} + +GtkWidget * +menu_quick_item (char *cmd, char *label, GtkWidget * menu, int flags, + gpointer userdata, char *icon) +{ + GtkWidget *img, *item; + char *path; + + if (!label) + item = gtk_menu_item_new (); + else + { + if (icon) + { + /*if (flags & XCMENU_MARKUP) + item = gtk_image_menu_item_new_with_markup (label); + else*/ + item = gtk_image_menu_item_new_with_mnemonic (label); + img = NULL; + if (access (icon, R_OK) == 0) /* try fullpath */ + img = gtk_image_new_from_file (icon); + else + { + /* try relative to ~/.xchat2 */ + path = g_strdup_printf ("%s/%s", get_xdir_fs (), icon); + if (access (path, R_OK) == 0) + img = gtk_image_new_from_file (path); + else + img = gtk_image_new_from_stock (icon, GTK_ICON_SIZE_MENU); + g_free (path); + } + + if (img) + gtk_image_menu_item_set_image ((GtkImageMenuItem *)item, img); + } + else + { + if (flags & XCMENU_MARKUP) + { + item = gtk_menu_item_new_with_label (""); + if (flags & XCMENU_MNEMONIC) + gtk_label_set_markup_with_mnemonic (GTK_LABEL (GTK_BIN (item)->child), label); + else + gtk_label_set_markup (GTK_LABEL (GTK_BIN (item)->child), label); + } else + { + if (flags & XCMENU_MNEMONIC) + item = gtk_menu_item_new_with_mnemonic (label); + else + item = gtk_menu_item_new_with_label (label); + } + } + } + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + g_object_set_data (G_OBJECT (item), "u", userdata); + if (cmd) + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (popup_menu_cb), cmd); + if (flags & XCMENU_SHADED) + gtk_widget_set_sensitive (GTK_WIDGET (item), FALSE); + gtk_widget_show_all (item); + + return item; +} + +static void +menu_quick_item_with_callback (void *callback, char *label, GtkWidget * menu, + void *arg) +{ + GtkWidget *item; + + item = gtk_menu_item_new_with_label (label); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (callback), arg); + gtk_widget_show (item); +} + +GtkWidget * +menu_quick_sub (char *name, GtkWidget *menu, GtkWidget **sub_item_ret, int flags, int pos) +{ + GtkWidget *sub_menu; + GtkWidget *sub_item; + + if (!name) + return menu; + + /* Code to add a submenu */ + sub_menu = gtk_menu_new (); + if (flags & XCMENU_MARKUP) + { + sub_item = gtk_menu_item_new_with_label (""); + gtk_label_set_markup (GTK_LABEL (GTK_BIN (sub_item)->child), name); + } + else + { + if (flags & XCMENU_MNEMONIC) + sub_item = gtk_menu_item_new_with_mnemonic (name); + else + sub_item = gtk_menu_item_new_with_label (name); + } + gtk_menu_shell_insert (GTK_MENU_SHELL (menu), sub_item, pos); + gtk_widget_show (sub_item); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (sub_item), sub_menu); + + if (sub_item_ret) + *sub_item_ret = sub_item; + + if (flags & XCMENU_DOLIST) + /* We create a new element in the list */ + submenu_list = g_slist_prepend (submenu_list, sub_menu); + return sub_menu; +} + +static GtkWidget * +menu_quick_endsub () +{ + /* Just delete the first element in the linked list pointed to by first */ + if (submenu_list) + submenu_list = g_slist_remove (submenu_list, submenu_list->data); + + if (submenu_list) + return (submenu_list->data); + else + return NULL; +} + +static void +toggle_cb (GtkWidget *item, char *pref_name) +{ + char buf[256]; + + if (GTK_CHECK_MENU_ITEM (item)->active) + snprintf (buf, sizeof (buf), "set %s 1", pref_name); + else + snprintf (buf, sizeof (buf), "set %s 0", pref_name); + + handle_command (current_sess, buf, FALSE); +} + +static int +is_in_path (char *cmd) +{ + char *prog = strdup (cmd + 1); /* 1st char is "!" */ + char *space, *path, *orig; + + orig = prog; /* save for free()ing */ + /* special-case these default entries. */ + /* 123456789012345678 */ + if (strncmp (prog, "gnome-terminal -x ", 18) == 0) + /* don't check for gnome-terminal, but the thing it's executing! */ + prog += 18; + + space = strchr (prog, ' '); /* this isn't 100% but good enuf */ + if (space) + *space = 0; + + path = g_find_program_in_path (prog); + if (path) + { + g_free (path); + g_free (orig); + return 1; + } + + g_free (orig); + return 0; +} + +/* syntax: "LABEL~ICON~STUFF~ADDED~LATER~" */ + +static void +menu_extract_icon (char *name, char **label, char **icon) +{ + char *p = name; + char *start = NULL; + char *end = NULL; + + while (*p) + { + if (*p == '~') + { + /* escape \~ */ + if (p == name || p[-1] != '\\') + { + if (!start) + start = p + 1; + else if (!end) + end = p + 1; + } + } + p++; + } + + if (!end) + end = p; + + if (start && start != end) + { + *label = g_strndup (name, (start - name) - 1); + *icon = g_strndup (start, (end - start) - 1); + } + else + { + *label = g_strdup (name); + *icon = NULL; + } +} + +/* append items to "menu" using the (struct popup*) list provided */ + +void +menu_create (GtkWidget *menu, GSList *list, char *target, int check_path) +{ + struct popup *pop; + GtkWidget *tempmenu = menu, *subitem = NULL; + int childcount = 0; + + submenu_list = g_slist_prepend (0, menu); + while (list) + { + pop = (struct popup *) list->data; + + if (!strncasecmp (pop->name, "SUB", 3)) + { + childcount = 0; + tempmenu = menu_quick_sub (pop->cmd, tempmenu, &subitem, XCMENU_DOLIST|XCMENU_MNEMONIC, -1); + + } else if (!strncasecmp (pop->name, "TOGGLE", 6)) + { + childcount++; + menu_toggle_item (pop->name + 7, tempmenu, toggle_cb, pop->cmd, + cfg_get_bool (pop->cmd)); + + } else if (!strncasecmp (pop->name, "ENDSUB", 6)) + { + /* empty sub menu due to no programs in PATH? */ + if (check_path && childcount < 1) + gtk_widget_destroy (subitem); + subitem = NULL; + + if (tempmenu != menu) + tempmenu = menu_quick_endsub (); + /* If we get here and tempmenu equals menu that means we havent got any submenus to exit from */ + + } else if (!strncasecmp (pop->name, "SEP", 3)) + { + menu_quick_item (0, 0, tempmenu, XCMENU_SHADED, 0, 0); + + } else + { + char *icon, *label; + + /* default command in xchat.c */ + if (pop->cmd[0] == 'n' && !strcmp (pop->cmd, "notify -n ASK %s")) + { + /* don't create this item if already in notify list */ + if (!target || notify_is_in_list (current_sess->server, target)) + { + list = list->next; + continue; + } + } + + menu_extract_icon (pop->name, &label, &icon); + + if (!check_path || pop->cmd[0] != '!') + { + menu_quick_item (pop->cmd, label, tempmenu, 0, target, icon); + /* check if the program is in path, if not, leave it out! */ + } else if (is_in_path (pop->cmd)) + { + childcount++; + menu_quick_item (pop->cmd, label, tempmenu, 0, target, icon); + } + + g_free (label); + g_free (icon); + } + + list = list->next; + } + + /* Let's clean up the linked list from mem */ + while (submenu_list) + submenu_list = g_slist_remove (submenu_list, submenu_list->data); +} + +static char *str_copy = NULL; /* for all pop-up menus */ +static GtkWidget *nick_submenu = NULL; /* user info submenu */ + +static void +menu_destroy (GtkWidget *menu, gpointer objtounref) +{ + gtk_widget_destroy (menu); + g_object_unref (menu); + if (objtounref) + g_object_unref (G_OBJECT (objtounref)); + nick_submenu = NULL; +} + +static void +menu_popup (GtkWidget *menu, GdkEventButton *event, gpointer objtounref) +{ +#if (GTK_MAJOR_VERSION != 2) || (GTK_MINOR_VERSION != 0) + if (event && event->window) + gtk_menu_set_screen (GTK_MENU (menu), gdk_drawable_get_screen (event->window)); +#endif + + g_object_ref (menu); + g_object_ref_sink (menu); + g_object_unref (menu); + g_signal_connect (G_OBJECT (menu), "selection-done", + G_CALLBACK (menu_destroy), objtounref); + gtk_menu_popup (GTK_MENU (menu), NULL, NULL, NULL, NULL, + 0, event ? event->time : 0); +} + +static void +menu_nickinfo_cb (GtkWidget *menu, session *sess) +{ + char buf[512]; + + if (!is_session (sess)) + return; + + /* issue a /WHOIS */ + snprintf (buf, sizeof (buf), "WHOIS %s %s", str_copy, str_copy); + handle_command (sess, buf, FALSE); + /* and hide the output */ + sess->server->skip_next_whois = 1; +} + +static void +copy_to_clipboard_cb (GtkWidget *item, char *url) +{ + gtkutil_copy_to_clipboard (item, NULL, url); +} + +/* returns boolean: Some data is missing */ + +static gboolean +menu_create_nickinfo_menu (struct User *user, GtkWidget *submenu) +{ + char buf[512]; + char unknown[96]; + char *real, *fmt; + struct away_msg *away; + gboolean missing = FALSE; + GtkWidget *item; + + /* let the translators tweak this if need be */ + fmt = _("<tt><b>%-11s</b></tt> %s"); + snprintf (unknown, sizeof (unknown), "<i>%s</i>", _("Unknown")); + + if (user->realname) + { + real = strip_color (user->realname, -1, STRIP_ALL|STRIP_ESCMARKUP); + snprintf (buf, sizeof (buf), fmt, _("Real Name:"), real); + g_free (real); + } else + { + snprintf (buf, sizeof (buf), fmt, _("Real Name:"), unknown); + } + item = menu_quick_item (0, buf, submenu, XCMENU_MARKUP, 0, 0); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (copy_to_clipboard_cb), + user->realname ? user->realname : unknown); + + snprintf (buf, sizeof (buf), fmt, _("User:"), + user->hostname ? user->hostname : unknown); + item = menu_quick_item (0, buf, submenu, XCMENU_MARKUP, 0, 0); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (copy_to_clipboard_cb), + user->hostname ? user->hostname : unknown); + + snprintf (buf, sizeof (buf), fmt, _("Country:"), + user->hostname ? country(user->hostname) : unknown); + item = menu_quick_item (0, buf, submenu, XCMENU_MARKUP, 0, 0); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (copy_to_clipboard_cb), + user->hostname ? country(user->hostname) : unknown); + + snprintf (buf, sizeof (buf), fmt, _("Server:"), + user->servername ? user->servername : unknown); + item = menu_quick_item (0, buf, submenu, XCMENU_MARKUP, 0, 0); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (copy_to_clipboard_cb), + user->servername ? user->servername : unknown); + + if (user->lasttalk) + { + char min[96]; + + snprintf (min, sizeof (min), _("%u minutes ago"), + (unsigned int) ((time (0) - user->lasttalk) / 60)); + snprintf (buf, sizeof (buf), fmt, _("Last Msg:"), min); + } else + { + snprintf (buf, sizeof (buf), fmt, _("Last Msg:"), unknown); + } + menu_quick_item (0, buf, submenu, XCMENU_MARKUP, 0, 0); + + if (user->away) + { + away = server_away_find_message (current_sess->server, user->nick); + if (away) + { + char *msg = strip_color (away->message ? away->message : unknown, -1, STRIP_ALL|STRIP_ESCMARKUP); + snprintf (buf, sizeof (buf), fmt, _("Away Msg:"), msg); + g_free (msg); + item = menu_quick_item (0, buf, submenu, XCMENU_MARKUP, 0, 0); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (copy_to_clipboard_cb), + away->message ? away->message : unknown); + } + else + missing = TRUE; + } + + return missing; +} + +void +fe_userlist_update (session *sess, struct User *user) +{ + GList *items, *next; + + if (!nick_submenu || !str_copy) + return; + + /* not the same nick as the menu? */ + if (sess->server->p_cmp (user->nick, str_copy)) + return; + + /* get rid of the "show" signal */ + g_signal_handlers_disconnect_by_func (nick_submenu, menu_nickinfo_cb, sess); + + /* destroy all the old items */ + items = ((GtkMenuShell *) nick_submenu)->children; + while (items) + { + next = items->next; + gtk_widget_destroy (items->data); + items = next; + } + + /* and re-create them with new info */ + menu_create_nickinfo_menu (user, nick_submenu); +} + +void +menu_nickmenu (session *sess, GdkEventButton *event, char *nick, int num_sel) +{ + char buf[512]; + struct User *user; + GtkWidget *submenu, *menu = gtk_menu_new (); + + if (str_copy) + free (str_copy); + str_copy = strdup (nick); + + submenu_list = 0; /* first time through, might not be 0 */ + + /* more than 1 nick selected? */ + if (num_sel > 1) + { + snprintf (buf, sizeof (buf), _("%d nicks selected."), num_sel); + menu_quick_item (0, buf, menu, 0, 0, 0); + menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0); + } else + { + user = userlist_find (sess, nick); /* lasttalk is channel specific */ + if (!user) + user = userlist_find_global (current_sess->server, nick); + if (user) + { + nick_submenu = submenu = menu_quick_sub (nick, menu, NULL, XCMENU_DOLIST, -1); + + if (menu_create_nickinfo_menu (user, submenu) || + !user->hostname || !user->realname || !user->servername) + { + g_signal_connect (G_OBJECT (submenu), "show", G_CALLBACK (menu_nickinfo_cb), sess); + } + + menu_quick_endsub (); + menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0); + } + } + + if (num_sel > 1) + menu_create (menu, popup_list, NULL, FALSE); + else + menu_create (menu, popup_list, str_copy, FALSE); + + if (num_sel == 0) /* xtext click */ + menu_add_plugin_items (menu, "\x5$NICK", str_copy); + else /* userlist treeview click */ + menu_add_plugin_items (menu, "\x5$NICK", NULL); + + menu_popup (menu, event, NULL); +} + +/* stuff for the View menu */ + +static void +menu_showhide_cb (session *sess) +{ + if (prefs.hidemenu) + gtk_widget_hide (sess->gui->menu); + else + gtk_widget_show (sess->gui->menu); +} + +static void +menu_topic_showhide_cb (session *sess) +{ + if (prefs.topicbar) + gtk_widget_show (sess->gui->topic_bar); + else + gtk_widget_hide (sess->gui->topic_bar); +} + +static void +menu_userlist_showhide_cb (session *sess) +{ + mg_decide_userlist (sess, TRUE); +} + +static void +menu_ulbuttons_showhide_cb (session *sess) +{ + if (prefs.userlistbuttons) + gtk_widget_show (sess->gui->button_box); + else + gtk_widget_hide (sess->gui->button_box); +} + +static void +menu_cmbuttons_showhide_cb (session *sess) +{ + switch (sess->type) + { + case SESS_CHANNEL: + if (prefs.chanmodebuttons) + gtk_widget_show (sess->gui->topicbutton_box); + else + gtk_widget_hide (sess->gui->topicbutton_box); + break; + default: + gtk_widget_hide (sess->gui->topicbutton_box); + } +} + +static void +menu_setting_foreach (void (*callback) (session *), int id, guint state) +{ + session *sess; + GSList *list; + int maindone = FALSE; /* do it only once for EVERY tab */ + + list = sess_list; + while (list) + { + sess = list->data; + + if (!sess->gui->is_tab || !maindone) + { + if (sess->gui->is_tab) + maindone = TRUE; + if (id != -1) + GTK_CHECK_MENU_ITEM (sess->gui->menu_item[id])->active = state; + if (callback) + callback (sess); + } + + list = list->next; + } +} + +void +menu_bar_toggle (void) +{ + prefs.hidemenu = !prefs.hidemenu; + menu_setting_foreach (menu_showhide_cb, MENU_ID_MENUBAR, !prefs.hidemenu); +} + +static void +menu_bar_toggle_cb (void) +{ + menu_bar_toggle (); + if (prefs.hidemenu) + fe_message (_("The Menubar is now hidden. You can show it again" + " by pressing F9 or right-clicking in a blank part of" + " the main text area."), FE_MSG_INFO); +} + +static void +menu_topicbar_toggle (GtkWidget *wid, gpointer ud) +{ + prefs.topicbar = !prefs.topicbar; + menu_setting_foreach (menu_topic_showhide_cb, MENU_ID_TOPICBAR, + prefs.topicbar); +} + +static void +menu_userlist_toggle (GtkWidget *wid, gpointer ud) +{ + prefs.hideuserlist = !prefs.hideuserlist; + menu_setting_foreach (menu_userlist_showhide_cb, MENU_ID_USERLIST, + !prefs.hideuserlist); +} + +static void +menu_ulbuttons_toggle (GtkWidget *wid, gpointer ud) +{ + prefs.userlistbuttons = !prefs.userlistbuttons; + menu_setting_foreach (menu_ulbuttons_showhide_cb, MENU_ID_ULBUTTONS, + prefs.userlistbuttons); +} + +static void +menu_cmbuttons_toggle (GtkWidget *wid, gpointer ud) +{ + prefs.chanmodebuttons = !prefs.chanmodebuttons; + menu_setting_foreach (menu_cmbuttons_showhide_cb, MENU_ID_MODEBUTTONS, + prefs.chanmodebuttons); +} + +void +menu_middlemenu (session *sess, GdkEventButton *event) +{ + GtkWidget *menu; + GtkAccelGroup *accel_group; + + accel_group = gtk_accel_group_new (); + menu = menu_create_main (accel_group, FALSE, sess->server->is_away, !sess->gui->is_tab, NULL); + menu_popup (menu, event, accel_group); +} + +static void +open_url_cb (GtkWidget *item, char *url) +{ + char buf[512]; + + /* pass this to /URL so it can handle irc:// */ + snprintf (buf, sizeof (buf), "URL %s", url); + handle_command (current_sess, buf, FALSE); +} + +void +menu_urlmenu (GdkEventButton *event, char *url) +{ + GtkWidget *menu; + char *tmp, *chop; + + if (str_copy) + free (str_copy); + str_copy = strdup (url); + + menu = gtk_menu_new (); + /* more than 51 chars? Chop it */ + if (g_utf8_strlen (str_copy, -1) >= 52) + { + tmp = strdup (str_copy); + chop = g_utf8_offset_to_pointer (tmp, 48); + chop[0] = chop[1] = chop[2] = '.'; + chop[3] = 0; + menu_quick_item (0, tmp, menu, XCMENU_SHADED, 0, 0); + free (tmp); + } else + { + menu_quick_item (0, str_copy, menu, XCMENU_SHADED, 0, 0); + } + menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0); + + /* Two hardcoded entries */ + if (strncmp (str_copy, "irc://", 6) == 0 || + strncmp (str_copy, "ircs://",7) == 0) + menu_quick_item_with_callback (open_url_cb, _("Connect"), menu, str_copy); + else + menu_quick_item_with_callback (open_url_cb, _("Open Link in Browser"), menu, str_copy); + menu_quick_item_with_callback (copy_to_clipboard_cb, _("Copy Selected Link"), menu, str_copy); + /* custom ones from urlhandlers.conf */ + menu_create (menu, urlhandler_list, str_copy, TRUE); + menu_add_plugin_items (menu, "\x4$URL", str_copy); + menu_popup (menu, event, NULL); +} + +static void +menu_chan_cycle (GtkWidget * menu, char *chan) +{ + char tbuf[256]; + + if (current_sess) + { + snprintf (tbuf, sizeof tbuf, "CYCLE %s", chan); + handle_command (current_sess, tbuf, FALSE); + } +} + +static void +menu_chan_part (GtkWidget * menu, char *chan) +{ + char tbuf[256]; + + if (current_sess) + { + snprintf (tbuf, sizeof tbuf, "part %s", chan); + handle_command (current_sess, tbuf, FALSE); + } +} + +static void +menu_chan_join (GtkWidget * menu, char *chan) +{ + char tbuf[256]; + + if (current_sess) + { + snprintf (tbuf, sizeof tbuf, "join %s", chan); + handle_command (current_sess, tbuf, FALSE); + } +} + +void +menu_chanmenu (struct session *sess, GdkEventButton * event, char *chan) +{ + GtkWidget *menu; + int is_joined = FALSE; + + if (find_channel (sess->server, chan)) + is_joined = TRUE; + + if (str_copy) + free (str_copy); + str_copy = strdup (chan); + + menu = gtk_menu_new (); + + menu_quick_item (0, chan, menu, XCMENU_SHADED, str_copy, 0); + menu_quick_item (0, 0, menu, XCMENU_SHADED, str_copy, 0); + + if (!is_joined) + menu_quick_item_with_callback (menu_chan_join, _("Join Channel"), menu, + str_copy); + else + { + menu_quick_item_with_callback (menu_chan_part, _("Part Channel"), menu, + str_copy); + menu_quick_item_with_callback (menu_chan_cycle, _("Cycle Channel"), menu, + str_copy); + } + + menu_addfavoritemenu (sess->server, menu, str_copy); + + menu_add_plugin_items (menu, "\x5$CHAN", str_copy); + menu_popup (menu, event, NULL); +} + +static void +menu_delfav_cb (GtkWidget *item, server *serv) +{ + servlist_autojoinedit (serv->network, str_copy, FALSE); +} + +static void +menu_addfav_cb (GtkWidget *item, server *serv) +{ + servlist_autojoinedit (serv->network, str_copy, TRUE); +} + +void +menu_addfavoritemenu (server *serv, GtkWidget *menu, char *channel) +{ + if (!serv->network) + return; + + if (channel != str_copy) + { + if (str_copy) + free (str_copy); + str_copy = strdup (channel); + } + + if (joinlist_is_in_list (serv, channel)) + mg_create_icon_item (_("_Remove from Favorites"), GTK_STOCK_REMOVE, menu, menu_delfav_cb, serv); + else + mg_create_icon_item (_("_Add to Favorites"), GTK_STOCK_ADD, menu, menu_addfav_cb, serv); +} + +static void +menu_open_server_list (GtkWidget *wid, gpointer none) +{ + fe_serverlist_open (current_sess); +} + +static void +menu_settings (GtkWidget * wid, gpointer none) +{ + extern void setup_open (void); + setup_open (); +} + +static void +menu_usermenu (void) +{ + editlist_gui_open (NULL, NULL, usermenu_list, _("XChat: User menu"), + "usermenu", "usermenu.conf", 0); +} + +static void +usermenu_create (GtkWidget *menu) +{ + menu_create (menu, usermenu_list, "", FALSE); + menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0); /* sep */ + menu_quick_item_with_callback (menu_usermenu, _("Edit This Menu..."), menu, 0); +} + +static void +usermenu_destroy (GtkWidget * menu) +{ + GList *items = ((GtkMenuShell *) menu)->children; + GList *next; + + while (items) + { + next = items->next; + gtk_widget_destroy (items->data); + items = next; + } +} + +void +usermenu_update (void) +{ + int done_main = FALSE; + GSList *list = sess_list; + session *sess; + GtkWidget *menu; + + while (list) + { + sess = list->data; + menu = sess->gui->menu_item[MENU_ID_USERMENU]; + if (sess->gui->is_tab) + { + if (!done_main && menu) + { + usermenu_destroy (menu); + usermenu_create (menu); + done_main = TRUE; + } + } else if (menu) + { + usermenu_destroy (menu); + usermenu_create (menu); + } + list = list->next; + } +} + +static void +menu_newserver_window (GtkWidget * wid, gpointer none) +{ + int old = prefs.tabchannels; + + prefs.tabchannels = 0; + new_ircwindow (NULL, NULL, SESS_SERVER, 0); + prefs.tabchannels = old; +} + +static void +menu_newchannel_window (GtkWidget * wid, gpointer none) +{ + int old = prefs.tabchannels; + + prefs.tabchannels = 0; + new_ircwindow (current_sess->server, NULL, SESS_CHANNEL, 0); + prefs.tabchannels = old; +} + +static void +menu_newserver_tab (GtkWidget * wid, gpointer none) +{ + int old = prefs.tabchannels; + int oldf = prefs.newtabstofront; + + prefs.tabchannels = 1; + /* force focus if setting is "only requested tabs" */ + if (prefs.newtabstofront == 2) + prefs.newtabstofront = 1; + new_ircwindow (NULL, NULL, SESS_SERVER, 0); + prefs.tabchannels = old; + prefs.newtabstofront = oldf; +} + +static void +menu_newchannel_tab (GtkWidget * wid, gpointer none) +{ + int old = prefs.tabchannels; + + prefs.tabchannels = 1; + new_ircwindow (current_sess->server, NULL, SESS_CHANNEL, 0); + prefs.tabchannels = old; +} + +static void +menu_rawlog (GtkWidget * wid, gpointer none) +{ + open_rawlog (current_sess->server); +} + +static void +menu_detach (GtkWidget * wid, gpointer none) +{ + mg_detach (current_sess, 0); +} + +static void +menu_close (GtkWidget * wid, gpointer none) +{ + mg_close_sess (current_sess); +} + +static void +menu_quit (GtkWidget * wid, gpointer none) +{ + mg_open_quit_dialog (FALSE); +} + +static void +menu_search () +{ + search_open (current_sess); +} + +static void +menu_resetmarker (GtkWidget * wid, gpointer none) +{ + gtk_xtext_reset_marker_pos (GTK_XTEXT (current_sess->gui->xtext)); +} + +static void +menu_flushbuffer (GtkWidget * wid, gpointer none) +{ + fe_text_clear (current_sess, 0); +} + +static void +savebuffer_req_done (session *sess, char *file) +{ + int fh; + + if (!file) + return; + + fh = open (file, O_TRUNC | O_WRONLY | O_CREAT, 0600); + if (fh != -1) + { + gtk_xtext_save (GTK_XTEXT (sess->gui->xtext), fh); + close (fh); + } +} + +static void +menu_savebuffer (GtkWidget * wid, gpointer none) +{ + gtkutil_file_req (_("Select an output filename"), savebuffer_req_done, + current_sess, NULL, FRF_WRITE); +} + +static void +menu_disconnect (GtkWidget * wid, gpointer none) +{ + handle_command (current_sess, "DISCON", FALSE); +} + +static void +menu_reconnect (GtkWidget * wid, gpointer none) +{ + if (current_sess->server->hostname[0]) + handle_command (current_sess, "RECONNECT", FALSE); + else + fe_serverlist_open (current_sess); +} + +static void +menu_join_cb (GtkWidget *dialog, gint response, GtkEntry *entry) +{ + switch (response) + { + case GTK_RESPONSE_ACCEPT: + menu_chan_join (NULL, entry->text); + break; + + case GTK_RESPONSE_HELP: + chanlist_opengui (current_sess->server, TRUE); + break; + } + + gtk_widget_destroy (dialog); +} + +static void +menu_join_entry_cb (GtkWidget *entry, GtkDialog *dialog) +{ + gtk_dialog_response (dialog, GTK_RESPONSE_ACCEPT); +} + +static void +menu_join (GtkWidget * wid, gpointer none) +{ + GtkWidget *hbox, *dialog, *entry, *label; + + dialog = gtk_dialog_new_with_buttons (_("Join Channel"), + GTK_WINDOW (parent_window), 0, + _("Retrieve channel list..."), GTK_RESPONSE_HELP, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + NULL); + gtk_box_set_homogeneous (GTK_BOX (GTK_DIALOG (dialog)->vbox), TRUE); + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + hbox = gtk_hbox_new (TRUE, 0); + + entry = gtk_entry_new (); + GTK_ENTRY (entry)->editable = 0; /* avoid auto-selection */ + gtk_entry_set_text (GTK_ENTRY (entry), "#"); + g_signal_connect (G_OBJECT (entry), "activate", + G_CALLBACK (menu_join_entry_cb), dialog); + gtk_box_pack_end (GTK_BOX (hbox), entry, 0, 0, 0); + + label = gtk_label_new (_("Enter Channel to Join:")); + gtk_box_pack_end (GTK_BOX (hbox), label, 0, 0, 0); + + g_signal_connect (G_OBJECT (dialog), "response", + G_CALLBACK (menu_join_cb), entry); + + gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), hbox); + + gtk_widget_show_all (dialog); + + gtk_editable_set_editable (GTK_EDITABLE (entry), TRUE); + gtk_editable_set_position (GTK_EDITABLE (entry), 1); +} + +static void +menu_away (GtkCheckMenuItem *item, gpointer none) +{ + handle_command (current_sess, item->active ? "away" : "back", FALSE); +} + +static void +menu_chanlist (GtkWidget * wid, gpointer none) +{ + chanlist_opengui (current_sess->server, FALSE); +} + +static void +menu_banlist (GtkWidget * wid, gpointer none) +{ + banlist_opengui (current_sess); +} + +#ifdef USE_PLUGIN + +static void +menu_loadplugin (void) +{ + plugingui_load (); +} + +static void +menu_pluginlist (void) +{ + plugingui_open (); +} + +#else + +#define menu_pluginlist 0 +#define menu_loadplugin 0 + +#endif + +#define usercommands_help _("User Commands - Special codes:\n\n"\ + "%c = current channel\n"\ + "%e = current network name\n"\ + "%m = machine info\n"\ + "%n = your nick\n"\ + "%t = time/date\n"\ + "%v = xchat version\n"\ + "%2 = word 2\n"\ + "%3 = word 3\n"\ + "&2 = word 2 to the end of line\n"\ + "&3 = word 3 to the end of line\n\n"\ + "eg:\n"\ + "/cmd john hello\n\n"\ + "%2 would be \042john\042\n"\ + "&2 would be \042john hello\042.") + +#define ulbutton_help _("Userlist Buttons - Special codes:\n\n"\ + "%a = all selected nicks\n"\ + "%c = current channel\n"\ + "%e = current network name\n"\ + "%h = selected nick's hostname\n"\ + "%m = machine info\n"\ + "%n = your nick\n"\ + "%s = selected nick\n"\ + "%t = time/date\n") + +#define dlgbutton_help _("Dialog Buttons - Special codes:\n\n"\ + "%a = all selected nicks\n"\ + "%c = current channel\n"\ + "%e = current network name\n"\ + "%h = selected nick's hostname\n"\ + "%m = machine info\n"\ + "%n = your nick\n"\ + "%s = selected nick\n"\ + "%t = time/date\n") + +#define ctcp_help _("CTCP Replies - Special codes:\n\n"\ + "%d = data (the whole ctcp)\n"\ + "%e = current network name\n"\ + "%m = machine info\n"\ + "%s = nick who sent the ctcp\n"\ + "%t = time/date\n"\ + "%2 = word 2\n"\ + "%3 = word 3\n"\ + "&2 = word 2 to the end of line\n"\ + "&3 = word 3 to the end of line\n\n") + +#define url_help _("URL Handlers - Special codes:\n\n"\ + "%s = the URL string\n\n"\ + "Putting a ! infront of the command\n"\ + "indicates it should be sent to a\n"\ + "shell instead of XChat") + +static void +menu_usercommands (void) +{ + editlist_gui_open (NULL, NULL, command_list, _("XChat: User Defined Commands"), + "commands", "commands.conf", usercommands_help); +} + +static void +menu_ulpopup (void) +{ + editlist_gui_open (NULL, NULL, popup_list, _("XChat: Userlist Popup menu"), "popup", + "popup.conf", ulbutton_help); +} + +static void +menu_rpopup (void) +{ + editlist_gui_open (_("Text"), _("Replace with"), replace_list, _("XChat: Replace"), "replace", + "replace.conf", 0); +} + +static void +menu_urlhandlers (void) +{ + editlist_gui_open (NULL, NULL, urlhandler_list, _("XChat: URL Handlers"), "urlhandlers", + "urlhandlers.conf", url_help); +} + +static void +menu_evtpopup (void) +{ + pevent_dialog_show (); +} + +static void +menu_keypopup (void) +{ + key_dialog_show (); +} + +static void +menu_ulbuttons (void) +{ + editlist_gui_open (NULL, NULL, button_list, _("XChat: Userlist buttons"), "buttons", + "buttons.conf", ulbutton_help); +} + +static void +menu_dlgbuttons (void) +{ + editlist_gui_open (NULL, NULL, dlgbutton_list, _("XChat: Dialog buttons"), "dlgbuttons", + "dlgbuttons.conf", dlgbutton_help); +} + +static void +menu_ctcpguiopen (void) +{ + editlist_gui_open (NULL, NULL, ctcp_list, _("XChat: CTCP Replies"), "ctcpreply", + "ctcpreply.conf", ctcp_help); +} + +static void +menu_docs (GtkWidget *wid, gpointer none) +{ + fe_open_url ("http://xchat.org/docs/"); +} + +/*static void +menu_webpage (GtkWidget *wid, gpointer none) +{ + fe_open_url ("http://xchat.org"); +}*/ + +static void +menu_dcc_win (GtkWidget *wid, gpointer none) +{ + fe_dcc_open_recv_win (FALSE); + fe_dcc_open_send_win (FALSE); +} + +static void +menu_dcc_chat_win (GtkWidget *wid, gpointer none) +{ + fe_dcc_open_chat_win (FALSE); +} + +void +menu_change_layout (void) +{ + if (prefs.tab_layout == 0) + { + menu_setting_foreach (NULL, MENU_ID_LAYOUT_TABS, 1); + menu_setting_foreach (NULL, MENU_ID_LAYOUT_TREE, 0); + mg_change_layout (0); + } else + { + menu_setting_foreach (NULL, MENU_ID_LAYOUT_TABS, 0); + menu_setting_foreach (NULL, MENU_ID_LAYOUT_TREE, 1); + mg_change_layout (2); + } +} + +static void +menu_layout_cb (GtkWidget *item, gpointer none) +{ + prefs.tab_layout = 2; + if (GTK_CHECK_MENU_ITEM (item)->active) + prefs.tab_layout = 0; + + menu_change_layout (); +} + +static void +menu_apply_metres_cb (session *sess) +{ + mg_update_meters (sess->gui); +} + +static void +menu_metres_off (GtkWidget *item, gpointer none) +{ + if (GTK_CHECK_MENU_ITEM (item)->active) + { + prefs.lagometer = 0; + prefs.throttlemeter = 0; + menu_setting_foreach (menu_apply_metres_cb, -1, 0); + } +} + +static void +menu_metres_text (GtkWidget *item, gpointer none) +{ + if (GTK_CHECK_MENU_ITEM (item)->active) + { + prefs.lagometer = 2; + prefs.throttlemeter = 2; + menu_setting_foreach (menu_apply_metres_cb, -1, 0); + } +} + +static void +menu_metres_graph (GtkWidget *item, gpointer none) +{ + if (GTK_CHECK_MENU_ITEM (item)->active) + { + prefs.lagometer = 1; + prefs.throttlemeter = 1; + menu_setting_foreach (menu_apply_metres_cb, -1, 0); + } +} + +static void +menu_metres_both (GtkWidget *item, gpointer none) +{ + if (GTK_CHECK_MENU_ITEM (item)->active) + { + prefs.lagometer = 3; + prefs.throttlemeter = 3; + menu_setting_foreach (menu_apply_metres_cb, -1, 0); + } +} + +static struct mymenu mymenu[] = { + {N_("_XChat"), 0, 0, M_NEWMENU, 0, 0, 1}, + {N_("Network Li_st..."), menu_open_server_list, (char *)&pix_book, M_MENUPIX, 0, 0, 1, GDK_s}, + {0, 0, 0, M_SEP, 0, 0, 0}, + + {N_("_New"), 0, GTK_STOCK_NEW, M_MENUSUB, 0, 0, 1}, + {N_("Server Tab..."), menu_newserver_tab, 0, M_MENUITEM, 0, 0, 1, GDK_t}, + {N_("Channel Tab..."), menu_newchannel_tab, 0, M_MENUITEM, 0, 0, 1}, + {N_("Server Window..."), menu_newserver_window, 0, M_MENUITEM, 0, 0, 1}, + {N_("Channel Window..."), menu_newchannel_window, 0, M_MENUITEM, 0, 0, 1}, + {0, 0, 0, M_END, 0, 0, 0}, + {0, 0, 0, M_SEP, 0, 0, 0}, + +#ifdef USE_PLUGIN + {N_("_Load Plugin or Script..."), menu_loadplugin, GTK_STOCK_REVERT_TO_SAVED, M_MENUSTOCK, 0, 0, 1}, +#else + {N_("_Load Plugin or Script..."), 0, GTK_STOCK_REVERT_TO_SAVED, M_MENUSTOCK, 0, 0, 0}, +#endif + {0, 0, 0, M_SEP, 0, 0, 0}, /* 11 */ +#define DETACH_OFFSET (12) + {0, menu_detach, GTK_STOCK_REDO, M_MENUSTOCK, 0, 0, 1, GDK_I}, /* 12 */ +#define CLOSE_OFFSET (13) + {0, menu_close, GTK_STOCK_CLOSE, M_MENUSTOCK, 0, 0, 1, GDK_w}, + {0, 0, 0, M_SEP, 0, 0, 0}, + {N_("_Quit"), menu_quit, GTK_STOCK_QUIT, M_MENUSTOCK, 0, 0, 1, GDK_q}, /* 15 */ + + {N_("_View"), 0, 0, M_NEWMENU, 0, 0, 1}, +#define MENUBAR_OFFSET (17) + {N_("_Menu Bar"), menu_bar_toggle_cb, 0, M_MENUTOG, MENU_ID_MENUBAR, 0, 1, GDK_F9}, + {N_("_Topic Bar"), menu_topicbar_toggle, 0, M_MENUTOG, MENU_ID_TOPICBAR, 0, 1}, + {N_("_User List"), menu_userlist_toggle, 0, M_MENUTOG, MENU_ID_USERLIST, 0, 1, GDK_F7}, + {N_("U_serlist Buttons"), menu_ulbuttons_toggle, 0, M_MENUTOG, MENU_ID_ULBUTTONS, 0, 1}, + {N_("M_ode Buttons"), menu_cmbuttons_toggle, 0, M_MENUTOG, MENU_ID_MODEBUTTONS, 0, 1}, + {0, 0, 0, M_SEP, 0, 0, 0}, + {N_("_Channel Switcher"), 0, 0, M_MENUSUB, 0, 0, 1}, /* 23 */ +#define TABS_OFFSET (24) + {N_("_Tabs"), menu_layout_cb, 0, M_MENURADIO, MENU_ID_LAYOUT_TABS, 0, 1}, + {N_("T_ree"), 0, 0, M_MENURADIO, MENU_ID_LAYOUT_TREE, 0, 1}, + {0, 0, 0, M_END, 0, 0, 0}, + {N_("_Network Meters"), 0, 0, M_MENUSUB, 0, 0, 1}, /* 27 */ +#define METRE_OFFSET (28) + {N_("Off"), menu_metres_off, 0, M_MENURADIO, 0, 0, 1}, + {N_("Graph"), menu_metres_graph, 0, M_MENURADIO, 0, 0, 1}, + {N_("Text"), menu_metres_text, 0, M_MENURADIO, 0, 0, 1}, + {N_("Both"), menu_metres_both, 0, M_MENURADIO, 0, 0, 1}, + {0, 0, 0, M_END, 0, 0, 0}, /* 32 */ + + {N_("_Server"), 0, 0, M_NEWMENU, 0, 0, 1}, + {N_("_Disconnect"), menu_disconnect, GTK_STOCK_DISCONNECT, M_MENUSTOCK, MENU_ID_DISCONNECT, 0, 1}, + {N_("_Reconnect"), menu_reconnect, GTK_STOCK_CONNECT, M_MENUSTOCK, MENU_ID_RECONNECT, 0, 1}, + {N_("Join a Channel..."), menu_join, GTK_STOCK_JUMP_TO, M_MENUSTOCK, MENU_ID_JOIN, 0, 1}, + {N_("List of Channels..."), menu_chanlist, GTK_STOCK_INDEX, M_MENUITEM, 0, 0, 1}, + {0, 0, 0, M_SEP, 0, 0, 0}, +#define AWAY_OFFSET (39) + {N_("Marked Away"), menu_away, 0, M_MENUTOG, MENU_ID_AWAY, 0, 1, GDK_a}, + + {N_("_Usermenu"), 0, 0, M_NEWMENU, MENU_ID_USERMENU, 0, 1}, /* 40 */ + + {N_("S_ettings"), 0, 0, M_NEWMENU, 0, 0, 1}, + {N_("_Preferences"), menu_settings, GTK_STOCK_PREFERENCES, M_MENUSTOCK, 0, 0, 1}, + + {N_("Advanced"), 0, GTK_STOCK_JUSTIFY_LEFT, M_MENUSUB, 0, 0, 1}, + {N_("Auto Replace..."), menu_rpopup, 0, M_MENUITEM, 0, 0, 1}, + {N_("CTCP Replies..."), menu_ctcpguiopen, 0, M_MENUITEM, 0, 0, 1}, + {N_("Dialog Buttons..."), menu_dlgbuttons, 0, M_MENUITEM, 0, 0, 1}, + {N_("Keyboard Shortcuts..."), menu_keypopup, 0, M_MENUITEM, 0, 0, 1}, + {N_("Text Events..."), menu_evtpopup, 0, M_MENUITEM, 0, 0, 1}, + {N_("URL Handlers..."), menu_urlhandlers, 0, M_MENUITEM, 0, 0, 1}, + {N_("User Commands..."), menu_usercommands, 0, M_MENUITEM, 0, 0, 1}, + {N_("Userlist Buttons..."), menu_ulbuttons, 0, M_MENUITEM, 0, 0, 1}, + {N_("Userlist Popup..."), menu_ulpopup, 0, M_MENUITEM, 0, 0, 1}, + {0, 0, 0, M_END, 0, 0, 0}, /* 53 */ + + {N_("_Window"), 0, 0, M_NEWMENU, 0, 0, 1}, + {N_("Ban List..."), menu_banlist, 0, M_MENUITEM, 0, 0, 1}, + {N_("Character Chart..."), ascii_open, 0, M_MENUITEM, 0, 0, 1}, + {N_("Direct Chat..."), menu_dcc_chat_win, 0, M_MENUITEM, 0, 0, 1}, + {N_("File Transfers..."), menu_dcc_win, 0, M_MENUITEM, 0, 0, 1}, + {N_("Friends List..."), notify_opengui, 0, M_MENUITEM, 0, 0, 1}, + {N_("Ignore List..."), ignore_gui_open, 0, M_MENUITEM, 0, 0, 1}, + {N_("Plugins and Scripts..."), menu_pluginlist, 0, M_MENUITEM, 0, 0, 1}, + {N_("Raw Log..."), menu_rawlog, 0, M_MENUITEM, 0, 0, 1}, /* 62 */ + {N_("URL Grabber..."), url_opengui, 0, M_MENUITEM, 0, 0, 1}, + {0, 0, 0, M_SEP, 0, 0, 0}, + {N_("Reset Marker Line"), menu_resetmarker, 0, M_MENUITEM, 0, 0, 1, GDK_m}, + {N_("C_lear Text"), menu_flushbuffer, GTK_STOCK_CLEAR, M_MENUSTOCK, 0, 0, 1, GDK_l}, +#define SEARCH_OFFSET 67 + {N_("Search Text..."), menu_search, GTK_STOCK_FIND, M_MENUSTOCK, 0, 0, 1, GDK_f}, + {N_("Save Text..."), menu_savebuffer, GTK_STOCK_SAVE, M_MENUSTOCK, 0, 0, 1}, + + {N_("_Help"), 0, 0, M_NEWMENU, 0, 0, 1}, /* 69 */ + {N_("_Contents"), menu_docs, GTK_STOCK_HELP, M_MENUSTOCK, 0, 0, 1, GDK_F1}, +#if 0 + {N_("Check for updates"), menu_update, 0, M_MENUITEM, 0, 1}, +#endif + {N_("_About"), menu_about, GTK_STOCK_ABOUT, M_MENUSTOCK, 0, 0, 1}, + + {0, 0, 0, M_END, 0, 0, 0}, +}; + +GtkWidget * +create_icon_menu (char *labeltext, void *stock_name, int is_stock) +{ + GtkWidget *item, *img; + + if (is_stock) + img = gtk_image_new_from_stock (stock_name, GTK_ICON_SIZE_MENU); + else + img = gtk_image_new_from_pixbuf (*((GdkPixbuf **)stock_name)); + item = gtk_image_menu_item_new_with_mnemonic (labeltext); + gtk_image_menu_item_set_image ((GtkImageMenuItem *)item, img); + gtk_widget_show (img); + + return item; +} + +#if GTK_CHECK_VERSION(2,4,0) + +/* Override the default GTK2.4 handler, which would make menu + bindings not work when the menu-bar is hidden. */ +static gboolean +menu_canacaccel (GtkWidget *widget, guint signal_id, gpointer user_data) +{ + /* GTK2.2 behaviour */ +#if GTK_CHECK_VERSION(2,20,0) + return gtk_widget_is_sensitive (widget); +#else + return GTK_WIDGET_IS_SENSITIVE (widget); +#endif +} + +#endif + + +/* === STUFF FOR /MENU === */ + +static GtkMenuItem * +menu_find_item (GtkWidget *menu, char *name) +{ + GList *items = ((GtkMenuShell *) menu)->children; + GtkMenuItem *item; + GtkWidget *child; + const char *labeltext; + + while (items) + { + item = items->data; + child = GTK_BIN (item)->child; + if (child) /* separators arn't labels, skip them */ + { + labeltext = g_object_get_data (G_OBJECT (item), "name"); + if (!labeltext) + labeltext = gtk_label_get_text (GTK_LABEL (child)); + if (!menu_streq (labeltext, name, 1)) + return item; + } else if (name == NULL) + { + return item; + } + items = items->next; + } + + return NULL; +} + +static GtkWidget * +menu_find_path (GtkWidget *menu, char *path) +{ + GtkMenuItem *item; + char *s; + char name[128]; + int len; + + /* grab the next part of the path */ + s = strchr (path, '/'); + len = s - path; + if (!s) + len = strlen (path); + len = MIN (len, sizeof (name) - 1); + memcpy (name, path, len); + name[len] = 0; + + item = menu_find_item (menu, name); + if (!item) + return NULL; + + menu = gtk_menu_item_get_submenu (item); + if (!menu) + return NULL; + + path += len; + if (*path == 0) + return menu; + + return menu_find_path (menu, path + 1); +} + +static GtkWidget * +menu_find (GtkWidget *menu, char *path, char *label) +{ + GtkWidget *item = NULL; + + if (path[0] != 0) + menu = menu_find_path (menu, path); + if (menu) + item = (GtkWidget *)menu_find_item (menu, label); + return item; +} + +static void +menu_foreach_gui (menu_entry *me, void (*callback) (GtkWidget *, menu_entry *, char *)) +{ + GSList *list = sess_list; + int tabdone = FALSE; + session *sess; + + if (!me->is_main) + return; /* not main menu */ + + while (list) + { + sess = list->data; + /* do it only once for tab sessions, since they share a GUI */ + if (!sess->gui->is_tab || !tabdone) + { + callback (sess->gui->menu, me, NULL); + if (sess->gui->is_tab) + tabdone = TRUE; + } + list = list->next; + } +} + +static void +menu_update_cb (GtkWidget *menu, menu_entry *me, char *target) +{ + GtkWidget *item; + + item = menu_find (menu, me->path, me->label); + if (item) + { + gtk_widget_set_sensitive (item, me->enable); + /* must do it without triggering the callback */ + if (GTK_IS_CHECK_MENU_ITEM (item)) + GTK_CHECK_MENU_ITEM (item)->active = me->state; + } +} + +/* radio state changed via mouse click */ +static void +menu_radio_cb (GtkCheckMenuItem *item, menu_entry *me) +{ + me->state = 0; + if (item->active) + me->state = 1; + + /* update the state, incase this was changed via right-click. */ + /* This will update all other windows and menu bars */ + menu_foreach_gui (me, menu_update_cb); + + if (me->state && me->cmd) + handle_command (current_sess, me->cmd, FALSE); +} + +/* toggle state changed via mouse click */ +static void +menu_toggle_cb (GtkCheckMenuItem *item, menu_entry *me) +{ + me->state = 0; + if (item->active) + me->state = 1; + + /* update the state, incase this was changed via right-click. */ + /* This will update all other windows and menu bars */ + menu_foreach_gui (me, menu_update_cb); + + if (me->state) + handle_command (current_sess, me->cmd, FALSE); + else + handle_command (current_sess, me->ucmd, FALSE); +} + +static GtkWidget * +menu_radio_item (char *label, GtkWidget *menu, void *callback, void *userdata, + int state, char *groupname) +{ + GtkWidget *item; + GtkMenuItem *parent; + GSList *grouplist = NULL; + + parent = menu_find_item (menu, groupname); + if (parent) + grouplist = gtk_radio_menu_item_get_group ((GtkRadioMenuItem *)parent); + + item = gtk_radio_menu_item_new_with_label (grouplist, label); + gtk_check_menu_item_set_active ((GtkCheckMenuItem*)item, state); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (callback), userdata); + gtk_widget_show (item); + + return item; +} + +static void +menu_reorder (GtkMenu *menu, GtkWidget *item, int pos) +{ + if (pos == 0xffff) /* outbound.c uses this default */ + return; + + if (pos < 0) /* position offset from end/bottom */ + gtk_menu_reorder_child (menu, item, (g_list_length (GTK_MENU_SHELL (menu)->children) + pos) - 1); + else + gtk_menu_reorder_child (menu, item, pos); +} + +static GtkWidget * +menu_add_radio (GtkWidget *menu, menu_entry *me) +{ + GtkWidget *item = NULL; + char *path = me->path + me->root_offset; + + if (path[0] != 0) + menu = menu_find_path (menu, path); + if (menu) + { + item = menu_radio_item (me->label, menu, menu_radio_cb, me, me->state, me->group); + menu_reorder (GTK_MENU (menu), item, me->pos); + } + return item; +} + +static GtkWidget * +menu_add_toggle (GtkWidget *menu, menu_entry *me) +{ + GtkWidget *item = NULL; + char *path = me->path + me->root_offset; + + if (path[0] != 0) + menu = menu_find_path (menu, path); + if (menu) + { + item = menu_toggle_item (me->label, menu, menu_toggle_cb, me, me->state); + menu_reorder (GTK_MENU (menu), item, me->pos); + } + return item; +} + +static GtkWidget * +menu_add_item (GtkWidget *menu, menu_entry *me, char *target) +{ + GtkWidget *item = NULL; + char *path = me->path + me->root_offset; + + if (path[0] != 0) + menu = menu_find_path (menu, path); + if (menu) + { + item = menu_quick_item (me->cmd, me->label, menu, me->markup ? XCMENU_MARKUP|XCMENU_MNEMONIC : XCMENU_MNEMONIC, target, me->icon); + menu_reorder (GTK_MENU (menu), item, me->pos); + } + return item; +} + +static GtkWidget * +menu_add_sub (GtkWidget *menu, menu_entry *me) +{ + GtkWidget *item = NULL; + char *path = me->path + me->root_offset; + int pos; + + if (path[0] != 0) + menu = menu_find_path (menu, path); + if (menu) + { + pos = me->pos; + if (pos < 0) /* position offset from end/bottom */ + pos = g_list_length (GTK_MENU_SHELL (menu)->children) + pos; + menu_quick_sub (me->label, menu, &item, me->markup ? XCMENU_MARKUP|XCMENU_MNEMONIC : XCMENU_MNEMONIC, pos); + } + return item; +} + +static void +menu_del_cb (GtkWidget *menu, menu_entry *me, char *target) +{ + GtkWidget *item = menu_find (menu, me->path + me->root_offset, me->label); + if (item) + gtk_widget_destroy (item); +} + +static void +menu_add_cb (GtkWidget *menu, menu_entry *me, char *target) +{ + GtkWidget *item; + GtkAccelGroup *accel_group; + + if (me->group) /* have a group name? Must be a radio item */ + item = menu_add_radio (menu, me); + else if (me->ucmd) /* have unselect-cmd? Must be a toggle item */ + item = menu_add_toggle (menu, me); + else if (me->cmd || !me->label) /* label=NULL for separators */ + item = menu_add_item (menu, me, target); + else + item = menu_add_sub (menu, me); + + if (item) + { + gtk_widget_set_sensitive (item, me->enable); + if (me->key) + { + accel_group = g_object_get_data (G_OBJECT (menu), "accel"); + if (accel_group) /* popup menus don't have them */ + gtk_widget_add_accelerator (item, "activate", accel_group, me->key, + me->modifier, GTK_ACCEL_VISIBLE); + } + } +} + +char * +fe_menu_add (menu_entry *me) +{ + char *text; + + menu_foreach_gui (me, menu_add_cb); + + if (!me->markup) + return NULL; + + if (!pango_parse_markup (me->label, -1, 0, NULL, &text, NULL, NULL)) + return NULL; + + /* return the label with markup stripped */ + return text; +} + +void +fe_menu_del (menu_entry *me) +{ + menu_foreach_gui (me, menu_del_cb); +} + +void +fe_menu_update (menu_entry *me) +{ + menu_foreach_gui (me, menu_update_cb); +} + +/* used to add custom menus to the right-click menu */ + +static void +menu_add_plugin_mainmenu_items (GtkWidget *menu) +{ + GSList *list; + menu_entry *me; + + list = menu_list; /* outbound.c */ + while (list) + { + me = list->data; + if (me->is_main) + menu_add_cb (menu, me, NULL); + list = list->next; + } +} + +void +menu_add_plugin_items (GtkWidget *menu, char *root, char *target) +{ + GSList *list; + menu_entry *me; + + list = menu_list; /* outbound.c */ + while (list) + { + me = list->data; + if (!me->is_main && !strncmp (me->path, root + 1, root[0])) + menu_add_cb (menu, me, target); + list = list->next; + } +} + +/* === END STUFF FOR /MENU === */ + +GtkWidget * +menu_create_main (void *accel_group, int bar, int away, int toplevel, + GtkWidget **menu_widgets) +{ + int i = 0; + GtkWidget *item; + GtkWidget *menu = 0; + GtkWidget *menu_item = 0; + GtkWidget *menu_bar; + GtkWidget *usermenu = 0; + GtkWidget *submenu = 0; + int close_mask = GDK_CONTROL_MASK; + int away_mask = GDK_MOD1_MASK; + char *key_theme = NULL; + GtkSettings *settings; + GSList *group = NULL; + + if (bar) + menu_bar = gtk_menu_bar_new (); + else + menu_bar = gtk_menu_new (); + + /* /MENU needs to know this later */ + g_object_set_data (G_OBJECT (menu_bar), "accel", accel_group); + +#if GTK_CHECK_VERSION(2,4,0) + g_signal_connect (G_OBJECT (menu_bar), "can-activate-accel", + G_CALLBACK (menu_canacaccel), 0); +#endif + + /* set the initial state of toggles */ + mymenu[MENUBAR_OFFSET].state = !prefs.hidemenu; + mymenu[MENUBAR_OFFSET+1].state = prefs.topicbar; + mymenu[MENUBAR_OFFSET+2].state = !prefs.hideuserlist; + mymenu[MENUBAR_OFFSET+3].state = prefs.userlistbuttons; + mymenu[MENUBAR_OFFSET+4].state = prefs.chanmodebuttons; + + mymenu[AWAY_OFFSET].state = away; + + switch (prefs.tab_layout) + { + case 0: + mymenu[TABS_OFFSET].state = 1; + mymenu[TABS_OFFSET+1].state = 0; + break; + default: + mymenu[TABS_OFFSET].state = 0; + mymenu[TABS_OFFSET+1].state = 1; + } + + mymenu[METRE_OFFSET].state = 0; + mymenu[METRE_OFFSET+1].state = 0; + mymenu[METRE_OFFSET+2].state = 0; + mymenu[METRE_OFFSET+3].state = 0; + switch (prefs.lagometer) + { + case 0: + mymenu[METRE_OFFSET].state = 1; + break; + case 1: + mymenu[METRE_OFFSET+1].state = 1; + break; + case 2: + mymenu[METRE_OFFSET+2].state = 1; + break; + default: + mymenu[METRE_OFFSET+3].state = 1; + } + + /* change Close binding to ctrl-shift-w when using emacs keys */ + settings = gtk_widget_get_settings (menu_bar); + if (settings) + { + g_object_get (settings, "gtk-key-theme-name", &key_theme, NULL); + if (key_theme) + { + if (!strcasecmp (key_theme, "Emacs")) + { + close_mask = GDK_SHIFT_MASK | GDK_CONTROL_MASK; + mymenu[SEARCH_OFFSET].key = 0; + } + g_free (key_theme); + } + } + + /* Away binding to ctrl-alt-a if the _Help menu conflicts (FR/PT/IT) */ + { + char *help = _("_Help"); + char *under = strchr (help, '_'); + if (under && (under[1] == 'a' || under[1] == 'A')) + away_mask = GDK_MOD1_MASK | GDK_CONTROL_MASK; + } + + if (!toplevel) + { + mymenu[DETACH_OFFSET].text = N_("_Detach"); + mymenu[CLOSE_OFFSET].text = N_("_Close"); + } + else + { + mymenu[DETACH_OFFSET].text = N_("_Attach"); + mymenu[CLOSE_OFFSET].text = N_("_Close"); + } + + while (1) + { + item = NULL; + if (mymenu[i].id == MENU_ID_USERMENU && !prefs.gui_usermenu) + { + i++; + continue; + } + + switch (mymenu[i].type) + { + case M_NEWMENU: + if (menu) + gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), menu); + item = menu = gtk_menu_new (); + if (mymenu[i].id == MENU_ID_USERMENU) + usermenu = menu; + menu_item = gtk_menu_item_new_with_mnemonic (_(mymenu[i].text)); + /* record the English name for /menu */ + g_object_set_data (G_OBJECT (menu_item), "name", mymenu[i].text); + gtk_menu_shell_append (GTK_MENU_SHELL (menu_bar), menu_item); + gtk_widget_show (menu_item); + break; + + case M_MENUPIX: + item = create_icon_menu (_(mymenu[i].text), mymenu[i].image, FALSE); + goto normalitem; + + case M_MENUSTOCK: + item = create_icon_menu (_(mymenu[i].text), mymenu[i].image, TRUE); + goto normalitem; + + case M_MENUITEM: + item = gtk_menu_item_new_with_mnemonic (_(mymenu[i].text)); +normalitem: + if (mymenu[i].key != 0) + gtk_widget_add_accelerator (item, "activate", accel_group, + mymenu[i].key, + mymenu[i].key == GDK_F1 ? 0 : + mymenu[i].key == GDK_w ? close_mask : + GDK_CONTROL_MASK, + GTK_ACCEL_VISIBLE); + if (mymenu[i].callback) + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (mymenu[i].callback), 0); + if (submenu) + gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item); + else + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show (item); + break; + + case M_MENUTOG: + item = gtk_check_menu_item_new_with_mnemonic (_(mymenu[i].text)); +togitem: + /* must avoid callback for Radio buttons */ + GTK_CHECK_MENU_ITEM (item)->active = mymenu[i].state; + /*gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (item), + mymenu[i].state);*/ + if (mymenu[i].key != 0) + gtk_widget_add_accelerator (item, "activate", accel_group, + mymenu[i].key, mymenu[i].id == MENU_ID_AWAY ? + away_mask : GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); + if (mymenu[i].callback) + g_signal_connect (G_OBJECT (item), "toggled", + G_CALLBACK (mymenu[i].callback), 0); + if (submenu) + gtk_menu_shell_append (GTK_MENU_SHELL (submenu), item); + else + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show (item); + gtk_widget_set_sensitive (item, mymenu[i].sensitive); + break; + + case M_MENURADIO: + item = gtk_radio_menu_item_new_with_mnemonic (group, _(mymenu[i].text)); + group = gtk_radio_menu_item_get_group (GTK_RADIO_MENU_ITEM (item)); + goto togitem; + + case M_SEP: + item = gtk_menu_item_new (); + gtk_widget_set_sensitive (item, FALSE); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show (item); + break; + + case M_MENUSUB: + group = NULL; + submenu = gtk_menu_new (); + item = create_icon_menu (_(mymenu[i].text), mymenu[i].image, TRUE); + /* record the English name for /menu */ + g_object_set_data (G_OBJECT (item), "name", mymenu[i].text); + gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + gtk_widget_show (item); + break; + + /*case M_END:*/ default: + if (!submenu) + { + if (menu) + { + gtk_menu_item_set_submenu (GTK_MENU_ITEM (menu_item), menu); + menu_add_plugin_mainmenu_items (menu_bar); + } + if (usermenu) + usermenu_create (usermenu); + return (menu_bar); + } + submenu = NULL; + } + + /* record this GtkWidget * so it's state might be changed later */ + if (mymenu[i].id != 0 && menu_widgets) + /* this ends up in sess->gui->menu_item[MENU_ID_XXX] */ + menu_widgets[mymenu[i].id] = item; + + i++; + } +} diff --git a/src/fe-gtk/menu.h b/src/fe-gtk/menu.h new file mode 100644 index 00000000..7fef79cd --- /dev/null +++ b/src/fe-gtk/menu.h @@ -0,0 +1,41 @@ +GtkWidget *menu_create_main (void *accel_group, int bar, int away, int toplevel, GtkWidget **menu_widgets); +void menu_urlmenu (GdkEventButton * event, char *url); +void menu_chanmenu (session *sess, GdkEventButton * event, char *chan); +void menu_addfavoritemenu (server *serv, GtkWidget *menu, char *channel); +void menu_nickmenu (session *sess, GdkEventButton * event, char *nick, int num_sel); +void menu_middlemenu (session *sess, GdkEventButton *event); +void userlist_button_cb (GtkWidget * button, char *cmd); +void nick_command_parse (session *sess, char *cmd, char *nick, char *allnick); +void usermenu_update (void); +GtkWidget *menu_toggle_item (char *label, GtkWidget *menu, void *callback, void *userdata, int state); +GtkWidget *menu_quick_item (char *cmd, char *label, GtkWidget * menu, int flags, gpointer userdata, char *icon); +GtkWidget *menu_quick_sub (char *name, GtkWidget *menu, GtkWidget **sub_item_ret, int flags, int pos); +GtkWidget *create_icon_menu (char *labeltext, void *stock_name, int is_stock); +void menu_create (GtkWidget *menu, GSList *list, char *target, int check_path); +void menu_bar_toggle (void); +void menu_add_plugin_items (GtkWidget *menu, char *root, char *target); +void menu_change_layout (void); + +/* for menu_quick functions */ +#define XCMENU_DOLIST 1 +#define XCMENU_SHADED 1 +#define XCMENU_MARKUP 2 +#define XCMENU_MNEMONIC 4 + +/* menu items we keep a GtkWidget* for (to change their state) */ +#define MENU_ID_AWAY 1 +#define MENU_ID_MENUBAR 2 +#define MENU_ID_TOPICBAR 3 +#define MENU_ID_USERLIST 4 +#define MENU_ID_ULBUTTONS 5 +#define MENU_ID_MODEBUTTONS 6 +#define MENU_ID_LAYOUT_TABS 7 +#define MENU_ID_LAYOUT_TREE 8 +#define MENU_ID_DISCONNECT 9 +#define MENU_ID_RECONNECT 10 +#define MENU_ID_JOIN 11 +#define MENU_ID_USERMENU 12 + +#if (MENU_ID_NUM < MENU_ID_USERMENU) +#error MENU_ID_NUM is set wrong +#endif diff --git a/src/fe-gtk/mmx_cmod.S b/src/fe-gtk/mmx_cmod.S new file mode 100644 index 00000000..12e866de --- /dev/null +++ b/src/fe-gtk/mmx_cmod.S @@ -0,0 +1,530 @@ +/* + * Copyright (C) 1997-2001, Michael Jennings + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies of the Software, its documentation and marketing & publicity + * materials, and acknowledgment shall be given in the documentation, materials + * and software packages that this Software was used. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/* MMX routines for tinting XImages written by Willem Monsuwe <willem@stack.nl> */ + +/* Function calling conventions: + * shade_ximage_xx(void *data, int bpl, int w, int h, int rm, int gm, int bm); + */ + +#define data 8(%ebp) +#define bpl 12(%ebp) +#define w 16(%ebp) +#define h 20(%ebp) +#define rm 24(%ebp) +#define gm 28(%ebp) +#define bm 32(%ebp) + +#ifdef UNDERSCORE_SYMBOLS /* need this to link with msvc */ +#define SHADE_XIMAGE_15 _shade_ximage_15_mmx +#define SHADE_XIMAGE_16 _shade_ximage_16_mmx +#define SHADE_XIMAGE_32 _shade_ximage_32_mmx +#define HAVE_MMX _have_mmx +#else +#define SHADE_XIMAGE_15 shade_ximage_15_mmx +#define SHADE_XIMAGE_16 shade_ximage_16_mmx +#define SHADE_XIMAGE_32 shade_ximage_32_mmx +#define HAVE_MMX have_mmx +#endif + +.globl SHADE_XIMAGE_15 +.globl SHADE_XIMAGE_16 +.globl SHADE_XIMAGE_32 +.globl HAVE_MMX + +.bss +.text +.align 8 + +#define ENTER \ + pushl %ebp ;\ + movl %esp, %ebp ;\ + pushl %ebx ;\ + pushl %ecx ;\ + pushl %edx ;\ + pushl %edi ;\ + pushl %esi ;\ + movl data, %esi ;\ + movl w, %ebx ;\ + movl h, %edx + +#define LEAVE \ +4: ;\ + emms ;\ + popl %esi ;\ + popl %edi ;\ + popl %edx ;\ + popl %ecx ;\ + popl %ebx ;\ + movl %ebp, %esp ;\ + popl %ebp ;\ + ret + + +SHADE_XIMAGE_15: + ENTER + + leal -6(%esi, %ebx, 2), %esi + negl %ebx + jz 5f + + /* Setup multipliers */ + movd rm, %mm5 + movd gm, %mm6 + movd bm, %mm7 + punpcklwd %mm5, %mm5 /* 00 00 00 00 rm rm rm rm */ + punpcklwd %mm6, %mm6 /* 00 00 00 00 gm gm gm gm */ + punpcklwd %mm7, %mm7 /* 00 00 00 00 bm bm bm bm */ + punpckldq %mm5, %mm5 /* rm rm rm rm rm rm rm rm */ + punpckldq %mm6, %mm6 /* gm gm gm gm gm gm gm gm */ + punpckldq %mm7, %mm7 /* bm bm bm bm bm bm bm bm */ + + cmpl $256, rm + jg shade_ximage_15_mmx_saturate + cmpl $256, gm + jg shade_ximage_15_mmx_saturate + cmpl $256, bm + jg shade_ximage_15_mmx_saturate + +1: movl %ebx, %ecx + addl $3, %ecx + jns 3f +2: + movq (%esi, %ecx, 2), %mm0 + + movq %mm0, %mm1 /* rg gb */ + movq %mm0, %mm2 /* rg gb */ + psrlw $5, %mm1 /* 0r rg */ + psrlw $10, %mm0 /* 00 0r */ + psllw $11, %mm2 /* b0 00 */ + psllw $11, %mm1 /* g0 00 */ + psllw $8, %mm0 /* 0r 00 */ + psrlw $3, %mm1 /* 0g 00 */ + psrlw $3, %mm2 /* 0b 00 */ + + pmulhw %mm5, %mm0 /* 00 0r */ + pmulhw %mm6, %mm1 /* 00 0g */ + pmulhw %mm7, %mm2 /* 00 0b */ + + psllw $10, %mm0 /* r0 00 */ + psllw $5, %mm1 /* 0g g0 */ + por %mm2, %mm0 /* r0 0b */ + por %mm1, %mm0 /* rg gb */ + + movq %mm0, (%esi, %ecx, 2) + + addl $4, %ecx + js 2b + jmp 4f +3: + movw (%esi, %ecx, 2), %ax + movd %eax, %mm0 + + movq %mm0, %mm1 /* rg gb */ + movq %mm0, %mm2 /* rg gb */ + psrlw $5, %mm1 /* 0r rg */ + psrlw $10, %mm0 /* 00 0r */ + psllw $11, %mm2 /* b0 00 */ + psllw $11, %mm1 /* g0 00 */ + psllw $8, %mm0 /* 0r 00 */ + psrlw $3, %mm1 /* 0g 00 */ + psrlw $3, %mm2 /* 0b 00 */ + + pmulhw %mm5, %mm0 /* 00 0r */ + pmulhw %mm6, %mm1 /* 00 0g */ + pmulhw %mm7, %mm2 /* 00 0b */ + + psllw $10, %mm0 /* r0 00 */ + psllw $5, %mm1 /* 0g g0 */ + por %mm2, %mm0 /* r0 0b */ + por %mm1, %mm0 /* rg gb */ + + movd %mm0, %eax + movw %ax, (%esi, %ecx, 2) + + incl %ecx +4: + cmpl $2, %ecx + jng 3b + + addl bpl, %esi + decl %edx + jnz 1b +5: + LEAVE + + +shade_ximage_15_mmx_saturate: + + pcmpeqw %mm3, %mm3 + psllw $5, %mm3 /* ff e0 ff e0 ff e0 ff e0 */ + +1: movl %ebx, %ecx + addl $3, %ecx + jns 3f +2: + movq (%esi, %ecx, 2), %mm0 + + movq %mm0, %mm1 /* rg gb */ + movq %mm0, %mm2 /* rg gb */ + psrlw $5, %mm1 /* 0r rg */ + psrlw $10, %mm0 /* 00 0r */ + psllw $11, %mm2 /* b0 00 */ + psllw $11, %mm1 /* g0 00 */ + psllw $8, %mm0 /* 0r 00 */ + psrlw $3, %mm1 /* 0g 00 */ + psrlw $3, %mm2 /* 0b 00 */ + + pmulhw %mm5, %mm0 /* xx xr */ + pmulhw %mm6, %mm1 /* xx xg */ + pmulhw %mm7, %mm2 /* xx xb */ + + /* Saturate upper */ + paddusw %mm3, %mm0 /* ff er */ + paddusw %mm3, %mm1 /* ff eg */ + paddusw %mm3, %mm2 /* ff eb */ + + psubw %mm3, %mm0 /* 00 0r */ + psubw %mm3, %mm1 /* 00 0g */ + psubw %mm3, %mm2 /* 00 0b */ + + psllw $10, %mm0 /* r0 00 */ + psllw $5, %mm1 /* 0g g0 */ + por %mm2, %mm0 /* r0 0b */ + por %mm1, %mm0 /* rg gb */ + + movq %mm0, (%esi, %ecx, 2) + + addl $4, %ecx + js 2b + jmp 4f +3: + movw (%esi, %ecx, 2), %ax + movd %eax, %mm0 + + movq %mm0, %mm1 /* rg gb */ + movq %mm0, %mm2 /* rg gb */ + psrlw $5, %mm1 /* 0r rg */ + psrlw $10, %mm0 /* 00 0r */ + psllw $11, %mm2 /* b0 00 */ + psllw $11, %mm1 /* g0 00 */ + psllw $8, %mm0 /* 0r 00 */ + psrlw $3, %mm1 /* 0g 00 */ + psrlw $3, %mm2 /* 0b 00 */ + + pmulhw %mm5, %mm0 /* xx xr */ + pmulhw %mm6, %mm1 /* xx xg */ + pmulhw %mm7, %mm2 /* xx xb */ + + /* Saturate upper */ + paddusw %mm3, %mm0 /* ff er */ + paddusw %mm3, %mm1 /* ff eg */ + paddusw %mm3, %mm2 /* ff eb */ + + psubw %mm3, %mm0 /* 00 0r */ + psubw %mm3, %mm1 /* 00 0g */ + psubw %mm3, %mm2 /* 00 0b */ + + psllw $10, %mm0 /* r0 00 */ + psllw $5, %mm1 /* 0g g0 */ + por %mm2, %mm0 /* r0 0b */ + por %mm1, %mm0 /* rg gb */ + + movd %mm0, %eax + movw %ax, (%esi, %ecx, 2) + + incl %ecx +4: + cmpl $2, %ecx + jng 3b + + addl bpl, %esi + decl %edx + jnz 1b +5: + LEAVE + + +SHADE_XIMAGE_16: + ENTER + + leal -6(%esi, %ebx, 2), %esi + negl %ebx + jz 5f + + /* Setup multipliers */ + movd rm, %mm5 + movd gm, %mm6 + movd bm, %mm7 + punpcklwd %mm5, %mm5 /* 00 00 00 00 rm rm rm rm */ + punpcklwd %mm6, %mm6 /* 00 00 00 00 gm gm gm gm */ + punpcklwd %mm7, %mm7 /* 00 00 00 00 bm bm bm bm */ + punpckldq %mm5, %mm5 /* rm rm rm rm rm rm rm rm */ + punpckldq %mm6, %mm6 /* gm gm gm gm gm gm gm gm */ + punpckldq %mm7, %mm7 /* bm bm bm bm bm bm bm bm */ + + cmpl $256, rm + jg shade_ximage_16_mmx_saturate + cmpl $256, gm + jg shade_ximage_16_mmx_saturate + cmpl $256, bm + jg shade_ximage_16_mmx_saturate + +1: movl %ebx, %ecx + addl $3, %ecx + jns 3f +2: + movq (%esi, %ecx, 2), %mm0 + + movq %mm0, %mm1 /* rg gb */ + movq %mm0, %mm2 /* rg gb */ + psrlw $5, %mm1 /* 0r rg */ + psrlw $11, %mm0 /* 00 0r */ + psllw $11, %mm2 /* b0 00 */ + psllw $10, %mm1 /* g0 00 */ + psllw $8, %mm0 /* 0r 00 */ + psrlw $2, %mm1 /* 0g 00 */ + psrlw $3, %mm2 /* 0b 00 */ + + pmulhw %mm5, %mm0 /* 00 0r */ + pmulhw %mm6, %mm1 /* 00 0g */ + pmulhw %mm7, %mm2 /* 00 0b */ + + psllw $11, %mm0 /* r0 00 */ + psllw $5, %mm1 /* 0g g0 */ + por %mm2, %mm0 /* r0 0b */ + por %mm1, %mm0 /* rg gb */ + + movq %mm0, (%esi, %ecx, 2) + + addl $4, %ecx + js 2b + jmp 4f +3: + movw (%esi, %ecx, 2), %ax + movd %eax, %mm0 + + movq %mm0, %mm1 /* rg gb */ + movq %mm0, %mm2 /* rg gb */ + psrlw $5, %mm1 /* 0r rg */ + psrlw $11, %mm0 /* 00 0r */ + psllw $11, %mm2 /* b0 00 */ + psllw $10, %mm1 /* g0 00 */ + psllw $8, %mm0 /* 0r 00 */ + psrlw $2, %mm1 /* 0g 00 */ + psrlw $3, %mm2 /* 0b 00 */ + + pmulhw %mm5, %mm0 /* 00 0r */ + pmulhw %mm6, %mm1 /* 00 0g */ + pmulhw %mm7, %mm2 /* 00 0b */ + + psllw $11, %mm0 /* r0 00 */ + psllw $5, %mm1 /* 0g g0 */ + por %mm2, %mm0 /* r0 0b */ + por %mm1, %mm0 /* rg gb */ + + movd %mm0, %eax + movw %ax, (%esi, %ecx, 2) + + incl %ecx +4: + cmpl $2, %ecx + jng 3b + + addl bpl, %esi + decl %edx + jnz 1b +5: + LEAVE + + +shade_ximage_16_mmx_saturate: + + pcmpeqw %mm3, %mm3 + movq %mm3, %mm4 + psllw $5, %mm3 /* ff e0 ff e0 ff e0 ff e0 */ + psllw $6, %mm4 /* ff c0 ff c0 ff c0 ff c0 */ + +1: movl %ebx, %ecx + addl $3, %ecx + jns 3f +2: + movq (%esi, %ecx, 2), %mm0 + + movq %mm0, %mm1 /* rg gb */ + movq %mm0, %mm2 /* rg gb */ + psrlw $5, %mm1 /* 0r rg */ + psrlw $11, %mm0 /* 00 0r */ + psllw $11, %mm2 /* b0 00 */ + psllw $10, %mm1 /* g0 00 */ + psllw $8, %mm0 /* 0r 00 */ + psrlw $2, %mm1 /* 0g 00 */ + psrlw $3, %mm2 /* 0b 00 */ + + pmulhw %mm5, %mm0 /* xx xr */ + pmulhw %mm6, %mm1 /* xx xg */ + pmulhw %mm7, %mm2 /* xx xb */ + + /* Saturate upper */ + paddusw %mm3, %mm0 /* ff er */ + paddusw %mm4, %mm1 /* ff cg */ + paddusw %mm3, %mm2 /* ff eb */ + + psubw %mm4, %mm1 /* 00 0g */ + psubw %mm3, %mm2 /* 00 0b */ + + psllw $11, %mm0 /* r0 00 */ + psllw $5, %mm1 /* 0g g0 */ + por %mm2, %mm0 /* r0 0b */ + por %mm1, %mm0 /* rg gb */ + + movq %mm0, (%esi, %ecx, 2) + + addl $4, %ecx + js 2b + jmp 4f +3: + movw (%esi, %ecx, 2), %ax + movd %eax, %mm0 + + movq %mm0, %mm1 /* rg gb */ + movq %mm0, %mm2 /* rg gb */ + psrlw $5, %mm1 /* 0r rg */ + psrlw $11, %mm0 /* 00 0r */ + psllw $11, %mm2 /* b0 00 */ + psllw $10, %mm1 /* g0 00 */ + psllw $8, %mm0 /* 0r 00 */ + psrlw $2, %mm1 /* 0g 00 */ + psrlw $3, %mm2 /* 0b 00 */ + + pmulhw %mm5, %mm0 /* xx xr */ + pmulhw %mm6, %mm1 /* xx xg */ + pmulhw %mm7, %mm2 /* xx xb */ + + /* Saturate upper */ + paddusw %mm3, %mm0 /* ff er */ + paddusw %mm4, %mm1 /* ff cg */ + paddusw %mm3, %mm2 /* ff eb */ + + psubw %mm4, %mm1 /* 00 0g */ + psubw %mm3, %mm2 /* 00 0b */ + + psllw $11, %mm0 /* r0 00 */ + psllw $5, %mm1 /* 0g g0 */ + por %mm2, %mm0 /* r0 0b */ + por %mm1, %mm0 /* rg gb */ + + movd %mm0, %eax + movw %ax, (%esi, %ecx, 2) + + incl %ecx +4: + cmpl $2, %ecx + jng 3b + + addl bpl, %esi + decl %edx + jnz 1b +5: + LEAVE + + +SHADE_XIMAGE_32: + ENTER + + leal (%esi, %ebx, 4), %esi + negl %ebx + jz 3f + + movd rm, %mm4 + movd gm, %mm5 + movd bm, %mm6 + psllq $32, %mm4 + psllq $16, %mm5 + por %mm6, %mm4 + por %mm5, %mm4 + + pcmpeqw %mm6, %mm6 + psllw $15, %mm6 /* 80 00 80 00 80 00 80 00 */ + movq %mm6, %mm5 + pmulhw %mm4, %mm5 /* Get correction factor */ +1: + movl %ebx, %ecx +2: + movd (%esi, %ecx, 4), %mm1 /* 00 rr gg bb */ + pxor %mm0, %mm0 + punpcklbw %mm1, %mm0 /* 00 00 rr 00 gg 00 bb 00 */ + pxor %mm6, %mm0 /* Flip sign */ + + pmulhw %mm4, %mm0 /* 00 00 xx rr xx gg xx bb */ + psubw %mm5, %mm0 /* Correct range */ + packuswb %mm0, %mm0 /* 00 rr gg bb 00 rr gg bb */ + + movd %mm0, (%esi, %ecx, 4) + + incl %ecx + jnz 2b + + addl bpl, %esi + decl %edx + jnz 1b +3: + LEAVE + + +HAVE_MMX: + push %ebx +/* Check if bit 21 in flags word is writeable */ + pushfl + popl %eax + movl %eax,%ebx + xorl $0x00200000, %eax + pushl %eax + popfl + pushfl + popl %eax + + cmpl %eax, %ebx + je 8f + +/* OK, we have CPUID */ + + movl $1, %eax + cpuid + + test $0x00800000, %edx + jz 8f + + movl $1, %eax /* success, have mmx */ + popl %ebx + ret + +8: + xorl %eax,%eax /* failed, no mmx */ + popl %ebx + ret + +#if defined(__GNUC__) && !defined(_WIN32) +.section .note.GNU-stack, "", @progbits +.previous +#endif diff --git a/src/fe-gtk/mmx_cmod.h b/src/fe-gtk/mmx_cmod.h new file mode 100644 index 00000000..52d07102 --- /dev/null +++ b/src/fe-gtk/mmx_cmod.h @@ -0,0 +1,4 @@ +void shade_ximage_15_mmx(void *data, int bpl, int w, int h, int rm, int gm, int bm); +void shade_ximage_16_mmx(void *data, int bpl, int w, int h, int rm, int gm, int bm); +void shade_ximage_32_mmx(void *data, int bpl, int w, int h, int rm, int gm, int bm); +int have_mmx (void); diff --git a/src/fe-gtk/notifygui.c b/src/fe-gtk/notifygui.c new file mode 100644 index 00000000..5acb683a --- /dev/null +++ b/src/fe-gtk/notifygui.c @@ -0,0 +1,442 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <time.h> + +#include "fe-gtk.h" + +#include <gtk/gtkhbox.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkhbbox.h> +#include <gtk/gtkscrolledwindow.h> + +#include <gtk/gtklabel.h> +#include <gtk/gtkliststore.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkmessagedialog.h> +#include <gtk/gtktable.h> +#include <gtk/gtktreeview.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtkcellrenderertext.h> + +#include "../common/xchat.h" +#include "../common/notify.h" +#include "../common/cfgfiles.h" +#include "../common/fe.h" +#include "../common/server.h" +#include "../common/util.h" +#include "../common/userlist.h" +#include "gtkutil.h" +#include "maingui.h" +#include "palette.h" +#include "notifygui.h" + + +/* model for the notify treeview */ +enum +{ + USER_COLUMN, + STATUS_COLUMN, + SERVER_COLUMN, + SEEN_COLUMN, + COLOUR_COLUMN, + NPS_COLUMN, /* struct notify_per_server * */ + N_COLUMNS +}; + + +static GtkWidget *notify_window = 0; +static GtkWidget *notify_button_opendialog; +static GtkWidget *notify_button_remove; + + +static void +notify_closegui (void) +{ + notify_window = 0; +} + +/* Need this to be able to set the foreground colour property of a row + * from a GdkColor * in the model -Vince + */ +static void +notify_treecell_property_mapper (GtkTreeViewColumn *col, GtkCellRenderer *cell, + GtkTreeModel *model, GtkTreeIter *iter, + gpointer data) +{ + gchar *text; + GdkColor *colour; + int model_column = GPOINTER_TO_INT (data); + + gtk_tree_model_get (GTK_TREE_MODEL (model), iter, + COLOUR_COLUMN, &colour, + model_column, &text, -1); + g_object_set (G_OBJECT (cell), "text", text, NULL); + g_object_set (G_OBJECT (cell), "foreground-gdk", colour, NULL); + g_free (text); +} + +static void +notify_row_cb (GtkTreeSelection *sel, GtkTreeView *view) +{ + GtkTreeIter iter; + struct notify_per_server *servnot; + + if (gtkutil_treeview_get_selected (view, &iter, NPS_COLUMN, &servnot, -1)) + { + gtk_widget_set_sensitive (notify_button_opendialog, servnot ? servnot->ison : 0); + gtk_widget_set_sensitive (notify_button_remove, TRUE); + return; + } + + gtk_widget_set_sensitive (notify_button_opendialog, FALSE); + gtk_widget_set_sensitive (notify_button_remove, FALSE); +} + +static GtkWidget * +notify_treeview_new (GtkWidget *box) +{ + GtkListStore *store; + GtkWidget *view; + GtkTreeViewColumn *col; + int col_id; + + store = gtk_list_store_new (N_COLUMNS, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_STRING, + G_TYPE_POINTER, /* can't specify colour! */ + G_TYPE_POINTER + ); + g_return_val_if_fail (store != NULL, NULL); + + view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store), + notify_treecell_property_mapper, + USER_COLUMN, _("Name"), + STATUS_COLUMN, _("Status"), + SERVER_COLUMN, _("Network"), + SEEN_COLUMN, _("Last Seen"), -1); + gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (view), 0), TRUE); + + for (col_id=0; (col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), col_id)); + col_id++) + gtk_tree_view_column_set_alignment (col, 0.5); + + g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (view))), + "changed", G_CALLBACK (notify_row_cb), view); + + gtk_widget_show (view); + return view; +} + +void +notify_gui_update (void) +{ + struct notify *notify; + struct notify_per_server *servnot; + GSList *list = notify_list; + GSList *slist; + gchar *name, *status, *server, *seen; + int online, servcount; + time_t lastseen; + char agobuf[128]; + + GtkListStore *store; + GtkTreeView *view; + GtkTreeIter iter; + gboolean valid; /* true if we don't need to append a new tree row */ + + if (!notify_window) + return; + + view = g_object_get_data (G_OBJECT (notify_window), "view"); + store = GTK_LIST_STORE (gtk_tree_view_get_model (view)); + valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter); + + while (list) + { + notify = (struct notify *) list->data; + name = notify->name; + status = _("Offline"); + server = ""; + + online = FALSE; + lastseen = 0; + /* First see if they're online on any servers */ + slist = notify->server_list; + while (slist) + { + servnot = (struct notify_per_server *) slist->data; + if (servnot->ison) + online = TRUE; + if (servnot->lastseen > lastseen) + lastseen = servnot->lastseen; + slist = slist->next; + } + + if (!online) /* Offline on all servers */ + { + if (!lastseen) + seen = _("Never"); + else + { + snprintf (agobuf, sizeof (agobuf), _("%d minutes ago"), (int)(time (0) - lastseen) / 60); + seen = agobuf; + } + if (!valid) /* create new tree row if required */ + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, name, 1, status, + 2, server, 3, seen, 4, &colors[4], 5, NULL, -1); + if (valid) + valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter); + + } else + { + /* Online - add one line per server */ + servcount = 0; + slist = notify->server_list; + status = _("Online"); + while (slist) + { + servnot = (struct notify_per_server *) slist->data; + if (servnot->ison) + { + if (servcount > 0) + name = ""; + server = server_get_network (servnot->server, TRUE); + + snprintf (agobuf, sizeof (agobuf), _("%d minutes ago"), (int)(time (0) - lastseen) / 60); + seen = agobuf; + + if (!valid) /* create new tree row if required */ + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, name, 1, status, + 2, server, 3, seen, 4, &colors[3], 5, servnot, -1); + if (valid) + valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter); + + servcount++; + } + slist = slist->next; + } + } + + list = list->next; + } + + while (valid) + { + GtkTreeIter old = iter; + /* get next iter now because removing invalidates old one */ + valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store), + &iter); + gtk_list_store_remove (store, &old); + } +} + +static void +notify_opendialog_clicked (GtkWidget * igad) +{ + GtkTreeView *view; + GtkTreeIter iter; + struct notify_per_server *servnot; + + view = g_object_get_data (G_OBJECT (notify_window), "view"); + if (gtkutil_treeview_get_selected (view, &iter, NPS_COLUMN, &servnot, -1)) + { + if (servnot) + open_query (servnot->server, servnot->notify->name, TRUE); + } +} + +static void +notify_remove_clicked (GtkWidget * igad) +{ + GtkTreeView *view; + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreePath *path = NULL; + gboolean found = FALSE; + char *name; + + view = g_object_get_data (G_OBJECT (notify_window), "view"); + if (gtkutil_treeview_get_selected (view, &iter, USER_COLUMN, &name, -1)) + { + model = gtk_tree_view_get_model (view); + found = (*name != 0); + while (!found) /* the real nick is some previous node */ + { + g_free (name); /* it's useless to us */ + if (!path) + path = gtk_tree_model_get_path (model, &iter); + if (!gtk_tree_path_prev (path)) /* arrgh! no previous node! */ + { + g_warning ("notify list state is invalid\n"); + break; + } + if (!gtk_tree_model_get_iter (model, &iter, path)) + break; + gtk_tree_model_get (model, &iter, USER_COLUMN, &name, -1); + found = (*name != 0); + } + if (path) + gtk_tree_path_free (path); + if (!found) + return; + + /* ok, now we can remove it */ + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); + notify_deluser (name); + g_free (name); + } +} + +static void +notifygui_add_cb (GtkDialog *dialog, gint response, gpointer entry) +{ + char *networks; + char *text; + + text = GTK_ENTRY (entry)->text; + if (text[0] && response == GTK_RESPONSE_ACCEPT) + { + networks = GTK_ENTRY (g_object_get_data (G_OBJECT (entry), "net"))->text; + if (strcasecmp (networks, "ALL") == 0 || networks[0] == 0) + notify_adduser (text, NULL); + else + notify_adduser (text, networks); + } + + gtk_widget_destroy (GTK_WIDGET (dialog)); +} + +static void +notifygui_add_enter (GtkWidget *entry, GtkWidget *dialog) +{ + gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT); +} + +void +fe_notify_ask (char *nick, char *networks) +{ + GtkWidget *dialog; + GtkWidget *entry; + GtkWidget *label; + GtkWidget *wid; + GtkWidget *table; + char *msg = _("Enter nickname to add:"); + char buf[256]; + + dialog = gtk_dialog_new_with_buttons (msg, NULL, 0, + GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, + GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, + NULL); + if (parent_window) + gtk_window_set_transient_for (GTK_WINDOW (dialog), GTK_WINDOW (parent_window)); + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + + table = gtk_table_new (2, 3, FALSE); + gtk_container_set_border_width (GTK_CONTAINER (table), 12); + gtk_table_set_row_spacings (GTK_TABLE (table), 3); + gtk_table_set_col_spacings (GTK_TABLE (table), 8); + gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), table); + + label = gtk_label_new (msg); + gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, 0, 1); + + entry = gtk_entry_new (); + gtk_entry_set_text (GTK_ENTRY (entry), nick); + g_signal_connect (G_OBJECT (entry), "activate", + G_CALLBACK (notifygui_add_enter), dialog); + gtk_table_attach_defaults (GTK_TABLE (table), entry, 1, 2, 0, 1); + + g_signal_connect (G_OBJECT (dialog), "response", + G_CALLBACK (notifygui_add_cb), entry); + + label = gtk_label_new (_("Notify on these networks:")); + gtk_table_attach_defaults (GTK_TABLE (table), label, 0, 1, 2, 3); + + wid = gtk_entry_new (); + g_object_set_data (G_OBJECT (entry), "net", wid); + g_signal_connect (G_OBJECT (wid), "activate", + G_CALLBACK (notifygui_add_enter), dialog); + gtk_entry_set_text (GTK_ENTRY (wid), networks ? networks : "ALL"); + gtk_table_attach_defaults (GTK_TABLE (table), wid, 1, 2, 2, 3); + + label = gtk_label_new (NULL); + snprintf (buf, sizeof (buf), "<i><span size=\"smaller\">%s</span></i>", _("Comma separated list of networks is accepted.")); + gtk_label_set_markup (GTK_LABEL (label), buf); + gtk_table_attach_defaults (GTK_TABLE (table), label, 1, 2, 3, 4); + + gtk_widget_show_all (dialog); +} + +static void +notify_add_clicked (GtkWidget * igad) +{ + fe_notify_ask ("", NULL); +} + +void +notify_opengui (void) +{ + GtkWidget *vbox, *bbox; + GtkWidget *view; + + if (notify_window) + { + mg_bring_tofront (notify_window); + return; + } + + notify_window = + mg_create_generic_tab ("Notify", _("XChat: Friends List"), FALSE, TRUE, + notify_closegui, NULL, 400, 250, &vbox, 0); + + view = notify_treeview_new (vbox); + g_object_set_data (G_OBJECT (notify_window), "view", view); + + bbox = gtk_hbutton_box_new (); + gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_SPREAD); + gtk_container_set_border_width (GTK_CONTAINER (bbox), 5); + gtk_box_pack_end (GTK_BOX (vbox), bbox, 0, 0, 0); + gtk_widget_show (bbox); + + gtkutil_button (bbox, GTK_STOCK_NEW, 0, notify_add_clicked, 0, + _("Add...")); + + notify_button_remove = + gtkutil_button (bbox, GTK_STOCK_DELETE, 0, notify_remove_clicked, 0, + _("Remove")); + + notify_button_opendialog = + gtkutil_button (bbox, NULL, 0, notify_opendialog_clicked, 0, + _("Open Dialog")); + + gtk_widget_set_sensitive (notify_button_opendialog, FALSE); + gtk_widget_set_sensitive (notify_button_remove, FALSE); + + notify_gui_update (); + + gtk_widget_show (notify_window); +} diff --git a/src/fe-gtk/notifygui.h b/src/fe-gtk/notifygui.h new file mode 100644 index 00000000..360834dc --- /dev/null +++ b/src/fe-gtk/notifygui.h @@ -0,0 +1,2 @@ +void notify_gui_update (void); +void notify_opengui (void); diff --git a/src/fe-gtk/palette.c b/src/fe-gtk/palette.c new file mode 100644 index 00000000..ebae92ff --- /dev/null +++ b/src/fe-gtk/palette.c @@ -0,0 +1,226 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "fe-gtk.h" +#include "palette.h" + +#include "../common/xchat.h" +#include "../common/util.h" +#include "../common/cfgfiles.h" + + +GdkColor colors[] = { + /* colors for xtext */ + {0, 0xcccc, 0xcccc, 0xcccc}, /* 16 white */ + {0, 0x0000, 0x0000, 0x0000}, /* 17 black */ + {0, 0x35c2, 0x35c2, 0xb332}, /* 18 blue */ + {0, 0x2a3d, 0x8ccc, 0x2a3d}, /* 19 green */ + {0, 0xc3c3, 0x3b3b, 0x3b3b}, /* 20 red */ + {0, 0xc7c7, 0x3232, 0x3232}, /* 21 light red */ + {0, 0x8000, 0x2666, 0x7fff}, /* 22 purple */ + {0, 0x6666, 0x3636, 0x1f1f}, /* 23 orange */ + {0, 0xd999, 0xa6d3, 0x4147}, /* 24 yellow */ + {0, 0x3d70, 0xcccc, 0x3d70}, /* 25 green */ + {0, 0x199a, 0x5555, 0x5555}, /* 26 aqua */ + {0, 0x2eef, 0x8ccc, 0x74df}, /* 27 light aqua */ + {0, 0x451e, 0x451e, 0xe666}, /* 28 blue */ + {0, 0xb0b0, 0x3737, 0xb0b0}, /* 29 light purple */ + {0, 0x4c4c, 0x4c4c, 0x4c4c}, /* 30 grey */ + {0, 0x9595, 0x9595, 0x9595}, /* 31 light grey */ + + {0, 0xcccc, 0xcccc, 0xcccc}, /* 16 white */ + {0, 0x0000, 0x0000, 0x0000}, /* 17 black */ + {0, 0x35c2, 0x35c2, 0xb332}, /* 18 blue */ + {0, 0x2a3d, 0x8ccc, 0x2a3d}, /* 19 green */ + {0, 0xc3c3, 0x3b3b, 0x3b3b}, /* 20 red */ + {0, 0xc7c7, 0x3232, 0x3232}, /* 21 light red */ + {0, 0x8000, 0x2666, 0x7fff}, /* 22 purple */ + {0, 0x6666, 0x3636, 0x1f1f}, /* 23 orange */ + {0, 0xd999, 0xa6d3, 0x4147}, /* 24 yellow */ + {0, 0x3d70, 0xcccc, 0x3d70}, /* 25 green */ + {0, 0x199a, 0x5555, 0x5555}, /* 26 aqua */ + {0, 0x2eef, 0x8ccc, 0x74df}, /* 27 light aqua */ + {0, 0x451e, 0x451e, 0xe666}, /* 28 blue */ + {0, 0xb0b0, 0x3737, 0xb0b0}, /* 29 light purple */ + {0, 0x4c4c, 0x4c4c, 0x4c4c}, /* 30 grey */ + {0, 0x9595, 0x9595, 0x9595}, /* 31 light grey */ + + {0, 0xffff, 0xffff, 0xffff}, /* 32 marktext Fore (white) */ + {0, 0x3535, 0x6e6e, 0xc1c1}, /* 33 marktext Back (blue) */ + {0, 0x0000, 0x0000, 0x0000}, /* 34 foreground (black) */ + {0, 0xf0f0, 0xf0f0, 0xf0f0}, /* 35 background (white) */ + {0, 0xcccc, 0x1010, 0x1010}, /* 36 marker line (red) */ + + /* colors for GUI */ + {0, 0x9999, 0x0000, 0x0000}, /* 37 tab New Data (dark red) */ + {0, 0x0000, 0x0000, 0xffff}, /* 38 tab Nick Mentioned (blue) */ + {0, 0xffff, 0x0000, 0x0000}, /* 39 tab New Message (red) */ + {0, 0x9595, 0x9595, 0x9595}, /* 40 away user (grey) */ +}; +#define MAX_COL 40 + + +void +palette_alloc (GtkWidget * widget) +{ + int i; + static int done_alloc = FALSE; + GdkColormap *cmap; + + if (!done_alloc) /* don't do it again */ + { + done_alloc = TRUE; + cmap = gtk_widget_get_colormap (widget); + for (i = MAX_COL; i >= 0; i--) + gdk_colormap_alloc_color (cmap, &colors[i], FALSE, TRUE); + } +} + +/* maps XChat 2.0.x colors to current */ +static const int remap[] = +{ + 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15, + 33, /* 16:marktextback */ + 32, /* 17:marktextfore */ + 34, /* 18: fg */ + 35, /* 19: bg */ + 37, /* 20: newdata */ + 38, /* 21: blue */ + 39, /* 22: newmsg */ + 40 /* 23: away */ +}; + +void +palette_load (void) +{ + int i, j, l, fh, res; + char prefname[256]; + struct stat st; + char *cfg; + int red, green, blue; + int upgrade = FALSE; + + fh = xchat_open_file ("colors.conf", O_RDONLY, 0, 0); + if (fh == -1) + { + fh = xchat_open_file ("palette.conf", O_RDONLY, 0, 0); + upgrade = TRUE; + } + + if (fh != -1) + { + fstat (fh, &st); + cfg = malloc (st.st_size + 1); + if (cfg) + { + cfg[0] = '\0'; + l = read (fh, cfg, st.st_size); + if (l >= 0) + cfg[l] = '\0'; + + if (!upgrade) + { + /* mIRC colors 0-31 are here */ + for (i = 0; i < 32; i++) + { + snprintf (prefname, sizeof prefname, "color_%d", i); + cfg_get_color (cfg, prefname, &red, &green, &blue); + colors[i].red = red; + colors[i].green = green; + colors[i].blue = blue; + } + + /* our special colors are mapped at 256+ */ + for (i = 256, j = 32; j < MAX_COL+1; i++, j++) + { + snprintf (prefname, sizeof prefname, "color_%d", i); + cfg_get_color (cfg, prefname, &red, &green, &blue); + colors[j].red = red; + colors[j].green = green; + colors[j].blue = blue; + } + + } else + { + /* loading 2.0.x palette.conf */ + for (i = 0; i < MAX_COL+1; i++) + { + snprintf (prefname, sizeof prefname, "color_%d_red", i); + red = cfg_get_int (cfg, prefname); + + snprintf (prefname, sizeof prefname, "color_%d_grn", i); + green = cfg_get_int (cfg, prefname); + + snprintf (prefname, sizeof prefname, "color_%d_blu", i); + blue = cfg_get_int_with_result (cfg, prefname, &res); + + if (res) + { + colors[remap[i]].red = red; + colors[remap[i]].green = green; + colors[remap[i]].blue = blue; + } + } + + /* copy 0-15 to 16-31 */ + for (i = 0; i < 16; i++) + { + colors[i+16].red = colors[i].red; + colors[i+16].green = colors[i].green; + colors[i+16].blue = colors[i].blue; + } + } + free (cfg); + } + close (fh); + } +} + +void +palette_save (void) +{ + int i, j, fh; + char prefname[256]; + + fh = xchat_open_file ("colors.conf", O_TRUNC | O_WRONLY | O_CREAT, 0600, XOF_DOMODE); + if (fh != -1) + { + /* mIRC colors 0-31 are here */ + for (i = 0; i < 32; i++) + { + snprintf (prefname, sizeof prefname, "color_%d", i); + cfg_put_color (fh, colors[i].red, colors[i].green, colors[i].blue, prefname); + } + + /* our special colors are mapped at 256+ */ + for (i = 256, j = 32; j < MAX_COL+1; i++, j++) + { + snprintf (prefname, sizeof prefname, "color_%d", i); + cfg_put_color (fh, colors[j].red, colors[j].green, colors[j].blue, prefname); + } + + close (fh); + } +} diff --git a/src/fe-gtk/palette.h b/src/fe-gtk/palette.h new file mode 100644 index 00000000..c97693bb --- /dev/null +++ b/src/fe-gtk/palette.h @@ -0,0 +1,15 @@ +extern GdkColor colors[]; + +#define COL_MARK_FG 32 +#define COL_MARK_BG 33 +#define COL_FG 34 +#define COL_BG 35 +#define COL_MARKER 36 +#define COL_NEW_DATA 37 +#define COL_HILIGHT 38 +#define COL_NEW_MSG 39 +#define COL_AWAY 40 + +void palette_alloc (GtkWidget * widget); +void palette_load (void); +void palette_save (void); diff --git a/src/fe-gtk/pixmaps.c b/src/fe-gtk/pixmaps.c new file mode 100644 index 00000000..3d85c3b0 --- /dev/null +++ b/src/fe-gtk/pixmaps.c @@ -0,0 +1,123 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "fe-gtk.h" +#include "../common/xchat.h" +#include "../common/fe.h" + +#include <gdk-pixbuf/gdk-pixbuf.h> +#include <gdk-pixbuf/gdk-pixdata.h> +#include <gtk/gtkstock.h> + +#include "../pixmaps/inline_pngs.h" + +GdkPixbuf *pix_xchat; +GdkPixbuf *pix_book; + +GdkPixbuf *pix_purple; +GdkPixbuf *pix_red; +GdkPixbuf *pix_op; +GdkPixbuf *pix_hop; +GdkPixbuf *pix_voice; + +GdkPixbuf *pix_tray_msg; +GdkPixbuf *pix_tray_hilight; +GdkPixbuf *pix_tray_file; + +GdkPixbuf *pix_channel; +GdkPixbuf *pix_dialog; +GdkPixbuf *pix_server; +GdkPixbuf *pix_util; + + +static GdkPixmap * +pixmap_load_from_file_real (char *file) +{ + GdkPixbuf *img; + GdkPixmap *pixmap; + + img = gdk_pixbuf_new_from_file (file, 0); + if (!img) + return NULL; + gdk_pixbuf_render_pixmap_and_mask (img, &pixmap, NULL, 128); + gdk_pixbuf_unref (img); + + return pixmap; +} + +GdkPixmap * +pixmap_load_from_file (char *filename) +{ + char buf[256]; + GdkPixmap *pix; + + if (filename[0] == '\0') + return NULL; + + pix = pixmap_load_from_file_real (filename); + if (pix == NULL) + { + strcpy (buf, "Cannot open:\n\n"); + strncpy (buf + 14, filename, sizeof (buf) - 14); + buf[sizeof (buf) - 1] = 0; + fe_message (buf, FE_MSG_ERROR); + } + + return pix; +} + +#define LOADPIX(vv,pp,ff) \ + vv = gdk_pixbuf_new_from_file (XCHATSHAREDIR"/xchat/"ff, 0); \ + if (!vv) \ + vv = gdk_pixbuf_new_from_inline (-1, pp, FALSE, 0); + +#define LOADPIX_DISKONLY(vv,ff) \ + vv = gdk_pixbuf_new_from_file (XCHATSHAREDIR"/xchat/"ff, 0); + +#define EXT ".png" + +void +pixmaps_init (void) +{ + pix_book = gdk_pixbuf_new_from_inline (-1, bookpng, FALSE, 0); + + /* used in About window, tray icon and WindowManager icon. */ + LOADPIX (pix_xchat, xchatpng, "xchat"EXT); + + /* userlist icons, with inlined defaults */ + LOADPIX (pix_hop, hoppng, "hop"EXT); + LOADPIX (pix_purple, purplepng, "purple"EXT); + LOADPIX (pix_red, redpng, "red"EXT); + LOADPIX (pix_op, oppng, "op"EXT); + LOADPIX (pix_voice, voicepng, "voice"EXT); + + /* tray icons, with inlined defaults */ + LOADPIX (pix_tray_msg, traymsgpng, "message"EXT); + LOADPIX (pix_tray_hilight, trayhilightpng, "highlight"EXT); + LOADPIX (pix_tray_file, trayfilepng, "fileoffer"EXT); + + /* treeview icons, no defaults, load from disk only */ + LOADPIX_DISKONLY (pix_channel, "channel"EXT); + LOADPIX_DISKONLY (pix_dialog, "dialog"EXT); + LOADPIX_DISKONLY (pix_server, "server"EXT); + LOADPIX_DISKONLY (pix_util, "util"EXT); +} diff --git a/src/fe-gtk/pixmaps.h b/src/fe-gtk/pixmaps.h new file mode 100644 index 00000000..91b9696e --- /dev/null +++ b/src/fe-gtk/pixmaps.h @@ -0,0 +1,19 @@ +extern GdkPixbuf *pix_book; +extern GdkPixbuf *pix_hop; +extern GdkPixbuf *pix_purple; +extern GdkPixbuf *pix_red; +extern GdkPixbuf *pix_op; +extern GdkPixbuf *pix_voice; +extern GdkPixbuf *pix_xchat; + +extern GdkPixbuf *pix_tray_msg; +extern GdkPixbuf *pix_tray_hilight; +extern GdkPixbuf *pix_tray_file; + +extern GdkPixbuf *pix_channel; +extern GdkPixbuf *pix_dialog; +extern GdkPixbuf *pix_server; +extern GdkPixbuf *pix_util; + +extern GdkPixmap *pixmap_load_from_file (char *file); +extern void pixmaps_init (void); diff --git a/src/fe-gtk/plugin-tray.c b/src/fe-gtk/plugin-tray.c new file mode 100644 index 00000000..8603abf4 --- /dev/null +++ b/src/fe-gtk/plugin-tray.c @@ -0,0 +1,852 @@ +/* Copyright (C) 2006-2007 Peter Zelezny. */ + +#include <string.h> +#include <unistd.h> +#include "../common/xchat-plugin.h" +#include "../common/xchat.h" +#include "../common/xchatc.h" +#include "../common/inbound.h" +#include "../common/server.h" +#include "../common/fe.h" +#include "../common/util.h" +#include "fe-gtk.h" +#include "pixmaps.h" +#include "maingui.h" +#include "menu.h" +#include <gtk/gtk.h> + +#define LIBNOTIFY + +typedef enum /* current icon status */ +{ + TS_NONE, + TS_MESSAGE, + TS_HIGHLIGHT, + TS_FILEOFFER, + TS_CUSTOM /* plugin */ +} TrayStatus; + +typedef enum +{ + WS_FOCUSED, + WS_NORMAL, + WS_HIDDEN +} WinStatus; + +typedef GdkPixbuf* TrayIcon; +#define tray_icon_from_file(f) gdk_pixbuf_new_from_file(f,NULL) +#define tray_icon_free(i) g_object_unref(i) + +#define ICON_NORMAL pix_xchat +#define ICON_MSG pix_tray_msg +#define ICON_HILIGHT pix_tray_hilight +#define ICON_FILE pix_tray_file +#define TIMEOUT 500 + +static GtkStatusIcon *sticon; +static gint flash_tag; +static TrayStatus tray_status; +static xchat_plugin *ph; + +static TrayIcon custom_icon1; +static TrayIcon custom_icon2; + +static int tray_priv_count = 0; +static int tray_pub_count = 0; +static int tray_hilight_count = 0; +static int tray_file_count = 0; + + +void tray_apply_setup (void); + + +static WinStatus +tray_get_window_status (void) +{ + const char *st; + + st = xchat_get_info (ph, "win_status"); + + if (!st) + return WS_HIDDEN; + + if (!strcmp (st, "active")) + return WS_FOCUSED; + + if (!strcmp (st, "hidden")) + return WS_HIDDEN; + + return WS_NORMAL; +} + +static int +tray_count_channels (void) +{ + int cons = 0; + GSList *list; + session *sess; + + for (list = sess_list; list; list = list->next) + { + sess = list->data; + if (sess->server->connected && sess->channel[0] && + sess->type == SESS_CHANNEL) + cons++; + } + return cons; +} + +static int +tray_count_networks (void) +{ + int cons = 0; + GSList *list; + + for (list = serv_list; list; list = list->next) + { + if (((server *)list->data)->connected) + cons++; + } + return cons; +} + +void +fe_tray_set_tooltip (const char *text) +{ + if (sticon) + gtk_status_icon_set_tooltip (sticon, text); +} + +#ifdef LIBNOTIFY + +/* dynamic access to libnotify.so */ + +static void *nn_mod = NULL; +/* prototypes */ +static gboolean (*nn_init) (char *); +static void (*nn_uninit) (void); +/* recent versions of libnotify don't take the fourth GtkWidget argument, but passing an + * extra NULL argument will be fine */ +static void *(*nn_new) (const gchar *summary, const gchar *message, const gchar *icon, gpointer dummy); +static gboolean (*nn_show) (void *noti, GError **error); +static void (*nn_set_timeout) (void *noti, gint timeout); + +static void +libnotify_cleanup (void) +{ + if (nn_mod) + { + nn_uninit (); + g_module_close (nn_mod); + nn_mod = NULL; + } +} + +static gboolean +libnotify_notify_new (const char *title, const char *text, GtkStatusIcon *icon) +{ + void *noti; + + if (!nn_mod) + { + nn_mod = g_module_open ("libnotify", G_MODULE_BIND_LAZY); + if (!nn_mod) + { + nn_mod = g_module_open ("libnotify.so.1", G_MODULE_BIND_LAZY); + if (!nn_mod) + return FALSE; + } + + if (!g_module_symbol (nn_mod, "notify_init", (gpointer)&nn_init)) + goto bad; + if (!g_module_symbol (nn_mod, "notify_uninit", (gpointer)&nn_uninit)) + goto bad; + if (!g_module_symbol (nn_mod, "notify_notification_new", (gpointer)&nn_new)) + goto bad; + if (!g_module_symbol (nn_mod, "notify_notification_show", (gpointer)&nn_show)) + goto bad; + if (!g_module_symbol (nn_mod, "notify_notification_set_timeout", (gpointer)&nn_set_timeout)) + goto bad; + if (!nn_init (PACKAGE_NAME)) + goto bad; + } + + text = strip_color (text, -1, STRIP_ALL|STRIP_ESCMARKUP); + title = strip_color (title, -1, STRIP_ALL); + noti = nn_new (title, text, XCHATSHAREDIR"/pixmaps/xchat.png", NULL); + g_free ((char *)title); + g_free ((char *)text); + + nn_set_timeout (noti, prefs.input_balloon_time*1000); + nn_show (noti, NULL); + g_object_unref (G_OBJECT (noti)); + + return TRUE; + +bad: + g_module_close (nn_mod); + nn_mod = NULL; + return FALSE; +} + +#endif + +void +fe_tray_set_balloon (const char *title, const char *text) +{ +#ifndef WIN32 + const char *argv[8]; + const char *path; + char time[16]; + WinStatus ws; + + /* no balloons if the window is focused */ + ws = tray_get_window_status (); + if (ws == WS_FOCUSED) + return; + + /* bit 1 of flags means "no balloons unless hidden/iconified" */ + if (ws != WS_HIDDEN && (prefs.gui_tray_flags & 2)) + return; + + /* FIXME: this should close the current balloon */ + if (!text) + return; + +#ifdef LIBNOTIFY + /* try it via libnotify.so */ + if (libnotify_notify_new (title, text, sticon)) + return; /* success */ +#endif + + /* try it the crude way */ + path = g_find_program_in_path ("notify-send"); + if (path) + { + sprintf(time, "%d000",prefs.input_balloon_time); + argv[0] = path; + argv[1] = "-i"; + argv[2] = "gtk-dialog-info"; + if (access (XCHATSHAREDIR"/pixmaps/xchat.png", R_OK) == 0) + argv[2] = XCHATSHAREDIR"/pixmaps/xchat.png"; + argv[3] = "-t"; + argv[4] = time; + argv[5] = title; + text = strip_color (text, -1, STRIP_ALL|STRIP_ESCMARKUP); + argv[6] = text; + argv[7] = NULL; + xchat_execv (argv); + g_free ((char *)path); + g_free ((char *)text); + } + else + { + /* show this error only once */ + static unsigned char said_it = FALSE; + if (!said_it) + { + said_it = TRUE; + fe_message (_("Cannot find 'notify-send' to open balloon alerts.\nPlease install libnotify."), FE_MSG_ERROR); + } + } +#endif +} + +static void +tray_set_balloonf (const char *text, const char *format, ...) +{ + va_list args; + char *buf; + + va_start (args, format); + buf = g_strdup_vprintf (format, args); + va_end (args); + + fe_tray_set_balloon (buf, text); + g_free (buf); +} + +static void +tray_set_tipf (const char *format, ...) +{ + va_list args; + char *buf; + + va_start (args, format); + buf = g_strdup_vprintf (format, args); + va_end (args); + + fe_tray_set_tooltip (buf); + g_free (buf); +} + +static void +tray_stop_flash (void) +{ + int nets, chans; + + if (flash_tag) + { + g_source_remove (flash_tag); + flash_tag = 0; + } + + if (sticon) + { + gtk_status_icon_set_from_pixbuf (sticon, ICON_NORMAL); + nets = tray_count_networks (); + chans = tray_count_channels (); + if (nets) + tray_set_tipf (_("XChat: Connected to %u networks and %u channels"), + nets, chans); + else + tray_set_tipf ("XChat: %s", _("Not connected.")); + } + + if (custom_icon1) + { + tray_icon_free (custom_icon1); + custom_icon1 = NULL; + } + + if (custom_icon2) + { + tray_icon_free (custom_icon2); + custom_icon2 = NULL; + } + + tray_status = TS_NONE; +} + +static void +tray_reset_counts (void) +{ + tray_priv_count = 0; + tray_pub_count = 0; + tray_hilight_count = 0; + tray_file_count = 0; +} + +static int +tray_timeout_cb (TrayIcon icon) +{ + if (custom_icon1) + { + if (gtk_status_icon_get_pixbuf (sticon) == custom_icon1) + { + if (custom_icon2) + gtk_status_icon_set_from_pixbuf (sticon, custom_icon2); + else + gtk_status_icon_set_from_pixbuf (sticon, ICON_NORMAL); + } + else + { + gtk_status_icon_set_from_pixbuf (sticon, custom_icon1); + } + } + else + { + if (gtk_status_icon_get_pixbuf (sticon) == ICON_NORMAL) + gtk_status_icon_set_from_pixbuf (sticon, icon); + else + gtk_status_icon_set_from_pixbuf (sticon, ICON_NORMAL); + } + return 1; +} + +static void +tray_set_flash (TrayIcon icon) +{ + if (!sticon) + return; + + /* already flashing the same icon */ + if (flash_tag && gtk_status_icon_get_pixbuf (sticon) == icon) + return; + + /* no flashing if window is focused */ + if (tray_get_window_status () == WS_FOCUSED) + return; + + tray_stop_flash (); + + gtk_status_icon_set_from_pixbuf (sticon, icon); + flash_tag = g_timeout_add (TIMEOUT, (GSourceFunc) tray_timeout_cb, icon); +} + +void +fe_tray_set_flash (const char *filename1, const char *filename2, int tout) +{ + tray_apply_setup (); + if (!sticon) + return; + + tray_stop_flash (); + + if (tout == -1) + tout = TIMEOUT; + + custom_icon1 = tray_icon_from_file (filename1); + if (filename2) + custom_icon2 = tray_icon_from_file (filename2); + + gtk_status_icon_set_from_pixbuf (sticon, custom_icon1); + flash_tag = g_timeout_add (tout, (GSourceFunc) tray_timeout_cb, NULL); + tray_status = TS_CUSTOM; +} + +void +fe_tray_set_icon (feicon icon) +{ + tray_apply_setup (); + if (!sticon) + return; + + tray_stop_flash (); + + switch (icon) + { + case FE_ICON_NORMAL: + break; + case FE_ICON_MESSAGE: + tray_set_flash (ICON_MSG); + break; + case FE_ICON_HIGHLIGHT: + case FE_ICON_PRIVMSG: + tray_set_flash (ICON_HILIGHT); + break; + case FE_ICON_FILEOFFER: + tray_set_flash (ICON_FILE); + } +} + +void +fe_tray_set_file (const char *filename) +{ + tray_apply_setup (); + if (!sticon) + return; + + tray_stop_flash (); + + if (filename) + { + custom_icon1 = tray_icon_from_file (filename); + gtk_status_icon_set_from_pixbuf (sticon, custom_icon1); + tray_status = TS_CUSTOM; + } +} + +gboolean +tray_toggle_visibility (gboolean force_hide) +{ + static int x, y; + static GdkScreen *screen; + GtkWindow *win; + + if (!sticon) + return FALSE; + + /* ph may have an invalid context now */ + xchat_set_context (ph, xchat_find_context (ph, NULL, NULL)); + + win = (GtkWindow *)xchat_get_info (ph, "win_ptr"); + + tray_stop_flash (); + tray_reset_counts (); + + if (!win) + return FALSE; + +#if GTK_CHECK_VERSION(2,20,0) + if (force_hide || gtk_widget_get_visible (GTK_WIDGET (win))) +#else + if (force_hide || GTK_WIDGET_VISIBLE (win)) +#endif + { + gtk_window_get_position (win, &x, &y); + screen = gtk_window_get_screen (win); + gtk_widget_hide (GTK_WIDGET (win)); + } + else + { + gtk_window_set_screen (win, screen); + gtk_window_move (win, x, y); + gtk_widget_show (GTK_WIDGET (win)); + gtk_window_present (win); + } + + return TRUE; +} + +static void +tray_menu_restore_cb (GtkWidget *item, gpointer userdata) +{ + tray_toggle_visibility (FALSE); +} + +static void +tray_menu_quit_cb (GtkWidget *item, gpointer userdata) +{ + mg_open_quit_dialog (FALSE); +} + +/* returns 0-mixed 1-away 2-back */ + +static int +tray_find_away_status (void) +{ + GSList *list; + server *serv; + int away = 0; + int back = 0; + + for (list = serv_list; list; list = list->next) + { + serv = list->data; + + if (serv->is_away || serv->reconnect_away) + away++; + else + back++; + } + + if (away && back) + return 0; + + if (away) + return 1; + + return 2; +} + +static void +tray_foreach_server (GtkWidget *item, char *cmd) +{ + GSList *list; + server *serv; + + for (list = serv_list; list; list = list->next) + { + serv = list->data; + if (serv->connected) + handle_command (serv->server_session, cmd, FALSE); + } +} + +static GtkWidget * +tray_make_item (GtkWidget *menu, char *label, void *callback, void *userdata) +{ + GtkWidget *item; + + if (label) + item = gtk_menu_item_new_with_mnemonic (label); + else + item = gtk_menu_item_new (); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (callback), userdata); + gtk_widget_show (item); + + return item; +} + +static void +tray_toggle_cb (GtkCheckMenuItem *item, unsigned int *setting) +{ + *setting = item->active; +} + +static void +blink_item (unsigned int *setting, GtkWidget *menu, char *label) +{ + menu_toggle_item (label, menu, tray_toggle_cb, setting, *setting); +} + +static void +tray_menu_destroy (GtkWidget *menu, gpointer userdata) +{ + gtk_widget_destroy (menu); + g_object_unref (menu); +} + +static void +tray_menu_cb (GtkWidget *widget, guint button, guint time, gpointer userdata) +{ + GtkWidget *menu; + GtkWidget *submenu; + GtkWidget *item; + int away_status; + + /* ph may have an invalid context now */ + xchat_set_context (ph, xchat_find_context (ph, NULL, NULL)); + + menu = gtk_menu_new (); + /*gtk_menu_set_screen (GTK_MENU (menu), gtk_widget_get_screen (widget));*/ + + if (tray_get_window_status () == WS_HIDDEN) + tray_make_item (menu, _("_Restore"), tray_menu_restore_cb, NULL); + else + tray_make_item (menu, _("_Hide"), tray_menu_restore_cb, NULL); + tray_make_item (menu, NULL, tray_menu_quit_cb, NULL); + + submenu = mg_submenu (menu, _("_Blink on")); + blink_item (&prefs.input_tray_chans, submenu, _("Channel Message")); + blink_item (&prefs.input_tray_priv, submenu, _("Private Message")); + blink_item (&prefs.input_tray_hilight, submenu, _("Highlighted Message")); + /*blink_item (BIT_FILEOFFER, submenu, _("File Offer"));*/ + + submenu = mg_submenu (menu, _("_Change status")); + away_status = tray_find_away_status (); + item = tray_make_item (submenu, _("_Away"), tray_foreach_server, "away"); + if (away_status == 1) + gtk_widget_set_sensitive (item, FALSE); + item = tray_make_item (submenu, _("_Back"), tray_foreach_server, "back"); + if (away_status == 2) + gtk_widget_set_sensitive (item, FALSE); + + tray_make_item (menu, NULL, tray_menu_quit_cb, NULL); + mg_create_icon_item (_("_Quit"), GTK_STOCK_QUIT, menu, tray_menu_quit_cb, NULL); + + menu_add_plugin_items (menu, "\x5$TRAY", NULL); + + g_object_ref (menu); + g_object_ref_sink (menu); + g_object_unref (menu); + g_signal_connect (G_OBJECT (menu), "selection-done", + G_CALLBACK (tray_menu_destroy), NULL); + + gtk_menu_popup (GTK_MENU (menu), NULL, NULL, gtk_status_icon_position_menu, + userdata, button, time); +} + +static void +tray_init (void) +{ + flash_tag = 0; + tray_status = TS_NONE; + custom_icon1 = NULL; + custom_icon2 = NULL; + + sticon = gtk_status_icon_new_from_pixbuf (ICON_NORMAL); + if (!sticon) + return; + g_signal_connect (G_OBJECT (sticon), "popup-menu", + G_CALLBACK (tray_menu_cb), sticon); + g_signal_connect (G_OBJECT (sticon), "activate", + G_CALLBACK (tray_menu_restore_cb), NULL); +} + +static int +tray_hilight_cb (char *word[], void *userdata) +{ + /*if (tray_status == TS_HIGHLIGHT) + return XCHAT_EAT_NONE;*/ + + if (prefs.input_tray_hilight) + { + tray_set_flash (ICON_HILIGHT); + + /* FIXME: hides any previous private messages */ + tray_hilight_count++; + if (tray_hilight_count == 1) + tray_set_tipf (_("XChat: Highlighted message from: %s (%s)"), + word[1], xchat_get_info (ph, "channel")); + else + tray_set_tipf (_("XChat: %u highlighted messages, latest from: %s (%s)"), + tray_hilight_count, word[1], xchat_get_info (ph, "channel")); + } + + if (prefs.input_balloon_hilight) + tray_set_balloonf (word[2], _("XChat: Highlighted message from: %s (%s)"), + word[1], xchat_get_info (ph, "channel")); + + return XCHAT_EAT_NONE; +} + +static int +tray_message_cb (char *word[], void *userdata) +{ + if (/*tray_status == TS_MESSAGE ||*/ tray_status == TS_HIGHLIGHT) + return XCHAT_EAT_NONE; + + if (prefs.input_tray_chans) + { + tray_set_flash (ICON_MSG); + + tray_pub_count++; + if (tray_pub_count == 1) + tray_set_tipf (_("XChat: New public message from: %s (%s)"), + word[1], xchat_get_info (ph, "channel")); + else + tray_set_tipf (_("XChat: %u new public messages."), tray_pub_count); + } + + if (prefs.input_balloon_chans) + tray_set_balloonf (word[2], _("XChat: New public message from: %s (%s)"), + word[1], xchat_get_info (ph, "channel")); + + return XCHAT_EAT_NONE; +} + +static void +tray_priv (char *from, char *text) +{ + const char *network; + + if (alert_match_word (from, prefs.irc_no_hilight)) + return; + + tray_set_flash (ICON_HILIGHT); + + network = xchat_get_info (ph, "network"); + if (!network) + network = xchat_get_info (ph, "server"); + + tray_priv_count++; + if (tray_priv_count == 1) + tray_set_tipf (_("XChat: Private message from: %s (%s)"), + from, network); + else + tray_set_tipf (_("XChat: %u private messages, latest from: %s (%s)"), + tray_priv_count, from, network); + + if (prefs.input_balloon_priv) + tray_set_balloonf (text, _("XChat: Private message from: %s (%s)"), + from, network); +} + +static int +tray_priv_cb (char *word[], void *userdata) +{ + /*if (tray_status == TS_HIGHLIGHT) + return XCHAT_EAT_NONE;*/ + + if (prefs.input_tray_priv) + tray_priv (word[1], word[2]); + + return XCHAT_EAT_NONE; +} + +static int +tray_invited_cb (char *word[], void *userdata) +{ + /*if (tray_status == TS_HIGHLIGHT) + return XCHAT_EAT_NONE;*/ + + if (prefs.input_tray_priv) + tray_priv (word[2], "Invited"); + + return XCHAT_EAT_NONE; +} + +static int +tray_dcc_cb (char *word[], void *userdata) +{ + const char *network; + +/* if (tray_status == TS_FILEOFFER) + return XCHAT_EAT_NONE;*/ + + network = xchat_get_info (ph, "network"); + if (!network) + network = xchat_get_info (ph, "server"); + + if (prefs.input_tray_priv) + { + tray_set_flash (ICON_FILE); + + tray_file_count++; + if (tray_file_count == 1) + tray_set_tipf (_("XChat: File offer from: %s (%s)"), + word[1], network); + else + tray_set_tipf (_("XChat: %u file offers, latest from: %s (%s)"), + tray_file_count, word[1], network); + } + + if (prefs.input_balloon_priv) + tray_set_balloonf ("", _("XChat: File offer from: %s (%s)"), + word[1], network); + + return XCHAT_EAT_NONE; +} + +static int +tray_focus_cb (char *word[], void *userdata) +{ + tray_stop_flash (); + tray_reset_counts (); + return XCHAT_EAT_NONE; +} + +static void +tray_cleanup (void) +{ + tray_stop_flash (); + + if (sticon) + { + g_object_unref ((GObject *)sticon); + sticon = NULL; + } +} + +void +tray_apply_setup (void) +{ + if (sticon) + { + if (!prefs.gui_tray) + tray_cleanup (); + } + else + { + if (prefs.gui_tray) + tray_init (); + } +} + +int +tray_plugin_init (xchat_plugin *plugin_handle, char **plugin_name, + char **plugin_desc, char **plugin_version, char *arg) +{ + /* we need to save this for use with any xchat_* functions */ + ph = plugin_handle; + + *plugin_name = ""; + *plugin_desc = ""; + *plugin_version = ""; + + xchat_hook_print (ph, "Channel Msg Hilight", -1, tray_hilight_cb, NULL); + xchat_hook_print (ph, "Channel Action Hilight", -1, tray_hilight_cb, NULL); + + xchat_hook_print (ph, "Channel Message", -1, tray_message_cb, NULL); + xchat_hook_print (ph, "Channel Action", -1, tray_message_cb, NULL); + xchat_hook_print (ph, "Channel Notice", -1, tray_message_cb, NULL); + + xchat_hook_print (ph, "Private Message", -1, tray_priv_cb, NULL); + xchat_hook_print (ph, "Private Message to Dialog", -1, tray_priv_cb, NULL); + xchat_hook_print (ph, "Notice", -1, tray_priv_cb, NULL); + xchat_hook_print (ph, "Invited", -1, tray_invited_cb, NULL); + + xchat_hook_print (ph, "DCC Offer", -1, tray_dcc_cb, NULL); + + xchat_hook_print (ph, "Focus Window", -1, tray_focus_cb, NULL); + + if (prefs.gui_tray) + tray_init (); + + return 1; /* return 1 for success */ +} + +int +tray_plugin_deinit (xchat_plugin *plugin_handle) +{ +#ifdef WIN32 + tray_cleanup (); +#elif defined(LIBNOTIFY) + libnotify_cleanup (); +#endif + return 1; +} diff --git a/src/fe-gtk/plugin-tray.h b/src/fe-gtk/plugin-tray.h new file mode 100644 index 00000000..d54be5a4 --- /dev/null +++ b/src/fe-gtk/plugin-tray.h @@ -0,0 +1,4 @@ +int tray_plugin_init (void *, char **, char **, char **, char *); +int tray_plugin_deinit (void *); +gboolean tray_toggle_visibility (gboolean force_hide); +void tray_apply_setup (void); diff --git a/src/fe-gtk/plugingui.c b/src/fe-gtk/plugingui.c new file mode 100644 index 00000000..de59e649 --- /dev/null +++ b/src/fe-gtk/plugingui.c @@ -0,0 +1,239 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <string.h> +#include <stdio.h> +#include <stdlib.h> + +#include "fe-gtk.h" + +#include <gtk/gtkdialog.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkbox.h> +#include <gtk/gtkscrolledwindow.h> + +#include <gtk/gtkliststore.h> +#include <gtk/gtktreeview.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtkcellrenderertext.h> + +#include "../common/xchat.h" +#define PLUGIN_C +typedef struct session xchat_context; +#include "../common/xchat-plugin.h" +#include "../common/plugin.h" +#include "../common/util.h" +#include "../common/outbound.h" +#include "../common/fe.h" +#include "../common/xchatc.h" +#include "gtkutil.h" + +/* model for the plugin treeview */ +enum +{ + NAME_COLUMN, + VERSION_COLUMN, + FILE_COLUMN, + DESC_COLUMN, + N_COLUMNS +}; + +static GtkWidget *plugin_window = NULL; + + +static GtkWidget * +plugingui_treeview_new (GtkWidget *box) +{ + GtkListStore *store; + GtkWidget *view; + GtkTreeViewColumn *col; + int col_id; + + store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_STRING); + g_return_val_if_fail (store != NULL, NULL); + view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store), NULL, + NAME_COLUMN, _("Name"), + VERSION_COLUMN, _("Version"), + FILE_COLUMN, _("File"), + DESC_COLUMN, _("Description"), -1); + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (view), TRUE); + for (col_id=0; (col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), col_id)); + col_id++) + gtk_tree_view_column_set_alignment (col, 0.5); + + gtk_widget_show (view); + return view; +} + +static void +plugingui_close_button (GtkWidget * wid, gpointer a) +{ + gtk_widget_destroy (plugin_window); +} + +static void +plugingui_close (GtkWidget * wid, gpointer a) +{ + plugin_window = NULL; +} + +extern GSList *plugin_list; + +void +fe_pluginlist_update (void) +{ + xchat_plugin *pl; + GSList *list; + GtkTreeView *view; + GtkListStore *store; + GtkTreeIter iter; + + if (!plugin_window) + return; + + view = g_object_get_data (G_OBJECT (plugin_window), "view"); + store = GTK_LIST_STORE (gtk_tree_view_get_model (view)); + gtk_list_store_clear (store); + + list = plugin_list; + while (list) + { + pl = list->data; + if (pl->version[0] != 0) + { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, NAME_COLUMN, pl->name, + VERSION_COLUMN, pl->version, + FILE_COLUMN, file_part (pl->filename), + DESC_COLUMN, pl->desc, -1); + } + list = list->next; + } +} + +static void +plugingui_load_cb (session *sess, char *file) +{ + if (file) + { + char *buf = malloc (strlen (file) + 9); + + if (strchr (file, ' ')) + sprintf (buf, "LOAD \"%s\"", file); + else + sprintf (buf, "LOAD %s", file); + handle_command (sess, buf, FALSE); + free (buf); + } +} + +void +plugingui_load (void) +{ + gtkutil_file_req (_("Select a Plugin or Script to load"), plugingui_load_cb, + current_sess, NULL, FRF_ADDFOLDER); +} + +static void +plugingui_loadbutton_cb (GtkWidget * wid, gpointer unused) +{ + plugingui_load (); +} + +static void +plugingui_unload (GtkWidget * wid, gpointer unused) +{ + int len; + char *modname, *file, *buf; + GtkTreeView *view; + GtkTreeIter iter; + + view = g_object_get_data (G_OBJECT (plugin_window), "view"); + if (!gtkutil_treeview_get_selected (view, &iter, NAME_COLUMN, &modname, + FILE_COLUMN, &file, -1)) + return; + + len = strlen (file); +#ifdef WIN32 + if (len > 4 && strcasecmp (file + len - 4, ".dll") == 0) +#else +#if defined(__hpux) + if (len > 3 && strcasecmp (file + len - 3, ".sl") == 0) +#else + if (len > 3 && strcasecmp (file + len - 3, ".so") == 0) +#endif +#endif + { + if (plugin_kill (modname, FALSE) == 2) + fe_message (_("That plugin is refusing to unload.\n"), FE_MSG_ERROR); + } else + { + /* let python.so or perl.so handle it */ + buf = malloc (strlen (file) + 10); + if (strchr (file, ' ')) + sprintf (buf, "UNLOAD \"%s\"", file); + else + sprintf (buf, "UNLOAD %s", file); + handle_command (current_sess, buf, FALSE); + free (buf); + } + + g_free (modname); + g_free (file); +} + +void +plugingui_open (void) +{ + GtkWidget *view; + GtkWidget *vbox, *action_area; + + if (plugin_window) + { + gtk_window_present (GTK_WINDOW (plugin_window)); + return; + } + + plugin_window = gtk_dialog_new (); + g_signal_connect (G_OBJECT (plugin_window), "destroy", + G_CALLBACK (plugingui_close), 0); + gtk_window_set_default_size (GTK_WINDOW (plugin_window), 500, 250); + vbox = GTK_DIALOG (plugin_window)->vbox; + action_area = GTK_DIALOG (plugin_window)->action_area; + gtk_container_set_border_width (GTK_CONTAINER (vbox), 4); + gtk_window_set_position (GTK_WINDOW (plugin_window), GTK_WIN_POS_CENTER); + gtk_window_set_title (GTK_WINDOW (plugin_window), _("XChat: Plugins and Scripts")); + + view = plugingui_treeview_new (vbox); + g_object_set_data (G_OBJECT (plugin_window), "view", view); + + gtkutil_button (action_area, GTK_STOCK_REVERT_TO_SAVED, NULL, + plugingui_loadbutton_cb, NULL, _("_Load...")); + + gtkutil_button (action_area, GTK_STOCK_DELETE, NULL, + plugingui_unload, NULL, _("_UnLoad")); + + gtkutil_button (action_area, + GTK_STOCK_CLOSE, NULL, plugingui_close_button, + NULL, _("_Close")); + + fe_pluginlist_update (); + + gtk_widget_show (plugin_window); +} diff --git a/src/fe-gtk/plugingui.h b/src/fe-gtk/plugingui.h new file mode 100644 index 00000000..945d9a01 --- /dev/null +++ b/src/fe-gtk/plugingui.h @@ -0,0 +1,2 @@ +void plugingui_open (void); +void plugingui_load (void); diff --git a/src/fe-gtk/rawlog.c b/src/fe-gtk/rawlog.c new file mode 100644 index 00000000..56ca0510 --- /dev/null +++ b/src/fe-gtk/rawlog.c @@ -0,0 +1,152 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> + +#include "fe-gtk.h" + +#include <gtk/gtkbutton.h> +#include <gtk/gtkhbbox.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkvscrollbar.h> +#include <gtk/gtkstock.h> + +#include "../common/xchat.h" +#include "../common/xchatc.h" +#include "../common/cfgfiles.h" +#include "../common/server.h" +#include "gtkutil.h" +#include "palette.h" +#include "maingui.h" +#include "rawlog.h" +#include "xtext.h" + + +static void +close_rawlog (GtkWidget *wid, server *serv) +{ + if (is_server (serv)) + serv->gui->rawlog_window = 0; +} + +static void +rawlog_save (server *serv, char *file) +{ + int fh = -1; + + if (file) + { + if (serv->gui->rawlog_window) + fh = xchat_open_file (file, O_TRUNC | O_WRONLY | O_CREAT, + 0600, XOF_DOMODE | XOF_FULLPATH); + if (fh != -1) + { + gtk_xtext_save (GTK_XTEXT (serv->gui->rawlog_textlist), fh); + close (fh); + } + } +} + +static int +rawlog_clearbutton (GtkWidget * wid, server *serv) +{ + gtk_xtext_clear (GTK_XTEXT (serv->gui->rawlog_textlist)->buffer, 0); + return FALSE; +} + +static int +rawlog_savebutton (GtkWidget * wid, server *serv) +{ + gtkutil_file_req (_("Save As..."), rawlog_save, serv, NULL, FRF_WRITE); + return FALSE; +} + +void +open_rawlog (struct server *serv) +{ + GtkWidget *hbox, *vscrollbar, *vbox; + char tbuf[256]; + + if (serv->gui->rawlog_window) + { + mg_bring_tofront (serv->gui->rawlog_window); + return; + } + + snprintf (tbuf, sizeof tbuf, _("XChat: Rawlog (%s)"), serv->servername); + serv->gui->rawlog_window = + mg_create_generic_tab ("RawLog", tbuf, FALSE, TRUE, close_rawlog, serv, + 640, 320, &vbox, serv); + + hbox = gtk_hbox_new (FALSE, 2); + gtk_container_add (GTK_CONTAINER (vbox), hbox); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 4); + gtk_widget_show (hbox); + + serv->gui->rawlog_textlist = gtk_xtext_new (colors, 0); + gtk_xtext_set_tint (GTK_XTEXT (serv->gui->rawlog_textlist), prefs.tint_red, prefs.tint_green, prefs.tint_blue); + gtk_xtext_set_background (GTK_XTEXT (serv->gui->rawlog_textlist), + channelwin_pix, prefs.transparent); + + gtk_container_add (GTK_CONTAINER (hbox), serv->gui->rawlog_textlist); + gtk_xtext_set_font (GTK_XTEXT (serv->gui->rawlog_textlist), prefs.font_normal); + GTK_XTEXT (serv->gui->rawlog_textlist)->ignore_hidden = 1; + gtk_widget_show (serv->gui->rawlog_textlist); + + vscrollbar = gtk_vscrollbar_new (GTK_XTEXT (serv->gui->rawlog_textlist)->adj); + gtk_box_pack_start (GTK_BOX (hbox), vscrollbar, FALSE, FALSE, 0); + show_and_unfocus (vscrollbar); + + hbox = gtk_hbutton_box_new (); + gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_SPREAD); + gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 0); + gtk_widget_show (hbox); + + gtkutil_button (hbox, GTK_STOCK_CLEAR, NULL, rawlog_clearbutton, + serv, _("Clear rawlog")); + + gtkutil_button (hbox, GTK_STOCK_SAVE_AS, NULL, rawlog_savebutton, + serv, _("Save As...")); + + gtk_widget_show (serv->gui->rawlog_window); +} + +void +fe_add_rawlog (server *serv, char *text, int len, int outbound) +{ + char *new_text; + + if (!serv->gui->rawlog_window) + return; + + new_text = malloc (len + 7); + + len = sprintf (new_text, "\0033>>\017 %s", text); + if (outbound) + { + new_text[1] = '4'; + new_text[2] = '<'; + new_text[3] = '<'; + } + gtk_xtext_append (GTK_XTEXT (serv->gui->rawlog_textlist)->buffer, new_text, len); + free (new_text); +} diff --git a/src/fe-gtk/rawlog.h b/src/fe-gtk/rawlog.h new file mode 100644 index 00000000..db41e2a7 --- /dev/null +++ b/src/fe-gtk/rawlog.h @@ -0,0 +1 @@ +void open_rawlog (server *serv); diff --git a/src/fe-gtk/search.c b/src/fe-gtk/search.c new file mode 100644 index 00000000..d62e79c7 --- /dev/null +++ b/src/fe-gtk/search.c @@ -0,0 +1,159 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "fe-gtk.h" + +#include <gtk/gtkentry.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkvbox.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkhbbox.h> +#include <gtk/gtkhseparator.h> +#include <gtk/gtkvseparator.h> +#include <gtk/gtkradiobutton.h> +#include <gtk/gtktogglebutton.h> +#include <gdk/gdkkeysyms.h> + +#include "../common/xchat.h" +#include "../common/fe.h" +#include "../common/util.h" +#include "../common/xchatc.h" +#include "gtkutil.h" +#include "xtext.h" +#include "maingui.h" + + +static textentry *last; /* our last search pos */ +static int case_match = 0; +static int search_backward = 0; + + +static void +search_search (session * sess, const gchar *text) +{ + if (!is_session (sess)) + { + fe_message (_("The window you opened this Search " + "for doesn't exist anymore."), FE_MSG_ERROR); + return; + } + + last = gtk_xtext_search (GTK_XTEXT (sess->gui->xtext), text, + last, case_match, search_backward); + if (!last) + fe_message (_("Search hit end, not found."), FE_MSG_ERROR); +} + +static void +search_find_cb (GtkWidget * button, session * sess) +{ + GtkEntry *entry; + const gchar *text; + + entry = g_object_get_data (G_OBJECT (button), "e"); + text = gtk_entry_get_text (entry); + search_search (sess, text); +} + +static void +search_close_cb (GtkWidget * button, GtkWidget * win) +{ + gtk_widget_destroy (win); +} + +static void +search_entry_cb (GtkWidget * entry, session * sess) +{ + search_search (sess, gtk_entry_get_text (GTK_ENTRY (entry))); +} + +static gboolean +search_key_cb (GtkWidget * window, GdkEventKey * key, gpointer userdata) +{ + if (key->keyval == GDK_Escape) + gtk_widget_destroy (window); + return FALSE; +} + +static void +search_caseign_cb (GtkToggleButton * but, session * sess) +{ + case_match = (but->active)? 1: 0; +} + +static void +search_dirbwd_cb (GtkToggleButton * but, session * sess) +{ + search_backward = (but->active)? 1: 0; +} + +void +search_open (session * sess) +{ + GtkWidget *win, *hbox, *vbox, *entry, *wid; + + last = NULL; + win = mg_create_generic_tab ("search", _("XChat: Search"), TRUE, FALSE, + NULL, NULL, 0, 0, &vbox, 0); + gtk_container_set_border_width (GTK_CONTAINER (win), 12); + gtk_box_set_spacing (GTK_BOX (vbox), 4); + + hbox = gtk_hbox_new (0, 10); + gtk_container_add (GTK_CONTAINER (vbox), hbox); + gtk_widget_show (hbox); + + gtkutil_label_new (_("Find:"), hbox); + + entry = gtk_entry_new (); + g_signal_connect (G_OBJECT (entry), "activate", + G_CALLBACK (search_entry_cb), sess); + gtk_container_add (GTK_CONTAINER (hbox), entry); + gtk_widget_show (entry); + gtk_widget_grab_focus (entry); + + wid = gtk_check_button_new_with_mnemonic (_("_Match case")); + GTK_TOGGLE_BUTTON (wid)->active = case_match; + g_signal_connect (G_OBJECT (wid), "toggled", G_CALLBACK (search_caseign_cb), sess); + gtk_container_add (GTK_CONTAINER (vbox), wid); + gtk_widget_show (wid); + + wid = gtk_check_button_new_with_mnemonic (_("Search _backwards")); + GTK_TOGGLE_BUTTON (wid)->active = search_backward; + g_signal_connect (G_OBJECT (wid), "toggled", G_CALLBACK (search_dirbwd_cb), sess); + gtk_container_add (GTK_CONTAINER (vbox), wid); + gtk_widget_show (wid); + + hbox = gtk_hbutton_box_new (); + gtk_box_pack_start (GTK_BOX (vbox), hbox, 0, 0, 4); + gtk_widget_show (hbox); + + gtkutil_button (hbox, GTK_STOCK_CLOSE, 0, search_close_cb, win, + _("_Close")); + wid = gtkutil_button (hbox, GTK_STOCK_FIND, 0, search_find_cb, sess, + _("_Find")); + g_object_set_data (G_OBJECT (wid), "e", entry); + + g_signal_connect (G_OBJECT (win), "key-press-event", G_CALLBACK (search_key_cb), win); + + gtk_widget_show (win); +} diff --git a/src/fe-gtk/search.h b/src/fe-gtk/search.h new file mode 100644 index 00000000..8fa1b628 --- /dev/null +++ b/src/fe-gtk/search.h @@ -0,0 +1 @@ +void search_open (session * sess); diff --git a/src/fe-gtk/servlistgui.c b/src/fe-gtk/servlistgui.c new file mode 100644 index 00000000..2ac0e6c9 --- /dev/null +++ b/src/fe-gtk/servlistgui.c @@ -0,0 +1,1918 @@ +/* X-Chat + * Copyright (C) 2004-2008 Peter Zelezny. + */ + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> + +#include <gtk/gtkversion.h> +#include <gtk/gtkcheckbutton.h> +#include <gtk/gtkcellrenderertext.h> +#include <gtk/gtkcomboboxentry.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkhbbox.h> +#include <gtk/gtkhseparator.h> +#include <gtk/gtkimage.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkliststore.h> +#include <gtk/gtkmenuitem.h> +#include <gtk/gtkmessagedialog.h> +#include <gtk/gtkscrolledwindow.h> +#include <gtk/gtkstock.h> +#include <gtk/gtktable.h> +#include <gtk/gtktogglebutton.h> +#include <gtk/gtktree.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtktreeview.h> +#include <gtk/gtkvbbox.h> +#include <gtk/gtkvbox.h> +#include <gtk/gtkwindow.h> +#include <gdk/gdkkeysyms.h> + +#include "../common/xchat.h" +#include "../common/xchatc.h" +#include "../common/servlist.h" +#include "../common/cfgfiles.h" +#include "../common/fe.h" + +#include "fe-gtk.h" +#include "gtkutil.h" +#include "menu.h" +#include "pixmaps.h" + + +/* servlistgui.c globals */ +static GtkWidget *serverlist_win = NULL; +static GtkWidget *networks_tree; /* network TreeView */ +static int ignore_changed = FALSE; +#ifdef WIN32 +static int win_width = 324; +static int win_height = 426; +#else +static int win_width = 364; +static int win_height = 478; +#endif + +/* global user info */ +static GtkWidget *entry_nick1; +static GtkWidget *entry_nick2; +static GtkWidget *entry_nick3; +static GtkWidget *entry_guser; +static GtkWidget *entry_greal; + +/* edit area */ +static GtkWidget *edit_win; +static GtkWidget *edit_entry_nick; +static GtkWidget *edit_entry_nick2; +static GtkWidget *edit_entry_user; +static GtkWidget *edit_entry_real; +static GtkWidget *edit_entry_join; +static GtkWidget *edit_entry_pass; +static GtkWidget *edit_entry_cmd; +static GtkWidget *edit_entry_nickserv; +static GtkWidget *edit_label_nick; +static GtkWidget *edit_label_nick2; +static GtkWidget *edit_label_real; +static GtkWidget *edit_label_user; +static GtkWidget *edit_tree; + +static ircnet *selected_net = NULL; +static ircserver *selected_serv = NULL; +static ircnet *fav_add_net = NULL; /* used in Add/Remove fav context menus */ +static session *servlist_sess; + +static void servlist_network_row_cb (GtkTreeSelection *sel, gpointer user_data); +static GtkWidget *servlist_open_edit (GtkWidget *parent, ircnet *net); + + +static const char *pages[]= +{ + "UTF-8 (Unicode)", + "IRC (Latin/Unicode Hybrid)", + "ISO-8859-15 (Western Europe)", + "ISO-8859-2 (Central Europe)", + "ISO-8859-7 (Greek)", + "ISO-8859-8 (Hebrew)", + "ISO-8859-9 (Turkish)", + "ISO-2022-JP (Japanese)", + "SJIS (Japanese)", + "CP949 (Korean)", + "KOI8-R (Cyrillic)", + "CP1251 (Cyrillic)", + "CP1256 (Arabic)", + "CP1257 (Baltic)", + "GB18030 (Chinese)", + "TIS-620 (Thai)", + NULL +}; + +static void +servlist_select_and_show (GtkTreeView *treeview, GtkTreeIter *iter, + GtkListStore *store) +{ + GtkTreePath *path; + GtkTreeSelection *sel; + + sel = gtk_tree_view_get_selection (treeview); + + /* select this network */ + gtk_tree_selection_select_iter (sel, iter); + /* and make sure it's visible */ + path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), iter); + if (path) + { + gtk_tree_view_scroll_to_cell (treeview, path, NULL, TRUE, 0.5, 0.5); + gtk_tree_view_set_cursor (treeview, path, NULL, FALSE); + gtk_tree_path_free (path); + } +} + +static void +servlist_servers_populate (ircnet *net, GtkWidget *treeview) +{ + GtkListStore *store; + GtkTreeIter iter; + int i; + ircserver *serv; + GSList *list = net->servlist; + + store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)); + gtk_list_store_clear (store); + + i = 0; + while (list) + { + serv = list->data; + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, serv->hostname, 1, 1, -1); + + if (net->selected == i) + /* select this server */ + servlist_select_and_show (GTK_TREE_VIEW (treeview), &iter, store); + + i++; + list = list->next; + } +} + +static void +servlist_networks_populate_ (GtkWidget *treeview, GSList *netlist, gboolean favorites) +{ + GtkListStore *store; + GtkTreeIter iter; + int i; + ircnet *net; + + if (!netlist) + { + net = servlist_net_add (_("New Network"), "", FALSE); + servlist_server_add (net, "newserver/6667"); + netlist = network_list; + } + store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)); + gtk_list_store_clear (store); + + i = 0; + while (netlist) + { + net = netlist->data; + if (!favorites || (net->flags & FLAG_FAVORITE)) + { + if (favorites) + gtk_list_store_insert_with_values (store, &iter, 0x7fffffff, 0, net->name, 1, 1, 2, 400, -1); + else + gtk_list_store_insert_with_values (store, &iter, 0x7fffffff, 0, net->name, 1, 1, 2, (net->flags & FLAG_FAVORITE) ? 800 : 400, -1); + if (i == prefs.slist_select) + { + /* select this network */ + servlist_select_and_show (GTK_TREE_VIEW (treeview), &iter, store); + selected_net = net; + } + } + i++; + netlist = netlist->next; + } +} + +static void +servlist_networks_populate (GtkWidget *treeview, GSList *netlist) +{ + servlist_networks_populate_ (treeview, netlist, prefs.slist_fav); +} + +static void +servlist_server_row_cb (GtkTreeSelection *sel, gpointer user_data) +{ + GtkTreeModel *model; + GtkTreeIter iter; + ircserver *serv; + char *servname; + int pos; + + if (!selected_net) + return; + + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + { + gtk_tree_model_get (model, &iter, 0, &servname, -1); + serv = servlist_server_find (selected_net, servname, &pos); + g_free (servname); + if (serv) + selected_net->selected = pos; + selected_serv = serv; + } +} + +static void +servlist_start_editing (GtkTreeView *tree) +{ + GtkTreeSelection *sel; + GtkTreeModel *model; + GtkTreeIter iter; + GtkTreePath *path; + + sel = gtk_tree_view_get_selection (tree); + + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + { + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), &iter); + if (path) + { + gtk_tree_view_set_cursor (tree, path, + gtk_tree_view_get_column (tree, 0), TRUE); + gtk_tree_path_free (path); + } + } +} + +static void +servlist_addserver_cb (GtkWidget *item, GtkWidget *treeview) +{ + GtkTreeIter iter; + GtkListStore *store; + + if (!selected_net) + return; + + store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (treeview)); + servlist_server_add (selected_net, "newserver/6667"); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, "newserver/6667", 1, 1, -1); + + /* select this server */ + servlist_select_and_show (GTK_TREE_VIEW (treeview), &iter, store); + /*servlist_start_editing (GTK_TREE_VIEW (treeview));*/ + + servlist_server_row_cb (gtk_tree_view_get_selection (GTK_TREE_VIEW (networks_tree)), NULL); +} + +static void +servlist_addnet_cb (GtkWidget *item, GtkTreeView *treeview) +{ + GtkTreeIter iter; + GtkListStore *store; + ircnet *net; + + net = servlist_net_add (_("New Network"), "", TRUE); + net->encoding = strdup ("IRC (Latin/Unicode Hybrid)"); + servlist_server_add (net, "newserver/6667"); + + store = (GtkListStore *)gtk_tree_view_get_model (treeview); + gtk_list_store_prepend (store, &iter); + gtk_list_store_set (store, &iter, 0, net->name, 1, 1, -1); + + /* select this network */ + servlist_select_and_show (GTK_TREE_VIEW (networks_tree), &iter, store); + servlist_start_editing (GTK_TREE_VIEW (networks_tree)); + + servlist_network_row_cb (gtk_tree_view_get_selection (GTK_TREE_VIEW (networks_tree)), NULL); +} + +static void +servlist_deletenetwork (ircnet *net) +{ + GtkTreeSelection *sel; + GtkTreeModel *model; + GtkTreeIter iter; + + /* remove from GUI */ + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (networks_tree)); + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); + + /* remove from list */ + servlist_net_remove (net); + + /* force something to be selected */ + gtk_tree_model_get_iter_first (model, &iter); + servlist_select_and_show (GTK_TREE_VIEW (networks_tree), &iter, + GTK_LIST_STORE (model)); + servlist_network_row_cb (sel, NULL); +} + +static void +servlist_deletenetdialog_cb (GtkDialog *dialog, gint arg1, ircnet *net) +{ + gtk_widget_destroy (GTK_WIDGET (dialog)); + if (arg1 == GTK_RESPONSE_OK) + servlist_deletenetwork (net); +} + +static void +servlist_move_server (ircserver *serv, int delta) +{ + int pos; + + pos = g_slist_index (selected_net->servlist, serv); + if (pos >= 0) + { + pos += delta; + if (pos >= 0) + { + selected_net->servlist = g_slist_remove (selected_net->servlist, serv); + selected_net->servlist = g_slist_insert (selected_net->servlist, serv, pos); + servlist_servers_populate (selected_net, edit_tree); + } + } +} + +static void +servlist_move_network (ircnet *net, int delta) +{ + int pos; + + pos = g_slist_index (network_list, net); + if (pos >= 0) + { + pos += delta; + if (pos >= 0) + { + /*prefs.slist_select += delta;*/ + network_list = g_slist_remove (network_list, net); + network_list = g_slist_insert (network_list, net, pos); + servlist_networks_populate (networks_tree, network_list); + } + } +} + +static gboolean +servlist_net_keypress_cb (GtkWidget *wid, GdkEventKey *evt, gpointer tree) +{ + if (!selected_net) + return FALSE; + + if (evt->state & GDK_SHIFT_MASK) + { + if (evt->keyval == GDK_Up) + { + servlist_move_network (selected_net, -1); + } + else if (evt->keyval == GDK_Down) + { + servlist_move_network (selected_net, +1); + } + } + + return FALSE; +} + +static gboolean +servlist_serv_keypress_cb (GtkWidget *wid, GdkEventKey *evt, gpointer userdata) +{ + if (!selected_net || !selected_serv) + return FALSE; + + if (evt->state & GDK_SHIFT_MASK) + { + if (evt->keyval == GDK_Up) + { + servlist_move_server (selected_serv, -1); + } + else if (evt->keyval == GDK_Down) + { + servlist_move_server (selected_serv, +1); + } + } + + return FALSE; +} + +static gint +servlist_compare (ircnet *net1, ircnet *net2) +{ + gchar *net1_casefolded, *net2_casefolded; + int result=0; + + net1_casefolded=g_utf8_casefold(net1->name,-1), + net2_casefolded=g_utf8_casefold(net2->name,-1), + + result=g_utf8_collate(net1_casefolded,net2_casefolded); + + g_free(net1_casefolded); + g_free(net2_casefolded); + + return result; + +} + +static void +servlist_sort (GtkWidget *button, gpointer none) +{ + network_list=g_slist_sort(network_list,(GCompareFunc)servlist_compare); + servlist_networks_populate (networks_tree, network_list); +} + +static gboolean +servlist_has_selection (GtkTreeView *tree) +{ + GtkTreeSelection *sel; + GtkTreeModel *model; + GtkTreeIter iter; + + /* make sure something is selected */ + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree)); + return gtk_tree_selection_get_selected (sel, &model, &iter); +} + +static void +servlist_favor (GtkWidget *button, gpointer none) +{ + GtkTreeSelection *sel; + GtkTreeModel *model; + GtkTreeIter iter; + + if (!selected_net) + return; + + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (networks_tree)); + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + { + if (selected_net->flags & FLAG_FAVORITE) + { + gtk_list_store_set (GTK_LIST_STORE (model), &iter, 2, 400, -1); + selected_net->flags &= ~FLAG_FAVORITE; + } + else + { + gtk_list_store_set (GTK_LIST_STORE (model), &iter, 2, 800, -1); + selected_net->flags |= FLAG_FAVORITE; + } + } +} + +static void +servlist_update_from_entry (char **str, GtkWidget *entry) +{ + if (*str) + free (*str); + + if (GTK_ENTRY (entry)->text[0] == 0) + *str = NULL; + else + *str = strdup (GTK_ENTRY (entry)->text); +} + +static void +servlist_edit_update (ircnet *net) +{ + servlist_update_from_entry (&net->nick, edit_entry_nick); + servlist_update_from_entry (&net->nick2, edit_entry_nick2); + servlist_update_from_entry (&net->user, edit_entry_user); + servlist_update_from_entry (&net->real, edit_entry_real); + + servlist_update_from_entry (&net->autojoin, edit_entry_join); + servlist_update_from_entry (&net->command, edit_entry_cmd); + servlist_update_from_entry (&net->nickserv, edit_entry_nickserv); + servlist_update_from_entry (&net->pass, edit_entry_pass); +} + +static void +servlist_edit_close_cb (GtkWidget *button, gpointer userdata) +{ + if (selected_net) + servlist_edit_update (selected_net); + + gtk_widget_destroy (edit_win); + edit_win = NULL; +} + +static gint +servlist_editwin_delete_cb (GtkWidget *win, GdkEventAny *event, gpointer none) +{ + servlist_edit_close_cb (NULL, NULL); + return FALSE; +} + +static gboolean +servlist_configure_cb (GtkWindow *win, GdkEventConfigure *event, gpointer none) +{ + /* remember the window size */ + gtk_window_get_size (win, &win_width, &win_height); + return FALSE; +} + +static void +servlist_edit_cb (GtkWidget *but, gpointer none) +{ + if (!servlist_has_selection (GTK_TREE_VIEW (networks_tree))) + return; + + edit_win = servlist_open_edit (serverlist_win, selected_net); + gtkutil_set_icon (edit_win); + servlist_servers_populate (selected_net, edit_tree); + g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (edit_tree))), + "changed", G_CALLBACK (servlist_server_row_cb), NULL); + g_signal_connect (G_OBJECT (edit_win), "delete_event", + G_CALLBACK (servlist_editwin_delete_cb), 0); + g_signal_connect (G_OBJECT (edit_tree), "key_press_event", + G_CALLBACK (servlist_serv_keypress_cb), 0); + gtk_widget_show (edit_win); +} + +static void +servlist_deletenet_cb (GtkWidget *item, ircnet *net) +{ + GtkWidget *dialog; + + if (!servlist_has_selection (GTK_TREE_VIEW (networks_tree))) + return; + + net = selected_net; + if (!net) + return; + dialog = gtk_message_dialog_new (GTK_WINDOW (serverlist_win), + GTK_DIALOG_DESTROY_WITH_PARENT | + GTK_DIALOG_MODAL, + GTK_MESSAGE_QUESTION, + GTK_BUTTONS_OK_CANCEL, + _("Really remove network \"%s\" and all its servers?"), + net->name); + g_signal_connect (dialog, "response", + G_CALLBACK (servlist_deletenetdialog_cb), net); + gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_MOUSE); + gtk_widget_show (dialog); +} + +static void +servlist_deleteserver (ircserver *serv, GtkTreeModel *model) +{ + GtkTreeSelection *sel; + GtkTreeIter iter; + + /* don't remove the last server */ + if (selected_net && g_slist_length (selected_net->servlist) < 2) + return; + + /* remove from GUI */ + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (edit_tree)); + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); + + /* remove from list */ + if (selected_net) + servlist_server_remove (selected_net, serv); +} + +static void +servlist_editserverbutton_cb (GtkWidget *item, gpointer none) +{ + servlist_start_editing (GTK_TREE_VIEW (edit_tree)); +} + +static void +servlist_deleteserver_cb (GtkWidget *item, gpointer none) +{ + GtkTreeSelection *sel; + GtkTreeModel *model; + GtkTreeIter iter; + char *servname; + ircserver *serv; + int pos; + + /* find the selected item in the GUI */ + model = gtk_tree_view_get_model (GTK_TREE_VIEW (edit_tree)); + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (edit_tree)); + + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + { + gtk_tree_model_get (model, &iter, 0, &servname, -1); + serv = servlist_server_find (selected_net, servname, &pos); + g_free (servname); + if (serv) + servlist_deleteserver (serv, model); + } +} + +static ircnet * +servlist_find_selected_net (GtkTreeSelection *sel) +{ + GtkTreeModel *model; + GtkTreeIter iter; + char *netname; + int pos; + ircnet *net = NULL; + + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + { + gtk_tree_model_get (model, &iter, 0, &netname, -1); + net = servlist_net_find (netname, &pos, strcmp); + g_free (netname); + if (net) + prefs.slist_select = pos; + } + + return net; +} + +static void +servlist_network_row_cb (GtkTreeSelection *sel, gpointer user_data) +{ + ircnet *net; + + selected_net = NULL; + + net = servlist_find_selected_net (sel); + if (net) + selected_net = net; +} + +static int +servlist_savegui (void) +{ + char *sp; + + /* check for blank username, ircd will not allow this */ + if (GTK_ENTRY (entry_guser)->text[0] == 0) + return 1; + + if (GTK_ENTRY (entry_greal)->text[0] == 0) + return 1; + + strcpy (prefs.nick1, GTK_ENTRY (entry_nick1)->text); + strcpy (prefs.nick2, GTK_ENTRY (entry_nick2)->text); + strcpy (prefs.nick3, GTK_ENTRY (entry_nick3)->text); + strcpy (prefs.username, GTK_ENTRY (entry_guser)->text); + sp = strchr (prefs.username, ' '); + if (sp) + sp[0] = 0; /* spaces will break the login */ + strcpy (prefs.realname, GTK_ENTRY (entry_greal)->text); + servlist_save (); + + return 0; +} + +static gboolean +servlist_get_iter_from_name (GtkTreeModel *model, gchar *name, GtkTreeIter *iter) +{ + GtkTreePath *path = gtk_tree_path_new_from_string (name); + + if (!gtk_tree_model_get_iter (model, iter, path)) + { + gtk_tree_path_free (path); + return FALSE; + } + + gtk_tree_path_free (path); + return TRUE; +} + +static void +servlist_editchannel_cb (GtkCellRendererText *cell, gchar *name, gchar *newval, GtkTreeModel *model) +{ + GtkTreeIter iter; + static int loop_guard = FALSE; + + if (loop_guard) + return; + + if (!servlist_get_iter_from_name (model, name, &iter)) + return; + + loop_guard = TRUE; + /* delete empty item */ + if (newval[0] == 0) + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); + else + gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, newval, -1); + loop_guard = FALSE; +} + +static void +servlist_editkey_cb (GtkCellRendererText *cell, gchar *name, gchar *newval, GtkTreeModel *model) +{ + GtkTreeIter iter; + + if (!servlist_get_iter_from_name (model, name, &iter)) + return; + + gtk_list_store_set (GTK_LIST_STORE (model), &iter, 1, newval, -1); +} + +static void +servlist_addchannel (GtkWidget *tree, char *channel) +{ + GtkTreeIter iter; + GtkListStore *store; + + store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (tree)); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, channel, 1, "", 2, TRUE, -1); + + /* select this server */ + servlist_select_and_show (GTK_TREE_VIEW (tree), &iter, store); + servlist_start_editing (GTK_TREE_VIEW (tree)); +} + +static void +servlist_addchannel_cb (GtkWidget *item, GtkWidget *tree) +{ + servlist_addchannel (tree, _("#channel")); +} + +static void +servlist_deletechannel_cb (GtkWidget *item, GtkWidget *tree) +{ + GtkTreeSelection *sel; + GtkTreeModel *model; + GtkTreeIter iter; + + /* find the selected item in the GUI */ + model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree)); + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree)); + + if (gtk_tree_selection_get_selected (sel, &model, &iter)) + gtk_list_store_remove (GTK_LIST_STORE (model), &iter); +} + +static void +servlist_editchannelbutton_cb (GtkWidget *item, GtkWidget *tree) +{ + servlist_start_editing (GTK_TREE_VIEW (tree)); +} + +/* save everything from the GUI to the GtkEntry */ + +static void +servlist_autojoineditok_cb (GtkWidget *button, GtkWidget *tree) +{ + GtkTreeModel *model; + GtkTreeIter iter; + char *channel, *key; + char *autojoin; + GSList *channels = NULL, *keys = NULL; + + model = gtk_tree_view_get_model (GTK_TREE_VIEW (tree)); + + if (gtk_tree_model_get_iter_first (model, &iter)) + { + do + { + gtk_tree_model_get (model, &iter, 0, &channel, 1, &key, -1); + channels = g_slist_append (channels, channel); + if (key && key[0] == 0) + { + /* NULL out empty keys */ + g_free (key); + keys = g_slist_append (keys, NULL); + } + else + keys = g_slist_append (keys, key); + } + while (gtk_tree_model_iter_next (model, &iter)); + } + + gtk_widget_destroy (gtk_widget_get_toplevel (button)); + + autojoin = joinlist_merge (channels, keys); + if (autojoin) + { + if (edit_win && selected_net) + gtk_entry_set_text (GTK_ENTRY (edit_entry_join), autojoin); + else + { + if (fav_add_net->autojoin) + free (fav_add_net->autojoin); + fav_add_net->autojoin = strdup (autojoin); + } + g_free (autojoin); + } + + /* this does g_free too */ + joinlist_free (channels, keys); + + if (fav_add_net) + servlist_save (); +} + +void +servlist_autojoinedit (ircnet *net, char *channel, gboolean add) +{ + GtkWidget *win; + GtkWidget *scrolledwindow; + GtkTreeModel *model; + GtkListStore *store; + GtkCellRenderer *renderer; + GtkWidget *tree; + GtkWidget *table; + GtkWidget *label; + GtkWidget *label2; + GtkWidget *bbox; + GtkWidget *wid; + + GtkWidget *vbuttonbox1; + GtkWidget *buttonadd; + GtkWidget *buttonremove; + GtkWidget *buttonedit; + + char buf[128]; + char lab[128]; + GSList *channels, *keys; + GSList *clist, *klist; + GtkTreeIter iter; + + if (edit_win && selected_net) + /* update net->autojoin */ + servlist_edit_update (selected_net); + + win = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_container_set_border_width (GTK_CONTAINER (win), 4); + gtk_window_set_title (GTK_WINDOW (win), _("XChat: Favorite Channels (Auto-Join List)")); + gtk_window_set_default_size (GTK_WINDOW (win), 354, 256); + gtk_window_set_position (GTK_WINDOW (win), GTK_WIN_POS_MOUSE); + if (edit_win) + gtk_window_set_transient_for (GTK_WINDOW (win), GTK_WINDOW (edit_win)); + gtk_window_set_modal (GTK_WINDOW (win), TRUE); + gtk_window_set_type_hint (GTK_WINDOW (win), GDK_WINDOW_TYPE_HINT_DIALOG); + gtk_window_set_role (GTK_WINDOW (win), "editserv"); + + table = gtk_table_new (1, 1, FALSE); + gtk_container_add (GTK_CONTAINER (win), table); + gtk_widget_show (table); + + snprintf (buf, sizeof (buf), _("These channels will be joined whenever you connect to %s."), net->name); + label = gtk_label_new (buf); + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER); + gtk_table_attach (GTK_TABLE (table), label, 0, 2, 0, 1, GTK_FILL|GTK_EXPAND, GTK_FILL, 3, 3); + gtk_widget_show (label); + + label2 = gtk_label_new (""); + gtk_label_set_justify (GTK_LABEL (label2), GTK_JUSTIFY_CENTER); + gtk_table_attach (GTK_TABLE (table), label2, 0, 2, 1, 2, GTK_FILL, 0, 3, 3); + gtk_widget_show (label2); + + scrolledwindow = gtk_scrolled_window_new (NULL, NULL); + gtk_table_attach (GTK_TABLE (table), scrolledwindow, 0, 1, 2, 3, GTK_FILL|GTK_EXPAND, GTK_FILL|GTK_EXPAND, 0, 0); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow), + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow), + GTK_SHADOW_IN); + gtk_widget_show (scrolledwindow); + + store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN); + model = GTK_TREE_MODEL (store); + + tree = gtk_tree_view_new_with_model (model); + g_object_unref (model); + gtk_container_add (GTK_CONTAINER (scrolledwindow), tree); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tree), TRUE); + gtk_widget_show (tree); + + renderer = gtk_cell_renderer_text_new (); + g_signal_connect (G_OBJECT (renderer), "edited", + G_CALLBACK (servlist_editchannel_cb), model); + gtk_tree_view_insert_column_with_attributes ( + GTK_TREE_VIEW (tree), -1, + _("Channel"), renderer, + "text", 0, + "editable", 2, + NULL); + + renderer = gtk_cell_renderer_text_new (); + g_signal_connect (G_OBJECT (renderer), "edited", + G_CALLBACK (servlist_editkey_cb), model); + gtk_tree_view_insert_column_with_attributes ( + GTK_TREE_VIEW (tree), -1, + _("Key (Password)"), renderer, + "text", 1, + "editable", 2, + NULL); + + gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (tree), 0), TRUE); + gtk_tree_view_column_set_expand (gtk_tree_view_get_column (GTK_TREE_VIEW (tree), 1), TRUE); + + gtk_tree_sortable_set_sort_column_id ((GtkTreeSortable *)model, 0, GTK_SORT_ASCENDING); + + /* ===BUTTONS=== */ + vbuttonbox1 = gtk_vbutton_box_new (); + gtk_box_set_spacing (GTK_BOX (vbuttonbox1), 3); + gtk_button_box_set_layout (GTK_BUTTON_BOX (vbuttonbox1), GTK_BUTTONBOX_START); + gtk_widget_show (vbuttonbox1); + gtk_table_attach (GTK_TABLE (table), vbuttonbox1, 1, 2, 2, 3, GTK_FILL, GTK_FILL, 3, 0); + + buttonadd = gtk_button_new_from_stock ("gtk-add"); + g_signal_connect (G_OBJECT (buttonadd), "clicked", + G_CALLBACK (servlist_addchannel_cb), tree); + gtk_widget_show (buttonadd); + gtk_container_add (GTK_CONTAINER (vbuttonbox1), buttonadd); + GTK_WIDGET_SET_FLAGS (buttonadd, GTK_CAN_DEFAULT); + + buttonremove = gtk_button_new_from_stock ("gtk-remove"); + g_signal_connect (G_OBJECT (buttonremove), "clicked", + G_CALLBACK (servlist_deletechannel_cb), tree); + gtk_widget_show (buttonremove); + gtk_container_add (GTK_CONTAINER (vbuttonbox1), buttonremove); + GTK_WIDGET_SET_FLAGS (buttonremove, GTK_CAN_DEFAULT); + + buttonedit = gtk_button_new_with_mnemonic (_("_Edit")); + g_signal_connect (G_OBJECT (buttonedit), "clicked", + G_CALLBACK (servlist_editchannelbutton_cb), tree); + gtk_widget_show (buttonedit); + gtk_container_add (GTK_CONTAINER (vbuttonbox1), buttonedit); + GTK_WIDGET_SET_FLAGS (buttonedit, GTK_CAN_DEFAULT); + + bbox = gtk_hbutton_box_new (); + gtk_button_box_set_layout (GTK_BUTTON_BOX (bbox), GTK_BUTTONBOX_END); + gtk_box_set_spacing (GTK_BOX (bbox), 4); + gtk_table_attach (GTK_TABLE (table), bbox, 0, 1, 3, 4, GTK_FILL|GTK_EXPAND, GTK_FILL, 0, 4); + gtk_widget_show (bbox); + + wid = gtk_button_new_from_stock (GTK_STOCK_CANCEL); + g_signal_connect (G_OBJECT (wid), "clicked", G_CALLBACK (gtkutil_destroy), win); + gtk_container_add (GTK_CONTAINER (bbox), wid); + gtk_widget_show (wid); + + wid = gtk_button_new_from_stock (GTK_STOCK_OK); + g_signal_connect (G_OBJECT (wid), "clicked", G_CALLBACK (servlist_autojoineditok_cb), tree); + gtk_container_add (GTK_CONTAINER (bbox), wid); + gtk_widget_show (wid); + gtk_widget_grab_focus (wid); + /* =========== */ + + if (net->autojoin) + { + joinlist_split (net->autojoin, &channels, &keys); + + clist = channels; + klist = keys; + + while (clist) + { + if (channel && !add && !rfc_casecmp (channel, clist->data)) + { + snprintf (buf, sizeof (buf), _("%s has been removed."), channel); + snprintf (lab, sizeof (lab), "<span foreground=\"#2222DD\">%s</span>", buf); + gtk_label_set_markup (GTK_LABEL (label2), lab); + } + else + { + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, 0, clist->data, 1, klist->data, 2, TRUE, -1); + } + + klist = klist->next; + clist = clist->next; + } + + joinlist_free (channels, keys); + } + + if (channel && add) + { + servlist_addchannel (tree, channel); + snprintf (buf, sizeof (buf), _("%s has been added."), channel); + snprintf (lab, sizeof (lab), "<span foreground=\"#2222DD\">%s</span>", buf); + gtk_label_set_markup (GTK_LABEL (label2), lab); + } + + fav_add_net = net; + + gtk_widget_show (win); +} + +static void +servlist_autojoinedit_cb (GtkWidget *button, ircnet *net) +{ + servlist_autojoinedit (net, NULL, FALSE); +} + +static void +servlist_connect_cb (GtkWidget *button, gpointer userdata) +{ + if (!selected_net) + return; + + if (servlist_savegui () != 0) + { + fe_message (_("User name and Real name cannot be left blank."), FE_MSG_ERROR); + return; + } + + if (!is_session (servlist_sess)) + servlist_sess = NULL; /* open a new one */ + + { + GSList *list; + session *sess; + session *chosen = servlist_sess; + + servlist_sess = NULL; /* open a new one */ + + for (list = sess_list; list; list = list->next) + { + sess = list->data; + if (sess->server->network == selected_net) + { + servlist_sess = sess; + if (sess->server->connected) + servlist_sess = NULL; /* open a new one */ + break; + } + } + + /* use the chosen one, if it's empty */ + if (!servlist_sess && + chosen && + !chosen->server->connected && + chosen->server->server_session->channel[0] == 0) + { + servlist_sess = chosen; + } + } + + servlist_connect (servlist_sess, selected_net, TRUE); + + gtk_widget_destroy (serverlist_win); + serverlist_win = NULL; + selected_net = NULL; +} + +static void +servlist_celledit_cb (GtkCellRendererText *cell, gchar *arg1, gchar *arg2, + gpointer user_data) +{ + GtkTreeModel *model = (GtkTreeModel *)user_data; + GtkTreeIter iter; + GtkTreePath *path; + char *netname; + ircnet *net; + + if (!arg1 || !arg2) + return; + + path = gtk_tree_path_new_from_string (arg1); + if (!path) + return; + + if (!gtk_tree_model_get_iter (model, &iter, path)) + { + gtk_tree_path_free (path); + return; + } + gtk_tree_model_get (model, &iter, 0, &netname, -1); + + net = servlist_net_find (netname, NULL, strcmp); + g_free (netname); + if (net) + { + /* delete empty item */ + if (arg2[0] == 0) + { + servlist_deletenetwork (net); + gtk_tree_path_free (path); + return; + } + + netname = net->name; + net->name = strdup (arg2); + gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, net->name, -1); + free (netname); + } + + gtk_tree_path_free (path); +} + +static void +servlist_check_cb (GtkWidget *but, gpointer num_p) +{ + int num = GPOINTER_TO_INT (num_p); + + if (!selected_net) + return; + + if ((1 << num) == FLAG_CYCLE || (1 << num) == FLAG_USE_PROXY) + { + /* these ones are reversed, so it's compat with 2.0.x */ + if (GTK_TOGGLE_BUTTON (but)->active) + selected_net->flags &= ~(1 << num); + else + selected_net->flags |= (1 << num); + } else + { + if (GTK_TOGGLE_BUTTON (but)->active) + selected_net->flags |= (1 << num); + else + selected_net->flags &= ~(1 << num); + } + + if ((1 << num) == FLAG_USE_GLOBAL) + { + if (GTK_TOGGLE_BUTTON (but)->active) + { + gtk_widget_hide (edit_label_nick); + gtk_widget_hide (edit_entry_nick); + + gtk_widget_hide (edit_label_nick2); + gtk_widget_hide (edit_entry_nick2); + + gtk_widget_hide (edit_label_user); + gtk_widget_hide (edit_entry_user); + + gtk_widget_hide (edit_label_real); + gtk_widget_hide (edit_entry_real); + } else + { + gtk_widget_show (edit_label_nick); + gtk_widget_show (edit_entry_nick); + + gtk_widget_show (edit_label_nick2); + gtk_widget_show (edit_entry_nick2); + + gtk_widget_show (edit_label_user); + gtk_widget_show (edit_entry_user); + + gtk_widget_show (edit_label_real); + gtk_widget_show (edit_entry_real); + } + } +} + +static GtkWidget * +servlist_create_check (int num, int state, GtkWidget *table, int row, int col, char *labeltext) +{ + GtkWidget *but; + + but = gtk_check_button_new_with_label (labeltext); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (but), state); + g_signal_connect (G_OBJECT (but), "toggled", + G_CALLBACK (servlist_check_cb), GINT_TO_POINTER (num)); + gtk_table_attach (GTK_TABLE (table), but, col, col+2, row, row+1, + GTK_FILL|GTK_EXPAND, 0, 0, 0); + gtk_widget_show (but); + + return but; +} + +static GtkWidget * +servlist_create_entry (GtkWidget *table, char *labeltext, int row, + char *def, GtkWidget **label_ret, char *tip) +{ + GtkWidget *label, *entry; + + label = gtk_label_new_with_mnemonic (labeltext); + if (label_ret) + *label_ret = label; + gtk_widget_show (label); + gtk_table_attach (GTK_TABLE (table), label, 1, 2, row, row+1, + GTK_FILL, 0, 0, 0); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + + entry = gtk_entry_new (); + add_tip (entry, tip); + gtk_widget_show (entry); + gtk_entry_set_text (GTK_ENTRY (entry), def ? def : ""); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), entry); + + if (row == 15) /* for "Channels to Join:" */ + { + GtkWidget *button, *box; + + box = gtk_hbox_new (0, 0); + button = gtk_button_new_with_label ("..."); + g_signal_connect (G_OBJECT (button), "clicked", + G_CALLBACK (servlist_autojoinedit_cb), selected_net); + + gtk_box_pack_start (GTK_BOX (box), entry, TRUE, TRUE, 0); + gtk_box_pack_end (GTK_BOX (box), button, 0, 0, 0); + gtk_widget_show_all (box); + + gtk_table_attach (GTK_TABLE (table), box, 2, 3, row, row+1, + GTK_FILL|GTK_EXPAND, 0, 0, 0); + } + else + { + gtk_table_attach (GTK_TABLE (table), entry, 2, 3, row, row+1, + GTK_FILL|GTK_EXPAND, 0, 0, 0); + } + + return entry; +} + +static gint +servlist_delete_cb (GtkWidget *win, GdkEventAny *event, gpointer userdata) +{ + servlist_savegui (); + serverlist_win = NULL; + selected_net = NULL; + + if (sess_list == NULL) + xchat_exit (); + + return FALSE; +} + +static void +servlist_close_cb (GtkWidget *button, gpointer userdata) +{ + servlist_savegui (); + gtk_widget_destroy (serverlist_win); + serverlist_win = NULL; + selected_net = NULL; + + if (sess_list == NULL) + xchat_exit (); +} + +/* convert "host:port" format to "host/port" */ + +static char * +servlist_sanitize_hostname (char *host) +{ + char *ret, *c, *e; + + ret = strdup (host); + + c = strchr (ret, ':'); + e = strrchr (ret, ':'); + + /* if only one colon exists it's probably not IPv6 */ + if (c && c == e) + *c = '/'; + + return ret; +} + +static void +servlist_editserver_cb (GtkCellRendererText *cell, gchar *arg1, gchar *arg2, + gpointer user_data) +{ + GtkTreeModel *model = (GtkTreeModel *)user_data; + GtkTreeIter iter; + GtkTreePath *path; + char *servname; + ircserver *serv; + + if (!selected_net) + return; + + path = gtk_tree_path_new_from_string (arg1); + + if (!gtk_tree_model_get_iter (model, &iter, path)) + { + gtk_tree_path_free (path); + return; + } + + gtk_tree_model_get (model, &iter, 0, &servname, -1); + serv = servlist_server_find (selected_net, servname, NULL); + g_free (servname); + + if (serv) + { + /* delete empty item */ + if (arg2[0] == 0) + { + servlist_deleteserver (serv, model); + gtk_tree_path_free (path); + return; + } + + servname = serv->hostname; + serv->hostname = servlist_sanitize_hostname (arg2); + gtk_list_store_set (GTK_LIST_STORE (model), &iter, 0, serv->hostname, -1); + free (servname); + } + + gtk_tree_path_free (path); +} + +static void +servlist_combo_cb (GtkEntry *entry, gpointer userdata) +{ + if (!selected_net) + return; + + if (!ignore_changed) + { + if (selected_net->encoding) + free (selected_net->encoding); + selected_net->encoding = strdup (entry->text); + } +} + +static GtkWidget * +servlist_create_charsetcombo (void) +{ + GtkWidget *cb; + int i; + + cb = gtk_combo_box_entry_new_text (); + gtk_combo_box_append_text (GTK_COMBO_BOX (cb), "System default"); + i = 0; + while (pages[i]) + { + gtk_combo_box_append_text (GTK_COMBO_BOX (cb), (char *)pages[i]); + i++; + } + g_signal_connect (G_OBJECT (GTK_BIN (cb)->child), "changed", + G_CALLBACK (servlist_combo_cb), NULL); + + return cb; +} + +static void +no_servlist (GtkWidget * igad, gpointer serv) +{ + if (GTK_TOGGLE_BUTTON (igad)->active) + prefs.slist_skip = TRUE; + else + prefs.slist_skip = FALSE; +} + +static void +fav_servlist (GtkWidget * igad, gpointer serv) +{ + if (GTK_TOGGLE_BUTTON (igad)->active) + prefs.slist_fav = TRUE; + else + prefs.slist_fav = FALSE; + + servlist_networks_populate (networks_tree, network_list); +} + +static GtkWidget * +bold_label (char *text) +{ + char buf[128]; + GtkWidget *label; + + snprintf (buf, sizeof (buf), "<b>%s</b>", text); + label = gtk_label_new (buf); + gtk_label_set_use_markup (GTK_LABEL (label), TRUE); + gtk_misc_set_alignment (GTK_MISC (label), 0, 0.5); + gtk_widget_show (label); + + return label; +} + +static GtkWidget * +servlist_open_edit (GtkWidget *parent, ircnet *net) +{ + GtkWidget *editwindow; + GtkWidget *vbox5; + GtkWidget *table3; + GtkWidget *label17; + GtkWidget *label16; + GtkWidget *label21; + GtkWidget *label34; + GtkWidget *comboboxentry_charset; + GtkWidget *hbox1; + GtkWidget *scrolledwindow2; + GtkWidget *treeview_servers; + GtkWidget *vbuttonbox1; + GtkWidget *buttonadd; + GtkWidget *buttonremove; + GtkWidget *buttonedit; + GtkWidget *hseparator2; + GtkWidget *hbuttonbox4; + GtkWidget *button10; + GtkWidget *check; + GtkTreeModel *model; + GtkListStore *store; + GtkCellRenderer *renderer; + char buf[128]; + char buf2[128 + 8]; + + editwindow = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_container_set_border_width (GTK_CONTAINER (editwindow), 4); + snprintf (buf, sizeof (buf), _("XChat: Edit %s"), net->name); + gtk_window_set_title (GTK_WINDOW (editwindow), buf); + gtk_window_set_default_size (GTK_WINDOW (editwindow), 354, 0); + gtk_window_set_position (GTK_WINDOW (editwindow), GTK_WIN_POS_MOUSE); + gtk_window_set_transient_for (GTK_WINDOW (editwindow), GTK_WINDOW (parent)); + gtk_window_set_modal (GTK_WINDOW (editwindow), TRUE); + gtk_window_set_type_hint (GTK_WINDOW (editwindow), GDK_WINDOW_TYPE_HINT_DIALOG); + gtk_window_set_role (GTK_WINDOW (editwindow), "editserv"); + + vbox5 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox5); + gtk_container_add (GTK_CONTAINER (editwindow), vbox5); + + table3 = gtk_table_new (17, 3, FALSE); + gtk_widget_show (table3); + gtk_box_pack_start (GTK_BOX (vbox5), table3, TRUE, TRUE, 0); + gtk_table_set_row_spacings (GTK_TABLE (table3), 2); + gtk_table_set_col_spacings (GTK_TABLE (table3), 8); + + snprintf (buf, sizeof (buf), _("Servers for %s"), net->name); + snprintf (buf2, sizeof (buf2), "<b>%s</b>", buf); + label16 = gtk_label_new (buf2); + gtk_widget_show (label16); + gtk_table_attach (GTK_TABLE (table3), label16, 0, 3, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 3); + gtk_label_set_use_markup (GTK_LABEL (label16), TRUE); + gtk_misc_set_alignment (GTK_MISC (label16), 0, 0.5); + + check = servlist_create_check (0, !(net->flags & FLAG_CYCLE), table3, + 2, 1, _("Connect to selected server only")); + add_tip (check, _("Don't cycle through all the servers when the connection fails.")); + + label17 = bold_label (_("Your Details")); + gtk_table_attach (GTK_TABLE (table3), label17, 0, 3, 3, 4, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 3); + + servlist_create_check (1, net->flags & FLAG_USE_GLOBAL, table3, + 4, 1, _("Use global user information")); + + edit_entry_nick = + servlist_create_entry (table3, _("_Nick name:"), 5, net->nick, + &edit_label_nick, 0); + + edit_entry_nick2 = + servlist_create_entry (table3, _("Second choice:"), 6, net->nick2, + &edit_label_nick2, 0); + + edit_entry_user = + servlist_create_entry (table3, _("_User name:"), 7, net->user, + &edit_label_user, 0); + + edit_entry_real = + servlist_create_entry (table3, _("Rea_l name:"), 8, net->real, + &edit_label_real, 0); + + label21 = bold_label (_("Connecting")); + gtk_table_attach (GTK_TABLE (table3), label21, 0, 3, 9, 10, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 3); + + servlist_create_check (3, net->flags & FLAG_AUTO_CONNECT, table3, + 11, 1, _("Auto connect to this network at startup")); + servlist_create_check (4, !(net->flags & FLAG_USE_PROXY), table3, + 12, 1, _("Bypass proxy server")); + check = servlist_create_check (2, net->flags & FLAG_USE_SSL, table3, + 13, 1, _("Use SSL for all the servers on this network")); +#ifndef USE_OPENSSL + gtk_widget_set_sensitive (check, FALSE); +#endif + check = servlist_create_check (5, net->flags & FLAG_ALLOW_INVALID, table3, + 14, 1, _("Accept invalid SSL certificate")); +#ifndef USE_OPENSSL + gtk_widget_set_sensitive (check, FALSE); +#endif + + edit_entry_join = + servlist_create_entry (table3, _("_Favorite channels:"), 15, + net->autojoin, 0, + _("Channels to join, separated by commas, but not spaces!")); + + edit_entry_cmd = + servlist_create_entry (table3, _("Connect command:"), 16, + net->command, 0, + _("Extra command to execute after connecting. If you need more than one, set this to LOAD -e <filename>, where <filename> is a text-file full of commands to execute.")); + + edit_entry_nickserv = + servlist_create_entry (table3, _("Nickserv password:"), 17, + net->nickserv, 0, + _("If your nickname requires a password, enter it here. Not all IRC networks support this.")); + gtk_entry_set_visibility (GTK_ENTRY (edit_entry_nickserv), FALSE); + + edit_entry_pass = + servlist_create_entry (table3, _("Server password:"), 18, + net->pass, 0, + _("Password for the server, if in doubt, leave blank.")); + gtk_entry_set_visibility (GTK_ENTRY (edit_entry_pass), FALSE); + + label34 = gtk_label_new (_("Character set:")); + gtk_widget_show (label34); + gtk_table_attach (GTK_TABLE (table3), label34, 1, 2, 19, 20, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label34), 0, 0.5); + + comboboxentry_charset = servlist_create_charsetcombo (); + ignore_changed = TRUE; + gtk_entry_set_text (GTK_ENTRY (GTK_BIN (comboboxentry_charset)->child), net->encoding ? net->encoding : "System default"); + ignore_changed = FALSE; + gtk_widget_show (comboboxentry_charset); + gtk_table_attach (GTK_TABLE (table3), comboboxentry_charset, 2, 3, 19, 20, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + + hbox1 = gtk_hbox_new (FALSE, 0); + gtk_widget_show (hbox1); + gtk_table_attach (GTK_TABLE (table3), hbox1, 1, 3, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 0); + + scrolledwindow2 = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (scrolledwindow2); + gtk_box_pack_start (GTK_BOX (hbox1), scrolledwindow2, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow2), + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow2), + GTK_SHADOW_IN); + + store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_BOOLEAN); + model = GTK_TREE_MODEL (store); + + edit_tree = treeview_servers = gtk_tree_view_new_with_model (model); + g_object_unref (model); + gtk_widget_show (treeview_servers); + gtk_container_add (GTK_CONTAINER (scrolledwindow2), treeview_servers); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview_servers), + FALSE); + + renderer = gtk_cell_renderer_text_new (); + g_signal_connect (G_OBJECT (renderer), "edited", + G_CALLBACK (servlist_editserver_cb), model); + gtk_tree_view_insert_column_with_attributes ( + GTK_TREE_VIEW (treeview_servers), -1, + 0, renderer, + "text", 0, + "editable", 1, + NULL); + + vbuttonbox1 = gtk_vbutton_box_new (); + gtk_box_set_spacing (GTK_BOX (vbuttonbox1), 3); + gtk_button_box_set_layout (GTK_BUTTON_BOX (vbuttonbox1), GTK_BUTTONBOX_START); + gtk_widget_show (vbuttonbox1); + gtk_box_pack_start (GTK_BOX (hbox1), vbuttonbox1, FALSE, FALSE, 3); + + buttonadd = gtk_button_new_from_stock ("gtk-add"); + g_signal_connect (G_OBJECT (buttonadd), "clicked", + G_CALLBACK (servlist_addserver_cb), edit_tree); + gtk_widget_show (buttonadd); + gtk_container_add (GTK_CONTAINER (vbuttonbox1), buttonadd); + GTK_WIDGET_SET_FLAGS (buttonadd, GTK_CAN_DEFAULT); + + buttonremove = gtk_button_new_from_stock ("gtk-remove"); + g_signal_connect (G_OBJECT (buttonremove), "clicked", + G_CALLBACK (servlist_deleteserver_cb), NULL); + gtk_widget_show (buttonremove); + gtk_container_add (GTK_CONTAINER (vbuttonbox1), buttonremove); + GTK_WIDGET_SET_FLAGS (buttonremove, GTK_CAN_DEFAULT); + + buttonedit = gtk_button_new_with_mnemonic (_("_Edit")); + g_signal_connect (G_OBJECT (buttonedit), "clicked", + G_CALLBACK (servlist_editserverbutton_cb), NULL); + gtk_widget_show (buttonedit); + gtk_container_add (GTK_CONTAINER (vbuttonbox1), buttonedit); + GTK_WIDGET_SET_FLAGS (buttonedit, GTK_CAN_DEFAULT); + + hseparator2 = gtk_hseparator_new (); + gtk_widget_show (hseparator2); + gtk_box_pack_start (GTK_BOX (vbox5), hseparator2, FALSE, FALSE, 8); + + hbuttonbox4 = gtk_hbutton_box_new (); + gtk_widget_show (hbuttonbox4); + gtk_box_pack_start (GTK_BOX (vbox5), hbuttonbox4, FALSE, FALSE, 0); + gtk_button_box_set_layout (GTK_BUTTON_BOX (hbuttonbox4), + GTK_BUTTONBOX_END); + + button10 = gtk_button_new_from_stock ("gtk-close"); + g_signal_connect (G_OBJECT (button10), "clicked", + G_CALLBACK (servlist_edit_close_cb), 0); + gtk_widget_show (button10); + gtk_container_add (GTK_CONTAINER (hbuttonbox4), button10); + GTK_WIDGET_SET_FLAGS (button10, GTK_CAN_DEFAULT); + + if (net->flags & FLAG_USE_GLOBAL) + { + gtk_widget_hide (edit_label_nick); + gtk_widget_hide (edit_entry_nick); + + gtk_widget_hide (edit_label_nick2); + gtk_widget_hide (edit_entry_nick2); + + gtk_widget_hide (edit_label_user); + gtk_widget_hide (edit_entry_user); + + gtk_widget_hide (edit_label_real); + gtk_widget_hide (edit_entry_real); + } + + gtk_widget_grab_focus (button10); + gtk_widget_grab_default (button10); + + return editwindow; +} + +static GtkWidget * +servlist_open_networks (void) +{ + GtkWidget *servlist; + GtkWidget *vbox1; + GtkWidget *label2; + GtkWidget *table1; + GtkWidget *label3; + GtkWidget *label4; + GtkWidget *label5; + GtkWidget *label6; + GtkWidget *label7; + GtkWidget *entry1; + GtkWidget *entry2; + GtkWidget *entry3; + GtkWidget *entry4; + GtkWidget *entry5; + GtkWidget *vbox2; + GtkWidget *label1; + GtkWidget *table4; + GtkWidget *scrolledwindow3; + GtkWidget *treeview_networks; + GtkWidget *checkbutton_skip; + GtkWidget *checkbutton_fav; + GtkWidget *hbox; + GtkWidget *vbuttonbox2; + GtkWidget *button_add; + GtkWidget *button_remove; + GtkWidget *button_edit; + GtkWidget *button_sort; + GtkWidget *hseparator1; + GtkWidget *hbuttonbox1; + GtkWidget *button_connect; + GtkWidget *button_close; + GtkTreeModel *model; + GtkListStore *store; + GtkCellRenderer *renderer; + + servlist = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_container_set_border_width (GTK_CONTAINER (servlist), 4); + gtk_window_set_title (GTK_WINDOW (servlist), _("XChat: Network List")); + gtk_window_set_default_size (GTK_WINDOW (servlist), win_width, win_height); + gtk_window_set_position (GTK_WINDOW (servlist), GTK_WIN_POS_MOUSE); + gtk_window_set_role (GTK_WINDOW (servlist), "servlist"); + gtk_window_set_type_hint (GTK_WINDOW (servlist), GDK_WINDOW_TYPE_HINT_DIALOG); + if (current_sess) + gtk_window_set_transient_for (GTK_WINDOW (servlist), GTK_WINDOW (current_sess->gui->window)); + + vbox1 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox1); + gtk_container_add (GTK_CONTAINER (servlist), vbox1); + + label2 = bold_label (_("User Information")); + gtk_box_pack_start (GTK_BOX (vbox1), label2, FALSE, FALSE, 0); + + table1 = gtk_table_new (5, 2, FALSE); + gtk_widget_show (table1); + gtk_box_pack_start (GTK_BOX (vbox1), table1, FALSE, FALSE, 0); + gtk_container_set_border_width (GTK_CONTAINER (table1), 8); + gtk_table_set_row_spacings (GTK_TABLE (table1), 2); + gtk_table_set_col_spacings (GTK_TABLE (table1), 4); + + label3 = gtk_label_new_with_mnemonic (_("_Nick name:")); + gtk_widget_show (label3); + gtk_table_attach (GTK_TABLE (table1), label3, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label3), 0, 0.5); + + label4 = gtk_label_new (_("Second choice:")); + gtk_widget_show (label4); + gtk_table_attach (GTK_TABLE (table1), label4, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label4), 0, 0.5); + + label5 = gtk_label_new (_("Third choice:")); + gtk_widget_show (label5); + gtk_table_attach (GTK_TABLE (table1), label5, 0, 1, 2, 3, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label5), 0, 0.5); + + label6 = gtk_label_new_with_mnemonic (_("_User name:")); + gtk_widget_show (label6); + gtk_table_attach (GTK_TABLE (table1), label6, 0, 1, 3, 4, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label6), 0, 0.5); + + label7 = gtk_label_new_with_mnemonic (_("Rea_l name:")); + gtk_widget_show (label7); + gtk_table_attach (GTK_TABLE (table1), label7, 0, 1, 4, 5, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label7), 0, 0.5); + + entry_nick1 = entry1 = gtk_entry_new (); + gtk_entry_set_text (GTK_ENTRY (entry1), prefs.nick1); + gtk_widget_show (entry1); + gtk_table_attach (GTK_TABLE (table1), entry1, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + entry_nick2 = entry2 = gtk_entry_new (); + gtk_entry_set_text (GTK_ENTRY (entry2), prefs.nick2); + gtk_widget_show (entry2); + gtk_table_attach (GTK_TABLE (table1), entry2, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + entry_nick3 = entry3 = gtk_entry_new (); + gtk_entry_set_text (GTK_ENTRY (entry3), prefs.nick3); + gtk_widget_show (entry3); + gtk_table_attach (GTK_TABLE (table1), entry3, 1, 2, 2, 3, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + entry_guser = entry4 = gtk_entry_new (); + gtk_entry_set_text (GTK_ENTRY (entry4), prefs.username); + gtk_widget_show (entry4); + gtk_table_attach (GTK_TABLE (table1), entry4, 1, 2, 3, 4, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + entry_greal = entry5 = gtk_entry_new (); + gtk_entry_set_text (GTK_ENTRY (entry5), prefs.realname); + gtk_widget_show (entry5); + gtk_table_attach (GTK_TABLE (table1), entry5, 1, 2, 4, 5, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + vbox2 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox2); + gtk_box_pack_start (GTK_BOX (vbox1), vbox2, TRUE, TRUE, 0); + + label1 = bold_label (_("Networks")); + gtk_box_pack_start (GTK_BOX (vbox2), label1, FALSE, FALSE, 0); + + table4 = gtk_table_new (2, 2, FALSE); + gtk_widget_show (table4); + gtk_box_pack_start (GTK_BOX (vbox2), table4, TRUE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (table4), 8); + gtk_table_set_row_spacings (GTK_TABLE (table4), 2); + gtk_table_set_col_spacings (GTK_TABLE (table4), 3); + + scrolledwindow3 = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (scrolledwindow3); + gtk_table_attach (GTK_TABLE (table4), scrolledwindow3, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), 0, 0); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow3), + GTK_POLICY_NEVER, GTK_POLICY_ALWAYS); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow3), + GTK_SHADOW_IN); + + store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_INT); + model = GTK_TREE_MODEL (store); + + networks_tree = treeview_networks = gtk_tree_view_new_with_model (model); + g_object_unref (model); + gtk_widget_show (treeview_networks); + gtk_container_add (GTK_CONTAINER (scrolledwindow3), treeview_networks); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview_networks), + FALSE); + + renderer = gtk_cell_renderer_text_new (); + g_signal_connect (G_OBJECT (renderer), "edited", + G_CALLBACK (servlist_celledit_cb), model); + gtk_tree_view_insert_column_with_attributes ( + GTK_TREE_VIEW (treeview_networks), -1, + 0, renderer, + "text", 0, + "editable", 1, + "weight", 2, + NULL); + + hbox = gtk_hbox_new (0, FALSE); + gtk_table_attach (GTK_TABLE (table4), hbox, 0, 2, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_widget_show (hbox); + + checkbutton_skip = + gtk_check_button_new_with_mnemonic (_("Skip network list on startup")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbutton_skip), + prefs.slist_skip); + gtk_container_add (GTK_CONTAINER (hbox), checkbutton_skip); + g_signal_connect (G_OBJECT (checkbutton_skip), "toggled", + G_CALLBACK (no_servlist), 0); + gtk_widget_show (checkbutton_skip); + + checkbutton_fav = + gtk_check_button_new_with_mnemonic (_("Show favorites only")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (checkbutton_fav), + prefs.slist_fav); + gtk_container_add (GTK_CONTAINER (hbox), checkbutton_fav); + g_signal_connect (G_OBJECT (checkbutton_fav), "toggled", + G_CALLBACK (fav_servlist), 0); + gtk_widget_show (checkbutton_fav); + + vbuttonbox2 = gtk_vbutton_box_new (); + gtk_box_set_spacing (GTK_BOX (vbuttonbox2), 3); + gtk_button_box_set_layout (GTK_BUTTON_BOX (vbuttonbox2), GTK_BUTTONBOX_START); + gtk_widget_show (vbuttonbox2); + gtk_table_attach (GTK_TABLE (table4), vbuttonbox2, 1, 2, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (GTK_FILL), 0, 0); + + button_add = gtk_button_new_from_stock ("gtk-add"); + g_signal_connect (G_OBJECT (button_add), "clicked", + G_CALLBACK (servlist_addnet_cb), networks_tree); + gtk_widget_show (button_add); + gtk_container_add (GTK_CONTAINER (vbuttonbox2), button_add); + GTK_WIDGET_SET_FLAGS (button_add, GTK_CAN_DEFAULT); + + button_remove = gtk_button_new_from_stock ("gtk-remove"); + g_signal_connect (G_OBJECT (button_remove), "clicked", + G_CALLBACK (servlist_deletenet_cb), 0); + gtk_widget_show (button_remove); + gtk_container_add (GTK_CONTAINER (vbuttonbox2), button_remove); + GTK_WIDGET_SET_FLAGS (button_remove, GTK_CAN_DEFAULT); + + button_edit = gtk_button_new_with_mnemonic (_("_Edit...")); + g_signal_connect (G_OBJECT (button_edit), "clicked", + G_CALLBACK (servlist_edit_cb), 0); + gtk_widget_show (button_edit); + gtk_container_add (GTK_CONTAINER (vbuttonbox2), button_edit); + GTK_WIDGET_SET_FLAGS (button_edit, GTK_CAN_DEFAULT); + + button_sort = gtk_button_new_with_mnemonic (_("_Sort")); + add_tip (button_sort, _("Sorts the network list in alphabetical order. " + "Use SHIFT-UP and SHIFT-DOWN keys to move a row.")); + g_signal_connect (G_OBJECT (button_sort), "clicked", + G_CALLBACK (servlist_sort), 0); + gtk_widget_show (button_sort); + gtk_container_add (GTK_CONTAINER (vbuttonbox2), button_sort); + GTK_WIDGET_SET_FLAGS (button_sort, GTK_CAN_DEFAULT); + + button_sort = gtk_button_new_with_mnemonic (_("_Favor")); + add_tip (button_sort, _("Mark or unmark this network as a favorite.")); + g_signal_connect (G_OBJECT (button_sort), "clicked", + G_CALLBACK (servlist_favor), 0); + gtk_widget_show (button_sort); + gtk_container_add (GTK_CONTAINER (vbuttonbox2), button_sort); + GTK_WIDGET_SET_FLAGS (button_sort, GTK_CAN_DEFAULT); + + hseparator1 = gtk_hseparator_new (); + gtk_widget_show (hseparator1); + gtk_box_pack_start (GTK_BOX (vbox1), hseparator1, FALSE, TRUE, 4); + + hbuttonbox1 = gtk_hbutton_box_new (); + gtk_widget_show (hbuttonbox1); + gtk_box_pack_start (GTK_BOX (vbox1), hbuttonbox1, FALSE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (hbuttonbox1), 8); + + button_close = gtk_button_new_from_stock ("gtk-close"); + gtk_widget_show (button_close); + g_signal_connect (G_OBJECT (button_close), "clicked", + G_CALLBACK (servlist_close_cb), 0); + gtk_container_add (GTK_CONTAINER (hbuttonbox1), button_close); + GTK_WIDGET_SET_FLAGS (button_close, GTK_CAN_DEFAULT); + + button_connect = gtkutil_button (hbuttonbox1, GTK_STOCK_CONNECT, NULL, + servlist_connect_cb, NULL, _("C_onnect")); + GTK_WIDGET_SET_FLAGS (button_connect, GTK_CAN_DEFAULT); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label3), entry1); + gtk_label_set_mnemonic_widget (GTK_LABEL (label6), entry4); + gtk_label_set_mnemonic_widget (GTK_LABEL (label7), entry5); + + gtk_widget_grab_focus (networks_tree); + gtk_widget_grab_default (button_close); + return servlist; +} + +void +fe_serverlist_open (session *sess) +{ + if (serverlist_win) + { + gtk_window_present (GTK_WINDOW (serverlist_win)); + return; + } + + servlist_sess = sess; + + serverlist_win = servlist_open_networks (); + gtkutil_set_icon (serverlist_win); + + servlist_networks_populate (networks_tree, network_list); + + g_signal_connect (G_OBJECT (serverlist_win), "delete_event", + G_CALLBACK (servlist_delete_cb), 0); + g_signal_connect (G_OBJECT (serverlist_win), "configure_event", + G_CALLBACK (servlist_configure_cb), 0); + g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW (networks_tree))), + "changed", G_CALLBACK (servlist_network_row_cb), NULL); + g_signal_connect (G_OBJECT (networks_tree), "key_press_event", + G_CALLBACK (servlist_net_keypress_cb), networks_tree); + + gtk_widget_show (serverlist_win); +} diff --git a/src/fe-gtk/setup.c b/src/fe-gtk/setup.c new file mode 100644 index 00000000..517e0944 --- /dev/null +++ b/src/fe-gtk/setup.c @@ -0,0 +1,2137 @@ +/* X-Chat + * Copyright (C) 2004-2007 Peter Zelezny. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "../common/xchat.h" +#include "../common/cfgfiles.h" +#include "../common/fe.h" +#include "../common/text.h" +#include "../common/userlist.h" +#include "../common/util.h" +#include "../common/xchatc.h" +#include "fe-gtk.h" +#include "gtkutil.h" +#include "maingui.h" +#include "palette.h" +#include "pixmaps.h" +#include "menu.h" + +#include <gtk/gtkcolorseldialog.h> +#include <gtk/gtktable.h> +#include <gtk/gtkentry.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkmisc.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkvbox.h> +#include <gtk/gtkalignment.h> +#include <gtk/gtknotebook.h> +#include <gtk/gtkframe.h> +#include <gtk/gtkfontsel.h> +#include <gtk/gtkcheckbutton.h> +#include <gtk/gtkscrolledwindow.h> +#include <gtk/gtkspinbutton.h> +#include <gtk/gtkstock.h> +#include <gtk/gtktreeview.h> +#include <gtk/gtkhbbox.h> +#include <gtk/gtkhseparator.h> +#include <gtk/gtkradiobutton.h> +#include <gtk/gtkcombobox.h> +#include <gtk/gtkliststore.h> +#include <gtk/gtktreestore.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtkcellrenderertext.h> +#include <gtk/gtkhscale.h> +#ifdef WIN32 +#include "../common/fe.h" +#endif +#ifdef USE_GTKSPELL +#include <gtk/gtktextview.h> +#include <gtkspell/gtkspell.h> +#endif +#ifdef USE_LIBSEXY +#include "sexy-spell-entry.h" +#endif + +GtkStyle *create_input_style (GtkStyle *); + +#define LABEL_INDENT 12 + +static int last_selected_page = 0; +static int last_selected_row = 0; /* sound row */ +static gboolean color_change; +static struct xchatprefs setup_prefs; +static GtkWidget *cancel_button; +static GtkWidget *font_dialog = NULL; + +enum +{ + ST_END, + ST_TOGGLE, + ST_TOGGLR, + ST_3OGGLE, + ST_ENTRY, + ST_EFONT, + ST_EFILE, + ST_EFOLDER, + ST_MENU, + ST_RADIO, + ST_NUMBER, + ST_HSCALE, + ST_HEADER, + ST_LABEL, + ST_ALERTHEAD +}; + +typedef struct +{ + int type; + char *label; + int offset; + char *tooltip; + char const *const *list; + int extra; +} setting; + + +static const setting textbox_settings[] = +{ + {ST_HEADER, N_("Text Box Appearance"),0,0,0}, + {ST_EFONT, N_("Font:"), P_OFFSETNL(font_normal), 0, 0, sizeof prefs.font_normal}, + {ST_EFILE, N_("Background image:"), P_OFFSETNL(background), 0, 0, sizeof prefs.background}, + {ST_NUMBER, N_("Scrollback lines:"), P_OFFINTNL(max_lines),0,0,100000}, + {ST_TOGGLE, N_("Colored nick names"), P_OFFINTNL(colorednicks), + N_("Give each person on IRC a different color"),0,0}, + {ST_TOGGLR, N_("Indent nick names"), P_OFFINTNL(indent_nicks), + N_("Make nick names right-justified"),0,0}, + {ST_TOGGLE, N_("Transparent background"), P_OFFINTNL(transparent),0,0,0}, + {ST_TOGGLR, N_("Show marker line"), P_OFFINTNL(show_marker), + N_("Insert a red line after the last read text."),0,0}, + {ST_HEADER, N_("Transparency Settings"), 0,0,0}, + {ST_HSCALE, N_("Red:"), P_OFFINTNL(tint_red),0,0,0}, + {ST_HSCALE, N_("Green:"), P_OFFINTNL(tint_green),0,0,0}, + {ST_HSCALE, N_("Blue:"), P_OFFINTNL(tint_blue),0,0,0}, + + {ST_HEADER, N_("Time Stamps"),0,0,0}, + {ST_TOGGLE, N_("Enable time stamps"), P_OFFINTNL(timestamp),0,0,2}, + {ST_ENTRY, N_("Time stamp format:"), P_OFFSETNL(stamp_format), + N_("See strftime manpage for details."),0,sizeof prefs.stamp_format}, + + {ST_END, 0, 0, 0, 0, 0} +}; + +static const char *const tabcompmenu[] = +{ + N_("A-Z"), + N_("Last-spoke order"), + NULL +}; + +static const setting inputbox_settings[] = +{ + {ST_HEADER, N_("Input box"),0,0,0}, + {ST_TOGGLE, N_("Use the Text box font and colors"), P_OFFINTNL(style_inputbox),0,0,0}, +#if defined(USE_GTKSPELL) || defined(USE_LIBSEXY) + {ST_TOGGLE, N_("Spell checking"), P_OFFINTNL(gui_input_spell),0,0,0}, +#endif + + {ST_HEADER, N_("Nick Completion"),0,0,0}, + {ST_TOGGLE, N_("Automatic nick completion (without TAB key)"), P_OFFINTNL(nickcompletion), + 0,0,0}, + {ST_ENTRY, N_("Nick completion suffix:"), P_OFFSETNL(nick_suffix),0,0,sizeof prefs.nick_suffix}, + {ST_MENU, N_("Nick completion sorted:"), P_OFFINTNL(completion_sort), 0, tabcompmenu, 0}, + +#if 0 /* obsolete */ + {ST_HEADER, N_("Input Box Codes"),0,0,0}, + {ST_TOGGLE, N_("Interpret %nnn as an ASCII value"), P_OFFINTNL(perc_ascii),0,0,0}, + {ST_TOGGLE, N_("Interpret %C, %B as Color, Bold etc"), P_OFFINTNL(perc_color),0,0,0}, +#endif + + {ST_END, 0, 0, 0, 0, 0} +}; + +/*static const char *const lagmenutext[] = +{ + N_("Off"), + N_("Graph"), + N_("Info text"), + N_("Both"), + NULL +};*/ + +static const char *const ulmenutext[] = +{ + N_("A-Z, Ops first"), + N_("A-Z"), + N_("Z-A, Ops last"), + N_("Z-A"), + N_("Unsorted"), + NULL +}; + +static const char *const cspos[] = +{ + N_("Left (Upper)"), + N_("Left (Lower)"), + N_("Right (Upper)"), + N_("Right (Lower)"), + N_("Top"), + N_("Bottom"), + N_("Hidden"), + NULL +}; + +static const char *const ulpos[] = +{ + N_("Left (Upper)"), + N_("Left (Lower)"), + N_("Right (Upper)"), + N_("Right (Lower)"), + NULL +}; + +static const setting userlist_settings[] = +{ + {ST_HEADER, N_("User List"),0,0,0}, + {ST_TOGGLE, N_("Show hostnames in user list"), P_OFFINTNL(showhostname_in_userlist), 0, 0, 0}, + {ST_TOGGLE, N_("Use the Text box font and colors"), P_OFFINTNL(style_namelistgad),0,0,0}, +/* {ST_TOGGLE, N_("Resizable user list"), P_OFFINTNL(paned_userlist),0,0,0},*/ + {ST_MENU, N_("User list sorted by:"), P_OFFINTNL(userlist_sort), 0, ulmenutext, 0}, + {ST_MENU, N_("Show user list at:"), P_OFFINTNL(gui_ulist_pos), 0, ulpos, 1}, + + {ST_HEADER, N_("Away tracking"),0,0,0}, + {ST_TOGGLE, N_("Track the Away status of users and mark them in a different color"), P_OFFINTNL(away_track),0,0,2}, + {ST_NUMBER, N_("On channels smaller than:"), P_OFFINTNL(away_size_max),0,0,10000}, + + {ST_HEADER, N_("Action Upon Double Click"),0,0,0}, + {ST_ENTRY, N_("Execute command:"), P_OFFSETNL(doubleclickuser), 0, 0, sizeof prefs.doubleclickuser}, + +/* {ST_HEADER, N_("Extra Gadgets"),0,0,0}, + {ST_MENU, N_("Lag meter:"), P_OFFINTNL(lagometer), 0, lagmenutext, 0}, + {ST_MENU, N_("Throttle meter:"), P_OFFINTNL(throttlemeter), 0, lagmenutext, 0},*/ + + {ST_END, 0, 0, 0, 0, 0} +}; + +static const char *const tabwin[] = +{ + N_("Windows"), + N_("Tabs"), + NULL +}; + +#if 0 +static const char *const focusnewtabsmenu[] = +{ + N_("Never"), + N_("Always"), + N_("Only requested tabs"), + NULL +}; +#endif + +static const char *const swtype[] = +{ + N_("Tabs"), /* 0 tabs */ + "", /* 1 reserved */ + N_("Tree"), /* 2 tree */ + NULL +}; + +static const setting tabs_settings[] = +{ + /*{ST_HEADER, N_("Channel Switcher"),0,0,0},*/ + {ST_RADIO, N_("Switcher type:"),P_OFFINTNL(tab_layout), 0, swtype, 0}, + {ST_TOGGLE, N_("Open an extra tab for server messages"), P_OFFINTNL(use_server_tab), 0, 0, 0}, + {ST_TOGGLE, N_("Open an extra tab for server notices"), P_OFFINTNL(notices_tabs), 0, 0, 0}, + {ST_TOGGLE, N_("Open a new tab when you receive a private message"), P_OFFINTNL(autodialog), 0, 0, 0}, + {ST_TOGGLE, N_("Sort tabs in alphabetical order"), P_OFFINTNL(tab_sort), 0, 0, 0}, + {ST_TOGGLE, N_("Smaller text"), P_OFFINTNL(tab_small), 0, 0, 0}, +#if 0 + {ST_MENU, N_("Focus new tabs:"), P_OFFINTNL(newtabstofront), 0, focusnewtabsmenu, 0}, +#endif + {ST_MENU, N_("Show channel switcher at:"), P_OFFINTNL(tab_pos), 0, cspos, 1}, + {ST_NUMBER, N_("Shorten tab labels to:"), P_OFFINTNL(truncchans), 0, (const char **)N_("letters."), 99}, + + {ST_HEADER, N_("Tabs or Windows"),0,0,0}, + {ST_MENU, N_("Open channels in:"), P_OFFINTNL(tabchannels), 0, tabwin, 0}, + {ST_MENU, N_("Open dialogs in:"), P_OFFINTNL(privmsgtab), 0, tabwin, 0}, + {ST_MENU, N_("Open utilities in:"), P_OFFINTNL(windows_as_tabs), N_("Open DCC, Ignore, Notify etc, in tabs or windows?"), tabwin, 0}, + + {ST_END, 0, 0, 0, 0, 0} +}; + +static const char *const dccaccept[] = +{ + N_("No"), + N_("Yes"), + N_("Browse for save folder every time"), + NULL +}; + +static const setting filexfer_settings[] = +{ + {ST_HEADER, N_("Files and Directories"), 0, 0, 0}, + {ST_MENU, N_("Auto accept file offers:"), P_OFFINTNL(autodccsend), 0, dccaccept, 0}, + {ST_EFOLDER,N_("Download files to:"), P_OFFSETNL(dccdir), 0, 0, sizeof prefs.dccdir}, + {ST_EFOLDER,N_("Move completed files to:"), P_OFFSETNL(dcc_completed_dir), 0, 0, sizeof prefs.dcc_completed_dir}, + {ST_TOGGLE, N_("Save nick name in filenames"), P_OFFINTNL(dccwithnick), 0, 0, 0}, + + {ST_HEADER, N_("Network Settings"), 0, 0, 0}, + {ST_TOGGLE, N_("Get my address from the IRC server"), P_OFFINTNL(ip_from_server), + N_("Asks the IRC server for your real address. Use this if you have a 192.168.*.* address!"), 0, 0}, + {ST_ENTRY, N_("DCC IP address:"), P_OFFSETNL(dcc_ip_str), + N_("Claim you are at this address when offering files."), 0, sizeof prefs.dcc_ip_str}, + {ST_NUMBER, N_("First DCC send port:"), P_OFFINTNL(first_dcc_send_port), 0, 0, 65535}, + {ST_NUMBER, N_("Last DCC send port:"), P_OFFINTNL(last_dcc_send_port), 0, + (const char **)N_("!Leave ports at zero for full range."), 65535}, + + {ST_HEADER, N_("Maximum File Transfer Speeds (bytes per second)"), 0, 0, 0}, + {ST_NUMBER, N_("One upload:"), P_OFFINTNL(dcc_max_send_cps), + N_("Maximum speed for one transfer"), 0, 1000000}, + {ST_NUMBER, N_("One download:"), P_OFFINTNL(dcc_max_get_cps), + N_("Maximum speed for one transfer"), 0, 1000000}, + {ST_NUMBER, N_("All uploads combined:"), P_OFFINTNL(dcc_global_max_send_cps), + N_("Maximum speed for all files"), 0, 1000000}, + {ST_NUMBER, N_("All downloads combined:"), P_OFFINTNL(dcc_global_max_get_cps), + N_("Maximum speed for all files"), 0, 1000000}, + + {ST_END, 0, 0, 0, 0, 0} +}; + +static const int balloonlist[3] = +{ + P_OFFINTNL(input_balloon_chans), P_OFFINTNL(input_balloon_priv), P_OFFINTNL(input_balloon_hilight) +}; + +static const int trayblinklist[3] = +{ + P_OFFINTNL(input_tray_chans), P_OFFINTNL(input_tray_priv), P_OFFINTNL(input_tray_hilight) +}; + +static const int taskbarlist[3] = +{ + P_OFFINTNL(input_flash_chans), P_OFFINTNL(input_flash_priv), P_OFFINTNL(input_flash_hilight) +}; + +static const int beeplist[3] = +{ + P_OFFINTNL(input_beep_chans), P_OFFINTNL(input_beep_priv), P_OFFINTNL(input_beep_hilight) +}; + +static const setting alert_settings[] = +{ + {ST_HEADER, N_("Alerts"),0,0,0}, + + {ST_ALERTHEAD}, +#ifndef WIN32 + {ST_3OGGLE, N_("Show tray balloons on:"), 0, 0, (void *)balloonlist, 0}, +#endif + {ST_3OGGLE, N_("Blink tray icon on:"), 0, 0, (void *)trayblinklist, 0}, + {ST_3OGGLE, N_("Blink task bar on:"), 0, 0, (void *)taskbarlist, 0}, + {ST_3OGGLE, N_("Make a beep sound on:"), 0, 0, (void *)beeplist, 0}, + + {ST_TOGGLE, N_("Enable system tray icon"), P_OFFINTNL(gui_tray), 0, 0, 0}, + + {ST_HEADER, N_("Highlighted Messages"),0,0,0}, + {ST_LABEL, N_("Highlighted messages are ones where your nickname is mentioned, but also:"), 0, 0, 0, 1}, + + {ST_ENTRY, N_("Extra words to highlight:"), P_OFFSETNL(irc_extra_hilight), 0, 0, sizeof prefs.irc_extra_hilight}, + {ST_ENTRY, N_("Nick names not to highlight:"), P_OFFSETNL(irc_no_hilight), 0, 0, sizeof prefs.irc_no_hilight}, + {ST_ENTRY, N_("Nick names to always highlight:"), P_OFFSETNL(irc_nick_hilight), 0, 0, sizeof prefs.irc_nick_hilight}, + {ST_LABEL, N_("Separate multiple words with commas.\nWildcards are accepted.")}, + {ST_END, 0, 0, 0, 0, 0} +}; + +static const setting general_settings[] = +{ + {ST_HEADER, N_("Default Messages"),0,0,0}, + {ST_ENTRY, N_("Quit:"), P_OFFSETNL(quitreason), 0, 0, sizeof prefs.quitreason}, + {ST_ENTRY, N_("Leave channel:"), P_OFFSETNL(partreason), 0, 0, sizeof prefs.partreason}, + {ST_ENTRY, N_("Away:"), P_OFFSETNL(awayreason), 0, 0, sizeof prefs.awayreason}, + + {ST_HEADER, N_("Away"),0,0,0}, + {ST_TOGGLE, N_("Announce away messages"), P_OFFINTNL(show_away_message), + N_("Announce your away messages to all channels"), 0, 0}, + {ST_TOGGLE, N_("Show away once"), P_OFFINTNL(show_away_once), N_("Show identical away messages only once"), 0, 0}, + {ST_TOGGLE, N_("Automatically unmark away"), P_OFFINTNL(auto_unmark_away), N_("Unmark yourself as away before sending messages"), 0, 0}, + {ST_END, 0, 0, 0, 0, 0} +}; + +#if 0 +static const setting advanced_settings[] = +{ + {ST_HEADER, N_("Advanced Settings"),0,0,0}, + {ST_NUMBER, N_("Auto reconnect delay:"), P_OFFINTNL(recon_delay), 0, 0, 9999}, + {ST_TOGGLE, N_("Display MODEs in raw form"), P_OFFINTNL(raw_modes), 0, 0, 0}, + {ST_TOGGLE, N_("Whois on notify"), P_OFFINTNL(whois_on_notifyonline), N_("Sends a /WHOIS when a user comes online in your notify list"), 0, 0}, + {ST_TOGGLE, N_("Hide join and part messages"), P_OFFINTNL(confmode), N_("Hide channel join/part messages by default"), 0, 0}, + {ST_HEADER, N_("Auto Open DCC Windows"),0,0,0}, + {ST_TOGGLE, N_("Send window"), P_OFFINTNL(autoopendccsendwindow), 0, 0, 0}, + {ST_TOGGLE, N_("Receive window"), P_OFFINTNL(autoopendccrecvwindow), 0, 0, 0}, + {ST_TOGGLE, N_("Chat window"), P_OFFINTNL(autoopendccchatwindow), 0, 0, 0}, + + {ST_END, 0, 0, 0, 0, 0} +}; +#endif + +static const setting logging_settings[] = +{ + {ST_HEADER, N_("Logging"),0,0,0}, + {ST_TOGGLE, N_("Display scrollback from previous session"), P_OFFINTNL(text_replay), 0, 0, 0}, + {ST_TOGGLE, N_("Enable logging of conversations to disk"), P_OFFINTNL(logging), 0, 0, 2}, + {ST_ENTRY, N_("Log filename:"), P_OFFSETNL(logmask), 0, 0, sizeof prefs.logmask}, + {ST_LABEL, N_("%s=Server %c=Channel %n=Network.")}, + + {ST_HEADER, N_("Time Stamps"),0,0,0}, + {ST_TOGGLE, N_("Insert timestamps in logs"), P_OFFINTNL(timestamp_logs), 0, 0, 2}, + {ST_ENTRY, N_("Log timestamp format:"), P_OFFSETNL(timestamp_log_format), 0, 0, sizeof prefs.timestamp_log_format}, + {ST_LABEL, N_("See strftime manpage for details.")}, + + {ST_END, 0, 0, 0, 0, 0} +}; + +static const char *const proxytypes[] = +{ + N_("(Disabled)"), + N_("Wingate"), + N_("Socks4"), + N_("Socks5"), + N_("HTTP"), +#ifdef USE_MSPROXY + N_("MS Proxy (ISA)"), +#endif +#ifdef USE_LIBPROXY + N_("Auto"), +#endif + NULL +}; + +static const char *const proxyuse[] = +{ + N_("All Connections"), + N_("IRC Server Only"), + N_("DCC Get Only"), + NULL +}; + +static const setting network_settings[] = +{ + {ST_HEADER, N_("Your Address"), 0, 0, 0, 0}, + {ST_ENTRY, N_("Bind to:"), P_OFFSETNL(hostname), 0, 0, sizeof prefs.hostname}, + {ST_LABEL, N_("Only useful for computers with multiple addresses.")}, + + {ST_HEADER, N_("Proxy Server"), 0, 0, 0, 0}, + {ST_ENTRY, N_("Hostname:"), P_OFFSETNL(proxy_host), 0, 0, sizeof prefs.proxy_host}, + {ST_NUMBER, N_("Port:"), P_OFFINTNL(proxy_port), 0, 0, 65535}, + {ST_MENU, N_("Type:"), P_OFFINTNL(proxy_type), 0, proxytypes, 0}, + {ST_MENU, N_("Use proxy for:"), P_OFFINTNL(proxy_use), 0, proxyuse, 0}, + + {ST_HEADER, N_("Proxy Authentication"), 0, 0, 0, 0}, +#ifdef USE_MSPROXY + {ST_TOGGLE, N_("Use Authentication (MS Proxy, HTTP or Socks5 only)"), P_OFFINTNL(proxy_auth), 0, 0, 0}, +#else + {ST_TOGGLE, N_("Use Authentication (HTTP or Socks5 only)"), P_OFFINTNL(proxy_auth), 0, 0, 0}, +#endif + {ST_ENTRY, N_("Username:"), P_OFFSETNL(proxy_user), 0, 0, sizeof prefs.proxy_user}, + {ST_ENTRY, N_("Password:"), P_OFFSETNL(proxy_pass), 0, GINT_TO_POINTER(1), sizeof prefs.proxy_pass}, + + {ST_END, 0, 0, 0, 0, 0} +}; + +#define setup_get_str(pr,set) (((char *)pr)+set->offset) +#define setup_get_int(pr,set) *(((int *)pr)+set->offset) +#define setup_get_int3(pr,off) *(((int *)pr)+off) + +#define setup_set_int(pr,set,num) *((int *)pr+set->offset)=num +#define setup_set_str(pr,set,str) strcpy(((char *)pr)+set->offset,str) + + +static void +setup_3oggle_cb (GtkToggleButton *but, unsigned int *setting) +{ + *setting = but->active; +} + +static void +setup_headlabel (GtkWidget *tab, int row, int col, char *text) +{ + GtkWidget *label; + char buf[128]; + char *sp; + + snprintf (buf, sizeof (buf), "<b><span size=\"smaller\">%s</span></b>", text); + sp = strchr (buf + 17, ' '); + if (sp) + *sp = '\n'; + + label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL (label), buf); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (tab), label, col, col + 1, row, row + 1, 0, 0, 4, 0); +} + +static void +setup_create_alert_header (GtkWidget *tab, int row, const setting *set) +{ + setup_headlabel (tab, row, 3, _("Channel Message")); + setup_headlabel (tab, row, 4, _("Private Message")); + setup_headlabel (tab, row, 5, _("Highlighted Message")); +} + +/* makes 3 toggles side-by-side */ + +static void +setup_create_3oggle (GtkWidget *tab, int row, const setting *set) +{ + GtkWidget *label, *wid; + int *offsets = (int *)set->list; + + label = gtk_label_new (_(set->label)); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (tab), label, 2, 3, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + + wid = gtk_check_button_new (); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), + setup_get_int3 (&setup_prefs, offsets[0])); + g_signal_connect (G_OBJECT (wid), "toggled", + G_CALLBACK (setup_3oggle_cb), ((int *)&setup_prefs) + offsets[0]); + gtk_table_attach (GTK_TABLE (tab), wid, 3, 4, row, row + 1, 0, 0, 0, 0); + + wid = gtk_check_button_new (); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), + setup_get_int3 (&setup_prefs, offsets[1])); + g_signal_connect (G_OBJECT (wid), "toggled", + G_CALLBACK (setup_3oggle_cb), ((int *)&setup_prefs) + offsets[1]); + gtk_table_attach (GTK_TABLE (tab), wid, 4, 5, row, row + 1, 0, 0, 0, 0); + + wid = gtk_check_button_new (); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), + setup_get_int3 (&setup_prefs, offsets[2])); + g_signal_connect (G_OBJECT (wid), "toggled", + G_CALLBACK (setup_3oggle_cb), ((int *)&setup_prefs) + offsets[2]); + gtk_table_attach (GTK_TABLE (tab), wid, 5, 6, row, row + 1, 0, 0, 0, 0); +} + +static void +setup_toggle_cb (GtkToggleButton *but, const setting *set) +{ + GtkWidget *label, *disable_wid; + + setup_set_int (&setup_prefs, set, but->active ? 1 : 0); + + /* does this toggle also enable/disable another widget? */ + disable_wid = g_object_get_data (G_OBJECT (but), "nxt"); + if (disable_wid) + { + gtk_widget_set_sensitive (disable_wid, but->active); + label = g_object_get_data (G_OBJECT (disable_wid), "lbl"); + gtk_widget_set_sensitive (label, but->active); + } +} + +static void +setup_create_toggleR (GtkWidget *tab, int row, const setting *set) +{ + GtkWidget *wid; + + wid = gtk_check_button_new_with_label (_(set->label)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), + setup_get_int (&setup_prefs, set)); + g_signal_connect (G_OBJECT (wid), "toggled", + G_CALLBACK (setup_toggle_cb), (gpointer)set); + if (set->tooltip) + add_tip (wid, _(set->tooltip)); + gtk_table_attach (GTK_TABLE (tab), wid, 4, 5, row, row + 1, + GTK_EXPAND | GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); +} + +static GtkWidget * +setup_create_toggleL (GtkWidget *tab, int row, const setting *set) +{ + GtkWidget *wid; + + wid = gtk_check_button_new_with_label (_(set->label)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), + setup_get_int (&setup_prefs, set)); + g_signal_connect (G_OBJECT (wid), "toggled", + G_CALLBACK (setup_toggle_cb), (gpointer)set); + if (set->tooltip) + add_tip (wid, _(set->tooltip)); + gtk_table_attach (GTK_TABLE (tab), wid, 2, row==6 ? 6 : 4, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + + return wid; +} + +#if 0 +static void +setup_create_toggle (GtkWidget *box, int row, const setting *set) +{ + GtkWidget *wid; + + wid = gtk_check_button_new_with_label (_(set->label)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), + setup_get_int (&setup_prefs, set)); + g_signal_connect (G_OBJECT (wid), "toggled", + G_CALLBACK (setup_toggle_cb), (gpointer)set); + if (set->tooltip) + add_tip (wid, _(set->tooltip)); + gtk_box_pack_start (GTK_BOX (box), wid, 0, 0, 0); +} +#endif + +static GtkWidget * +setup_create_italic_label (char *text) +{ + GtkWidget *label; + char buf[256]; + + label = gtk_label_new (NULL); + snprintf (buf, sizeof (buf), "<i><span size=\"smaller\">%s</span></i>", text); + gtk_label_set_markup (GTK_LABEL (label), buf); + gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER); + + return label; +} + +static void +setup_spin_cb (GtkSpinButton *spin, const setting *set) +{ + setup_set_int (&setup_prefs, set, gtk_spin_button_get_value_as_int (spin)); +} + +static GtkWidget * +setup_create_spin (GtkWidget *table, int row, const setting *set) +{ + GtkWidget *label, *wid, *rbox, *align; + char *text; + + label = gtk_label_new (_(set->label)); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), label, 2, 3, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + + align = gtk_alignment_new (0.0, 0.5, 0.0, 0.0); + gtk_table_attach (GTK_TABLE (table), align, 3, 4, row, row + 1, + GTK_EXPAND | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + + rbox = gtk_hbox_new (0, 0); + gtk_container_add (GTK_CONTAINER (align), rbox); + + wid = gtk_spin_button_new_with_range (0, set->extra, 1); + g_object_set_data (G_OBJECT (wid), "lbl", label); + if (set->tooltip) + add_tip (wid, _(set->tooltip)); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (wid), + setup_get_int (&setup_prefs, set)); + g_signal_connect (G_OBJECT (wid), "value_changed", + G_CALLBACK (setup_spin_cb), (gpointer)set); + gtk_box_pack_start (GTK_BOX (rbox), wid, 0, 0, 0); + + if (set->list) + { + text = _((char *)set->list); + if (text[0] == '!') + label = setup_create_italic_label (text + 1); + else + label = gtk_label_new (text); + gtk_box_pack_start (GTK_BOX (rbox), label, 0, 0, 6); + } + + return wid; +} + +static gint +setup_apply_tint (int *tag) +{ + prefs.tint_red = setup_prefs.tint_red; + prefs.tint_green = setup_prefs.tint_green; + prefs.tint_blue = setup_prefs.tint_blue; + mg_update_xtext (current_sess->gui->xtext); + *tag = 0; + return 0; +} + +static void +setup_hscale_cb (GtkHScale *wid, const setting *set) +{ + static int tag = 0; + + setup_set_int (&setup_prefs, set, gtk_range_get_value(GTK_RANGE(wid))); + if(tag == 0) + tag = g_idle_add ((GSourceFunc)setup_apply_tint, &tag); +} + +static void +setup_create_hscale (GtkWidget *table, int row, const setting *set) +{ + GtkWidget *wid; + + wid = gtk_label_new (_(set->label)); + gtk_misc_set_alignment (GTK_MISC (wid), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), wid, 2, 3, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + + wid = gtk_hscale_new_with_range (0., 255., 1.); + gtk_scale_set_value_pos (GTK_SCALE (wid), GTK_POS_RIGHT); + gtk_range_set_value (GTK_RANGE (wid), setup_get_int (&setup_prefs, set)); + g_signal_connect (G_OBJECT(wid), "value_changed", + G_CALLBACK (setup_hscale_cb), (gpointer)set); + gtk_table_attach (GTK_TABLE (table), wid, 3, 6, row, row + 1, + GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); +} + + +static GtkWidget *proxy_user; /* username GtkEntry */ +static GtkWidget *proxy_pass; /* password GtkEntry */ + +static void +setup_menu_cb (GtkWidget *cbox, const setting *set) +{ + int n = gtk_combo_box_get_active (GTK_COMBO_BOX (cbox)); + + /* set the prefs.<field> */ + setup_set_int (&setup_prefs, set, n + set->extra); + + if (set->list == proxytypes) + { + /* only HTTP and Socks5 can use a username/pass */ + gtk_widget_set_sensitive (proxy_user, (n == 3 || n == 4 || n == 5)); + gtk_widget_set_sensitive (proxy_pass, (n == 3 || n == 4 || n == 5)); + } +} + +static void +setup_radio_cb (GtkWidget *item, const setting *set) +{ + if (GTK_TOGGLE_BUTTON (item)->active) + { + int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "n")); + /* set the prefs.<field> */ + setup_set_int (&setup_prefs, set, n); + } +} + +static int +setup_create_radio (GtkWidget *table, int row, const setting *set) +{ + GtkWidget *wid, *hbox; + int i; + const char **text = (const char **)set->list; + GSList *group; + + wid = gtk_label_new (_(set->label)); + gtk_misc_set_alignment (GTK_MISC (wid), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), wid, 2, 3, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + + hbox = gtk_hbox_new (0, 0); + gtk_table_attach (GTK_TABLE (table), hbox, 3, 4, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + + i = 0; + group = NULL; + while (text[i]) + { + if (text[i][0] != 0) + { + wid = gtk_radio_button_new_with_mnemonic (group, _(text[i])); + /*if (set->tooltip) + add_tip (wid, _(set->tooltip));*/ + group = gtk_radio_button_get_group (GTK_RADIO_BUTTON (wid)); + gtk_container_add (GTK_CONTAINER (hbox), wid); + if (i == setup_get_int (&setup_prefs, set)) + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (wid), TRUE); + g_object_set_data (G_OBJECT (wid), "n", GINT_TO_POINTER (i)); + g_signal_connect (G_OBJECT (wid), "toggled", + G_CALLBACK (setup_radio_cb), (gpointer)set); + } + i++; + row++; + } + + return i; +} + +/* +static const char *id_strings[] = +{ + "", + "*", + "%C4*%C18%B%B", + "%U" +}; + +static void +setup_id_menu_cb (GtkWidget *item, char *dest) +{ + int n = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (item), "n")); + + strcpy (dest, id_strings[n]); +} + +static void +setup_create_id_menu (GtkWidget *table, char *label, int row, char *dest) +{ + GtkWidget *wid, *menu, *item; + int i, def = 0; + static const char *text[] = + { + ("(disabled)"), + ("A star (*)"), + ("A red star (*)"), + ("Underlined") + }; + + wid = gtk_label_new (label); + gtk_misc_set_alignment (GTK_MISC (wid), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), wid, 2, 3, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + + wid = gtk_option_menu_new (); + menu = gtk_menu_new (); + + for (i = 0; i < 4; i++) + { + if (strcmp (id_strings[i], dest) == 0) + { + def = i; + break; + } + } + + i = 0; + while (text[i]) + { + item = gtk_menu_item_new_with_label (_(text[i])); + g_object_set_data (G_OBJECT (item), "n", GINT_TO_POINTER (i)); + + gtk_widget_show (item); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), item); + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (setup_id_menu_cb), dest); + i++; + } + + gtk_option_menu_set_menu (GTK_OPTION_MENU (wid), menu); + gtk_option_menu_set_history (GTK_OPTION_MENU (wid), def); + + gtk_table_attach (GTK_TABLE (table), wid, 3, 4, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); +} + +*/ + +static void +setup_create_menu (GtkWidget *table, int row, const setting *set) +{ + GtkWidget *wid, *cbox, *box; + const char **text = (const char **)set->list; + int i; + + wid = gtk_label_new (_(set->label)); + gtk_misc_set_alignment (GTK_MISC (wid), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), wid, 2, 3, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + + cbox = gtk_combo_box_new_text (); + + for (i = 0; text[i]; i++) + gtk_combo_box_append_text (GTK_COMBO_BOX (cbox), _(text[i])); + + gtk_combo_box_set_active (GTK_COMBO_BOX (cbox), + setup_get_int (&setup_prefs, set) - set->extra); + g_signal_connect (G_OBJECT (cbox), "changed", + G_CALLBACK (setup_menu_cb), (gpointer)set); + + box = gtk_hbox_new (0, 0); + gtk_box_pack_start (GTK_BOX (box), cbox, 0, 0, 0); + gtk_table_attach (GTK_TABLE (table), box, 3, 4, row, row + 1, + GTK_EXPAND | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); +} + +static void +setup_filereq_cb (GtkWidget *entry, char *file) +{ + if (file) + { + if (file[0]) + gtk_entry_set_text (GTK_ENTRY (entry), file); + } +} + +static void +setup_browsefile_cb (GtkWidget *button, GtkWidget *entry) +{ + gtkutil_file_req (_("Select an Image File"), setup_filereq_cb, entry, NULL, 0); +} + +static void +setup_fontsel_destroy (GtkWidget *button, GtkFontSelectionDialog *dialog) +{ + font_dialog = NULL; +} + +static void +setup_fontsel_cb (GtkWidget *button, GtkFontSelectionDialog *dialog) +{ + GtkWidget *entry; + char *font_name; + + entry = g_object_get_data (G_OBJECT (button), "e"); + font_name = gtk_font_selection_dialog_get_font_name (dialog); + + gtk_entry_set_text (GTK_ENTRY (entry), font_name); + + g_free (font_name); + gtk_widget_destroy (GTK_WIDGET (dialog)); + font_dialog = NULL; +} + +static void +setup_fontsel_cancel (GtkWidget *button, GtkFontSelectionDialog *dialog) +{ + gtk_widget_destroy (GTK_WIDGET (dialog)); + font_dialog = NULL; +} + +static void +setup_browsefolder_cb (GtkWidget *button, GtkEntry *entry) +{ + gtkutil_file_req (_("Select Download Folder"), setup_filereq_cb, entry, entry->text, FRF_CHOOSEFOLDER); +} + +static void +setup_browsefont_cb (GtkWidget *button, GtkWidget *entry) +{ + GtkFontSelection *sel; + GtkFontSelectionDialog *dialog; + + dialog = (GtkFontSelectionDialog *) gtk_font_selection_dialog_new (_("Select font")); + font_dialog = (GtkWidget *)dialog; /* global var */ + + sel = (GtkFontSelection *) dialog->fontsel; + + if (GTK_ENTRY (entry)->text[0]) + gtk_font_selection_set_font_name (sel, GTK_ENTRY (entry)->text); + + g_object_set_data (G_OBJECT (dialog->ok_button), "e", entry); + + g_signal_connect (G_OBJECT (dialog), "destroy", + G_CALLBACK (setup_fontsel_destroy), dialog); + g_signal_connect (G_OBJECT (dialog->ok_button), "clicked", + G_CALLBACK (setup_fontsel_cb), dialog); + g_signal_connect (G_OBJECT (dialog->cancel_button), "clicked", + G_CALLBACK (setup_fontsel_cancel), dialog); + + gtk_widget_show (GTK_WIDGET (dialog)); +} + +static void +setup_entry_cb (GtkEntry *entry, setting *set) +{ + int size; + int pos; + int len = strlen (entry->text); + unsigned char *p = entry->text; + + /* need to truncate? */ + if (len >= set->extra) + { + len = pos = 0; + while (1) + { + size = g_utf8_skip [*p]; + len += size; + p += size; + /* truncate to "set->extra" BYTES */ + if (len >= set->extra) + { + gtk_editable_delete_text (GTK_EDITABLE (entry), pos, -1); + break; + } + pos++; + } + } + else + { + setup_set_str (&setup_prefs, set, entry->text); + } +} + +static void +setup_create_label (GtkWidget *table, int row, const setting *set) +{ + gtk_table_attach (GTK_TABLE (table), setup_create_italic_label (_(set->label)), + set->extra ? 1 : 3, 5, row, row + 1, GTK_FILL, + GTK_SHRINK | GTK_FILL, 0, 0); +} + +static GtkWidget * +setup_create_entry (GtkWidget *table, int row, const setting *set) +{ + GtkWidget *label; + GtkWidget *wid, *bwid; + + label = gtk_label_new (_(set->label)); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), label, 2, 3, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + + wid = gtk_entry_new (); + g_object_set_data (G_OBJECT (wid), "lbl", label); + if (set->list) + gtk_entry_set_visibility (GTK_ENTRY (wid), FALSE); + if (set->tooltip) + add_tip (wid, _(set->tooltip)); + gtk_entry_set_max_length (GTK_ENTRY (wid), set->extra - 1); + gtk_entry_set_text (GTK_ENTRY (wid), setup_get_str (&setup_prefs, set)); + g_signal_connect (G_OBJECT (wid), "changed", + G_CALLBACK (setup_entry_cb), (gpointer)set); + + if (set->offset == P_OFFSETNL(proxy_user)) + proxy_user = wid; + if (set->offset == P_OFFSETNL(proxy_pass)) + proxy_pass = wid; + + /* only http and Socks5 can auth */ + if ( (set->offset == P_OFFSETNL(proxy_pass) || + set->offset == P_OFFSETNL(proxy_user)) && + (setup_prefs.proxy_type != 4 && setup_prefs.proxy_type != 3 && setup_prefs.proxy_type != 5) ) + gtk_widget_set_sensitive (wid, FALSE); + + if (set->type == ST_ENTRY) + gtk_table_attach (GTK_TABLE (table), wid, 3, 6, row, row + 1, + GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0); + else + { + gtk_table_attach (GTK_TABLE (table), wid, 3, 5, row, row + 1, + GTK_EXPAND | GTK_FILL, GTK_SHRINK, 0, 0); + bwid = gtk_button_new_with_label (_("Browse...")); + gtk_table_attach (GTK_TABLE (table), bwid, 5, 6, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_FILL, 0, 0); + if (set->type == ST_EFILE) + g_signal_connect (G_OBJECT (bwid), "clicked", + G_CALLBACK (setup_browsefile_cb), wid); + if (set->type == ST_EFONT) + g_signal_connect (G_OBJECT (bwid), "clicked", + G_CALLBACK (setup_browsefont_cb), wid); + if (set->type == ST_EFOLDER) + g_signal_connect (G_OBJECT (bwid), "clicked", + G_CALLBACK (setup_browsefolder_cb), wid); + } + + return wid; +} + +static void +setup_create_header (GtkWidget *table, int row, char *labeltext) +{ + GtkWidget *label; + char buf[128]; + + if (row == 0) + snprintf (buf, sizeof (buf), "<b>%s</b>", _(labeltext)); + else + snprintf (buf, sizeof (buf), "\n<b>%s</b>", _(labeltext)); + + label = gtk_label_new (NULL); + gtk_label_set_markup (GTK_LABEL (label), buf); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (table), label, 0, 4, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 5); +} + +static GtkWidget * +setup_create_frame (GtkWidget **left, GtkWidget *box) +{ + GtkWidget *tab, *hbox, *inbox = box; + + tab = gtk_table_new (3, 2, FALSE); + gtk_container_set_border_width (GTK_CONTAINER (tab), 6); + gtk_table_set_row_spacings (GTK_TABLE (tab), 2); + gtk_table_set_col_spacings (GTK_TABLE (tab), 3); + gtk_container_add (GTK_CONTAINER (inbox), tab); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (inbox), hbox); + + *left = gtk_vbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (hbox), *left, 0, 0, 0); + + return tab; +} + +static void +open_data_cb (GtkWidget *button, gpointer data) +{ + fe_open_url (get_xdir_utf8 ()); +} + +static GtkWidget * +setup_create_page (const setting *set) +{ + int i, row, do_disable; + GtkWidget *tab, *box, *left; + GtkWidget *wid = NULL, *prev; + + box = gtk_vbox_new (FALSE, 1); + gtk_container_set_border_width (GTK_CONTAINER (box), 6); + + tab = setup_create_frame (&left, box); + + i = row = do_disable = 0; + while (set[i].type != ST_END) + { + prev = wid; + + switch (set[i].type) + { + case ST_HEADER: + setup_create_header (tab, row, set[i].label); + break; + case ST_EFONT: + case ST_ENTRY: + case ST_EFILE: + case ST_EFOLDER: + wid = setup_create_entry (tab, row, &set[i]); + break; + case ST_TOGGLR: + row--; + setup_create_toggleR (tab, row, &set[i]); + break; + case ST_TOGGLE: + wid = setup_create_toggleL (tab, row, &set[i]); + do_disable = set[i].extra; + break; + case ST_3OGGLE: + setup_create_3oggle (tab, row, &set[i]); + break; + case ST_MENU: + setup_create_menu (tab, row, &set[i]); + break; + case ST_RADIO: + row += setup_create_radio (tab, row, &set[i]); + break; + case ST_NUMBER: + wid = setup_create_spin (tab, row, &set[i]); + break; + case ST_HSCALE: + setup_create_hscale (tab, row, &set[i]); + break; + case ST_LABEL: + setup_create_label (tab, row, &set[i]); + break; + case ST_ALERTHEAD: + setup_create_alert_header (tab, row, &set[i]); + } + + /* will this toggle disable the "next" widget? */ + do_disable--; + if (do_disable == 0) + { + /* setup_toggle_cb uses this data */ + g_object_set_data (G_OBJECT (prev), "nxt", wid); + /* force initial sensitive state */ + gtk_widget_set_sensitive (wid, GTK_TOGGLE_BUTTON (prev)->active); + gtk_widget_set_sensitive (g_object_get_data (G_OBJECT (wid), "lbl"), + GTK_TOGGLE_BUTTON (prev)->active); + } + + i++; + row++; + } + +#if 0 + if (set == general_settings) + { + setup_create_id_menu (tab, _("Mark identified users with:"), + row, setup_prefs.irc_id_ytext); + setup_create_id_menu (tab, _("Mark not-identified users with:"), + row + 1, setup_prefs.irc_id_ntext); + } +#endif + + if (set == logging_settings) + { + GtkWidget *but = gtk_button_new_with_label (_("Open Data Folder")); + gtk_box_pack_start (GTK_BOX (left), but, 0, 0, 0); + g_signal_connect (G_OBJECT (but), "clicked", + G_CALLBACK (open_data_cb), 0); + } + + return box; +} + +static void +setup_color_ok_cb (GtkWidget *button, GtkWidget *dialog) +{ + GtkColorSelectionDialog *cdialog = GTK_COLOR_SELECTION_DIALOG (dialog); + GdkColor *col; + GdkColor old_color; + GtkStyle *style; + + col = g_object_get_data (G_OBJECT (button), "c"); + old_color = *col; + + button = g_object_get_data (G_OBJECT (button), "b"); + + if (!GTK_IS_WIDGET (button)) + { + gtk_widget_destroy (dialog); + return; + } + + color_change = TRUE; + + gtk_color_selection_get_current_color (GTK_COLOR_SELECTION (cdialog->colorsel), col); + + gdk_colormap_alloc_color (gtk_widget_get_colormap (button), col, TRUE, TRUE); + + style = gtk_style_new (); + style->bg[0] = *col; + gtk_widget_set_style (button, style); + g_object_unref (style); + + /* is this line correct?? */ + gdk_colormap_free_colors (gtk_widget_get_colormap (button), &old_color, 1); + + gtk_widget_destroy (dialog); +} + +static void +setup_color_cb (GtkWidget *button, gpointer userdata) +{ + GtkWidget *dialog; + GtkColorSelectionDialog *cdialog; + GdkColor *color; + + color = &colors[GPOINTER_TO_INT (userdata)]; + + dialog = gtk_color_selection_dialog_new (_("Select color")); + cdialog = GTK_COLOR_SELECTION_DIALOG (dialog); + + gtk_widget_hide (cdialog->help_button); + g_signal_connect (G_OBJECT (cdialog->ok_button), "clicked", + G_CALLBACK (setup_color_ok_cb), dialog); + g_signal_connect (G_OBJECT (cdialog->cancel_button), "clicked", + G_CALLBACK (gtkutil_destroy), dialog); + g_object_set_data (G_OBJECT (cdialog->ok_button), "c", color); + g_object_set_data (G_OBJECT (cdialog->ok_button), "b", button); + gtk_widget_set_sensitive (cdialog->help_button, FALSE); + gtk_color_selection_set_current_color (GTK_COLOR_SELECTION (cdialog->colorsel), color); + gtk_widget_show (dialog); +} + +static void +setup_create_color_button (GtkWidget *table, int num, int row, int col) +{ + GtkWidget *but; + GtkStyle *style; + char buf[64]; + + if (num > 31) + strcpy (buf, "<span size=\"x-small\"> </span>"); + else + /* 12345678901 23456789 01 23456789 */ + sprintf (buf, "<span size=\"x-small\">%d</span>", num); + but = gtk_button_new_with_label (" "); + gtk_label_set_markup (GTK_LABEL (GTK_BIN (but)->child), buf); + /* win32 build uses this to turn off themeing */ + g_object_set_data (G_OBJECT (but), "xchat-color", (gpointer)1); + gtk_table_attach (GTK_TABLE (table), but, col, col+1, row, row+1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, 0, 0); + g_signal_connect (G_OBJECT (but), "clicked", + G_CALLBACK (setup_color_cb), GINT_TO_POINTER (num)); + style = gtk_style_new (); + style->bg[GTK_STATE_NORMAL] = colors[num]; + gtk_widget_set_style (but, style); + g_object_unref (style); +} + +static void +setup_create_other_colorR (char *text, int num, int row, GtkWidget *tab) +{ + GtkWidget *label; + + label = gtk_label_new (text); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (tab), label, 5, 9, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + setup_create_color_button (tab, num, row, 9); +} + +static void +setup_create_other_color (char *text, int num, int row, GtkWidget *tab) +{ + GtkWidget *label; + + label = gtk_label_new (text); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (tab), label, 2, 3, row, row + 1, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + setup_create_color_button (tab, num, row, 3); +} + +static GtkWidget * +setup_create_color_page (void) +{ + GtkWidget *tab, *box, *label; + int i; + + box = gtk_vbox_new (FALSE, 0); + gtk_container_set_border_width (GTK_CONTAINER (box), 6); + + tab = gtk_table_new (9, 2, FALSE); + gtk_container_set_border_width (GTK_CONTAINER (tab), 6); + gtk_table_set_row_spacings (GTK_TABLE (tab), 2); + gtk_table_set_col_spacings (GTK_TABLE (tab), 3); + gtk_container_add (GTK_CONTAINER (box), tab); + + setup_create_header (tab, 0, N_("Text Colors")); + + label = gtk_label_new (_("mIRC colors:")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (tab), label, 2, 3, 1, 2, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + + for (i = 0; i < 16; i++) + setup_create_color_button (tab, i, 1, i+3); + + label = gtk_label_new (_("Local colors:")); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_table_attach (GTK_TABLE (tab), label, 2, 3, 2, 3, + GTK_SHRINK | GTK_FILL, GTK_SHRINK | GTK_FILL, LABEL_INDENT, 0); + + for (i = 16; i < 32; i++) + setup_create_color_button (tab, i, 2, (i+3) - 16); + + setup_create_other_color (_("Foreground:"), COL_FG, 3, tab); + setup_create_other_colorR (_("Background:"), COL_BG, 3, tab); + + setup_create_header (tab, 5, N_("Marking Text")); + + setup_create_other_color (_("Foreground:"), COL_MARK_FG, 6, tab); + setup_create_other_colorR (_("Background:"), COL_MARK_BG, 6, tab); + + setup_create_header (tab, 8, N_("Interface Colors")); + + setup_create_other_color (_("New data:"), COL_NEW_DATA, 9, tab); + setup_create_other_colorR (_("Marker line:"), COL_MARKER, 9, tab); + setup_create_other_color (_("New message:"), COL_NEW_MSG, 10, tab); + setup_create_other_colorR (_("Away user:"), COL_AWAY, 10, tab); + setup_create_other_color (_("Highlight:"), COL_HILIGHT, 11, tab); + + return box; +} + +/* === GLOBALS for sound GUI === */ + +static GtkWidget *sndprog_entry; +static GtkWidget *sndfile_entry; +static GtkWidget *snddir_entry; +static int ignore_changed = FALSE; + +extern struct text_event te[]; /* text.c */ +extern char *sound_files[]; + +static void +setup_snd_apply (void) +{ + strcpy (setup_prefs.sounddir, GTK_ENTRY (snddir_entry)->text); + strcpy (setup_prefs.soundcmd, GTK_ENTRY (sndprog_entry)->text); +} + +static void +setup_snd_populate (GtkTreeView * treeview) +{ + GtkListStore *store; + GtkTreeIter iter; + GtkTreeSelection *sel; + GtkTreePath *path; + int i; + + sel = gtk_tree_view_get_selection (treeview); + store = (GtkListStore *)gtk_tree_view_get_model (treeview); + + for (i = NUM_XP-1; i >= 0; i--) + { + gtk_list_store_prepend (store, &iter); + if (sound_files[i]) + gtk_list_store_set (store, &iter, 0, te[i].name, 1, sound_files[i], 2, i, -1); + else + gtk_list_store_set (store, &iter, 0, te[i].name, 1, "", 2, i, -1); + if (i == last_selected_row) + { + gtk_tree_selection_select_iter (sel, &iter); + path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter); + if (path) + { + gtk_tree_view_scroll_to_cell (treeview, path, NULL, TRUE, 0.5, 0.5); + gtk_tree_view_set_cursor (treeview, path, NULL, FALSE); + gtk_tree_path_free (path); + } + } + } +} + +static int +setup_snd_get_selected (GtkTreeSelection *sel, GtkTreeIter *iter) +{ + int n; + GtkTreeModel *model; + + if (!gtk_tree_selection_get_selected (sel, &model, iter)) + return -1; + + gtk_tree_model_get (model, iter, 2, &n, -1); + return n; +} + +static void +setup_snd_row_cb (GtkTreeSelection *sel, gpointer user_data) +{ + int n; + GtkTreeIter iter; + + n = setup_snd_get_selected (sel, &iter); + if (n == -1) + return; + last_selected_row = n; + + ignore_changed = TRUE; + if (sound_files[n]) + gtk_entry_set_text (GTK_ENTRY (sndfile_entry), sound_files[n]); + else + gtk_entry_set_text (GTK_ENTRY (sndfile_entry), ""); + ignore_changed = FALSE; +} + +static void +setup_snd_add_columns (GtkTreeView * treeview) +{ + GtkCellRenderer *renderer; + GtkTreeModel *model; + + /* event column */ + renderer = gtk_cell_renderer_text_new (); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview), + -1, _("Event"), renderer, + "text", 0, NULL); + + /* file column */ + renderer = gtk_cell_renderer_text_new (); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview), + -1, _("Sound file"), renderer, + "text", 1, NULL); + + model = GTK_TREE_MODEL (gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_INT)); + gtk_tree_view_set_model (GTK_TREE_VIEW (treeview), model); + g_object_unref (model); +} + +static void +setup_autotoggle_cb (GtkToggleButton *but, GtkToggleButton *ext) +{ + if (but->active) + { + setup_prefs.soundcmd[0] = 0; + gtk_entry_set_text (GTK_ENTRY (sndprog_entry), ""); + gtk_widget_set_sensitive (sndprog_entry, FALSE); + } else + { + gtk_widget_set_sensitive (sndprog_entry, TRUE); + } +} + +static void +setup_snd_filereq_cb (GtkWidget *entry, char *file) +{ + if (file) + { + if (file[0]) + gtk_entry_set_text (GTK_ENTRY (entry), file); + } +} + +static void +setup_snd_browse_cb (GtkWidget *button, GtkEntry *entry) +{ + gtkutil_file_req (_("Select a sound file"), setup_snd_filereq_cb, entry, NULL, 0); +} + +static void +setup_snd_play_cb (GtkWidget *button, GtkEntry *entry) +{ + sound_play (entry->text, FALSE); +} + +static void +setup_snd_changed_cb (GtkEntry *ent, GtkTreeView *tree) +{ + int n; + GtkTreeIter iter; + GtkListStore *store; + GtkTreeSelection *sel; + + if (ignore_changed) + return; + + sel = gtk_tree_view_get_selection (tree); + n = setup_snd_get_selected (sel, &iter); + if (n == -1) + return; + + /* get the new sound file */ + if (sound_files[n]) + free (sound_files[n]); + sound_files[n] = strdup (GTK_ENTRY (ent)->text); + + /* update the TreeView list */ + store = (GtkListStore *)gtk_tree_view_get_model (tree); + gtk_list_store_set (store, &iter, 1, sound_files[n], -1); + + gtk_widget_set_sensitive (cancel_button, FALSE); +} + +static GtkWidget * +setup_create_sound_page (void) +{ + GtkWidget *vbox1; + GtkWidget *vbox2; + GtkWidget *table2; + GtkWidget *label2; + GtkWidget *label3; + GtkWidget *radio_external; + GSList *radio_group = NULL; + GtkWidget *radio_auto; + GtkWidget *label4; + GtkWidget *entry3; + GtkWidget *scrolledwindow1; + GtkWidget *sound_tree; + GtkWidget *table1; + GtkWidget *sound_label; + GtkWidget *sound_browse; + GtkWidget *sound_play; + GtkTreeSelection *sel; + + vbox1 = gtk_vbox_new (FALSE, 0); + gtk_container_set_border_width (GTK_CONTAINER (vbox1), 6); + gtk_widget_show (vbox1); + + vbox2 = gtk_vbox_new (FALSE, 0); + gtk_widget_show (vbox2); + gtk_container_add (GTK_CONTAINER (vbox1), vbox2); + + table2 = gtk_table_new (4, 3, FALSE); + gtk_widget_show (table2); + gtk_box_pack_start (GTK_BOX (vbox2), table2, FALSE, TRUE, 8); + gtk_table_set_row_spacings (GTK_TABLE (table2), 2); + gtk_table_set_col_spacings (GTK_TABLE (table2), 4); + + label2 = gtk_label_new (_("Sound playing method:")); + gtk_widget_show (label2); + gtk_table_attach (GTK_TABLE (table2), label2, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label2), 0, 0.5); + + label3 = + gtk_label_new_with_mnemonic (_("External sound playing _program:")); + gtk_widget_show (label3); + gtk_table_attach (GTK_TABLE (table2), label3, 0, 1, 2, 3, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label3), 0, 0.5); + + sndprog_entry = gtk_entry_new (); + if (setup_prefs.soundcmd[0] == 0) + gtk_widget_set_sensitive (sndprog_entry, FALSE); + else + gtk_entry_set_text (GTK_ENTRY (sndprog_entry), setup_prefs.soundcmd); + gtk_widget_show (sndprog_entry); + gtk_table_attach (GTK_TABLE (table2), sndprog_entry, 1, 3, 2, 3, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + radio_external = + gtk_radio_button_new_with_mnemonic (NULL, _("_External program")); + gtk_widget_show (radio_external); + gtk_table_attach (GTK_TABLE (table2), radio_external, 1, 3, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_radio_button_set_group (GTK_RADIO_BUTTON (radio_external), + radio_group); + radio_group = + gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio_external)); + + radio_auto = gtk_radio_button_new_with_mnemonic (NULL, _("_Automatic")); + g_signal_connect (G_OBJECT (radio_auto), "toggled", + G_CALLBACK (setup_autotoggle_cb), radio_external); + gtk_widget_show (radio_auto); + gtk_table_attach (GTK_TABLE (table2), radio_auto, 1, 3, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_radio_button_set_group (GTK_RADIO_BUTTON (radio_auto), + radio_group); + radio_group = + gtk_radio_button_get_group (GTK_RADIO_BUTTON (radio_auto)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (radio_auto), setup_prefs.soundcmd[0] == 0); + + label4 = gtk_label_new_with_mnemonic (_("Sound files _directory:")); + gtk_widget_show (label4); + gtk_table_attach (GTK_TABLE (table2), label4, 0, 1, 3, 4, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (label4), 0, 0.5); + + snddir_entry = entry3 = gtk_entry_new (); + gtk_entry_set_text (GTK_ENTRY (entry3), setup_prefs.sounddir); + gtk_widget_show (entry3); + gtk_table_attach (GTK_TABLE (table2), entry3, 1, 3, 3, 4, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + scrolledwindow1 = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (scrolledwindow1); + gtk_container_add (GTK_CONTAINER (vbox2), scrolledwindow1); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledwindow1), + GTK_POLICY_AUTOMATIC, GTK_POLICY_ALWAYS); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolledwindow1), + GTK_SHADOW_IN); + + sound_tree = gtk_tree_view_new (); + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (sound_tree)); + gtk_tree_selection_set_mode (sel, GTK_SELECTION_SINGLE); + setup_snd_add_columns (GTK_TREE_VIEW (sound_tree)); + setup_snd_populate (GTK_TREE_VIEW (sound_tree)); + g_signal_connect (G_OBJECT (sel), "changed", + G_CALLBACK (setup_snd_row_cb), NULL); + gtk_widget_show (sound_tree); + gtk_container_add (GTK_CONTAINER (scrolledwindow1), sound_tree); + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (sound_tree), TRUE); + + table1 = gtk_table_new (2, 3, FALSE); + gtk_widget_show (table1); + gtk_box_pack_start (GTK_BOX (vbox2), table1, FALSE, TRUE, 8); + gtk_table_set_row_spacings (GTK_TABLE (table1), 2); + gtk_table_set_col_spacings (GTK_TABLE (table1), 4); + + sound_label = gtk_label_new_with_mnemonic (_("Sound file:")); + gtk_widget_show (sound_label); + gtk_table_attach (GTK_TABLE (table1), sound_label, 0, 1, 0, 1, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + gtk_misc_set_alignment (GTK_MISC (sound_label), 0, 0.5); + + sndfile_entry = gtk_entry_new (); + g_signal_connect (G_OBJECT (sndfile_entry), "changed", + G_CALLBACK (setup_snd_changed_cb), sound_tree); + gtk_widget_show (sndfile_entry); + gtk_table_attach (GTK_TABLE (table1), sndfile_entry, 0, 1, 1, 2, + (GtkAttachOptions) (GTK_EXPAND | GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + sound_browse = gtk_button_new_with_mnemonic (_("_Browse...")); + g_signal_connect (G_OBJECT (sound_browse), "clicked", + G_CALLBACK (setup_snd_browse_cb), sndfile_entry); + gtk_widget_show (sound_browse); + gtk_table_attach (GTK_TABLE (table1), sound_browse, 1, 2, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + +#ifdef GTK_STOCK_MEDIA_PLAY + sound_play = gtk_button_new_from_stock (GTK_STOCK_MEDIA_PLAY); +#else + sound_play = gtk_button_new_with_mnemonic (_("_Play")); +#endif + g_signal_connect (G_OBJECT (sound_play), "clicked", + G_CALLBACK (setup_snd_play_cb), sndfile_entry); + gtk_widget_show (sound_play); + gtk_table_attach (GTK_TABLE (table1), sound_play, 2, 3, 1, 2, + (GtkAttachOptions) (GTK_FILL), + (GtkAttachOptions) (0), 0, 0); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label3), sndprog_entry); + gtk_label_set_mnemonic_widget (GTK_LABEL (label4), entry3); + setup_snd_row_cb (sel, NULL); + + return vbox1; +} + +static void +setup_add_page (const char *title, GtkWidget *book, GtkWidget *tab) +{ + GtkWidget *oframe, *frame, *label, *vvbox; + char buf[128]; + + /* frame for whole page */ + oframe = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (oframe), GTK_SHADOW_IN); + + vvbox = gtk_vbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (oframe), vvbox); + + /* border for the label */ + frame = gtk_frame_new (NULL); + gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT); + gtk_box_pack_start (GTK_BOX (vvbox), frame, FALSE, TRUE, 0); + + /* label */ + label = gtk_label_new (NULL); + snprintf (buf, sizeof (buf), "<b><big>%s</big></b>", _(title)); + gtk_label_set_markup (GTK_LABEL (label), buf); + gtk_misc_set_alignment (GTK_MISC (label), 0.0, 0.5); + gtk_misc_set_padding (GTK_MISC (label), 2, 1); + gtk_container_add (GTK_CONTAINER (frame), label); + + gtk_container_add (GTK_CONTAINER (vvbox), tab); + + gtk_notebook_append_page (GTK_NOTEBOOK (book), oframe, NULL); +} + +static const char *const cata[] = +{ + N_("Interface"), + N_("Text box"), + N_("Input box"), + N_("User list"), + N_("Channel switcher"), + N_("Colors"), + NULL, + N_("Chatting"), + N_("Alerts"), + N_("General"), + N_("Logging"), + N_("Sound"), +/* N_("Advanced"),*/ + NULL, + N_("Network"), + N_("Network setup"), + N_("File transfers"), + NULL, + NULL +}; + +static GtkWidget * +setup_create_pages (GtkWidget *box) +{ + GtkWidget *book; + + book = gtk_notebook_new (); + + setup_add_page (cata[1], book, setup_create_page (textbox_settings)); + setup_add_page (cata[2], book, setup_create_page (inputbox_settings)); + setup_add_page (cata[3], book, setup_create_page (userlist_settings)); + setup_add_page (cata[4], book, setup_create_page (tabs_settings)); + setup_add_page (cata[5], book, setup_create_color_page ()); + setup_add_page (cata[8], book, setup_create_page (alert_settings)); + setup_add_page (cata[9], book, setup_create_page (general_settings)); + setup_add_page (cata[10], book, setup_create_page (logging_settings)); + setup_add_page (cata[11], book, setup_create_sound_page ()); + setup_add_page (cata[14], book, setup_create_page (network_settings)); + setup_add_page (cata[15], book, setup_create_page (filexfer_settings)); + + gtk_notebook_set_show_tabs (GTK_NOTEBOOK (book), FALSE); + gtk_notebook_set_show_border (GTK_NOTEBOOK (book), FALSE); + gtk_container_add (GTK_CONTAINER (box), book); + + return book; +} + +static void +setup_tree_cb (GtkTreeView *treeview, GtkWidget *book) +{ + GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview); + GtkTreeIter iter; + GtkTreeModel *model; + int page; + + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + { + gtk_tree_model_get (model, &iter, 1, &page, -1); + if (page != -1) + { + gtk_notebook_set_current_page (GTK_NOTEBOOK (book), page); + last_selected_page = page; + } + } +} + +static gboolean +setup_tree_select_filter (GtkTreeSelection *selection, GtkTreeModel *model, + GtkTreePath *path, gboolean path_selected, + gpointer data) +{ + if (gtk_tree_path_get_depth (path) > 1) + return TRUE; + return FALSE; +} + +static void +setup_create_tree (GtkWidget *box, GtkWidget *book) +{ + GtkWidget *tree; + GtkWidget *frame; + GtkTreeStore *model; + GtkTreeIter iter; + GtkTreeIter child_iter; + GtkTreeIter *sel_iter = NULL; + GtkCellRenderer *renderer; + GtkTreeSelection *sel; + int i, page; + + model = gtk_tree_store_new (2, G_TYPE_STRING, G_TYPE_INT); + + i = 0; + page = 0; + do + { + gtk_tree_store_append (model, &iter, NULL); + gtk_tree_store_set (model, &iter, 0, _(cata[i]), 1, -1, -1); + i++; + + do + { + gtk_tree_store_append (model, &child_iter, &iter); + gtk_tree_store_set (model, &child_iter, 0, _(cata[i]), 1, page, -1); + if (page == last_selected_page) + sel_iter = gtk_tree_iter_copy (&child_iter); + page++; + i++; + } while (cata[i]); + + i++; + + } while (cata[i]); + + tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (model)); + g_object_unref (G_OBJECT (model)); + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree)); + gtk_tree_selection_set_mode (sel, GTK_SELECTION_BROWSE); + gtk_tree_selection_set_select_function (sel, setup_tree_select_filter, + NULL, NULL); + g_signal_connect (G_OBJECT (tree), "cursor_changed", + G_CALLBACK (setup_tree_cb), book); + + renderer = gtk_cell_renderer_text_new (); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (tree), + -1, _("Categories"), renderer, "text", 0, NULL); + gtk_tree_view_expand_all (GTK_TREE_VIEW (tree)); + + frame = gtk_frame_new (NULL); + gtk_container_add (GTK_CONTAINER (frame), tree); + gtk_box_pack_start (GTK_BOX (box), frame, 0, 0, 0); + gtk_box_reorder_child (GTK_BOX (box), frame, 0); + + if (sel_iter) + { + gtk_tree_selection_select_iter (sel, sel_iter); + gtk_tree_iter_free (sel_iter); + } +} + +static void +setup_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 +setup_apply_to_sess (session_gui *gui) +{ +#ifdef USE_GTKSPELL + GtkSpell *spell; +#endif + + mg_update_xtext (gui->xtext); + + if (prefs.style_namelistgad) + gtk_widget_set_style (gui->user_tree, input_style); + + if (prefs.style_inputbox) + { + extern char cursor_color_rc[]; + char buf[256]; + sprintf (buf, cursor_color_rc, + (colors[COL_FG].red >> 8), + (colors[COL_FG].green >> 8), + (colors[COL_FG].blue >> 8)); + gtk_rc_parse_string (buf); + + setup_apply_entry_style (gui->input_box); + setup_apply_entry_style (gui->limit_entry); + setup_apply_entry_style (gui->key_entry); + setup_apply_entry_style (gui->topic_entry); + } + + if (prefs.userlistbuttons) + gtk_widget_show (gui->button_box); + else + gtk_widget_hide (gui->button_box); + +#ifdef USE_GTKSPELL + spell = gtkspell_get_from_text_view (GTK_TEXT_VIEW (gui->input_box)); + if (prefs.gui_input_spell) + { + if (!spell) + gtkspell_new_attach (GTK_TEXT_VIEW (gui->input_box), NULL, NULL); + } + else + { + if (spell) + gtkspell_detach (spell); + } +#endif + +#ifdef USE_LIBSEXY + sexy_spell_entry_set_checked ((SexySpellEntry *)gui->input_box, prefs.gui_input_spell); +#endif +} + +static void +unslash (char *dir) +{ + if (dir[0]) + { + int len = strlen (dir) - 1; +#ifdef WIN32 + if (dir[len] == '/' || dir[len] == '\\') +#else + if (dir[len] == '/') +#endif + dir[len] = 0; + } +} + +void +setup_apply_real (int new_pix, int do_ulist, int do_layout) +{ + GSList *list; + session *sess; + int done_main = FALSE; + + /* remove trailing slashes */ + unslash (prefs.dccdir); + unslash (prefs.dcc_completed_dir); + + mkdir_utf8 (prefs.dccdir); + mkdir_utf8 (prefs.dcc_completed_dir); + + if (new_pix) + { + if (channelwin_pix) + g_object_unref (channelwin_pix); + channelwin_pix = pixmap_load_from_file (prefs.background); + } + + input_style = create_input_style (input_style); + + list = sess_list; + while (list) + { + sess = list->data; + if (sess->gui->is_tab) + { + /* only apply to main tabwindow once */ + if (!done_main) + { + done_main = TRUE; + setup_apply_to_sess (sess->gui); + } + } else + { + setup_apply_to_sess (sess->gui); + } + + log_open_or_close (sess); + + if (do_ulist) + userlist_rehash (sess); + + list = list->next; + } + + mg_apply_setup (); + tray_apply_setup (); + + if (do_layout) + menu_change_layout (); +} + +static void +setup_apply (struct xchatprefs *pr) +{ + int new_pix = FALSE; + int noapply = FALSE; + int do_ulist = FALSE; + int do_layout = FALSE; + + if (strcmp (pr->background, prefs.background) != 0) + new_pix = TRUE; + +#define DIFF(a) (pr->a != prefs.a) + + if (DIFF (paned_userlist)) + noapply = TRUE; + if (DIFF (lagometer)) + noapply = TRUE; + if (DIFF (throttlemeter)) + noapply = TRUE; + if (DIFF (showhostname_in_userlist)) + noapply = TRUE; + if (DIFF (tab_small)) + noapply = TRUE; + if (DIFF (tab_sort)) + noapply = TRUE; + if (DIFF (use_server_tab)) + noapply = TRUE; + if (DIFF (style_namelistgad)) + noapply = TRUE; + if (DIFF (truncchans)) + noapply = TRUE; + if (DIFF (tab_layout)) + do_layout = TRUE; + + if (color_change || (DIFF (away_size_max)) || (DIFF (away_track))) + do_ulist = TRUE; + + if ((pr->tab_pos == 5 || pr->tab_pos == 6) && + pr->tab_layout == 2 && pr->tab_pos != prefs.tab_pos) + fe_message (_("You cannot place the tree on the top or bottom!\n" + "Please change to the <b>Tabs</b> layout in the <b>View</b>" + " menu first."), + FE_MSG_WARN | FE_MSG_MARKUP); + + memcpy (&prefs, pr, sizeof (prefs)); + + setup_apply_real (new_pix, do_ulist, do_layout); + + if (noapply) + fe_message (_("Some settings were changed that require a" + " restart to take full effect."), FE_MSG_WARN); + +#ifndef WIN32 + if (prefs.autodccsend == 1) + { + if (!strcmp ((char *)g_get_home_dir (), prefs.dccdir)) + { + fe_message (_("*WARNING*\n" + "Auto accepting DCC to your home directory\n" + "can be dangerous and is exploitable. Eg:\n" + "Someone could send you a .bash_profile"), FE_MSG_WARN); + } + } +#endif +} + +#if 0 +static void +setup_apply_cb (GtkWidget *but, GtkWidget *win) +{ + /* setup_prefs -> xchat */ + setup_apply (&setup_prefs); +} +#endif + +static void +setup_ok_cb (GtkWidget *but, GtkWidget *win) +{ + setup_snd_apply (); + gtk_widget_destroy (win); + setup_apply (&setup_prefs); + save_config (); + palette_save (); +} + +static GtkWidget * +setup_window_open (void) +{ + GtkWidget *wid, *win, *vbox, *hbox, *hbbox; + + win = gtkutil_window_new (_("XChat: Preferences"), "prefs", 0, 0, 3); + + vbox = gtk_vbox_new (FALSE, 5); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 6); + gtk_container_add (GTK_CONTAINER (win), vbox); + + hbox = gtk_hbox_new (FALSE, 4); + gtk_container_add (GTK_CONTAINER (vbox), hbox); + + setup_create_tree (hbox, setup_create_pages (hbox)); + + hbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_end (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + + /* prepare the button box */ + hbbox = gtk_hbutton_box_new (); + gtk_box_set_spacing (GTK_BOX (hbbox), 4); + gtk_box_pack_end (GTK_BOX (hbox), hbbox, FALSE, FALSE, 0); + + /* standard buttons */ + /* GNOME doesn't like apply */ +#if 0 + wid = gtk_button_new_from_stock (GTK_STOCK_APPLY); + g_signal_connect (G_OBJECT (wid), "clicked", + G_CALLBACK (setup_apply_cb), win); + gtk_box_pack_start (GTK_BOX (hbbox), wid, FALSE, FALSE, 0); +#endif + + cancel_button = wid = gtk_button_new_from_stock (GTK_STOCK_CANCEL); + g_signal_connect (G_OBJECT (wid), "clicked", + G_CALLBACK (gtkutil_destroy), win); + gtk_box_pack_start (GTK_BOX (hbbox), wid, FALSE, FALSE, 0); + + wid = gtk_button_new_from_stock (GTK_STOCK_OK); + g_signal_connect (G_OBJECT (wid), "clicked", + G_CALLBACK (setup_ok_cb), win); + gtk_box_pack_start (GTK_BOX (hbbox), wid, FALSE, FALSE, 0); + + wid = gtk_hseparator_new (); + gtk_box_pack_end (GTK_BOX (vbox), wid, FALSE, FALSE, 0); + + gtk_widget_show_all (win); + + return win; +} + +static void +setup_close_cb (GtkWidget *win, GtkWidget **swin) +{ + *swin = NULL; + + if (font_dialog) + { + gtk_widget_destroy (font_dialog); + font_dialog = NULL; + } +} + +void +setup_open (void) +{ + static GtkWidget *setup_window = NULL; + + if (setup_window) + { + gtk_window_present (GTK_WINDOW (setup_window)); + return; + } + + memcpy (&setup_prefs, &prefs, sizeof (prefs)); + + color_change = FALSE; + setup_window = setup_window_open (); + + g_signal_connect (G_OBJECT (setup_window), "destroy", + G_CALLBACK (setup_close_cb), &setup_window); +} diff --git a/src/fe-gtk/sexy-spell-entry.c b/src/fe-gtk/sexy-spell-entry.c new file mode 100644 index 00000000..d67ffe2d --- /dev/null +++ b/src/fe-gtk/sexy-spell-entry.c @@ -0,0 +1,1329 @@ +/* + * @file libsexy/sexy-icon-entry.c Entry widget + * + * @Copyright (C) 2004-2006 Christian Hammond. + * Some of this code is from gtkspell, Copyright (C) 2002 Evan Martin. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include <gtk/gtk.h> +#include "sexy-spell-entry.h" +#include <string.h> +#include <glib/gi18n.h> +#include <sys/types.h> +/*#include "gtkspell-iso-codes.h" +#include "sexy-marshal.h"*/ + +/* + * Bunch of poop to make enchant into a runtime dependency rather than a + * compile-time dependency. This makes it so I don't have to hear the + * complaints from people with binary distributions who don't get spell + * checking because they didn't check their configure output. + */ +struct EnchantDict; +struct EnchantBroker; + +typedef void (*EnchantDictDescribeFn) (const char * const lang_tag, + const char * const provider_name, + const char * const provider_desc, + const char * const provider_file, + void * user_data); + +static struct EnchantBroker * (*enchant_broker_init) (void); +static void (*enchant_broker_free) (struct EnchantBroker * broker); +static void (*enchant_broker_free_dict) (struct EnchantBroker * broker, struct EnchantDict * dict); +static void (*enchant_broker_list_dicts) (struct EnchantBroker * broker, EnchantDictDescribeFn fn, void * user_data); +static struct EnchantDict * (*enchant_broker_request_dict) (struct EnchantBroker * broker, const char *const tag); + +static void (*enchant_dict_add_to_personal) (struct EnchantDict * dict, const char *const word, ssize_t len); +static void (*enchant_dict_add_to_session) (struct EnchantDict * dict, const char *const word, ssize_t len); +static int (*enchant_dict_check) (struct EnchantDict * dict, const char *const word, ssize_t len); +static void (*enchant_dict_describe) (struct EnchantDict * dict, EnchantDictDescribeFn fn, void * user_data); +static void (*enchant_dict_free_suggestions) (struct EnchantDict * dict, char **suggestions); +static void (*enchant_dict_store_replacement) (struct EnchantDict * dict, const char *const mis, ssize_t mis_len, const char *const cor, ssize_t cor_len); +static char ** (*enchant_dict_suggest) (struct EnchantDict * dict, const char *const word, ssize_t len, size_t * out_n_suggs); +static gboolean have_enchant = FALSE; + +struct _SexySpellEntryPriv +{ + struct EnchantBroker *broker; + PangoAttrList *attr_list; + gint mark_character; + GHashTable *dict_hash; + GSList *dict_list; + gchar **words; + gint *word_starts; + gint *word_ends; + gboolean checked; +}; + +static void sexy_spell_entry_class_init(SexySpellEntryClass *klass); +static void sexy_spell_entry_editable_init (GtkEditableClass *iface); +static void sexy_spell_entry_init(SexySpellEntry *entry); +static void sexy_spell_entry_finalize(GObject *obj); +static void sexy_spell_entry_destroy(GtkObject *obj); +static gint sexy_spell_entry_expose(GtkWidget *widget, GdkEventExpose *event); +static gint sexy_spell_entry_button_press(GtkWidget *widget, GdkEventButton *event); + +/* GtkEditable handlers */ +static void sexy_spell_entry_changed(GtkEditable *editable, gpointer data); + +/* Other handlers */ +static gboolean sexy_spell_entry_popup_menu(GtkWidget *widget, SexySpellEntry *entry); + +/* Internal utility functions */ +static gint gtk_entry_find_position (GtkEntry *entry, + gint x); +static gboolean word_misspelled (SexySpellEntry *entry, + int start, + int end); +static gboolean default_word_check (SexySpellEntry *entry, + const gchar *word); +static gboolean sexy_spell_entry_activate_language_internal (SexySpellEntry *entry, + const gchar *lang, + GError **error); +static gchar *get_lang_from_dict (struct EnchantDict *dict); +static void sexy_spell_entry_recheck_all (SexySpellEntry *entry); +static void entry_strsplit_utf8 (GtkEntry *entry, + gchar ***set, + gint **starts, + gint **ends); + +static GtkEntryClass *parent_class = NULL; + +G_DEFINE_TYPE_EXTENDED(SexySpellEntry, sexy_spell_entry, GTK_TYPE_ENTRY, 0, G_IMPLEMENT_INTERFACE(GTK_TYPE_EDITABLE, sexy_spell_entry_editable_init)); + +enum +{ + WORD_CHECK, + LAST_SIGNAL +}; +static guint signals[LAST_SIGNAL] = {0}; + +static gboolean +spell_accumulator(GSignalInvocationHint *hint, GValue *return_accu, const GValue *handler_return, gpointer data) +{ + gboolean ret = g_value_get_boolean(handler_return); + /* Handlers return TRUE if the word is misspelled. In this + * case, it means that we want to stop if the word is checked + * as correct */ + g_value_set_boolean (return_accu, ret); + return ret; +} + +static void +initialize_enchant () +{ + GModule *enchant; + gpointer funcptr; + + enchant = g_module_open("libenchant", 0); + if (enchant == NULL) + { + enchant = g_module_open("libenchant.so.1", 0); + if (enchant == NULL) + return; + } + + have_enchant = TRUE; + +#define MODULE_SYMBOL(name, func) \ + g_module_symbol(enchant, (name), &funcptr); \ + (func) = funcptr; + + MODULE_SYMBOL("enchant_broker_init", enchant_broker_init) + MODULE_SYMBOL("enchant_broker_free", enchant_broker_free) + MODULE_SYMBOL("enchant_broker_free_dict", enchant_broker_free_dict) + MODULE_SYMBOL("enchant_broker_list_dicts", enchant_broker_list_dicts) + MODULE_SYMBOL("enchant_broker_request_dict", enchant_broker_request_dict) + + MODULE_SYMBOL("enchant_dict_add_to_personal", enchant_dict_add_to_personal) + MODULE_SYMBOL("enchant_dict_add_to_session", enchant_dict_add_to_session) + MODULE_SYMBOL("enchant_dict_check", enchant_dict_check) + MODULE_SYMBOL("enchant_dict_describe", enchant_dict_describe) + MODULE_SYMBOL("enchant_dict_free_suggestions", + enchant_dict_free_suggestions) + MODULE_SYMBOL("enchant_dict_store_replacement", + enchant_dict_store_replacement) + MODULE_SYMBOL("enchant_dict_suggest", enchant_dict_suggest) + +#undef MODULE_SYMBOL +} + +static void +sexy_spell_entry_class_init(SexySpellEntryClass *klass) +{ + GObjectClass *gobject_class; + GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + GtkEntryClass *entry_class; + + initialize_enchant(); + + parent_class = g_type_class_peek_parent(klass); + + gobject_class = G_OBJECT_CLASS(klass); + object_class = GTK_OBJECT_CLASS(klass); + widget_class = GTK_WIDGET_CLASS(klass); + entry_class = GTK_ENTRY_CLASS(klass); + + if (have_enchant) + klass->word_check = default_word_check; + + gobject_class->finalize = sexy_spell_entry_finalize; + + object_class->destroy = sexy_spell_entry_destroy; + + widget_class->expose_event = sexy_spell_entry_expose; + widget_class->button_press_event = sexy_spell_entry_button_press; + + /** + * SexySpellEntry::word-check: + * @entry: The entry on which the signal is emitted. + * @word: The word to check. + * + * The ::word-check signal is emitted whenever the entry has to check + * a word. This allows the application to mark words as correct even + * if none of the active dictionaries contain it, such as nicknames in + * a chat client. + * + * Returns: %FALSE to indicate that the word should be marked as + * correct. + */ +/* signals[WORD_CHECK] = g_signal_new("word_check", + G_TYPE_FROM_CLASS(object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET(SexySpellEntryClass, word_check), + (GSignalAccumulator) spell_accumulator, NULL, + sexy_marshal_BOOLEAN__STRING, + G_TYPE_BOOLEAN, + 1, G_TYPE_STRING);*/ +} + +static void +sexy_spell_entry_editable_init (GtkEditableClass *iface) +{ +} + +static gint +gtk_entry_find_position (GtkEntry *entry, gint x) +{ + PangoLayout *layout; + PangoLayoutLine *line; + const gchar *text; + gint cursor_index; + gint index; + gint pos; + gboolean trailing; + + x = x + entry->scroll_offset; + + layout = gtk_entry_get_layout(entry); + text = pango_layout_get_text(layout); + cursor_index = g_utf8_offset_to_pointer(text, entry->current_pos) - text; + + line = pango_layout_get_lines(layout)->data; + pango_layout_line_x_to_index(line, x * PANGO_SCALE, &index, &trailing); + + if (index >= cursor_index && entry->preedit_length) { + if (index >= cursor_index + entry->preedit_length) { + index -= entry->preedit_length; + } else { + index = cursor_index; + trailing = FALSE; + } + } + + pos = g_utf8_pointer_to_offset (text, text + index); + pos += trailing; + + return pos; +} + +static void +insert_underline(SexySpellEntry *entry, guint start, guint end) +{ + PangoAttribute *ucolor = pango_attr_underline_color_new (65535, 0, 0); + PangoAttribute *unline = pango_attr_underline_new (PANGO_UNDERLINE_ERROR); + + ucolor->start_index = start; + unline->start_index = start; + + ucolor->end_index = end; + unline->end_index = end; + + pango_attr_list_insert (entry->priv->attr_list, ucolor); + pango_attr_list_insert (entry->priv->attr_list, unline); +} + +static void +get_word_extents_from_position(SexySpellEntry *entry, gint *start, gint *end, guint position) +{ + const gchar *text; + gint i, bytes_pos; + + *start = -1; + *end = -1; + + if (entry->priv->words == NULL) + return; + + text = gtk_entry_get_text(GTK_ENTRY(entry)); + bytes_pos = (gint) (g_utf8_offset_to_pointer(text, position) - text); + + for (i = 0; entry->priv->words[i]; i++) { + if (bytes_pos >= entry->priv->word_starts[i] && + bytes_pos <= entry->priv->word_ends[i]) { + *start = entry->priv->word_starts[i]; + *end = entry->priv->word_ends[i]; + return; + } + } +} + +static void +add_to_dictionary(GtkWidget *menuitem, SexySpellEntry *entry) +{ + char *word; + gint start, end; + struct EnchantDict *dict; + + if (!have_enchant) + return; + + get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character); + word = gtk_editable_get_chars(GTK_EDITABLE(entry), start, end); + + dict = (struct EnchantDict *) g_object_get_data(G_OBJECT(menuitem), "enchant-dict"); + if (dict) + enchant_dict_add_to_personal(dict, word, -1); + + g_free(word); + + if (entry->priv->words) { + g_strfreev(entry->priv->words); + g_free(entry->priv->word_starts); + g_free(entry->priv->word_ends); + } + entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends); + sexy_spell_entry_recheck_all(entry); +} + +static void +ignore_all(GtkWidget *menuitem, SexySpellEntry *entry) +{ + char *word; + gint start, end; + GSList *li; + + if (!have_enchant) + return; + + get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character); + word = gtk_editable_get_chars(GTK_EDITABLE(entry), start, end); + + for (li = entry->priv->dict_list; li; li = g_slist_next (li)) { + struct EnchantDict *dict = (struct EnchantDict *) li->data; + enchant_dict_add_to_session(dict, word, -1); + } + + g_free(word); + + if (entry->priv->words) { + g_strfreev(entry->priv->words); + g_free(entry->priv->word_starts); + g_free(entry->priv->word_ends); + } + entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends); + sexy_spell_entry_recheck_all(entry); +} + +static void +replace_word(GtkWidget *menuitem, SexySpellEntry *entry) +{ + char *oldword; + const char *newword; + gint start, end; + gint cursor; + struct EnchantDict *dict; + + if (!have_enchant) + return; + + get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character); + oldword = gtk_editable_get_chars(GTK_EDITABLE(entry), start, end); + newword = gtk_label_get_text(GTK_LABEL(GTK_BIN(menuitem)->child)); + + cursor = gtk_editable_get_position(GTK_EDITABLE(entry)); + /* is the cursor at the end? If so, restore it there */ + if (g_utf8_strlen(gtk_entry_get_text(GTK_ENTRY(entry)), -1) == cursor) + cursor = -1; + else if(cursor > start && cursor <= end) + cursor = start; + + gtk_editable_delete_text(GTK_EDITABLE(entry), start, end); + gtk_editable_set_position(GTK_EDITABLE(entry), start); + gtk_editable_insert_text(GTK_EDITABLE(entry), newword, strlen(newword), + &start); + gtk_editable_set_position(GTK_EDITABLE(entry), cursor); + + dict = (struct EnchantDict *) g_object_get_data(G_OBJECT(menuitem), "enchant-dict"); + + if (dict) + enchant_dict_store_replacement(dict, + oldword, -1, + newword, -1); + + g_free(oldword); +} + +static void +build_suggestion_menu(SexySpellEntry *entry, GtkWidget *menu, struct EnchantDict *dict, const gchar *word) +{ + GtkWidget *mi; + gchar **suggestions; + size_t n_suggestions, i; + + if (!have_enchant) + return; + + suggestions = enchant_dict_suggest(dict, word, -1, &n_suggestions); + + if (suggestions == NULL || n_suggestions == 0) { + /* no suggestions. put something in the menu anyway... */ + GtkWidget *label = gtk_label_new(""); + gtk_label_set_markup(GTK_LABEL(label), _("<i>(no suggestions)</i>")); + + mi = gtk_separator_menu_item_new(); + gtk_container_add(GTK_CONTAINER(mi), label); + gtk_widget_show_all(mi); + gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi); + } else { + /* build a set of menus with suggestions */ + for (i = 0; i < n_suggestions; i++) { + if ((i != 0) && (i % 10 == 0)) { + mi = gtk_separator_menu_item_new(); + gtk_widget_show(mi); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); + + mi = gtk_menu_item_new_with_label(_("More...")); + gtk_widget_show(mi); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); + + menu = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), menu); + } + + mi = gtk_menu_item_new_with_label(suggestions[i]); + g_object_set_data(G_OBJECT(mi), "enchant-dict", dict); + g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(replace_word), entry); + gtk_widget_show(mi); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), mi); + } + } + + enchant_dict_free_suggestions(dict, suggestions); +} + +static GtkWidget * +build_spelling_menu(SexySpellEntry *entry, const gchar *word) +{ + struct EnchantDict *dict; + GtkWidget *topmenu, *mi; + gchar *label; + + if (!have_enchant) + return NULL; + + topmenu = gtk_menu_new(); + + if (entry->priv->dict_list == NULL) + return topmenu; + +#if 1 + dict = (struct EnchantDict *) entry->priv->dict_list->data; + build_suggestion_menu(entry, topmenu, dict, word); +#else + /* Suggestions */ + if (g_slist_length(entry->priv->dict_list) == 1) { + dict = (struct EnchantDict *) entry->priv->dict_list->data; + build_suggestion_menu(entry, topmenu, dict, word); + } else { + GSList *li; + GtkWidget *menu; + gchar *lang, *lang_name; + + for (li = entry->priv->dict_list; li; li = g_slist_next (li)) { + dict = (struct EnchantDict *) li->data; + lang = get_lang_from_dict(dict); + lang_name = gtkspell_iso_codes_lookup_name_for_code(lang); + if (lang_name) { + mi = gtk_menu_item_new_with_label(lang_name); + g_free(lang_name); + } else { + mi = gtk_menu_item_new_with_label(lang); + } + g_free(lang); + + gtk_widget_show(mi); + gtk_menu_shell_append(GTK_MENU_SHELL(topmenu), mi); + menu = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), menu); + build_suggestion_menu(entry, menu, dict, word); + } + } +#endif + + /* Separator */ + mi = gtk_separator_menu_item_new (); + gtk_widget_show(mi); + gtk_menu_shell_append(GTK_MENU_SHELL(topmenu), mi); + + /* + Add to Dictionary */ + label = g_strdup_printf(_("Add \"%s\" to Dictionary"), word); + mi = gtk_image_menu_item_new_with_label(label); + g_free(label); + + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), gtk_image_new_from_stock(GTK_STOCK_ADD, GTK_ICON_SIZE_MENU)); + +#if 1 + dict = (struct EnchantDict *) entry->priv->dict_list->data; + g_object_set_data(G_OBJECT(mi), "enchant-dict", dict); + g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(add_to_dictionary), entry); +#else + if (g_slist_length(entry->priv->dict_list) == 1) { + dict = (struct EnchantDict *) entry->priv->dict_list->data; + g_object_set_data(G_OBJECT(mi), "enchant-dict", dict); + g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(add_to_dictionary), entry); + } else { + GSList *li; + GtkWidget *menu, *submi; + gchar *lang, *lang_name; + + menu = gtk_menu_new(); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), menu); + + for (li = entry->priv->dict_list; li; li = g_slist_next(li)) { + dict = (struct EnchantDict *)li->data; + lang = get_lang_from_dict(dict); + lang_name = gtkspell_iso_codes_lookup_name_for_code(lang); + if (lang_name) { + submi = gtk_menu_item_new_with_label(lang_name); + g_free(lang_name); + } else { + submi = gtk_menu_item_new_with_label(lang); + } + g_free(lang); + g_object_set_data(G_OBJECT(submi), "enchant-dict", dict); + + g_signal_connect(G_OBJECT(submi), "activate", G_CALLBACK(add_to_dictionary), entry); + + gtk_widget_show(submi); + gtk_menu_shell_append(GTK_MENU_SHELL(menu), submi); + } + } +#endif + + gtk_widget_show_all(mi); + gtk_menu_shell_append(GTK_MENU_SHELL(topmenu), mi); + + /* - Ignore All */ + mi = gtk_image_menu_item_new_with_label(_("Ignore All")); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), gtk_image_new_from_stock(GTK_STOCK_REMOVE, GTK_ICON_SIZE_MENU)); + g_signal_connect(G_OBJECT(mi), "activate", G_CALLBACK(ignore_all), entry); + gtk_widget_show_all(mi); + gtk_menu_shell_append(GTK_MENU_SHELL(topmenu), mi); + + return topmenu; +} + +static void +sexy_spell_entry_populate_popup(SexySpellEntry *entry, GtkMenu *menu, gpointer data) +{ + GtkWidget *icon, *mi; + gint start, end; + gchar *word; + + if ((have_enchant == FALSE) || (entry->priv->checked == FALSE)) + return; + + if (g_slist_length(entry->priv->dict_list) == 0) + return; + + get_word_extents_from_position(entry, &start, &end, entry->priv->mark_character); + if (start == end) + return; + if (!word_misspelled(entry, start, end)) + return; + + /* separator */ + mi = gtk_separator_menu_item_new(); + gtk_widget_show(mi); + gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi); + + /* Above the separator, show the suggestions menu */ + icon = gtk_image_new_from_stock(GTK_STOCK_SPELL_CHECK, GTK_ICON_SIZE_MENU); + mi = gtk_image_menu_item_new_with_label(_("Spelling Suggestions")); + gtk_image_menu_item_set_image(GTK_IMAGE_MENU_ITEM(mi), icon); + + word = gtk_editable_get_chars(GTK_EDITABLE(entry), start, end); + g_assert(word != NULL); + gtk_menu_item_set_submenu(GTK_MENU_ITEM(mi), build_spelling_menu(entry, word)); + g_free(word); + + gtk_widget_show_all(mi); + gtk_menu_shell_prepend(GTK_MENU_SHELL(menu), mi); +} + +static void +sexy_spell_entry_init(SexySpellEntry *entry) +{ + entry->priv = g_new0(SexySpellEntryPriv, 1); + + entry->priv->dict_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + + if (have_enchant) + sexy_spell_entry_activate_default_languages(entry); + + entry->priv->attr_list = pango_attr_list_new(); + + entry->priv->checked = TRUE; + + g_signal_connect(G_OBJECT(entry), "popup-menu", G_CALLBACK(sexy_spell_entry_popup_menu), entry); + g_signal_connect(G_OBJECT(entry), "populate-popup", G_CALLBACK(sexy_spell_entry_populate_popup), NULL); + g_signal_connect(G_OBJECT(entry), "changed", G_CALLBACK(sexy_spell_entry_changed), NULL); +} + +static void +sexy_spell_entry_finalize(GObject *obj) +{ + SexySpellEntry *entry; + + g_return_if_fail(obj != NULL); + g_return_if_fail(SEXY_IS_SPELL_ENTRY(obj)); + + entry = SEXY_SPELL_ENTRY(obj); + + if (entry->priv->attr_list) + pango_attr_list_unref(entry->priv->attr_list); + if (entry->priv->dict_hash) + g_hash_table_destroy(entry->priv->dict_hash); + if (entry->priv->words) + g_strfreev(entry->priv->words); + if (entry->priv->word_starts) + g_free(entry->priv->word_starts); + if (entry->priv->word_ends) + g_free(entry->priv->word_ends); + + if (have_enchant) { + if (entry->priv->broker) { + GSList *li; + for (li = entry->priv->dict_list; li; li = g_slist_next(li)) { + struct EnchantDict *dict = (struct EnchantDict*) li->data; + enchant_broker_free_dict (entry->priv->broker, dict); + } + g_slist_free (entry->priv->dict_list); + + enchant_broker_free(entry->priv->broker); + } + } + + g_free(entry->priv); + + if (G_OBJECT_CLASS(parent_class)->finalize) + G_OBJECT_CLASS(parent_class)->finalize(obj); +} + +static void +sexy_spell_entry_destroy(GtkObject *obj) +{ + SexySpellEntry *entry; + + entry = SEXY_SPELL_ENTRY(obj); + + if (GTK_OBJECT_CLASS(parent_class)->destroy) + GTK_OBJECT_CLASS(parent_class)->destroy(obj); +} + +/** + * sexy_spell_entry_new + * + * Creates a new SexySpellEntry widget. + * + * Returns: a new #SexySpellEntry. + */ +GtkWidget * +sexy_spell_entry_new(void) +{ + return GTK_WIDGET(g_object_new(SEXY_TYPE_SPELL_ENTRY, NULL)); +} + +GQuark +sexy_spell_error_quark(void) +{ + static GQuark q = 0; + if (q == 0) + q = g_quark_from_static_string("sexy-spell-error-quark"); + return q; +} + +static gboolean +default_word_check(SexySpellEntry *entry, const gchar *word) +{ + gboolean result = TRUE; + GSList *li; + + if (!have_enchant) + return result; + + if (g_unichar_isalpha(*word) == FALSE) { + /* We only want to check words */ + return FALSE; + } + for (li = entry->priv->dict_list; li; li = g_slist_next (li)) { + struct EnchantDict *dict = (struct EnchantDict *) li->data; + if (enchant_dict_check(dict, word, strlen(word)) == 0) { + result = FALSE; + break; + } + } + return result; +} + +static gboolean +word_misspelled(SexySpellEntry *entry, int start, int end) +{ + const gchar *text; + gchar *word; + gboolean ret; + + if (start == end) + return FALSE; + text = gtk_entry_get_text(GTK_ENTRY(entry)); + word = g_new0(gchar, end - start + 2); + + g_strlcpy(word, text + start, end - start + 1); + +#if 0 + g_signal_emit(entry, signals[WORD_CHECK], 0, word, &ret); +#else + ret = default_word_check (entry, word); +#endif + + g_free(word); + return ret; +} + +static void +check_word(SexySpellEntry *entry, int start, int end) +{ + PangoAttrIterator *it; + + /* Check to see if we've got any attributes at this position. + * If so, free them, since we'll readd it if the word is misspelled */ + it = pango_attr_list_get_iterator(entry->priv->attr_list); + if (it == NULL) + return; + do { + gint s, e; + pango_attr_iterator_range(it, &s, &e); + if (s == start) { + GSList *attrs = pango_attr_iterator_get_attrs(it); + g_slist_foreach(attrs, (GFunc) pango_attribute_destroy, NULL); + g_slist_free(attrs); + } + } while (pango_attr_iterator_next(it)); + pango_attr_iterator_destroy(it); + + if (word_misspelled(entry, start, end)) + insert_underline(entry, start, end); +} + +static void +sexy_spell_entry_recheck_all(SexySpellEntry *entry) +{ + GdkRectangle rect; + GtkWidget *widget = GTK_WIDGET(entry); + PangoLayout *layout; + int length, i; + + if ((have_enchant == FALSE) || (entry->priv->checked == FALSE)) + return; + + if (g_slist_length(entry->priv->dict_list) == 0) + return; + + /* Remove all existing pango attributes. These will get readded as we check */ + pango_attr_list_unref(entry->priv->attr_list); + entry->priv->attr_list = pango_attr_list_new(); + + /* Loop through words */ + for (i = 0; entry->priv->words[i]; i++) { + length = strlen(entry->priv->words[i]); + if (length == 0) + continue; + check_word(entry, entry->priv->word_starts[i], entry->priv->word_ends[i]); + } + + layout = gtk_entry_get_layout(GTK_ENTRY(entry)); + pango_layout_set_attributes(layout, entry->priv->attr_list); + + if (GTK_WIDGET_REALIZED(GTK_WIDGET(entry))) { + rect.x = 0; rect.y = 0; + rect.width = widget->allocation.width; + rect.height = widget->allocation.height; + gdk_window_invalidate_rect(widget->window, &rect, TRUE); + } +} + +static gint +sexy_spell_entry_expose(GtkWidget *widget, GdkEventExpose *event) +{ + SexySpellEntry *entry = SEXY_SPELL_ENTRY(widget); + GtkEntry *gtk_entry = GTK_ENTRY(widget); + PangoLayout *layout; + + if (entry->priv->checked) { + layout = gtk_entry_get_layout(gtk_entry); + pango_layout_set_attributes(layout, entry->priv->attr_list); + } + + return GTK_WIDGET_CLASS(parent_class)->expose_event (widget, event); +} + +static gint +sexy_spell_entry_button_press(GtkWidget *widget, GdkEventButton *event) +{ + SexySpellEntry *entry = SEXY_SPELL_ENTRY(widget); + GtkEntry *gtk_entry = GTK_ENTRY(widget); + gint pos; + + pos = gtk_entry_find_position(gtk_entry, event->x); + entry->priv->mark_character = pos; + + return GTK_WIDGET_CLASS(parent_class)->button_press_event (widget, event); +} + +static gboolean +sexy_spell_entry_popup_menu(GtkWidget *widget, SexySpellEntry *entry) +{ + /* Menu popped up from a keybinding (menu key or <shift>+F10). Use + * the cursor position as the mark position */ + entry->priv->mark_character = gtk_editable_get_position (GTK_EDITABLE (entry)); + return FALSE; +} + +static void +entry_strsplit_utf8(GtkEntry *entry, gchar ***set, gint **starts, gint **ends) +{ + PangoLayout *layout; + PangoLogAttr *log_attrs; + const gchar *text; + gint n_attrs, n_strings, i, j; + + layout = gtk_entry_get_layout(GTK_ENTRY(entry)); + text = gtk_entry_get_text(GTK_ENTRY(entry)); + pango_layout_get_log_attrs(layout, &log_attrs, &n_attrs); + + /* Find how many words we have */ + n_strings = 0; + for (i = 0; i < n_attrs; i++) + if (log_attrs[i].is_word_start) + n_strings++; + + *set = g_new0(gchar *, n_strings + 1); + *starts = g_new0(gint, n_strings); + *ends = g_new0(gint, n_strings); + + /* Copy out strings */ + for (i = 0, j = 0; i < n_attrs; i++) { + if (log_attrs[i].is_word_start) { + gint cend, bytes; + gchar *start; + + /* Find the end of this string */ + cend = i; + while (!(log_attrs[cend].is_word_end)) + cend++; + + /* Copy sub-string */ + start = g_utf8_offset_to_pointer(text, i); + bytes = (gint) (g_utf8_offset_to_pointer(text, cend) - start); + (*set)[j] = g_new0(gchar, bytes + 1); + (*starts)[j] = (gint) (start - text); + (*ends)[j] = (gint) (start - text + bytes); + g_utf8_strncpy((*set)[j], start, cend - i); + + /* Move on to the next word */ + j++; + } + } + + g_free (log_attrs); +} + +static void +sexy_spell_entry_changed(GtkEditable *editable, gpointer data) +{ + SexySpellEntry *entry = SEXY_SPELL_ENTRY(editable); + if (entry->priv->checked == FALSE) + return; + if (g_slist_length(entry->priv->dict_list) == 0) + return; + + if (entry->priv->words) { + g_strfreev(entry->priv->words); + g_free(entry->priv->word_starts); + g_free(entry->priv->word_ends); + } + entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends); + sexy_spell_entry_recheck_all(entry); +} + +static gboolean +enchant_has_lang(const gchar *lang, GSList *langs) { + GSList *i; + for (i = langs; i; i = g_slist_next(i)) { + if (strcmp(lang, i->data) == 0) { + return TRUE; + } + } + return FALSE; +} + +/** + * sexy_spell_entry_activate_default_languages: + * @entry: A #SexySpellEntry. + * + * Activate spell checking for languages specified in the $LANG + * or $LANGUAGE environment variables. These languages are + * activated by default, so this function need only be called + * if they were previously deactivated. + */ +void +sexy_spell_entry_activate_default_languages(SexySpellEntry *entry) +{ +#if GLIB_CHECK_VERSION (2, 6, 0) + const gchar* const *langs; + int i; + gchar *lastprefix = NULL; + GSList *enchant_langs; + + if (!have_enchant) + return; + + if (!entry->priv->broker) + entry->priv->broker = enchant_broker_init(); + + + langs = g_get_language_names (); + + if (langs == NULL) + return; + + enchant_langs = sexy_spell_entry_get_languages(entry); + + for (i = 0; langs[i]; i++) { + if ((g_strncasecmp(langs[i], "C", 1) != 0) && + (strlen(langs[i]) >= 2) && + enchant_has_lang(langs[i], enchant_langs)) { + if ((lastprefix == NULL) || (g_str_has_prefix(langs[i], lastprefix) == FALSE)) + sexy_spell_entry_activate_language_internal(entry, langs[i], NULL); + if (lastprefix != NULL) + g_free(lastprefix); + lastprefix = g_strndup(langs[i], 2); + } + } + if (lastprefix != NULL) + g_free(lastprefix); + + g_slist_foreach(enchant_langs, (GFunc) g_free, NULL); + g_slist_free(enchant_langs); + + /* If we don't have any languages activated, use "en" */ + if (entry->priv->dict_list == NULL) + sexy_spell_entry_activate_language_internal(entry, "en", NULL); +#else + gchar *lang; + + if (!have_enchant) + return; + + lang = (gchar *) g_getenv("LANG"); + + if (lang != NULL) { + if (g_strncasecmp(lang, "C", 1) == 0) + lang = NULL; + else if (lang[0] == '\0') + lang = NULL; + } + + if (lang == NULL) + lang = "en"; + + sexy_spell_entry_activate_language_internal(entry, lang, NULL); +#endif +} + +static void +get_lang_from_dict_cb(const char * const lang_tag, + const char * const provider_name, + const char * const provider_desc, + const char * const provider_file, + void * user_data) { + gchar **lang = (gchar **)user_data; + *lang = g_strdup(lang_tag); +} + +static gchar * +get_lang_from_dict(struct EnchantDict *dict) +{ + gchar *lang; + + if (!have_enchant) + return NULL; + + enchant_dict_describe(dict, get_lang_from_dict_cb, &lang); + return lang; +} + +static gboolean +sexy_spell_entry_activate_language_internal(SexySpellEntry *entry, const gchar *lang, GError **error) +{ + struct EnchantDict *dict; + + if (!have_enchant) + return FALSE; + + if (!entry->priv->broker) + entry->priv->broker = enchant_broker_init(); + + if (g_hash_table_lookup(entry->priv->dict_hash, lang)) + return TRUE; + + dict = enchant_broker_request_dict(entry->priv->broker, lang); + + if (!dict) { + g_set_error(error, SEXY_SPELL_ERROR, SEXY_SPELL_ERROR_BACKEND, _("enchant error for language: %s"), lang); + return FALSE; + } + + entry->priv->dict_list = g_slist_append(entry->priv->dict_list, (gpointer) dict); + g_hash_table_insert(entry->priv->dict_hash, get_lang_from_dict(dict), (gpointer) dict); + + return TRUE; +} + +static void +dict_describe_cb(const char * const lang_tag, + const char * const provider_name, + const char * const provider_desc, + const char * const provider_file, + void * user_data) +{ + GSList **langs = (GSList **)user_data; + + *langs = g_slist_append(*langs, (gpointer)g_strdup(lang_tag)); +} + +/** + * sexy_spell_entry_get_languages: + * @entry: A #SexySpellEntry. + * + * Retrieve a list of language codes for which dictionaries are available. + * + * Returns: a new #GList object, or %NULL on error. + */ +GSList * +sexy_spell_entry_get_languages(const SexySpellEntry *entry) +{ + GSList *langs = NULL; + + g_return_val_if_fail(entry != NULL, NULL); + g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry), NULL); + + if (enchant_broker_list_dicts == NULL) + return NULL; + + if (!entry->priv->broker) + return NULL; + + enchant_broker_list_dicts(entry->priv->broker, dict_describe_cb, &langs); + + return langs; +} + +/** + * sexy_spell_entry_get_language_name: + * @entry: A #SexySpellEntry. + * @lang: The language code to lookup a friendly name for. + * + * Get a friendly name for a given locale. + * + * Returns: The name of the locale. Should be freed with g_free() + */ +gchar * +sexy_spell_entry_get_language_name(const SexySpellEntry *entry, + const gchar *lang) +{ + /*if (have_enchant) + return gtkspell_iso_codes_lookup_name_for_code(lang);*/ + return NULL; +} + +/** + * sexy_spell_entry_language_is_active: + * @entry: A #SexySpellEntry. + * @lang: The language to use, in a form enchant understands. + * + * Determine if a given language is currently active. + * + * Returns: TRUE if the language is active. + */ +gboolean +sexy_spell_entry_language_is_active(const SexySpellEntry *entry, + const gchar *lang) +{ + return (g_hash_table_lookup(entry->priv->dict_hash, lang) != NULL); +} + +/** + * sexy_spell_entry_activate_language: + * @entry: A #SexySpellEntry + * @lang: The language to use in a form Enchant understands. Typically either + * a two letter language code or a locale code in the form xx_XX. + * @error: Return location for error. + * + * Activate spell checking for the language specifed. + * + * Returns: FALSE if there was an error. + */ +gboolean +sexy_spell_entry_activate_language(SexySpellEntry *entry, const gchar *lang, GError **error) +{ + gboolean ret; + + g_return_val_if_fail(entry != NULL, FALSE); + g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry), FALSE); + g_return_val_if_fail(lang != NULL && lang != '\0', FALSE); + + if (!have_enchant) + return FALSE; + + if (error) + g_return_val_if_fail(*error == NULL, FALSE); + + ret = sexy_spell_entry_activate_language_internal(entry, lang, error); + + if (ret) { + if (entry->priv->words) { + g_strfreev(entry->priv->words); + g_free(entry->priv->word_starts); + g_free(entry->priv->word_ends); + } + entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends); + sexy_spell_entry_recheck_all(entry); + } + + return ret; +} + +/** + * sexy_spell_entry_deactivate_language: + * @entry: A #SexySpellEntry. + * @lang: The language in a form Enchant understands. Typically either + * a two letter language code or a locale code in the form xx_XX. + * + * Deactivate spell checking for the language specifed. + */ +void +sexy_spell_entry_deactivate_language(SexySpellEntry *entry, const gchar *lang) +{ + g_return_if_fail(entry != NULL); + g_return_if_fail(SEXY_IS_SPELL_ENTRY(entry)); + + if (!have_enchant) + return; + + if (!entry->priv->dict_list) + return; + + if (lang) { + struct EnchantDict *dict; + + dict = g_hash_table_lookup(entry->priv->dict_hash, lang); + if (!dict) + return; + enchant_broker_free_dict(entry->priv->broker, dict); + entry->priv->dict_list = g_slist_remove(entry->priv->dict_list, dict); + g_hash_table_remove (entry->priv->dict_hash, lang); + } else { + /* deactivate all */ + GSList *li; + struct EnchantDict *dict; + + for (li = entry->priv->dict_list; li; li = g_slist_next(li)) { + dict = (struct EnchantDict *)li->data; + enchant_broker_free_dict(entry->priv->broker, dict); + } + + g_slist_free (entry->priv->dict_list); + g_hash_table_destroy (entry->priv->dict_hash); + entry->priv->dict_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); + entry->priv->dict_list = NULL; + } + + if (entry->priv->words) { + g_strfreev(entry->priv->words); + g_free(entry->priv->word_starts); + g_free(entry->priv->word_ends); + } + entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends); + sexy_spell_entry_recheck_all(entry); +} + +/** + * sexy_spell_entry_set_active_languages: + * @entry: A #SexySpellEntry + * @langs: A list of language codes to activate, in a form Enchant understands. + * Typically either a two letter language code or a locale code in the + * form xx_XX. + * @error: Return location for error. + * + * Activate spell checking for only the languages specified. + * + * Returns: FALSE if there was an error. + */ +gboolean +sexy_spell_entry_set_active_languages(SexySpellEntry *entry, GSList *langs, GError **error) +{ + GSList *li; + + g_return_val_if_fail(entry != NULL, FALSE); + g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry), FALSE); + g_return_val_if_fail(langs != NULL, FALSE); + + if (!have_enchant) + return FALSE; + + /* deactivate all languages first */ + sexy_spell_entry_deactivate_language(entry, NULL); + + for (li = langs; li; li = g_slist_next(li)) { + if (sexy_spell_entry_activate_language_internal(entry, + (const gchar *) li->data, error) == FALSE) + return FALSE; + } + if (entry->priv->words) { + g_strfreev(entry->priv->words); + g_free(entry->priv->word_starts); + g_free(entry->priv->word_ends); + } + entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends); + sexy_spell_entry_recheck_all(entry); + return TRUE; +} + +/** + * sexy_spell_entry_get_active_languages: + * @entry: A #SexySpellEntry + * + * Retrieve a list of the currently active languages. + * + * Returns: A GSList of char* values with language codes (en, fr, etc). Both + * the data and the list must be freed by the user. + */ +GSList * +sexy_spell_entry_get_active_languages(SexySpellEntry *entry) +{ + GSList *ret = NULL, *li; + struct EnchantDict *dict; + gchar *lang; + + g_return_val_if_fail(entry != NULL, NULL); + g_return_val_if_fail(SEXY_IS_SPELL_ENTRY(entry), NULL); + + if (!have_enchant) + return NULL; + + for (li = entry->priv->dict_list; li; li = g_slist_next(li)) { + dict = (struct EnchantDict *) li->data; + lang = get_lang_from_dict(dict); + ret = g_slist_append(ret, lang); + } + return ret; +} + +/** + * sexy_spell_entry_is_checked: + * @entry: A #SexySpellEntry. + * + * Queries a #SexySpellEntry and returns whether the entry has spell-checking enabled. + * + * Returns: TRUE if the entry has spell-checking enabled. + */ +gboolean +sexy_spell_entry_is_checked(SexySpellEntry *entry) +{ + return entry->priv->checked; +} + +/** + * sexy_spell_entry_set_checked: + * @entry: A #SexySpellEntry. + * @checked: Whether to enable spell-checking + * + * Sets whether the entry has spell-checking enabled. + */ +void +sexy_spell_entry_set_checked(SexySpellEntry *entry, gboolean checked) +{ + GtkWidget *widget; + + if (entry->priv->checked == checked) + return; + + entry->priv->checked = checked; + widget = GTK_WIDGET(entry); + + if (checked == FALSE && GTK_WIDGET_REALIZED(widget)) { + PangoLayout *layout; + GdkRectangle rect; + + pango_attr_list_unref(entry->priv->attr_list); + entry->priv->attr_list = pango_attr_list_new(); + + layout = gtk_entry_get_layout(GTK_ENTRY(entry)); + pango_layout_set_attributes(layout, entry->priv->attr_list); + + rect.x = 0; rect.y = 0; + rect.width = widget->allocation.width; + rect.height = widget->allocation.height; + gdk_window_invalidate_rect(widget->window, &rect, TRUE); + } else { + if (entry->priv->words) { + g_strfreev(entry->priv->words); + g_free(entry->priv->word_starts); + g_free(entry->priv->word_ends); + } + entry_strsplit_utf8(GTK_ENTRY(entry), &entry->priv->words, &entry->priv->word_starts, &entry->priv->word_ends); + sexy_spell_entry_recheck_all(entry); + } +} diff --git a/src/fe-gtk/sexy-spell-entry.h b/src/fe-gtk/sexy-spell-entry.h new file mode 100644 index 00000000..61d1b795 --- /dev/null +++ b/src/fe-gtk/sexy-spell-entry.h @@ -0,0 +1,87 @@ +/* + * @file libsexy/sexy-spell-entry.h Entry widget + * + * @Copyright (C) 2004-2006 Christian Hammond. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#ifndef _SEXY_SPELL_ENTRY_H_ +#define _SEXY_SPELL_ENTRY_H_ + +typedef struct _SexySpellEntry SexySpellEntry; +typedef struct _SexySpellEntryClass SexySpellEntryClass; +typedef struct _SexySpellEntryPriv SexySpellEntryPriv; + +#include <gtk/gtkentry.h> + +#define SEXY_TYPE_SPELL_ENTRY (sexy_spell_entry_get_type()) +#define SEXY_SPELL_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), SEXY_TYPE_SPELL_ENTRY, SexySpellEntry)) +#define SEXY_SPELL_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), SEXY_TYPE_SPELL_ENTRY, SexySpellEntryClass)) +#define SEXY_IS_SPELL_ENTRY(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), SEXY_TYPE_SPELL_ENTRY)) +#define SEXY_IS_SPELL_ENTRY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), SEXY_TYPE_SPELL_ENTRY)) +#define SEXY_SPELL_ENTRY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), SEXY_TYPE_SPELL_ENTRY, SexySpellEntryClass)) + +#define SEXY_SPELL_ERROR (sexy_spell_error_quark()) + +typedef enum { + SEXY_SPELL_ERROR_BACKEND, +} SexySpellError; + +struct _SexySpellEntry +{ + GtkEntry parent_object; + + SexySpellEntryPriv *priv; + + void (*gtk_reserved1)(void); + void (*gtk_reserved2)(void); + void (*gtk_reserved3)(void); + void (*gtk_reserved4)(void); +}; + +struct _SexySpellEntryClass +{ + GtkEntryClass parent_class; + + /* Signals */ + gboolean (*word_check)(SexySpellEntry *entry, const gchar *word); + + void (*gtk_reserved1)(void); + void (*gtk_reserved2)(void); + void (*gtk_reserved3)(void); + void (*gtk_reserved4)(void); +}; + +G_BEGIN_DECLS + +GType sexy_spell_entry_get_type(void); +GtkWidget *sexy_spell_entry_new(void); +GQuark sexy_spell_error_quark(void); + +GSList *sexy_spell_entry_get_languages(const SexySpellEntry *entry); +gchar *sexy_spell_entry_get_language_name(const SexySpellEntry *entry, const gchar *lang); +gboolean sexy_spell_entry_language_is_active(const SexySpellEntry *entry, const gchar *lang); +gboolean sexy_spell_entry_activate_language(SexySpellEntry *entry, const gchar *lang, GError **error); +void sexy_spell_entry_deactivate_language(SexySpellEntry *entry, const gchar *lang); +gboolean sexy_spell_entry_set_active_languages(SexySpellEntry *entry, GSList *langs, GError **error); +GSList *sexy_spell_entry_get_active_languages(SexySpellEntry *entry); +gboolean sexy_spell_entry_is_checked(SexySpellEntry *entry); +void sexy_spell_entry_set_checked(SexySpellEntry *entry, gboolean checked); +void sexy_spell_entry_activate_default_languages(SexySpellEntry *entry); + +G_END_DECLS + +#endif diff --git a/src/fe-gtk/textgui.c b/src/fe-gtk/textgui.c new file mode 100644 index 00000000..604da44b --- /dev/null +++ b/src/fe-gtk/textgui.c @@ -0,0 +1,456 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "fe-gtk.h" + +#include <gtk/gtkbutton.h> +#include <gtk/gtkentry.h> +#include <gtk/gtkhbox.h> +#include <gtk/gtkhbbox.h> +#include <gtk/gtkliststore.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkvbox.h> +#include <gtk/gtkvpaned.h> +#include <gtk/gtkvscrollbar.h> + +#include "../common/xchat.h" +#include "../common/xchatc.h" +#include "../common/cfgfiles.h" +#include "../common/outbound.h" +#include "../common/fe.h" +#include "../common/text.h" +#include "gtkutil.h" +#include "xtext.h" +#include "maingui.h" +#include "palette.h" +#include "textgui.h" + +extern struct text_event te[]; +extern char *pntevts_text[]; +extern char *pntevts[]; + +static GtkWidget *pevent_dialog = NULL, *pevent_dialog_twid, + *pevent_dialog_entry, + *pevent_dialog_list, *pevent_dialog_hlist; + +enum +{ + COL_EVENT_NAME, + COL_EVENT_TEXT, + COL_ROW, + N_COLUMNS +}; + + +/* this is only used in xtext.c for indented timestamping */ +int +xtext_get_stamp_str (time_t tim, char **ret) +{ + return get_stamp_str (prefs.stamp_format, tim, ret); +} + +static void +PrintTextLine (xtext_buffer *xtbuf, unsigned char *text, int len, int indent, time_t timet) +{ + unsigned char *tab, *new_text; + int leftlen; + + if (len == 0) + len = 1; + + if (!indent) + { + if (prefs.timestamp) + { + int stamp_size; + char *stamp; + + if (timet == 0) + timet = time (0); + + stamp_size = get_stamp_str (prefs.stamp_format, timet, &stamp); + new_text = malloc (len + stamp_size + 1); + memcpy (new_text, stamp, stamp_size); + g_free (stamp); + memcpy (new_text + stamp_size, text, len); + gtk_xtext_append (xtbuf, new_text, len + stamp_size); + free (new_text); + } else + gtk_xtext_append (xtbuf, text, len); + return; + } + + tab = strchr (text, '\t'); + if (tab && tab < (text + len)) + { + leftlen = tab - text; + gtk_xtext_append_indent (xtbuf, + text, leftlen, tab + 1, len - (leftlen + 1), timet); + } else + gtk_xtext_append_indent (xtbuf, 0, 0, text, len, timet); +} + +void +PrintTextRaw (void *xtbuf, unsigned char *text, int indent, time_t stamp) +{ + char *last_text = text; + int len = 0; + int beep_done = FALSE; + + /* split the text into separate lines */ + while (1) + { + switch (*text) + { + case 0: + PrintTextLine (xtbuf, last_text, len, indent, stamp); + return; + case '\n': + PrintTextLine (xtbuf, last_text, len, indent, stamp); + text++; + if (*text == 0) + return; + last_text = text; + len = 0; + break; + case ATTR_BEEP: + *text = ' '; + if (!beep_done) /* beeps may be slow, so only do 1 per line */ + { + beep_done = TRUE; + if (!prefs.filterbeep) + gdk_beep (); + } + default: + text++; + len++; + } + } +} + +static void +pevent_dialog_close (GtkWidget *wid, gpointer arg) +{ + pevent_dialog = NULL; + pevent_save (NULL); +} + +static void +pevent_dialog_update (GtkWidget * wid, GtkWidget * twid) +{ + int len, m; + const char *text; + char *out; + int sig; + GtkTreeIter iter; + GtkListStore *store; + + if (!gtkutil_treeview_get_selected (GTK_TREE_VIEW (pevent_dialog_list), + &iter, COL_ROW, &sig, -1)) + return; + + text = gtk_entry_get_text (GTK_ENTRY (wid)); + len = strlen (text); + + if (pevt_build_string (text, &out, &m) != 0) + { + fe_message (_("There was an error parsing the string"), FE_MSG_ERROR); + return; + } + if (m > (te[sig].num_args & 0x7f)) + { + free (out); + out = malloc (4096); + snprintf (out, 4096, + _("This signal is only passed %d args, $%d is invalid"), + te[sig].num_args & 0x7f, m); + fe_message (out, FE_MSG_WARN); + free (out); + return; + } + + store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (pevent_dialog_list)); + gtk_list_store_set (store, &iter, COL_EVENT_TEXT, text, -1); + + if (pntevts_text[sig]) + free (pntevts_text[sig]); + if (pntevts[sig]) + free (pntevts[sig]); + + pntevts_text[sig] = malloc (len + 1); + memcpy (pntevts_text[sig], text, len + 1); + pntevts[sig] = out; + + out = malloc (len + 2); + memcpy (out, text, len + 1); + out[len] = '\n'; + out[len + 1] = 0; + check_special_chars (out, TRUE); + + PrintTextRaw (GTK_XTEXT (twid)->buffer, out, 0, 0); + free (out); + + /* save this when we exit */ + prefs.save_pevents = 1; +} + +static void +pevent_dialog_hfill (GtkWidget * list, int e) +{ + int i = 0; + char *text; + GtkTreeIter iter; + GtkListStore *store; + + store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (pevent_dialog_hlist)); + gtk_list_store_clear (store); + while (i < (te[e].num_args & 0x7f)) + { + text = _(te[e].help[i]); + i++; + if (text[0] == '\001') + text++; + gtk_list_store_insert_with_values (store, &iter, -1, + 0, i, + 1, text, -1); + } +} + +static void +pevent_dialog_unselect (void) +{ + gtk_entry_set_text (GTK_ENTRY (pevent_dialog_entry), ""); + gtk_list_store_clear ((GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (pevent_dialog_hlist))); +} + +static void +pevent_dialog_select (GtkTreeSelection *sel, gpointer store) +{ + char *text; + int sig; + GtkTreeIter iter; + + if (!gtkutil_treeview_get_selected (GTK_TREE_VIEW (pevent_dialog_list), + &iter, COL_ROW, &sig, -1)) + { + pevent_dialog_unselect (); + } + else + { + gtk_tree_model_get (store, &iter, COL_EVENT_TEXT, &text, -1); + gtk_entry_set_text (GTK_ENTRY (pevent_dialog_entry), text); + g_free (text); + pevent_dialog_hfill (pevent_dialog_hlist, sig); + } +} + +static void +pevent_dialog_fill (GtkWidget * list) +{ + int i; + GtkListStore *store; + GtkTreeIter iter; + + store = (GtkListStore *)gtk_tree_view_get_model (GTK_TREE_VIEW (list)); + gtk_list_store_clear (store); + + i = NUM_XP; + do + { + i--; + gtk_list_store_insert_with_values (store, &iter, 0, + COL_EVENT_NAME, te[i].name, + COL_EVENT_TEXT, pntevts_text[i], + COL_ROW, i, -1); + } + while (i != 0); +} + +static void +pevent_save_req_cb (void *arg1, char *file) +{ + if (file) + pevent_save (file); +} + +static void +pevent_save_cb (GtkWidget * wid, void *data) +{ + if (data) + { + gtkutil_file_req (_("Print Texts File"), pevent_save_req_cb, NULL, + NULL, FRF_WRITE); + return; + } + pevent_save (NULL); +} + +static void +pevent_load_req_cb (void *arg1, char *file) +{ + if (file) + { + pevent_load (file); + pevent_make_pntevts (); + pevent_dialog_fill (pevent_dialog_list); + pevent_dialog_unselect (); + prefs.save_pevents = 1; + } +} + +static void +pevent_load_cb (GtkWidget * wid, void *data) +{ + gtkutil_file_req (_("Print Texts File"), pevent_load_req_cb, NULL, NULL, 0); +} + +static void +pevent_ok_cb (GtkWidget * wid, void *data) +{ + gtk_widget_destroy (pevent_dialog); +} + +static void +pevent_test_cb (GtkWidget * wid, GtkWidget * twid) +{ + int len, n; + char *out, *text; + + for (n = 0; n < NUM_XP; n++) + { + text = _(pntevts_text[n]); + len = strlen (text); + + out = malloc (len + 2); + memcpy (out, text, len + 1); + out[len] = '\n'; + out[len + 1] = 0; + check_special_chars (out, TRUE); + + PrintTextRaw (GTK_XTEXT (twid)->buffer, out, 0, 0); + free (out); + } +} + +void +pevent_dialog_show () +{ + GtkWidget *vbox, *hbox, *tbox, *wid, *bh, *th; + GtkListStore *store, *hstore; + GtkTreeSelection *sel; + + if (pevent_dialog) + { + mg_bring_tofront (pevent_dialog); + return; + } + + pevent_dialog = + mg_create_generic_tab ("edit events", _("Edit Events"), + TRUE, FALSE, pevent_dialog_close, NULL, + 600, 455, &vbox, 0); + + wid = gtk_vpaned_new (); + th = gtk_vbox_new (0, 2); + bh = gtk_vbox_new (0, 2); + gtk_widget_show (th); + gtk_widget_show (bh); + gtk_paned_pack1 (GTK_PANED (wid), th, 1, 1); + gtk_paned_pack2 (GTK_PANED (wid), bh, 0, 1); + gtk_box_pack_start (GTK_BOX (vbox), wid, 1, 1, 0); + gtk_widget_show (wid); + + store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, + G_TYPE_STRING, G_TYPE_INT); + pevent_dialog_list = gtkutil_treeview_new (th, GTK_TREE_MODEL (store), NULL, + COL_EVENT_NAME, _("Event"), + COL_EVENT_TEXT, _("Text"), -1); + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (pevent_dialog_list)); + g_signal_connect (G_OBJECT (sel), "changed", + G_CALLBACK (pevent_dialog_select), store); + + pevent_dialog_twid = gtk_xtext_new (colors, 0); + gtk_xtext_set_tint (GTK_XTEXT (pevent_dialog_twid), prefs.tint_red, prefs.tint_green, prefs.tint_blue); + gtk_xtext_set_background (GTK_XTEXT (pevent_dialog_twid), + channelwin_pix, prefs.transparent); + + pevent_dialog_entry = gtk_entry_new_with_max_length (255); + g_signal_connect (G_OBJECT (pevent_dialog_entry), "activate", + G_CALLBACK (pevent_dialog_update), pevent_dialog_twid); + gtk_box_pack_start (GTK_BOX (bh), pevent_dialog_entry, 0, 0, 0); + gtk_widget_show (pevent_dialog_entry); + + tbox = gtk_hbox_new (0, 0); + gtk_container_add (GTK_CONTAINER (bh), tbox); + gtk_widget_show (tbox); + + gtk_widget_set_usize (pevent_dialog_twid, 150, 20); + gtk_container_add (GTK_CONTAINER (tbox), pevent_dialog_twid); + gtk_xtext_set_font (GTK_XTEXT (pevent_dialog_twid), prefs.font_normal); + + wid = gtk_vscrollbar_new (GTK_XTEXT (pevent_dialog_twid)->adj); + gtk_box_pack_start (GTK_BOX (tbox), wid, FALSE, FALSE, 0); + show_and_unfocus (wid); + + gtk_widget_show (pevent_dialog_twid); + + hstore = gtk_list_store_new (2, G_TYPE_INT, G_TYPE_STRING); + pevent_dialog_hlist = gtkutil_treeview_new (bh, GTK_TREE_MODEL (hstore), + NULL, + 0, _("$ Number"), + 1, _("Description"), -1); + gtk_widget_show (pevent_dialog_hlist); + + pevent_dialog_fill (pevent_dialog_list); + gtk_widget_show (pevent_dialog_list); + + hbox = gtk_hbutton_box_new (); + gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 2); + /*wid = gtk_button_new_with_label (_("Save")); + gtk_box_pack_end (GTK_BOX (hbox), wid, 0, 0, 0); + gtk_signal_connect (GTK_OBJECT (wid), "clicked", + GTK_SIGNAL_FUNC (pevent_save_cb), NULL); + gtk_widget_show (wid);*/ + gtkutil_button (hbox, GTK_STOCK_SAVE_AS, NULL, pevent_save_cb, + (void *) 1, _("Save As...")); + gtkutil_button (hbox, GTK_STOCK_OPEN, NULL, pevent_load_cb, + (void *) 0, _("Load From...")); + wid = gtk_button_new_with_label (_("Test All")); + gtk_box_pack_end (GTK_BOX (hbox), wid, 0, 0, 0); + g_signal_connect (G_OBJECT (wid), "clicked", + G_CALLBACK (pevent_test_cb), pevent_dialog_twid); + gtk_widget_show (wid); + + wid = gtk_button_new_from_stock (GTK_STOCK_OK); + gtk_box_pack_start (GTK_BOX (hbox), wid, 0, 0, 0); + g_signal_connect (G_OBJECT (wid), "clicked", + G_CALLBACK (pevent_ok_cb), NULL); + gtk_widget_show (wid); + + gtk_widget_show (hbox); + + gtk_widget_show (pevent_dialog); +} diff --git a/src/fe-gtk/textgui.h b/src/fe-gtk/textgui.h new file mode 100644 index 00000000..23db5848 --- /dev/null +++ b/src/fe-gtk/textgui.h @@ -0,0 +1,2 @@ +void PrintTextRaw (void *xtbuf, unsigned char *text, int indent, time_t stamp); +void pevent_dialog_show (void); diff --git a/src/fe-gtk/urlgrab.c b/src/fe-gtk/urlgrab.c new file mode 100644 index 00000000..6e5f1e0d --- /dev/null +++ b/src/fe-gtk/urlgrab.c @@ -0,0 +1,208 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "fe-gtk.h" + +#include <gtk/gtkhbox.h> +#include <gtk/gtkstock.h> +#include <gtk/gtkhbbox.h> +#include <gtk/gtkscrolledwindow.h> + +#include <gtk/gtkliststore.h> +#include <gtk/gtktreeview.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtkcellrenderertext.h> + +#include "../common/xchat.h" +#include "../common/cfgfiles.h" +#include "../common/fe.h" +#include "../common/url.h" +#include "../common/tree.h" +#include "gtkutil.h" +#include "menu.h" +#include "maingui.h" +#include "urlgrab.h" + +/* model for the URL treeview */ +enum +{ + URL_COLUMN, + N_COLUMNS +}; + +static GtkWidget *urlgrabberwindow = 0; + + +static gboolean +url_treeview_url_clicked_cb (GtkWidget *view, GdkEventButton *event, + gpointer data) +{ + GtkTreeIter iter; + gchar *url; + + if (!event || + !gtkutil_treeview_get_selected (GTK_TREE_VIEW (view), &iter, + URL_COLUMN, &url, -1)) + { + return FALSE; + } + + switch (event->button) + { + case 1: + if (event->type == GDK_2BUTTON_PRESS) + fe_open_url (url); + break; + case 3: + menu_urlmenu (event, url); + break; + default: + break; + } + g_free (url); + + return FALSE; +} + +static GtkWidget * +url_treeview_new (GtkWidget *box) +{ + GtkListStore *store; + GtkWidget *view; + + store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING); + g_return_val_if_fail (store != NULL, NULL); + + view = gtkutil_treeview_new (box, GTK_TREE_MODEL (store), NULL, + URL_COLUMN, _("URL"), -1); + g_signal_connect (G_OBJECT (view), "button_press_event", + G_CALLBACK (url_treeview_url_clicked_cb), NULL); + /* don't want column headers */ + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view), FALSE); + gtk_widget_show (view); + return view; +} + +static void +url_closegui (GtkWidget *wid, gpointer userdata) +{ + urlgrabberwindow = 0; +} + +static void +url_button_clear (void) +{ + GtkListStore *store; + + url_clear (); + store = GTK_LIST_STORE (g_object_get_data (G_OBJECT (urlgrabberwindow), + "model")); + gtk_list_store_clear (store); +} + +static void +url_button_copy (GtkWidget *widget, gpointer data) +{ + GtkTreeView *view = GTK_TREE_VIEW (data); + GtkTreeIter iter; + gchar *url = NULL; + + if (gtkutil_treeview_get_selected (view, &iter, URL_COLUMN, &url, -1)) + { + gtkutil_copy_to_clipboard (GTK_WIDGET (view), NULL, url); + g_free (url); + } +} + +static void +url_save_callback (void *arg1, char *file) +{ + if (file) + url_save (file, "w", TRUE); +} + +static void +url_button_save (void) +{ + gtkutil_file_req (_("Select an output filename"), + url_save_callback, NULL, NULL, FRF_WRITE); +} + +void +fe_url_add (const char *urltext) +{ + GtkListStore *store; + GtkTreeIter iter; + + if (urlgrabberwindow) + { + store = GTK_LIST_STORE (g_object_get_data (G_OBJECT (urlgrabberwindow), + "model")); + gtk_list_store_prepend (store, &iter); + gtk_list_store_set (store, &iter, + URL_COLUMN, urltext, + -1); + } +} + +static int +populate_cb (char *urltext, gpointer userdata) +{ + fe_url_add (urltext); + return TRUE; +} + +void +url_opengui () +{ + GtkWidget *vbox, *hbox, *view; + + if (urlgrabberwindow) + { + mg_bring_tofront (urlgrabberwindow); + return; + } + + urlgrabberwindow = + mg_create_generic_tab ("UrlGrabber", _("XChat: URL Grabber"), FALSE, + TRUE, url_closegui, NULL, 400, 256, &vbox, 0); + view = url_treeview_new (vbox); + g_object_set_data (G_OBJECT (urlgrabberwindow), "model", + gtk_tree_view_get_model (GTK_TREE_VIEW (view))); + + hbox = gtk_hbutton_box_new (); + gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_SPREAD); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 5); + gtk_box_pack_end (GTK_BOX (vbox), hbox, 0, 0, 0); + gtk_widget_show (hbox); + + gtkutil_button (hbox, GTK_STOCK_CLEAR, + _("Clear list"), url_button_clear, 0, _("Clear")); + gtkutil_button (hbox, GTK_STOCK_COPY, + _("Copy selected URL"), url_button_copy, view, _("Copy")); + gtkutil_button (hbox, GTK_STOCK_SAVE_AS, + _("Save list to a file"), url_button_save, 0, _("Save As...")); + + gtk_widget_show (urlgrabberwindow); + + tree_foreach (url_tree, (tree_traverse_func *)populate_cb, NULL); +} diff --git a/src/fe-gtk/urlgrab.h b/src/fe-gtk/urlgrab.h new file mode 100644 index 00000000..cc534241 --- /dev/null +++ b/src/fe-gtk/urlgrab.h @@ -0,0 +1,2 @@ +void url_autosave (void); +void url_opengui (void); diff --git a/src/fe-gtk/userlistgui.c b/src/fe-gtk/userlistgui.c new file mode 100644 index 00000000..f040a6a1 --- /dev/null +++ b/src/fe-gtk/userlistgui.c @@ -0,0 +1,718 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "fe-gtk.h" + +#include <gtk/gtkbox.h> +#include <gtk/gtklabel.h> +#include <gtk/gtkdnd.h> +#include <gtk/gtkentry.h> +#include <gtk/gtktreeview.h> +#include <gtk/gtktreeselection.h> +#include <gtk/gtkscrolledwindow.h> +#include <gtk/gtkcellrendererpixbuf.h> +#include <gtk/gtkcellrenderertext.h> +#include <gtk/gtkliststore.h> +#include <gdk/gdkkeysyms.h> + +#include "../common/xchat.h" +#include "../common/util.h" +#include "../common/userlist.h" +#include "../common/modes.h" +#include "../common/notify.h" +#include "../common/xchatc.h" +#include "gtkutil.h" +#include "palette.h" +#include "maingui.h" +#include "menu.h" +#include "pixmaps.h" +#include "userlistgui.h" + +#ifdef USE_GTKSPELL +#include <gtk/gtktextview.h> +#endif + + +enum +{ + COL_PIX=0, // GdkPixbuf * + COL_NICK=1, // char * + COL_HOST=2, // char * + COL_USER=3, // struct User * + COL_GDKCOLOR=4 // GdkColor * +}; + + +GdkPixbuf * +get_user_icon (server *serv, struct User *user) +{ + char *pre; + int level; + + if (!user) + return NULL; + + /* these ones are hardcoded */ + switch (user->prefix[0]) + { + case 0: return NULL; + case '@': return pix_op; + case '%': return pix_hop; + case '+': return pix_voice; + } + + /* find out how many levels above Op this user is */ + pre = strchr (serv->nick_prefixes, '@'); + if (pre && pre != serv->nick_prefixes) + { + pre--; + level = 0; + while (1) + { + if (pre[0] == user->prefix[0]) + { + switch (level) + { + case 0: return pix_red; /* 1 level above op */ + case 1: return pix_purple; /* 2 levels above op */ + } + break; /* 3+, no icons */ + } + level++; + if (pre == serv->nick_prefixes) + break; + pre--; + } + } + + return NULL; +} + +void +fe_userlist_numbers (session *sess) +{ + char tbuf[256]; + + if (sess == current_tab || !sess->gui->is_tab) + { + if (sess->total) + { + snprintf (tbuf, sizeof (tbuf), _("%d ops, %d total"), sess->ops, sess->total); + tbuf[sizeof (tbuf) - 1] = 0; + gtk_label_set_text (GTK_LABEL (sess->gui->namelistinfo), tbuf); + } else + { + gtk_label_set_text (GTK_LABEL (sess->gui->namelistinfo), NULL); + } + + if (sess->type == SESS_CHANNEL && prefs.gui_tweaks & 1) + fe_set_title (sess); + } +} + +static void +scroll_to_iter (GtkTreeIter *iter, GtkTreeView *treeview, GtkTreeModel *model) +{ + GtkTreePath *path = gtk_tree_model_get_path (model, iter); + if (path) + { + gtk_tree_view_scroll_to_cell (treeview, path, NULL, TRUE, 0.5, 0.5); + gtk_tree_path_free (path); + } +} + +/* select a row in the userlist by nick-name */ + +void +userlist_select (session *sess, char *name) +{ + GtkTreeIter iter; + GtkTreeView *treeview = GTK_TREE_VIEW (sess->gui->user_tree); + GtkTreeModel *model = gtk_tree_view_get_model (treeview); + GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview); + struct User *row_user; + + if (gtk_tree_model_get_iter_first (model, &iter)) + { + do + { + gtk_tree_model_get (model, &iter, COL_USER, &row_user, -1); + if (sess->server->p_cmp (row_user->nick, name) == 0) + { + if (gtk_tree_selection_iter_is_selected (selection, &iter)) + gtk_tree_selection_unselect_iter (selection, &iter); + else + gtk_tree_selection_select_iter (selection, &iter); + + /* and make sure it's visible */ + scroll_to_iter (&iter, treeview, model); + return; + } + } + while (gtk_tree_model_iter_next (model, &iter)); + } +} + +char ** +userlist_selection_list (GtkWidget *widget, int *num_ret) +{ + GtkTreeIter iter; + GtkTreeView *treeview = (GtkTreeView *) widget; + GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview); + GtkTreeModel *model = gtk_tree_view_get_model (treeview); + struct User *user; + int i, num_sel; + char **nicks; + + *num_ret = 0; + /* first, count the number of selections */ + num_sel = 0; + if (gtk_tree_model_get_iter_first (model, &iter)) + { + do + { + if (gtk_tree_selection_iter_is_selected (selection, &iter)) + num_sel++; + } + while (gtk_tree_model_iter_next (model, &iter)); + } + + if (num_sel < 1) + return NULL; + + nicks = malloc (sizeof (char *) * (num_sel + 1)); + + i = 0; + gtk_tree_model_get_iter_first (model, &iter); + do + { + if (gtk_tree_selection_iter_is_selected (selection, &iter)) + { + gtk_tree_model_get (model, &iter, COL_USER, &user, -1); + nicks[i] = g_strdup (user->nick); + i++; + nicks[i] = NULL; + } + } + while (gtk_tree_model_iter_next (model, &iter)); + + *num_ret = i; + return nicks; +} + +void +fe_userlist_set_selected (struct session *sess) +{ + GtkListStore *store = sess->res->user_model; + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (sess->gui->user_tree)); + GtkTreeIter iter; + struct User *user; + + /* if it's not front-most tab it doesn't own the GtkTreeView! */ + if (store != (GtkListStore*) gtk_tree_view_get_model (GTK_TREE_VIEW (sess->gui->user_tree))) + return; + + if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter)) + { + do + { + gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, COL_USER, &user, -1); + + if (gtk_tree_selection_iter_is_selected (selection, &iter)) + user->selected = 1; + else + user->selected = 0; + + } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter)); + } +} + +static GtkTreeIter * +find_row (GtkTreeView *treeview, GtkTreeModel *model, struct User *user, + int *selected) +{ + static GtkTreeIter iter; + struct User *row_user; + + *selected = FALSE; + if (gtk_tree_model_get_iter_first (model, &iter)) + { + do + { + gtk_tree_model_get (model, &iter, COL_USER, &row_user, -1); + if (row_user == user) + { + if (gtk_tree_view_get_model (treeview) == model) + { + if (gtk_tree_selection_iter_is_selected (gtk_tree_view_get_selection (treeview), &iter)) + *selected = TRUE; + } + return &iter; + } + } + while (gtk_tree_model_iter_next (model, &iter)); + } + + return NULL; +} + +void +userlist_set_value (GtkWidget *treeview, gfloat val) +{ + gtk_adjustment_set_value ( + gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (treeview)), val); +} + +gfloat +userlist_get_value (GtkWidget *treeview) +{ + return gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (treeview))->value; +} + +int +fe_userlist_remove (session *sess, struct User *user) +{ + GtkTreeIter *iter; +/* GtkAdjustment *adj; + gfloat val, end;*/ + int sel; + + iter = find_row (GTK_TREE_VIEW (sess->gui->user_tree), + sess->res->user_model, user, &sel); + if (!iter) + return 0; + +/* adj = gtk_tree_view_get_vadjustment (GTK_TREE_VIEW (sess->gui->user_tree)); + val = adj->value;*/ + + gtk_list_store_remove (sess->res->user_model, iter); + + /* is it the front-most tab? */ +/* if (gtk_tree_view_get_model (GTK_TREE_VIEW (sess->gui->user_tree)) + == sess->res->user_model) + { + end = adj->upper - adj->lower - adj->page_size; + if (val > end) + val = end; + gtk_adjustment_set_value (adj, val); + }*/ + + return sel; +} + +void +fe_userlist_rehash (session *sess, struct User *user) +{ + GtkTreeIter *iter; + int sel; + int do_away = TRUE; + + iter = find_row (GTK_TREE_VIEW (sess->gui->user_tree), + sess->res->user_model, user, &sel); + if (!iter) + return; + + if (prefs.away_size_max < 1 || !prefs.away_track) + do_away = FALSE; + + gtk_list_store_set (GTK_LIST_STORE (sess->res->user_model), iter, + COL_HOST, user->hostname, + COL_GDKCOLOR, (do_away) + ? (user->away ? &colors[COL_AWAY] : NULL) + : (NULL), + -1); +} + +void +fe_userlist_insert (session *sess, struct User *newuser, int row, int sel) +{ + GtkTreeModel *model = sess->res->user_model; + GdkPixbuf *pix = get_user_icon (sess->server, newuser); + GtkTreeIter iter; + int do_away = TRUE; + char *nick; + + if (prefs.away_size_max < 1 || !prefs.away_track) + do_away = FALSE; + + nick = newuser->nick; + if (prefs.gui_tweaks & 64) + { + nick = malloc (strlen (newuser->nick) + 2); + nick[0] = newuser->prefix[0]; + if (!nick[0] || nick[0] == ' ') + strcpy (nick, newuser->nick); + else + strcpy (nick + 1, newuser->nick); + pix = NULL; + } + + gtk_list_store_insert_with_values (GTK_LIST_STORE (model), &iter, row, + COL_PIX, pix, + COL_NICK, nick, + COL_HOST, newuser->hostname, + COL_USER, newuser, + COL_GDKCOLOR, (do_away) + ? (newuser->away ? &colors[COL_AWAY] : NULL) + : (NULL), + -1); + + if (prefs.gui_tweaks & 64) + free (nick); + + /* is it me? */ + if (newuser->me && sess->gui->nick_box) + { + if (!sess->gui->is_tab || sess == current_tab) + mg_set_access_icon (sess->gui, pix, sess->server->is_away); + } + +#if 0 + if (prefs.hilitenotify && notify_isnotify (sess, newuser->nick)) + { + gtk_clist_set_foreground ((GtkCList *) sess->gui->user_clist, row, + &colors[prefs.nu_color]); + } +#endif + + /* is it the front-most tab? */ + if (gtk_tree_view_get_model (GTK_TREE_VIEW (sess->gui->user_tree)) + == model) + { + if (sel) + gtk_tree_selection_select_iter (gtk_tree_view_get_selection + (GTK_TREE_VIEW (sess->gui->user_tree)), &iter); + } +} + +void +fe_userlist_move (session *sess, struct User *user, int new_row) +{ + fe_userlist_insert (sess, user, new_row, fe_userlist_remove (sess, user)); +} + +void +fe_userlist_clear (session *sess) +{ + gtk_list_store_clear (sess->res->user_model); +} + +static void +userlist_dnd_drop (GtkTreeView *widget, GdkDragContext *context, + gint x, gint y, GtkSelectionData *selection_data, + guint info, guint ttime, gpointer userdata) +{ + struct User *user; + GtkTreePath *path; + GtkTreeModel *model; + GtkTreeIter iter; + + if (!gtk_tree_view_get_path_at_pos (widget, x, y, &path, NULL, NULL, NULL)) + return; + + model = gtk_tree_view_get_model (widget); + if (!gtk_tree_model_get_iter (model, &iter, path)) + return; + gtk_tree_model_get (model, &iter, COL_USER, &user, -1); + + mg_dnd_drop_file (current_sess, user->nick, selection_data->data); +} + +static gboolean +userlist_dnd_motion (GtkTreeView *widget, GdkDragContext *context, gint x, + gint y, guint ttime, gpointer tree) +{ + GtkTreePath *path; + GtkTreeSelection *sel; + + if (!tree) + return FALSE; + + if (gtk_tree_view_get_path_at_pos (widget, x, y, &path, NULL, NULL, NULL)) + { + sel = gtk_tree_view_get_selection (widget); + gtk_tree_selection_unselect_all (sel); + gtk_tree_selection_select_path (sel, path); + } + + return FALSE; +} + +static gboolean +userlist_dnd_leave (GtkTreeView *widget, GdkDragContext *context, guint ttime) +{ + gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (widget)); + return TRUE; +} + +void * +userlist_create_model (void) +{ + return gtk_list_store_new (5, GDK_TYPE_PIXBUF, G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_POINTER, GDK_TYPE_COLOR); +} + +static void +userlist_add_columns (GtkTreeView * treeview) +{ + GtkCellRenderer *renderer; + + /* icon column */ + renderer = gtk_cell_renderer_pixbuf_new (); + if (prefs.gui_tweaks & 32) + g_object_set (G_OBJECT (renderer), "ypad", 0, NULL); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview), + -1, NULL, renderer, + "pixbuf", 0, NULL); + + /* nick column */ + renderer = gtk_cell_renderer_text_new (); + if (prefs.gui_tweaks & 32) + g_object_set (G_OBJECT (renderer), "ypad", 0, NULL); + gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer), 1); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview), + -1, NULL, renderer, + "text", 1, "foreground-gdk", 4, NULL); + + if (prefs.showhostname_in_userlist) + { + /* hostname column */ + renderer = gtk_cell_renderer_text_new (); + if (prefs.gui_tweaks & 32) + g_object_set (G_OBJECT (renderer), "ypad", 0, NULL); + gtk_cell_renderer_text_set_fixed_height_from_font (GTK_CELL_RENDERER_TEXT (renderer), 1); + gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (treeview), + -1, NULL, renderer, + "text", 2, NULL); + } +} + +static gint +userlist_click_cb (GtkWidget *widget, GdkEventButton *event, gpointer userdata) +{ + char **nicks; + int i; + GtkTreeSelection *sel; + GtkTreePath *path; + + if (!event) + return FALSE; + + if (!(event->state & GDK_CONTROL_MASK) && + event->type == GDK_2BUTTON_PRESS && prefs.doubleclickuser[0]) + { + nicks = userlist_selection_list (widget, &i); + if (nicks) + { + nick_command_parse (current_sess, prefs.doubleclickuser, nicks[0], + nicks[0]); + while (i) + { + i--; + g_free (nicks[i]); + } + free (nicks); + } + return TRUE; + } + + if (event->button == 3) + { + /* do we have a multi-selection? */ + nicks = userlist_selection_list (widget, &i); + if (nicks && i > 1) + { + menu_nickmenu (current_sess, event, nicks[0], i); + while (i) + { + i--; + g_free (nicks[i]); + } + free (nicks); + return TRUE; + } + if (nicks) + { + g_free (nicks[0]); + free (nicks); + } + + sel = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget)); + if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (widget), + event->x, event->y, &path, 0, 0, 0)) + { + gtk_tree_selection_unselect_all (sel); + gtk_tree_selection_select_path (sel, path); + gtk_tree_path_free (path); + nicks = userlist_selection_list (widget, &i); + if (nicks) + { + menu_nickmenu (current_sess, event, nicks[0], i); + while (i) + { + i--; + g_free (nicks[i]); + } + free (nicks); + } + } else + { + gtk_tree_selection_unselect_all (sel); + } + + return TRUE; + } + + return FALSE; +} + +static gboolean +userlist_key_cb (GtkWidget *wid, GdkEventKey *evt, gpointer userdata) +{ + if (evt->keyval >= GDK_asterisk && evt->keyval <= GDK_z) + { + /* dirty trick to avoid auto-selection */ + SPELL_ENTRY_SET_EDITABLE (current_sess->gui->input_box, FALSE); + gtk_widget_grab_focus (current_sess->gui->input_box); + SPELL_ENTRY_SET_EDITABLE (current_sess->gui->input_box, TRUE); + gtk_widget_event (current_sess->gui->input_box, (GdkEvent *)evt); + return TRUE; + } + + return FALSE; +} + +GtkWidget * +userlist_create (GtkWidget *box) +{ + GtkWidget *sw, *treeview; + static const GtkTargetEntry dnd_dest_targets[] = + { + {"text/uri-list", 0, 1}, + {"XCHAT_CHANVIEW", GTK_TARGET_SAME_APP, 75 } + }; + static const GtkTargetEntry dnd_src_target[] = + { + {"XCHAT_USERLIST", GTK_TARGET_SAME_APP, 75 } + }; + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), + GTK_SHADOW_ETCHED_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + prefs.showhostname_in_userlist ? + GTK_POLICY_AUTOMATIC : + GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX (box), sw, TRUE, TRUE, 0); + gtk_widget_show (sw); + + treeview = gtk_tree_view_new (); + gtk_widget_set_name (treeview, "xchat-userlist"); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (treeview), FALSE); + gtk_tree_selection_set_mode (gtk_tree_view_get_selection + (GTK_TREE_VIEW (treeview)), + GTK_SELECTION_MULTIPLE); + + /* set up drops */ + gtk_drag_dest_set (treeview, GTK_DEST_DEFAULT_ALL, dnd_dest_targets, 2, + GDK_ACTION_MOVE | GDK_ACTION_COPY | GDK_ACTION_LINK); + gtk_drag_source_set (treeview, GDK_BUTTON1_MASK, dnd_src_target, 1, GDK_ACTION_MOVE); + + /* file DND (for DCC) */ + g_signal_connect (G_OBJECT (treeview), "drag_motion", + G_CALLBACK (userlist_dnd_motion), treeview); + g_signal_connect (G_OBJECT (treeview), "drag_leave", + G_CALLBACK (userlist_dnd_leave), 0); + g_signal_connect (G_OBJECT (treeview), "drag_data_received", + G_CALLBACK (userlist_dnd_drop), treeview); + + g_signal_connect (G_OBJECT (treeview), "button_press_event", + G_CALLBACK (userlist_click_cb), 0); + g_signal_connect (G_OBJECT (treeview), "key_press_event", + G_CALLBACK (userlist_key_cb), 0); + + /* tree/chanview DND */ +#ifndef WIN32 /* leaks GDI pool memory, don't enable */ + g_signal_connect (G_OBJECT (treeview), "drag_begin", + G_CALLBACK (mg_drag_begin_cb), NULL); + g_signal_connect (G_OBJECT (treeview), "drag_drop", + G_CALLBACK (mg_drag_drop_cb), NULL); + g_signal_connect (G_OBJECT (treeview), "drag_motion", + G_CALLBACK (mg_drag_motion_cb), NULL); + g_signal_connect (G_OBJECT (treeview), "drag_end", + G_CALLBACK (mg_drag_end_cb), NULL); +#endif + + userlist_add_columns (GTK_TREE_VIEW (treeview)); + + gtk_container_add (GTK_CONTAINER (sw), treeview); + gtk_widget_show (treeview); + + return treeview; +} + +void +userlist_show (session *sess) +{ + gtk_tree_view_set_model (GTK_TREE_VIEW (sess->gui->user_tree), + sess->res->user_model); +} + +void +fe_uselect (session *sess, char *word[], int do_clear, int scroll_to) +{ + int thisname; + char *name; + GtkTreeIter iter; + GtkTreeView *treeview = GTK_TREE_VIEW (sess->gui->user_tree); + GtkTreeModel *model = gtk_tree_view_get_model (treeview); + GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview); + struct User *row_user; + + if (gtk_tree_model_get_iter_first (model, &iter)) + { + if (do_clear) + gtk_tree_selection_unselect_all (selection); + + do + { + if (*word[0]) + { + gtk_tree_model_get (model, &iter, COL_USER, &row_user, -1); + thisname = 0; + while ( *(name = word[thisname++]) ) + { + if (sess->server->p_cmp (row_user->nick, name) == 0) + { + gtk_tree_selection_select_iter (selection, &iter); + if (scroll_to) + scroll_to_iter (&iter, treeview, model); + break; + } + } + } + + } + while (gtk_tree_model_iter_next (model, &iter)); + } +} diff --git a/src/fe-gtk/userlistgui.h b/src/fe-gtk/userlistgui.h new file mode 100644 index 00000000..b49e2b9b --- /dev/null +++ b/src/fe-gtk/userlistgui.h @@ -0,0 +1,8 @@ +void userlist_set_value (GtkWidget *treeview, gfloat val); +gfloat userlist_get_value (GtkWidget *treeview); +GtkWidget *userlist_create (GtkWidget *box); +void *userlist_create_model (void); +void userlist_show (session *sess); +void userlist_select (session *sess, char *name); +char **userlist_selection_list (GtkWidget *widget, int *num_ret); +GdkPixbuf *get_user_icon (server *serv, struct User *user); diff --git a/src/fe-gtk/xtext.c b/src/fe-gtk/xtext.c new file mode 100644 index 00000000..fa9803c7 --- /dev/null +++ b/src/fe-gtk/xtext.c @@ -0,0 +1,5478 @@ +/* X-Chat + * Copyright (C) 1998 Peter Zelezny. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA + * ========================================================================= + * + * xtext, the text widget used by X-Chat. + * By Peter Zelezny <zed@xchat.org>. + * + */ + +#define XCHAT /* using xchat */ +#define TINT_VALUE 195 /* 195/255 of the brightness. */ +#define MOTION_MONITOR /* URL hilights. */ +#define SMOOTH_SCROLL /* line-by-line or pixel scroll? */ +#define SCROLL_HACK /* use XCopyArea scroll, or full redraw? */ +#undef COLOR_HILIGHT /* Color instead of underline? */ +/* Italic is buggy because it assumes drawing an italic string will have + identical extents to the normal font. This is only true some of the + time, so we can't use this hack yet. */ +#undef ITALIC /* support Italic? */ +#define GDK_MULTIHEAD_SAFE +#define USE_DB /* double buffer */ + +#define MARGIN 2 /* dont touch. */ +#define REFRESH_TIMEOUT 20 +#define WORDWRAP_LIMIT 24 + +#include <string.h> +#include <ctype.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> +#include <gtk/gtkmain.h> +#include <gtk/gtksignal.h> +#include <gtk/gtkselection.h> +#include <gtk/gtkclipboard.h> +#include <gtk/gtkversion.h> +#include <gtk/gtkwindow.h> + +#ifdef XCHAT +#include "../../config.h" /* can define USE_XLIB here */ +#else +#define USE_XLIB +#endif + +#ifdef USE_XLIB +#include <gdk/gdkx.h> +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#endif + +#ifdef USE_MMX +#include "mmx_cmod.h" +#endif + +#include "xtext.h" + +#define charlen(str) g_utf8_skip[*(guchar *)(str)] + +#ifdef WIN32 +#include <windows.h> +#include <gdk/gdkwin32.h> +#endif + +/* is delimiter */ +#define is_del(c) \ + (c == ' ' || c == '\n' || c == ')' || c == '(' || \ + c == '>' || c == '<' || c == ATTR_RESET || c == ATTR_BOLD || c == 0) + +#ifdef SCROLL_HACK +/* force scrolling off */ +#define dontscroll(buf) (buf)->last_pixel_pos = 0x7fffffff +#else +#define dontscroll(buf) +#endif + +static GtkWidgetClass *parent_class = NULL; + +struct textentry +{ + struct textentry *next; + struct textentry *prev; + unsigned char *str; + time_t stamp; + gint16 str_width; + gint16 str_len; + gint16 mark_start; + gint16 mark_end; + gint16 indent; + gint16 left_len; + gint16 lines_taken; +#define RECORD_WRAPS 4 + guint16 wrap_offset[RECORD_WRAPS]; + guchar mb; /* boolean: is multibyte? */ + guchar tag; + guchar pad1; + guchar pad2; /* 32-bit align : 44 bytes total */ +}; + +enum +{ + WORD_CLICK, + LAST_SIGNAL +}; + +/* values for selection info */ +enum +{ + TARGET_UTF8_STRING, + TARGET_STRING, + TARGET_TEXT, + TARGET_COMPOUND_TEXT +}; + +static guint xtext_signals[LAST_SIGNAL]; + +#ifdef XCHAT +char *nocasestrstr (const char *text, const char *tofind); /* util.c */ +int xtext_get_stamp_str (time_t, char **); +#endif +static void gtk_xtext_render_page (GtkXText * xtext); +static void gtk_xtext_calc_lines (xtext_buffer *buf, int); +#if defined(USE_XLIB) || defined(WIN32) +static void gtk_xtext_load_trans (GtkXText * xtext); +static void gtk_xtext_free_trans (GtkXText * xtext); +#endif +static char *gtk_xtext_selection_get_text (GtkXText *xtext, int *len_ret); +static textentry *gtk_xtext_nth (GtkXText *xtext, int line, int *subline); +static void gtk_xtext_adjustment_changed (GtkAdjustment * adj, + GtkXText * xtext); +static int gtk_xtext_render_ents (GtkXText * xtext, textentry *, textentry *); +static void gtk_xtext_recalc_widths (xtext_buffer *buf, int); +static void gtk_xtext_fix_indent (xtext_buffer *buf); +static int gtk_xtext_find_subline (GtkXText *xtext, textentry *ent, int line); +static char *gtk_xtext_conv_color (unsigned char *text, int len, int *newlen); +static unsigned char * +gtk_xtext_strip_color (unsigned char *text, int len, unsigned char *outbuf, + int *newlen, int *mb_ret, int strip_hidden); +static gboolean gtk_xtext_check_ent_visibility (GtkXText * xtext, textentry *find_ent, int add); +static int gtk_xtext_render_page_timeout (GtkXText * xtext); + +/* some utility functions first */ + +#ifndef XCHAT /* xchat has this in util.c */ + +static char * +nocasestrstr (const char *s, const char *tofind) +{ + register const size_t len = strlen (tofind); + + if (len == 0) + return (char *)s; + while (toupper(*s) != toupper(*tofind) || strncasecmp (s, tofind, len)) + if (*s++ == '\0') + return (char *)NULL; + return (char *)s; +} + +#endif + +/* gives width of a 8bit string - with no mIRC codes in it */ + +static int +gtk_xtext_text_width_8bit (GtkXText *xtext, unsigned char *str, int len) +{ + int width = 0; + + while (len) + { + width += xtext->fontwidth[*str]; + str++; + len--; + } + + return width; +} + +#ifdef WIN32 + +static void +win32_draw_bg (GtkXText *xtext, int x, int y, int width, int height) +{ + HDC hdc; + HWND hwnd; + HRGN rgn; + + if (xtext->shaded) + { + /* xtext->pixmap is really a GdkImage, created in win32_tint() */ + gdk_draw_image (xtext->draw_buf, xtext->bgc, (GdkImage*)xtext->pixmap, + x, y, x, y, width, height); + } else + { + hwnd = GDK_WINDOW_HWND (xtext->draw_buf); + hdc = GetDC (hwnd); + + rgn = CreateRectRgn (x, y, x + width, y + height); + SelectClipRgn (hdc, rgn); + + PaintDesktop (hdc); + + ReleaseDC (hwnd, hdc); + DeleteObject (rgn); + } +} + +static void +xtext_draw_bg (GtkXText *xtext, int x, int y, int width, int height) +{ + if (xtext->transparent) + win32_draw_bg (xtext, x, y, width, height); + else + gdk_draw_rectangle (xtext->draw_buf, xtext->bgc, 1, x, y, width, height); +} + +#else + +#define xtext_draw_bg(xt,x,y,w,h) gdk_draw_rectangle(xt->draw_buf, xt->bgc, \ + 1,x,y,w,h); + +#endif + +/* ========================================= */ +/* ========== XFT 1 and 2 BACKEND ========== */ +/* ========================================= */ + +#ifdef USE_XFT + +static void +backend_font_close (GtkXText *xtext) +{ + XftFontClose (GDK_WINDOW_XDISPLAY (xtext->draw_buf), xtext->font); +#ifdef ITALIC + XftFontClose (GDK_WINDOW_XDISPLAY (xtext->draw_buf), xtext->ifont); +#endif +} + +static void +backend_init (GtkXText *xtext) +{ + if (xtext->xftdraw == NULL) + { + xtext->xftdraw = XftDrawCreate ( + GDK_WINDOW_XDISPLAY (xtext->draw_buf), + GDK_WINDOW_XWINDOW (xtext->draw_buf), + GDK_VISUAL_XVISUAL (gdk_drawable_get_visual (xtext->draw_buf)), + GDK_COLORMAP_XCOLORMAP (gdk_drawable_get_colormap (xtext->draw_buf))); + XftDrawSetSubwindowMode (xtext->xftdraw, IncludeInferiors); + } +} + +static void +backend_deinit (GtkXText *xtext) +{ + if (xtext->xftdraw) + { + XftDrawDestroy (xtext->xftdraw); + xtext->xftdraw = NULL; + } +} + +static XftFont * +backend_font_open_real (Display *xdisplay, char *name, gboolean italics) +{ + XftFont *font = NULL; + PangoFontDescription *fontd; + int weight, slant, screen = DefaultScreen (xdisplay); + + fontd = pango_font_description_from_string (name); + + if (pango_font_description_get_size (fontd) != 0) + { + weight = pango_font_description_get_weight (fontd); + /* from pangoft2-fontmap.c */ + if (weight < (PANGO_WEIGHT_NORMAL + PANGO_WEIGHT_LIGHT) / 2) + weight = XFT_WEIGHT_LIGHT; + else if (weight < (PANGO_WEIGHT_NORMAL + 600) / 2) + weight = XFT_WEIGHT_MEDIUM; + else if (weight < (600 + PANGO_WEIGHT_BOLD) / 2) + weight = XFT_WEIGHT_DEMIBOLD; + else if (weight < (PANGO_WEIGHT_BOLD + PANGO_WEIGHT_ULTRABOLD) / 2) + weight = XFT_WEIGHT_BOLD; + else + weight = XFT_WEIGHT_BLACK; + + slant = pango_font_description_get_style (fontd); + if (slant == PANGO_STYLE_ITALIC) + slant = XFT_SLANT_ITALIC; + else if (slant == PANGO_STYLE_OBLIQUE) + slant = XFT_SLANT_OBLIQUE; + else + slant = XFT_SLANT_ROMAN; + + font = XftFontOpen (xdisplay, screen, + XFT_FAMILY, XftTypeString, pango_font_description_get_family (fontd), + XFT_CORE, XftTypeBool, False, + XFT_SIZE, XftTypeDouble, (double)pango_font_description_get_size (fontd)/PANGO_SCALE, + XFT_WEIGHT, XftTypeInteger, weight, + XFT_SLANT, XftTypeInteger, italics ? XFT_SLANT_ITALIC : slant, + NULL); + } + pango_font_description_free (fontd); + + if (font == NULL) + { + font = XftFontOpenName (xdisplay, screen, name); + if (font == NULL) + font = XftFontOpenName (xdisplay, screen, "sans-11"); + } + + return font; +} + +static void +backend_font_open (GtkXText *xtext, char *name) +{ + Display *dis = GDK_WINDOW_XDISPLAY (xtext->draw_buf); + + xtext->font = backend_font_open_real (dis, name, FALSE); +#ifdef ITALIC + xtext->ifont = backend_font_open_real (dis, name, TRUE); +#endif +} + +inline static int +backend_get_char_width (GtkXText *xtext, unsigned char *str, int *mbl_ret) +{ + XGlyphInfo ext; + + if (*str < 128) + { + *mbl_ret = 1; + return xtext->fontwidth[*str]; + } + + *mbl_ret = charlen (str); + XftTextExtentsUtf8 (GDK_WINDOW_XDISPLAY (xtext->draw_buf), xtext->font, str, *mbl_ret, &ext); + + return ext.xOff; +} + +static int +backend_get_text_width (GtkXText *xtext, guchar *str, int len, int is_mb) +{ + XGlyphInfo ext; + + if (!is_mb) + return gtk_xtext_text_width_8bit (xtext, str, len); + + XftTextExtentsUtf8 (GDK_WINDOW_XDISPLAY (xtext->draw_buf), xtext->font, str, len, &ext); + return ext.xOff; +} + +static void +backend_draw_text (GtkXText *xtext, int dofill, GdkGC *gc, int x, int y, + char *str, int len, int str_width, int is_mb) +{ + /*Display *xdisplay = GDK_WINDOW_XDISPLAY (xtext->draw_buf);*/ + void (*draw_func) (XftDraw *, XftColor *, XftFont *, int, int, XftChar8 *, int) = (void *)XftDrawString8; + XftFont *font; + + /* if all ascii, use String8 to avoid the conversion penalty */ + if (is_mb) + draw_func = (void *)XftDrawStringUtf8; + + if (dofill) + { +/* register GC xgc = GDK_GC_XGC (gc); + XSetForeground (xdisplay, xgc, xtext->xft_bg->pixel); + XFillRectangle (xdisplay, GDK_WINDOW_XWINDOW (xtext->draw_buf), xgc, x, + y - xtext->font->ascent, str_width, xtext->fontsize);*/ + XftDrawRect (xtext->xftdraw, xtext->xft_bg, x, + y - xtext->font->ascent, str_width, xtext->fontsize); + } + + font = xtext->font; +#ifdef ITALIC + if (xtext->italics) + font = xtext->ifont; +#endif + + draw_func (xtext->xftdraw, xtext->xft_fg, font, x, y, str, len); + + if (xtext->overdraw) + draw_func (xtext->xftdraw, xtext->xft_fg, font, x, y, str, len); + + if (xtext->bold) + draw_func (xtext->xftdraw, xtext->xft_fg, font, x + 1, y, str, len); +} + +/*static void +backend_set_clip (GtkXText *xtext, GdkRectangle *area) +{ + gdk_gc_set_clip_rectangle (xtext->fgc, area); + gdk_gc_set_clip_rectangle (xtext->bgc, area); +} + +static void +backend_clear_clip (GtkXText *xtext) +{ + gdk_gc_set_clip_rectangle (xtext->fgc, NULL); + gdk_gc_set_clip_rectangle (xtext->bgc, NULL); +}*/ + +/*static void +backend_set_clip (GtkXText *xtext, GdkRectangle *area) +{ + Region reg; + XRectangle rect; + + rect.x = area->x; + rect.y = area->y; + rect.width = area->width; + rect.height = area->height; + + reg = XCreateRegion (); + XUnionRectWithRegion (&rect, reg, reg); + XftDrawSetClip (xtext->xftdraw, reg); + XDestroyRegion (reg); + + gdk_gc_set_clip_rectangle (xtext->fgc, area); +} + +static void +backend_clear_clip (GtkXText *xtext) +{ + XftDrawSetClip (xtext->xftdraw, NULL); + gdk_gc_set_clip_rectangle (xtext->fgc, NULL); +} +*/ +#else /* !USE_XFT */ + +/* ======================================= */ +/* ============ PANGO BACKEND ============ */ +/* ======================================= */ + +static void +backend_font_close (GtkXText *xtext) +{ + pango_font_description_free (xtext->font->font); +#ifdef ITALIC + pango_font_description_free (xtext->font->ifont); +#endif +} + +static void +backend_init (GtkXText *xtext) +{ + if (xtext->layout == NULL) + { + xtext->layout = gtk_widget_create_pango_layout (GTK_WIDGET (xtext), 0); + if (xtext->font) + pango_layout_set_font_description (xtext->layout, xtext->font->font); + } +} + +static void +backend_deinit (GtkXText *xtext) +{ + if (xtext->layout) + { + g_object_unref (xtext->layout); + xtext->layout = NULL; + } +} + +static PangoFontDescription * +backend_font_open_real (char *name) +{ + PangoFontDescription *font; + + font = pango_font_description_from_string (name); + if (font && pango_font_description_get_size (font) == 0) + { + pango_font_description_free (font); + font = pango_font_description_from_string ("sans 11"); + } + if (!font) + font = pango_font_description_from_string ("sans 11"); + + return font; +} + +static void +backend_font_open (GtkXText *xtext, char *name) +{ + PangoLanguage *lang; + PangoContext *context; + PangoFontMetrics *metrics; + + xtext->font = &xtext->pango_font; + xtext->font->font = backend_font_open_real (name); + if (!xtext->font->font) + { + xtext->font = NULL; + return; + } +#ifdef ITALIC + xtext->font->ifont = backend_font_open_real (name); + pango_font_description_set_style (xtext->font->ifont, PANGO_STYLE_ITALIC); +#endif + + backend_init (xtext); + pango_layout_set_font_description (xtext->layout, xtext->font->font); + + /* vte does it this way */ + context = gtk_widget_get_pango_context (GTK_WIDGET (xtext)); + lang = pango_context_get_language (context); + metrics = pango_context_get_metrics (context, xtext->font->font, lang); + xtext->font->ascent = pango_font_metrics_get_ascent (metrics) / PANGO_SCALE; + xtext->font->descent = pango_font_metrics_get_descent (metrics) / PANGO_SCALE; + pango_font_metrics_unref (metrics); +} + +static int +backend_get_text_width (GtkXText *xtext, guchar *str, int len, int is_mb) +{ + int width; + + if (!is_mb) + return gtk_xtext_text_width_8bit (xtext, str, len); + + if (*str == 0) + return 0; + + pango_layout_set_text (xtext->layout, str, len); + pango_layout_get_pixel_size (xtext->layout, &width, NULL); + + return width; +} + +inline static int +backend_get_char_width (GtkXText *xtext, unsigned char *str, int *mbl_ret) +{ + int width; + + if (*str < 128) + { + *mbl_ret = 1; + return xtext->fontwidth[*str]; + } + + *mbl_ret = charlen (str); + pango_layout_set_text (xtext->layout, str, *mbl_ret); + pango_layout_get_pixel_size (xtext->layout, &width, NULL); + + return width; +} + +/* simplified version of gdk_draw_layout_line_with_colors() */ + +static void +xtext_draw_layout_line (GdkDrawable *drawable, + GdkGC *gc, + gint x, + gint y, + PangoLayoutLine *line) +{ + GSList *tmp_list = line->runs; + PangoRectangle logical_rect; + gint x_off = 0; + + while (tmp_list) + { + PangoLayoutRun *run = tmp_list->data; + + pango_glyph_string_extents (run->glyphs, run->item->analysis.font, + NULL, &logical_rect); + + gdk_draw_glyphs (drawable, gc, run->item->analysis.font, + x + x_off / PANGO_SCALE, y, run->glyphs); + + x_off += logical_rect.width; + tmp_list = tmp_list->next; + } +} + +static void +backend_draw_text (GtkXText *xtext, int dofill, GdkGC *gc, int x, int y, + char *str, int len, int str_width, int is_mb) +{ + GdkGCValues val; + GdkColor col; + PangoLayoutLine *line; + +#ifdef ITALIC + if (xtext->italics) + pango_layout_set_font_description (xtext->layout, xtext->font->ifont); +#endif + + pango_layout_set_text (xtext->layout, str, len); + + if (dofill) + { +#ifdef WIN32 + if (xtext->transparent && !xtext->backcolor) + win32_draw_bg (xtext, x, y - xtext->font->ascent, str_width, + xtext->fontsize); + else +#endif + { + gdk_gc_get_values (gc, &val); + col.pixel = val.background.pixel; + gdk_gc_set_foreground (gc, &col); + gdk_draw_rectangle (xtext->draw_buf, gc, 1, x, y - + xtext->font->ascent, str_width, xtext->fontsize); + col.pixel = val.foreground.pixel; + gdk_gc_set_foreground (gc, &col); + } + } + + line = pango_layout_get_lines (xtext->layout)->data; + + xtext_draw_layout_line (xtext->draw_buf, gc, x, y, line); + + if (xtext->overdraw) + xtext_draw_layout_line (xtext->draw_buf, gc, x, y, line); + + if (xtext->bold) + xtext_draw_layout_line (xtext->draw_buf, gc, x + 1, y, line); + +#ifdef ITALIC + if (xtext->italics) + pango_layout_set_font_description (xtext->layout, xtext->font->font); +#endif +} + +/*static void +backend_set_clip (GtkXText *xtext, GdkRectangle *area) +{ + gdk_gc_set_clip_rectangle (xtext->fgc, area); + gdk_gc_set_clip_rectangle (xtext->bgc, area); +} + +static void +backend_clear_clip (GtkXText *xtext) +{ + gdk_gc_set_clip_rectangle (xtext->fgc, NULL); + gdk_gc_set_clip_rectangle (xtext->bgc, NULL); +}*/ + +#endif /* !USE_PANGO */ + +static void +xtext_set_fg (GtkXText *xtext, GdkGC *gc, int index) +{ + GdkColor col; + + col.pixel = xtext->palette[index]; + gdk_gc_set_foreground (gc, &col); + +#ifdef USE_XFT + if (gc == xtext->fgc) + xtext->xft_fg = &xtext->color[index]; + else + xtext->xft_bg = &xtext->color[index]; +#endif +} + +#ifdef USE_XFT + +#define xtext_set_bg(xt,gc,index) xt->xft_bg = &xt->color[index] + +#else + +static void +xtext_set_bg (GtkXText *xtext, GdkGC *gc, int index) +{ + GdkColor col; + + col.pixel = xtext->palette[index]; + gdk_gc_set_background (gc, &col); +} + +#endif + +static void +gtk_xtext_init (GtkXText * xtext) +{ + xtext->pixmap = NULL; + xtext->io_tag = 0; + xtext->add_io_tag = 0; + xtext->scroll_tag = 0; + xtext->max_lines = 0; + xtext->col_back = XTEXT_BG; + xtext->col_fore = XTEXT_FG; + xtext->nc = 0; + xtext->pixel_offset = 0; + xtext->bold = FALSE; + xtext->underline = FALSE; + xtext->italics = FALSE; + xtext->hidden = FALSE; + xtext->font = NULL; +#ifdef USE_XFT + xtext->xftdraw = NULL; +#else + xtext->layout = NULL; +#endif + xtext->jump_out_offset = 0; + xtext->jump_in_offset = 0; + xtext->ts_x = 0; + xtext->ts_y = 0; + xtext->clip_x = 0; + xtext->clip_x2 = 1000000; + xtext->clip_y = 0; + xtext->clip_y2 = 1000000; + xtext->error_function = NULL; + xtext->urlcheck_function = NULL; + xtext->color_paste = FALSE; + xtext->skip_border_fills = FALSE; + xtext->skip_stamp = FALSE; + xtext->render_hilights_only = FALSE; + xtext->un_hilight = FALSE; + xtext->recycle = FALSE; + xtext->dont_render = FALSE; + xtext->dont_render2 = FALSE; + xtext->overdraw = FALSE; + xtext->tint_red = xtext->tint_green = xtext->tint_blue = TINT_VALUE; + + xtext->adj = (GtkAdjustment *) gtk_adjustment_new (0, 0, 1, 1, 1, 1); + g_object_ref (G_OBJECT (xtext->adj)); + g_object_ref_sink (G_OBJECT (xtext->adj)); + g_object_unref (G_OBJECT (xtext->adj)); + + xtext->vc_signal_tag = g_signal_connect (G_OBJECT (xtext->adj), + "value_changed", G_CALLBACK (gtk_xtext_adjustment_changed), xtext); + { + static const GtkTargetEntry targets[] = { + { "UTF8_STRING", 0, TARGET_UTF8_STRING }, + { "STRING", 0, TARGET_STRING }, + { "TEXT", 0, TARGET_TEXT }, + { "COMPOUND_TEXT", 0, TARGET_COMPOUND_TEXT } + }; + static const gint n_targets = sizeof (targets) / sizeof (targets[0]); + + gtk_selection_add_targets (GTK_WIDGET (xtext), GDK_SELECTION_PRIMARY, + targets, n_targets); + } + + if (getenv ("XCHAT_OVERDRAW")) + xtext->overdraw = TRUE; +} + +static void +gtk_xtext_adjustment_set (xtext_buffer *buf, int fire_signal) +{ + GtkAdjustment *adj = buf->xtext->adj; + + if (buf->xtext->buffer == buf) + { + adj->lower = 0; + adj->upper = buf->num_lines; + + if (adj->upper == 0) + adj->upper = 1; + + adj->page_size = + (GTK_WIDGET (buf->xtext)->allocation.height - + buf->xtext->font->descent) / buf->xtext->fontsize; + adj->page_increment = adj->page_size; + + if (adj->value > adj->upper - adj->page_size) + adj->value = adj->upper - adj->page_size; + + if (adj->value < 0) + adj->value = 0; + + if (fire_signal) + gtk_adjustment_changed (adj); + } +} + +static gint +gtk_xtext_adjustment_timeout (GtkXText * xtext) +{ + gtk_xtext_render_page (xtext); + xtext->io_tag = 0; + return 0; +} + +static void +gtk_xtext_adjustment_changed (GtkAdjustment * adj, GtkXText * xtext) +{ +#ifdef SMOOTH_SCROLL + if (xtext->buffer->old_value != xtext->adj->value) +#else + if ((int) xtext->buffer->old_value != (int) xtext->adj->value) +#endif + { + if (xtext->adj->value >= xtext->adj->upper - xtext->adj->page_size) + xtext->buffer->scrollbar_down = TRUE; + else + xtext->buffer->scrollbar_down = FALSE; + + if (xtext->adj->value + 1 == xtext->buffer->old_value || + xtext->adj->value - 1 == xtext->buffer->old_value) /* clicked an arrow? */ + { + if (xtext->io_tag) + { + g_source_remove (xtext->io_tag); + xtext->io_tag = 0; + } + gtk_xtext_render_page (xtext); + } else + { + if (!xtext->io_tag) + xtext->io_tag = g_timeout_add (REFRESH_TIMEOUT, + (GSourceFunc) + gtk_xtext_adjustment_timeout, + xtext); + } + } + xtext->buffer->old_value = adj->value; +} + +GtkWidget * +gtk_xtext_new (GdkColor palette[], int separator) +{ + GtkXText *xtext; + + xtext = g_object_new (gtk_xtext_get_type (), NULL); + xtext->separator = separator; + xtext->wordwrap = TRUE; + xtext->buffer = gtk_xtext_buffer_new (xtext); + xtext->orig_buffer = xtext->buffer; + + gtk_widget_set_double_buffered (GTK_WIDGET (xtext), FALSE); + gtk_xtext_set_palette (xtext, palette); + + return GTK_WIDGET (xtext); +} + +static void +gtk_xtext_destroy (GtkObject * object) +{ + GtkXText *xtext = GTK_XTEXT (object); + + if (xtext->add_io_tag) + { + g_source_remove (xtext->add_io_tag); + xtext->add_io_tag = 0; + } + + if (xtext->scroll_tag) + { + g_source_remove (xtext->scroll_tag); + xtext->scroll_tag = 0; + } + + if (xtext->io_tag) + { + g_source_remove (xtext->io_tag); + xtext->io_tag = 0; + } + + if (xtext->pixmap) + { +#if defined(USE_XLIB) || defined(WIN32) + if (xtext->transparent) + gtk_xtext_free_trans (xtext); + else +#endif + g_object_unref (xtext->pixmap); + xtext->pixmap = NULL; + } + + if (xtext->font) + { + backend_font_close (xtext); + xtext->font = NULL; + } + + if (xtext->adj) + { + g_signal_handlers_disconnect_matched (G_OBJECT (xtext->adj), + G_SIGNAL_MATCH_DATA, 0, 0, NULL, NULL, xtext); + /* gtk_signal_disconnect_by_data (GTK_OBJECT (xtext->adj), xtext);*/ + g_object_unref (G_OBJECT (xtext->adj)); + xtext->adj = NULL; + } + + if (xtext->bgc) + { + g_object_unref (xtext->bgc); + xtext->bgc = NULL; + } + + if (xtext->fgc) + { + g_object_unref (xtext->fgc); + xtext->fgc = NULL; + } + + if (xtext->light_gc) + { + g_object_unref (xtext->light_gc); + xtext->light_gc = NULL; + } + + if (xtext->dark_gc) + { + g_object_unref (xtext->dark_gc); + xtext->dark_gc = NULL; + } + + if (xtext->thin_gc) + { + g_object_unref (xtext->thin_gc); + xtext->thin_gc = NULL; + } + + if (xtext->marker_gc) + { + g_object_unref (xtext->marker_gc); + xtext->marker_gc = NULL; + } + + if (xtext->hand_cursor) + { + gdk_cursor_unref (xtext->hand_cursor); + xtext->hand_cursor = NULL; + } + + if (xtext->resize_cursor) + { + gdk_cursor_unref (xtext->resize_cursor); + xtext->resize_cursor = NULL; + } + + if (xtext->orig_buffer) + { + gtk_xtext_buffer_free (xtext->orig_buffer); + xtext->orig_buffer = NULL; + } + + if (GTK_OBJECT_CLASS (parent_class)->destroy) + (*GTK_OBJECT_CLASS (parent_class)->destroy) (object); +} + +static void +gtk_xtext_unrealize (GtkWidget * widget) +{ + backend_deinit (GTK_XTEXT (widget)); + + /* if there are still events in the queue, this'll avoid segfault */ + gdk_window_set_user_data (widget->window, NULL); + + if (parent_class->unrealize) + (* GTK_WIDGET_CLASS (parent_class)->unrealize) (widget); +} + +static void +gtk_xtext_realize (GtkWidget * widget) +{ + GtkXText *xtext; + GdkWindowAttr attributes; + GdkGCValues val; + GdkColor col; + GdkColormap *cmap; + + GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED); + xtext = GTK_XTEXT (widget); + + attributes.x = widget->allocation.x; + attributes.y = widget->allocation.y; + attributes.width = widget->allocation.width; + attributes.height = widget->allocation.height; + attributes.wclass = GDK_INPUT_OUTPUT; + attributes.window_type = GDK_WINDOW_CHILD; + attributes.event_mask = gtk_widget_get_events (widget) | + GDK_EXPOSURE_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK +#ifdef MOTION_MONITOR + | GDK_POINTER_MOTION_MASK | GDK_LEAVE_NOTIFY_MASK; +#else + | GDK_POINTER_MOTION_MASK; +#endif + + cmap = gtk_widget_get_colormap (widget); + attributes.colormap = cmap; + attributes.visual = gtk_widget_get_visual (widget); + + widget->window = gdk_window_new (widget->parent->window, &attributes, + GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | + GDK_WA_COLORMAP); + + gdk_window_set_user_data (widget->window, widget); + + xtext->depth = gdk_drawable_get_visual (widget->window)->depth; + + val.subwindow_mode = GDK_INCLUDE_INFERIORS; + val.graphics_exposures = 0; + + xtext->bgc = gdk_gc_new_with_values (widget->window, &val, + GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); + xtext->fgc = gdk_gc_new_with_values (widget->window, &val, + GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); + xtext->light_gc = gdk_gc_new_with_values (widget->window, &val, + GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); + xtext->dark_gc = gdk_gc_new_with_values (widget->window, &val, + GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); + xtext->thin_gc = gdk_gc_new_with_values (widget->window, &val, + GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); + xtext->marker_gc = gdk_gc_new_with_values (widget->window, &val, + GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); + + /* for the separator bar (light) */ + col.red = 0xffff; col.green = 0xffff; col.blue = 0xffff; + gdk_colormap_alloc_color (cmap, &col, FALSE, TRUE); + gdk_gc_set_foreground (xtext->light_gc, &col); + + /* for the separator bar (dark) */ + col.red = 0x1111; col.green = 0x1111; col.blue = 0x1111; + gdk_colormap_alloc_color (cmap, &col, FALSE, TRUE); + gdk_gc_set_foreground (xtext->dark_gc, &col); + + /* for the separator bar (thinline) */ + col.red = 0x8e38; col.green = 0x8e38; col.blue = 0x9f38; + gdk_colormap_alloc_color (cmap, &col, FALSE, TRUE); + gdk_gc_set_foreground (xtext->thin_gc, &col); + + /* for the marker bar (marker) */ + col.pixel = xtext->palette[XTEXT_MARKER]; + gdk_gc_set_foreground (xtext->marker_gc, &col); + + xtext_set_fg (xtext, xtext->fgc, XTEXT_FG); + xtext_set_bg (xtext, xtext->fgc, XTEXT_BG); + xtext_set_fg (xtext, xtext->bgc, XTEXT_BG); + + /* draw directly to window */ + xtext->draw_buf = widget->window; + +#if defined(USE_XLIB) || defined(WIN32) + if (xtext->transparent) + { + gtk_xtext_load_trans (xtext); + } else +#endif + if (xtext->pixmap) + { + gdk_gc_set_tile (xtext->bgc, xtext->pixmap); + gdk_gc_set_ts_origin (xtext->bgc, 0, 0); + xtext->ts_x = xtext->ts_y = 0; + gdk_gc_set_fill (xtext->bgc, GDK_TILED); + } + +#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0) + xtext->hand_cursor = gdk_cursor_new (GDK_HAND1); + xtext->resize_cursor = gdk_cursor_new (GDK_LEFT_SIDE); +#else + xtext->hand_cursor = gdk_cursor_new_for_display (gdk_drawable_get_display (widget->window), GDK_HAND1); + xtext->resize_cursor = gdk_cursor_new_for_display (gdk_drawable_get_display (widget->window), GDK_LEFT_SIDE); +#endif + + gdk_window_set_back_pixmap (widget->window, NULL, FALSE); + widget->style = gtk_style_attach (widget->style, widget->window); + + backend_init (xtext); +} + +static void +gtk_xtext_size_request (GtkWidget * widget, GtkRequisition * requisition) +{ + requisition->width = 200; + requisition->height = 90; +} + +static void +gtk_xtext_size_allocate (GtkWidget * widget, GtkAllocation * allocation) +{ + GtkXText *xtext = GTK_XTEXT (widget); + int height_only = FALSE; + int do_trans = TRUE; + + if (allocation->width == xtext->buffer->window_width) + height_only = TRUE; + + if (allocation->x == widget->allocation.x && + allocation->y == widget->allocation.y && xtext->avoid_trans) + do_trans = FALSE; + + xtext->avoid_trans = FALSE; + + widget->allocation = *allocation; + if (GTK_WIDGET_REALIZED (widget)) + { + xtext->buffer->window_width = allocation->width; + xtext->buffer->window_height = allocation->height; + + gdk_window_move_resize (widget->window, allocation->x, allocation->y, + allocation->width, allocation->height); + dontscroll (xtext->buffer); /* force scrolling off */ + if (!height_only) + gtk_xtext_calc_lines (xtext->buffer, FALSE); + else + { + xtext->buffer->pagetop_ent = NULL; + gtk_xtext_adjustment_set (xtext->buffer, FALSE); + } +#if defined(USE_XLIB) || defined(WIN32) + if (do_trans && xtext->transparent && xtext->shaded) + { + gtk_xtext_free_trans (xtext); + gtk_xtext_load_trans (xtext); + } +#endif + if (xtext->buffer->scrollbar_down) + gtk_adjustment_set_value (xtext->adj, xtext->adj->upper - + xtext->adj->page_size); + } +} + +static void +gtk_xtext_selection_clear_full (xtext_buffer *buf) +{ + textentry *ent = buf->text_first; + while (ent) + { + ent->mark_start = -1; + ent->mark_end = -1; + ent = ent->next; + } +} + +static int +gtk_xtext_selection_clear (xtext_buffer *buf) +{ + textentry *ent; + int ret = 0; + + ent = buf->last_ent_start; + while (ent) + { + if (ent->mark_start != -1) + ret = 1; + ent->mark_start = -1; + ent->mark_end = -1; + if (ent == buf->last_ent_end) + break; + ent = ent->next; + } + + return ret; +} + +static int +find_x (GtkXText *xtext, textentry *ent, unsigned char *text, int x, int indent) +{ + int xx = indent; + int i = 0; + int rcol = 0, bgcol = 0; + int hidden = FALSE; + unsigned char *orig = text; + int mbl; + int char_width; + + while (*text) + { + mbl = 1; + if (rcol > 0 && (isdigit (*text) || (*text == ',' && isdigit (text[1]) && !bgcol))) + { + if (text[1] != ',') rcol--; + if (*text == ',') + { + rcol = 2; + bgcol = 1; + } + text++; + } else + { + rcol = bgcol = 0; + switch (*text) + { + case ATTR_COLOR: + rcol = 2; + case ATTR_BEEP: + case ATTR_RESET: + case ATTR_REVERSE: + case ATTR_BOLD: + case ATTR_UNDERLINE: + case ATTR_ITALICS: + text++; + break; + case ATTR_HIDDEN: + if (xtext->ignore_hidden) + goto def; + hidden = !hidden; + text++; + break; + default: + def: + char_width = backend_get_char_width (xtext, text, &mbl); + if (!hidden) xx += char_width; + text += mbl; + if (xx >= x) + return i + (orig - ent->str); + } + } + + i += mbl; + if (text - orig >= ent->str_len) + return ent->str_len; + } + + return ent->str_len; +} + +static int +gtk_xtext_find_x (GtkXText * xtext, int x, textentry * ent, int subline, + int line, int *out_of_bounds) +{ + int indent; + unsigned char *str; + + if (subline < 1) + indent = ent->indent; + else + indent = xtext->buffer->indent; + + if (line > xtext->adj->page_size || line < 0) + return 0; + + if (xtext->buffer->grid_dirty || line > 255) + { + str = ent->str + gtk_xtext_find_subline (xtext, ent, subline); + if (str >= ent->str + ent->str_len) + return 0; + } else + { + if (xtext->buffer->grid_offset[line] > ent->str_len) + return 0; + str = ent->str + xtext->buffer->grid_offset[line]; + } + + if (x < indent) + { + *out_of_bounds = 1; + return (str - ent->str); + } + + *out_of_bounds = 0; + + return find_x (xtext, ent, str, x, indent); +} + +static textentry * +gtk_xtext_find_char (GtkXText * xtext, int x, int y, int *off, + int *out_of_bounds) +{ + textentry *ent; + int line; + int subline; + + line = (y + xtext->pixel_offset) / xtext->fontsize; + ent = gtk_xtext_nth (xtext, line + (int)xtext->adj->value, &subline); + if (!ent) + return 0; + + if (off) + *off = gtk_xtext_find_x (xtext, x, ent, subline, line, out_of_bounds); + + return ent; +} + +static void +gtk_xtext_draw_sep (GtkXText * xtext, int y) +{ + int x, height; + GdkGC *light, *dark; + + if (y == -1) + { + y = 0; + height = GTK_WIDGET (xtext)->allocation.height; + } else + { + height = xtext->fontsize; + } + + /* draw the separator line */ + if (xtext->separator && xtext->buffer->indent) + { + light = xtext->light_gc; + dark = xtext->dark_gc; + + x = xtext->buffer->indent - ((xtext->space_width + 1) / 2); + if (x < 1) + return; + + if (xtext->thinline) + { + if (xtext->moving_separator) + gdk_draw_line (xtext->draw_buf, light, x, y, x, y + height); + else + gdk_draw_line (xtext->draw_buf, xtext->thin_gc, x, y, x, y + height); + } else + { + if (xtext->moving_separator) + { + gdk_draw_line (xtext->draw_buf, light, x - 1, y, x - 1, y + height); + gdk_draw_line (xtext->draw_buf, dark, x, y, x, y + height); + } else + { + gdk_draw_line (xtext->draw_buf, dark, x - 1, y, x - 1, y + height); + gdk_draw_line (xtext->draw_buf, light, x, y, x, y + height); + } + } + } +} + +static void +gtk_xtext_draw_marker (GtkXText * xtext, textentry * ent, int y) +{ + int x, width, render_y; + + if (!xtext->marker) return; + + if (xtext->buffer->marker_pos == ent) + { + render_y = y + xtext->font->descent; + } + else if (xtext->buffer->marker_pos == ent->next && ent->next != NULL) + { + render_y = y + xtext->font->descent + xtext->fontsize * ent->lines_taken; + } + else return; + + x = 0; + width = GTK_WIDGET (xtext)->allocation.width; + + gdk_draw_line (xtext->draw_buf, xtext->marker_gc, x, render_y, x + width, render_y); + +#if GTK_CHECK_VERSION(2,4,0) + if (gtk_window_has_toplevel_focus (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (xtext))))) +#else + if (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (xtext)))->has_focus) +#endif + { + xtext->buffer->marker_seen = TRUE; + } +} + +#ifdef USE_SHM +static int +have_shm_pixmaps(Display *dpy) +{ + int major, minor; + static int checked = 0; + static int have = FALSE; + + if (!checked) + { + XShmQueryVersion (dpy, &major, &minor, &have); + checked = 1; + } + + return have; +} +#endif + +static void +gtk_xtext_paint (GtkWidget *widget, GdkRectangle *area) +{ + GtkXText *xtext = GTK_XTEXT (widget); + textentry *ent_start, *ent_end; + int x, y; + +#if defined(USE_XLIB) || defined(WIN32) + if (xtext->transparent) + { + gdk_window_get_origin (widget->window, &x, &y); + /* update transparency only if it moved */ + if (xtext->last_win_x != x || xtext->last_win_y != y) + { + xtext->last_win_x = x; + xtext->last_win_y = y; +#ifndef WIN32 +#ifdef USE_SHM + if (xtext->shaded && !have_shm_pixmaps(GDK_WINDOW_XDISPLAY (xtext->draw_buf))) +#else + if (xtext->shaded) +#endif + { + xtext->recycle = TRUE; + gtk_xtext_load_trans (xtext); + xtext->recycle = FALSE; + } else +#endif + { + gtk_xtext_free_trans (xtext); + gtk_xtext_load_trans (xtext); + } + } + } +#endif + + if (area->x == 0 && area->y == 0 && + area->height == widget->allocation.height && + area->width == widget->allocation.width) + { + dontscroll (xtext->buffer); /* force scrolling off */ + gtk_xtext_render_page (xtext); + return; + } + + ent_start = gtk_xtext_find_char (xtext, area->x, area->y, NULL, NULL); + if (!ent_start) + { + xtext_draw_bg (xtext, area->x, area->y, area->width, area->height); + goto xit; + } + ent_end = gtk_xtext_find_char (xtext, area->x + area->width, + area->y + area->height, NULL, NULL); + if (!ent_end) + ent_end = xtext->buffer->text_last; + + /* can't set a clip here, because fgc/bgc are used to draw the DB too */ +/* backend_set_clip (xtext, area);*/ + xtext->clip_x = area->x; + xtext->clip_x2 = area->x + area->width; + xtext->clip_y = area->y; + xtext->clip_y2 = area->y + area->height; + + /* y is the last pixel y location it rendered text at */ + y = gtk_xtext_render_ents (xtext, ent_start, ent_end); + + if (y && y < widget->allocation.height && !ent_end->next) + { + GdkRectangle rect; + + rect.x = 0; + rect.y = y; + rect.width = widget->allocation.width; + rect.height = widget->allocation.height - y; + + /* fill any space below the last line that also intersects with + the exposure rectangle */ + if (gdk_rectangle_intersect (area, &rect, &rect)) + { + xtext_draw_bg (xtext, rect.x, rect.y, rect.width, rect.height); + } + } + + /*backend_clear_clip (xtext);*/ + xtext->clip_x = 0; + xtext->clip_x2 = 1000000; + xtext->clip_y = 0; + xtext->clip_y2 = 1000000; + +xit: + x = xtext->buffer->indent - ((xtext->space_width + 1) / 2); + if (area->x <= x) + gtk_xtext_draw_sep (xtext, -1); +} + +static gboolean +gtk_xtext_expose (GtkWidget * widget, GdkEventExpose * event) +{ + gtk_xtext_paint (widget, &event->area); + return FALSE; +} + +/* render a selection that has extended or contracted upward */ + +static void +gtk_xtext_selection_up (GtkXText *xtext, textentry *start, textentry *end, + int start_offset) +{ + /* render all the complete lines */ + if (start->next == end) + gtk_xtext_render_ents (xtext, end, NULL); + else + gtk_xtext_render_ents (xtext, start->next, end); + + /* now the incomplete upper line */ + if (start == xtext->buffer->last_ent_start) + xtext->jump_in_offset = xtext->buffer->last_offset_start; + else + xtext->jump_in_offset = start_offset; + gtk_xtext_render_ents (xtext, start, NULL); + xtext->jump_in_offset = 0; +} + +/* render a selection that has extended or contracted downward */ + +static void +gtk_xtext_selection_down (GtkXText *xtext, textentry *start, textentry *end, + int end_offset) +{ + /* render all the complete lines */ + if (end->prev == start) + gtk_xtext_render_ents (xtext, start, NULL); + else + gtk_xtext_render_ents (xtext, start, end->prev); + + /* now the incomplete bottom line */ + if (end == xtext->buffer->last_ent_end) + xtext->jump_out_offset = xtext->buffer->last_offset_end; + else + xtext->jump_out_offset = end_offset; + gtk_xtext_render_ents (xtext, end, NULL); + xtext->jump_out_offset = 0; +} + +static void +gtk_xtext_selection_render (GtkXText *xtext, + textentry *start_ent, int start_offset, + textentry *end_ent, int end_offset) +{ + textentry *ent; + int start, end; + + xtext->skip_border_fills = TRUE; + xtext->skip_stamp = TRUE; + + /* force an optimized render if there was no previous selection */ + if (xtext->buffer->last_ent_start == NULL && start_ent == end_ent) + { + xtext->buffer->last_offset_start = start_offset; + xtext->buffer->last_offset_end = end_offset; + goto lamejump; + } + + /* mark changed within 1 ent only? */ + if (xtext->buffer->last_ent_start == start_ent && + xtext->buffer->last_ent_end == end_ent) + { + /* when only 1 end of the selection is changed, we can really + save on rendering */ + if (xtext->buffer->last_offset_start == start_offset || + xtext->buffer->last_offset_end == end_offset) + { +lamejump: + ent = end_ent; + /* figure out where to start and end the rendering */ + if (end_offset > xtext->buffer->last_offset_end) + { + end = end_offset; + start = xtext->buffer->last_offset_end; + } else if (end_offset < xtext->buffer->last_offset_end) + { + end = xtext->buffer->last_offset_end; + start = end_offset; + } else if (start_offset < xtext->buffer->last_offset_start) + { + end = xtext->buffer->last_offset_start; + start = start_offset; + ent = start_ent; + } else if (start_offset > xtext->buffer->last_offset_start) + { + end = start_offset; + start = xtext->buffer->last_offset_start; + ent = start_ent; + } else + { /* WORD selects end up here */ + end = end_offset; + start = start_offset; + } + } else + { + /* LINE selects end up here */ + /* so which ent actually changed? */ + ent = start_ent; + if (xtext->buffer->last_offset_start == start_offset) + ent = end_ent; + + end = MAX (xtext->buffer->last_offset_end, end_offset); + start = MIN (xtext->buffer->last_offset_start, start_offset); + } + + xtext->jump_out_offset = end; + xtext->jump_in_offset = start; + gtk_xtext_render_ents (xtext, ent, NULL); + xtext->jump_out_offset = 0; + xtext->jump_in_offset = 0; + } + /* marking downward? */ + else if (xtext->buffer->last_ent_start == start_ent && + xtext->buffer->last_offset_start == start_offset) + { + /* find the range that covers both old and new selection */ + ent = start_ent; + while (ent) + { + if (ent == xtext->buffer->last_ent_end) + { + gtk_xtext_selection_down (xtext, ent, end_ent, end_offset); + /*gtk_xtext_render_ents (xtext, ent, end_ent);*/ + break; + } + if (ent == end_ent) + { + gtk_xtext_selection_down (xtext, ent, xtext->buffer->last_ent_end, end_offset); + /*gtk_xtext_render_ents (xtext, ent, xtext->buffer->last_ent_end);*/ + break; + } + ent = ent->next; + } + } + /* marking upward? */ + else if (xtext->buffer->last_ent_end == end_ent && + xtext->buffer->last_offset_end == end_offset) + { + ent = end_ent; + while (ent) + { + if (ent == start_ent) + { + gtk_xtext_selection_up (xtext, xtext->buffer->last_ent_start, ent, start_offset); + /*gtk_xtext_render_ents (xtext, xtext->buffer->last_ent_start, ent);*/ + break; + } + if (ent == xtext->buffer->last_ent_start) + { + gtk_xtext_selection_up (xtext, start_ent, ent, start_offset); + /*gtk_xtext_render_ents (xtext, start_ent, ent);*/ + break; + } + ent = ent->prev; + } + } + else /* cross-over mark (stretched or shrunk at both ends) */ + { + /* unrender the old mark */ + gtk_xtext_render_ents (xtext, xtext->buffer->last_ent_start, xtext->buffer->last_ent_end); + /* now render the new mark, but skip overlaps */ + if (start_ent == xtext->buffer->last_ent_start) + { + /* if the new mark is a sub-set of the old, do nothing */ + if (start_ent != end_ent) + gtk_xtext_render_ents (xtext, start_ent->next, end_ent); + } else if (end_ent == xtext->buffer->last_ent_end) + { + /* if the new mark is a sub-set of the old, do nothing */ + if (start_ent != end_ent) + gtk_xtext_render_ents (xtext, start_ent, end_ent->prev); + } else + gtk_xtext_render_ents (xtext, start_ent, end_ent); + } + + xtext->buffer->last_ent_start = start_ent; + xtext->buffer->last_ent_end = end_ent; + xtext->buffer->last_offset_start = start_offset; + xtext->buffer->last_offset_end = end_offset; + + xtext->skip_border_fills = FALSE; + xtext->skip_stamp = FALSE; +} + +static void +gtk_xtext_selection_draw (GtkXText * xtext, GdkEventMotion * event, gboolean render) +{ + textentry *ent; + textentry *ent_end; + textentry *ent_start; + int offset_start; + int offset_end; + int low_x; + int low_y; + int high_x; + int high_y; + int tmp; + + if (xtext->select_start_y > xtext->select_end_y) + { + low_x = xtext->select_end_x; + low_y = xtext->select_end_y; + high_x = xtext->select_start_x; + high_y = xtext->select_start_y; + } else + { + low_x = xtext->select_start_x; + low_y = xtext->select_start_y; + high_x = xtext->select_end_x; + high_y = xtext->select_end_y; + } + + ent_start = gtk_xtext_find_char (xtext, low_x, low_y, &offset_start, &tmp); + if (!ent_start) + { + if (xtext->adj->value != xtext->buffer->old_value) + gtk_xtext_render_page (xtext); + return; + } + + ent_end = gtk_xtext_find_char (xtext, high_x, high_y, &offset_end, &tmp); + if (!ent_end) + { + ent_end = xtext->buffer->text_last; + if (!ent_end) + { + if (xtext->adj->value != xtext->buffer->old_value) + gtk_xtext_render_page (xtext); + return; + } + offset_end = ent_end->str_len; + } + + /* marking less than a complete line? */ + /* make sure "start" is smaller than "end" (swap them if need be) */ + if (ent_start == ent_end && offset_start > offset_end) + { + tmp = offset_start; + offset_start = offset_end; + offset_end = tmp; + } + + /* has the selection changed? Dont render unless necessary */ + if (xtext->buffer->last_ent_start == ent_start && + xtext->buffer->last_ent_end == ent_end && + xtext->buffer->last_offset_start == offset_start && + xtext->buffer->last_offset_end == offset_end) + return; + + /* set all the old mark_ fields to -1 */ + gtk_xtext_selection_clear (xtext->buffer); + + ent_start->mark_start = offset_start; + ent_start->mark_end = offset_end; + + if (ent_start != ent_end) + { + ent_start->mark_end = ent_start->str_len; + if (offset_end != 0) + { + ent_end->mark_start = 0; + ent_end->mark_end = offset_end; + } + + /* set all the mark_ fields of the ents within the selection */ + ent = ent_start->next; + while (ent && ent != ent_end) + { + ent->mark_start = 0; + ent->mark_end = ent->str_len; + ent = ent->next; + } + } + + if (render) + gtk_xtext_selection_render (xtext, ent_start, offset_start, ent_end, offset_end); +} + +static gint +gtk_xtext_scrolldown_timeout (GtkXText * xtext) +{ + int p_y, win_height; + + gdk_window_get_pointer (GTK_WIDGET (xtext)->window, 0, &p_y, 0); + gdk_drawable_get_size (GTK_WIDGET (xtext)->window, 0, &win_height); + + if (p_y > win_height && + xtext->adj->value < (xtext->adj->upper - xtext->adj->page_size)) + { + xtext->adj->value++; + gtk_adjustment_changed (xtext->adj); + gtk_xtext_render_page (xtext); + return 1; + } + + xtext->scroll_tag = 0; + return 0; +} + +static gint +gtk_xtext_scrollup_timeout (GtkXText * xtext) +{ + int p_y; + + gdk_window_get_pointer (GTK_WIDGET (xtext)->window, 0, &p_y, 0); + + if (p_y < 0 && xtext->adj->value > 0.0) + { + xtext->adj->value--; + gtk_adjustment_changed (xtext->adj); + gtk_xtext_render_page (xtext); + return 1; + } + + xtext->scroll_tag = 0; + return 0; +} + +static void +gtk_xtext_selection_update (GtkXText * xtext, GdkEventMotion * event, int p_y, gboolean render) +{ + int win_height; + int moved; + + gdk_drawable_get_size (GTK_WIDGET (xtext)->window, 0, &win_height); + + /* selecting past top of window, scroll up! */ + if (p_y < 0 && xtext->adj->value >= 0) + { + if (!xtext->scroll_tag) + xtext->scroll_tag = g_timeout_add (100, + (GSourceFunc) + gtk_xtext_scrollup_timeout, + xtext); + return; + } + + /* selecting past bottom of window, scroll down! */ + if (p_y > win_height && + xtext->adj->value < (xtext->adj->upper - xtext->adj->page_size)) + { + if (!xtext->scroll_tag) + xtext->scroll_tag = g_timeout_add (100, + (GSourceFunc) + gtk_xtext_scrolldown_timeout, + xtext); + return; + } + + moved = (int)xtext->adj->value - xtext->select_start_adj; + xtext->select_start_y -= (moved * xtext->fontsize); + xtext->select_start_adj = xtext->adj->value; + gtk_xtext_selection_draw (xtext, event, render); +} + +static char * +gtk_xtext_get_word (GtkXText * xtext, int x, int y, textentry ** ret_ent, + int *ret_off, int *ret_len) +{ + textentry *ent; + int offset; + unsigned char *str; + unsigned char *word; + int len; + int out_of_bounds = 0; + + ent = gtk_xtext_find_char (xtext, x, y, &offset, &out_of_bounds); + if (!ent) + return 0; + + if (out_of_bounds) + return 0; + + if (offset == ent->str_len) + return 0; + + if (offset < 1) + return 0; + + /*offset--;*/ /* FIXME: not all chars are 1 byte */ + + str = ent->str + offset; + + while (!is_del (*str) && str != ent->str) + str--; + word = str + 1; + + len = 0; + str = word; + while (!is_del (*str) && len != ent->str_len) + { + str++; + len++; + } + + if (len > 0 && word[len-1]=='.') + { + len--; + str--; + } + + if (ret_ent) + *ret_ent = ent; + if (ret_off) + *ret_off = word - ent->str; + if (ret_len) + *ret_len = str - word; + + return gtk_xtext_strip_color (word, len, xtext->scratch_buffer, NULL, NULL, FALSE); +} + +#ifdef MOTION_MONITOR + +static void +gtk_xtext_unrender_hilight (GtkXText *xtext) +{ + xtext->render_hilights_only = TRUE; + xtext->skip_border_fills = TRUE; + xtext->skip_stamp = TRUE; + xtext->un_hilight = TRUE; + + gtk_xtext_render_ents (xtext, xtext->hilight_ent, NULL); + + xtext->render_hilights_only = FALSE; + xtext->skip_border_fills = FALSE; + xtext->skip_stamp = FALSE; + xtext->un_hilight = FALSE; +} + +static gboolean +gtk_xtext_leave_notify (GtkWidget * widget, GdkEventCrossing * event) +{ + GtkXText *xtext = GTK_XTEXT (widget); + + if (xtext->cursor_hand) + { + gtk_xtext_unrender_hilight (xtext); + xtext->hilight_start = -1; + xtext->hilight_end = -1; + xtext->cursor_hand = FALSE; + gdk_window_set_cursor (widget->window, 0); + xtext->hilight_ent = NULL; + } + + if (xtext->cursor_resize) + { + gtk_xtext_unrender_hilight (xtext); + xtext->hilight_start = -1; + xtext->hilight_end = -1; + xtext->cursor_resize = FALSE; + gdk_window_set_cursor (widget->window, 0); + xtext->hilight_ent = NULL; + } + + return FALSE; +} + +#endif + +/* check if we should mark time stamps, and if a redraw is needed */ + +static gboolean +gtk_xtext_check_mark_stamp (GtkXText *xtext, GdkModifierType mask) +{ + gboolean redraw = FALSE; + + if ((mask & GDK_SHIFT_MASK)) + { + if (!xtext->mark_stamp) + { + redraw = TRUE; /* must redraw all */ + xtext->mark_stamp = TRUE; + } + } else + { + if (xtext->mark_stamp) + { + redraw = TRUE; /* must redraw all */ + xtext->mark_stamp = FALSE; + } + } + return redraw; +} + +static gboolean +gtk_xtext_motion_notify (GtkWidget * widget, GdkEventMotion * event) +{ + GtkXText *xtext = GTK_XTEXT (widget); + GdkModifierType mask; + int redraw, tmp, x, y, offset, len, line_x; + unsigned char *word; + textentry *word_ent; + + gdk_window_get_pointer (widget->window, &x, &y, &mask); + + if (xtext->moving_separator) + { + if (x < (3 * widget->allocation.width) / 5 && x > 15) + { + tmp = xtext->buffer->indent; + xtext->buffer->indent = x; + gtk_xtext_fix_indent (xtext->buffer); + if (tmp != xtext->buffer->indent) + { + gtk_xtext_recalc_widths (xtext->buffer, FALSE); + if (xtext->buffer->scrollbar_down) + gtk_adjustment_set_value (xtext->adj, xtext->adj->upper - + xtext->adj->page_size); + if (!xtext->io_tag) + xtext->io_tag = g_timeout_add (REFRESH_TIMEOUT, + (GSourceFunc) + gtk_xtext_adjustment_timeout, + xtext); + } + } + return FALSE; + } + + if (xtext->button_down) + { + redraw = gtk_xtext_check_mark_stamp (xtext, mask); + gtk_grab_add (widget); + /*gdk_pointer_grab (widget->window, TRUE, + GDK_BUTTON_RELEASE_MASK | + GDK_BUTTON_MOTION_MASK, NULL, NULL, 0);*/ + xtext->select_end_x = x; + xtext->select_end_y = y; + gtk_xtext_selection_update (xtext, event, y, !redraw); + xtext->hilighting = TRUE; + + /* user has pressed or released SHIFT, must redraw entire selection */ + if (redraw) + { + xtext->force_stamp = TRUE; + gtk_xtext_render_ents (xtext, xtext->buffer->last_ent_start, + xtext->buffer->last_ent_end); + xtext->force_stamp = FALSE; + } + return FALSE; + } +#ifdef MOTION_MONITOR + + if (xtext->separator && xtext->buffer->indent) + { + line_x = xtext->buffer->indent - ((xtext->space_width + 1) / 2); + if (line_x == x || line_x == x + 1 || line_x == x - 1) + { + if (!xtext->cursor_resize) + { + gdk_window_set_cursor (GTK_WIDGET (xtext)->window, + xtext->resize_cursor); + xtext->cursor_resize = TRUE; + } + return FALSE; + } + } + + if (xtext->urlcheck_function == NULL) + return FALSE; + + word = gtk_xtext_get_word (xtext, x, y, &word_ent, &offset, &len); + if (word) + { + if (xtext->urlcheck_function (GTK_WIDGET (xtext), word, len) > 0) + { + if (!xtext->cursor_hand || + xtext->hilight_ent != word_ent || + xtext->hilight_start != offset || + xtext->hilight_end != offset + len) + { + if (!xtext->cursor_hand) + { + gdk_window_set_cursor (GTK_WIDGET (xtext)->window, + xtext->hand_cursor); + xtext->cursor_hand = TRUE; + } + + /* un-render the old hilight */ + if (xtext->hilight_ent) + gtk_xtext_unrender_hilight (xtext); + + xtext->hilight_ent = word_ent; + xtext->hilight_start = offset; + xtext->hilight_end = offset + len; + + xtext->skip_border_fills = TRUE; + xtext->render_hilights_only = TRUE; + xtext->skip_stamp = TRUE; + + gtk_xtext_render_ents (xtext, word_ent, NULL); + + xtext->skip_border_fills = FALSE; + xtext->render_hilights_only = FALSE; + xtext->skip_stamp = FALSE; + } + return FALSE; + } + } + + gtk_xtext_leave_notify (widget, NULL); + +#endif + + return FALSE; +} + +static void +gtk_xtext_set_clip_owner (GtkWidget * xtext, GdkEventButton * event) +{ + char *str; + int len; + + if (GTK_XTEXT (xtext)->selection_buffer && + GTK_XTEXT (xtext)->selection_buffer != GTK_XTEXT (xtext)->buffer) + gtk_xtext_selection_clear (GTK_XTEXT (xtext)->selection_buffer); + + GTK_XTEXT (xtext)->selection_buffer = GTK_XTEXT (xtext)->buffer; + + str = gtk_xtext_selection_get_text (GTK_XTEXT (xtext), &len); + if (str) + { +#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0) + gtk_clipboard_set_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD), + str, len); +#else + gtk_clipboard_set_text (gtk_widget_get_clipboard (xtext, GDK_SELECTION_CLIPBOARD), + str, len); +#endif + free (str); + } + + gtk_selection_owner_set (xtext, GDK_SELECTION_PRIMARY, event->time); +} + +static void +gtk_xtext_unselect (GtkXText *xtext) +{ + xtext_buffer *buf = xtext->buffer; + + xtext->skip_border_fills = TRUE; + xtext->skip_stamp = TRUE; + + xtext->jump_in_offset = buf->last_ent_start->mark_start; + /* just a single ent was marked? */ + if (buf->last_ent_start == buf->last_ent_end) + { + xtext->jump_out_offset = buf->last_ent_start->mark_end; + buf->last_ent_end = NULL; + } + + gtk_xtext_selection_clear (xtext->buffer); + + /* FIXME: use jump_out on multi-line selects too! */ + gtk_xtext_render_ents (xtext, buf->last_ent_start, buf->last_ent_end); + + xtext->jump_in_offset = 0; + xtext->jump_out_offset = 0; + + xtext->skip_border_fills = FALSE; + xtext->skip_stamp = FALSE; + + xtext->buffer->last_ent_start = NULL; + xtext->buffer->last_ent_end = NULL; +} + +static gboolean +gtk_xtext_button_release (GtkWidget * widget, GdkEventButton * event) +{ + GtkXText *xtext = GTK_XTEXT (widget); + unsigned char *word; + int old; + + if (xtext->moving_separator) + { + xtext->moving_separator = FALSE; + old = xtext->buffer->indent; + if (event->x < (4 * widget->allocation.width) / 5 && event->x > 15) + xtext->buffer->indent = event->x; + gtk_xtext_fix_indent (xtext->buffer); + if (xtext->buffer->indent != old) + { + gtk_xtext_recalc_widths (xtext->buffer, FALSE); + gtk_xtext_adjustment_set (xtext->buffer, TRUE); + gtk_xtext_render_page (xtext); + } else + gtk_xtext_draw_sep (xtext, -1); + return FALSE; + } + + if (xtext->word_or_line_select) + { + xtext->word_or_line_select = FALSE; + xtext->button_down = FALSE; + return FALSE; + } + + if (event->button == 1) + { + xtext->button_down = FALSE; + + gtk_grab_remove (widget); + /*gdk_pointer_ungrab (0);*/ + + /* got a new selection? */ + if (xtext->buffer->last_ent_start) + { + xtext->color_paste = FALSE; + if (event->state & GDK_CONTROL_MASK) + xtext->color_paste = TRUE; + gtk_xtext_set_clip_owner (GTK_WIDGET (xtext), event); + } + + if (xtext->select_start_x == event->x && + xtext->select_start_y == event->y && + xtext->buffer->last_ent_start) + { + gtk_xtext_unselect (xtext); + xtext->mark_stamp = FALSE; + return FALSE; + } + + if (!xtext->hilighting) + { + word = gtk_xtext_get_word (xtext, event->x, event->y, 0, 0, 0); + g_signal_emit (G_OBJECT (xtext), xtext_signals[WORD_CLICK], 0, word ? word : NULL, event); + } else + { + xtext->hilighting = FALSE; + } + } + + + return FALSE; +} + +static gboolean +gtk_xtext_button_press (GtkWidget * widget, GdkEventButton * event) +{ + GtkXText *xtext = GTK_XTEXT (widget); + GdkModifierType mask; + textentry *ent; + unsigned char *word; + int line_x, x, y, offset, len; + + gdk_window_get_pointer (widget->window, &x, &y, &mask); + + if (event->button == 3 || event->button == 2) /* right/middle click */ + { + word = gtk_xtext_get_word (xtext, x, y, 0, 0, 0); + if (word) + { + g_signal_emit (G_OBJECT (xtext), xtext_signals[WORD_CLICK], 0, + word, event); + } else + g_signal_emit (G_OBJECT (xtext), xtext_signals[WORD_CLICK], 0, + "", event); + return FALSE; + } + + if (event->button != 1) /* we only want left button */ + return FALSE; + + if (event->type == GDK_2BUTTON_PRESS) /* WORD select */ + { + gtk_xtext_check_mark_stamp (xtext, mask); + if (gtk_xtext_get_word (xtext, x, y, &ent, &offset, &len)) + { + if (len == 0) + return FALSE; + gtk_xtext_selection_clear (xtext->buffer); + ent->mark_start = offset; + ent->mark_end = offset + len; + gtk_xtext_selection_render (xtext, ent, offset, ent, offset + len); + xtext->word_or_line_select = TRUE; + gtk_xtext_set_clip_owner (GTK_WIDGET (xtext), event); + } + + return FALSE; + } + + if (event->type == GDK_3BUTTON_PRESS) /* LINE select */ + { + gtk_xtext_check_mark_stamp (xtext, mask); + if (gtk_xtext_get_word (xtext, x, y, &ent, 0, 0)) + { + gtk_xtext_selection_clear (xtext->buffer); + ent->mark_start = 0; + ent->mark_end = ent->str_len; + gtk_xtext_selection_render (xtext, ent, 0, ent, ent->str_len); + xtext->word_or_line_select = TRUE; + gtk_xtext_set_clip_owner (GTK_WIDGET (xtext), event); + } + + return FALSE; + } + + /* check if it was a separator-bar click */ + if (xtext->separator && xtext->buffer->indent) + { + line_x = xtext->buffer->indent - ((xtext->space_width + 1) / 2); + if (line_x == x || line_x == x + 1 || line_x == x - 1) + { + xtext->moving_separator = TRUE; + /* draw the separator line */ + gtk_xtext_draw_sep (xtext, -1); + return FALSE; + } + } + + xtext->button_down = TRUE; + xtext->select_start_x = x; + xtext->select_start_y = y; + xtext->select_start_adj = xtext->adj->value; + + return FALSE; +} + +/* another program has claimed the selection */ + +static gboolean +gtk_xtext_selection_kill (GtkXText *xtext, GdkEventSelection *event) +{ +#ifndef WIN32 + if (xtext->buffer->last_ent_start) + gtk_xtext_unselect (xtext); +#endif + return TRUE; +} + +static char * +gtk_xtext_selection_get_text (GtkXText *xtext, int *len_ret) +{ + textentry *ent; + char *txt; + char *pos; + char *stripped; + int len; + int first = TRUE; + xtext_buffer *buf; + + buf = xtext->selection_buffer; + if (!buf) + return NULL; + + /* first find out how much we need to malloc ... */ + len = 0; + ent = buf->last_ent_start; + while (ent) + { + if (ent->mark_start != -1) + { + /* include timestamp? */ + if (ent->mark_start == 0 && xtext->mark_stamp) + { + char *time_str; + int stamp_size = xtext_get_stamp_str (ent->stamp, &time_str); + g_free (time_str); + len += stamp_size; + } + + if (ent->mark_end - ent->mark_start > 0) + len += (ent->mark_end - ent->mark_start) + 1; + else + len++; + } + if (ent == buf->last_ent_end) + break; + ent = ent->next; + } + + if (len < 1) + return NULL; + + /* now allocate mem and copy buffer */ + pos = txt = malloc (len); + ent = buf->last_ent_start; + while (ent) + { + if (ent->mark_start != -1) + { + if (!first) + { + *pos = '\n'; + pos++; + } + first = FALSE; + if (ent->mark_end - ent->mark_start > 0) + { + /* include timestamp? */ + if (ent->mark_start == 0 && xtext->mark_stamp) + { + char *time_str; + int stamp_size = xtext_get_stamp_str (ent->stamp, &time_str); + memcpy (pos, time_str, stamp_size); + g_free (time_str); + pos += stamp_size; + } + + memcpy (pos, ent->str + ent->mark_start, + ent->mark_end - ent->mark_start); + pos += ent->mark_end - ent->mark_start; + } + } + if (ent == buf->last_ent_end) + break; + ent = ent->next; + } + *pos = 0; + + if (xtext->color_paste) + { + /*stripped = gtk_xtext_conv_color (txt, strlen (txt), &len);*/ + stripped = txt; + len = strlen (txt); + } else + { + stripped = gtk_xtext_strip_color (txt, strlen (txt), NULL, &len, 0, FALSE); + free (txt); + } + + *len_ret = len; + return stripped; +} + +/* another program is asking for our selection */ + +static void +gtk_xtext_selection_get (GtkWidget * widget, + GtkSelectionData * selection_data_ptr, + guint info, guint time) +{ + GtkXText *xtext = GTK_XTEXT (widget); + char *stripped; + guchar *new_text; + int len; + gsize glen; + + stripped = gtk_xtext_selection_get_text (xtext, &len); + if (!stripped) + return; + + switch (info) + { + case TARGET_UTF8_STRING: + /* it's already in utf8 */ + gtk_selection_data_set_text (selection_data_ptr, stripped, len); + break; + case TARGET_TEXT: + case TARGET_COMPOUND_TEXT: + { + GdkAtom encoding; + gint format; + gint new_length; + +#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0) + gdk_string_to_compound_text ( +#else + gdk_string_to_compound_text_for_display ( + gdk_drawable_get_display (widget->window), +#endif + stripped, &encoding, &format, &new_text, + &new_length); + gtk_selection_data_set (selection_data_ptr, encoding, format, + new_text, new_length); + gdk_free_compound_text (new_text); + } + break; + default: + new_text = g_locale_from_utf8 (stripped, len, NULL, &glen, NULL); + gtk_selection_data_set (selection_data_ptr, GDK_SELECTION_TYPE_STRING, + 8, new_text, glen); + g_free (new_text); + } + + free (stripped); +} + +static gboolean +gtk_xtext_scroll (GtkWidget *widget, GdkEventScroll *event) +{ + GtkXText *xtext = GTK_XTEXT (widget); + gfloat new_value; + + if (event->direction == GDK_SCROLL_UP) /* mouse wheel pageUp */ + { + new_value = xtext->adj->value - (xtext->adj->page_increment / 10); + if (new_value < xtext->adj->lower) + new_value = xtext->adj->lower; + gtk_adjustment_set_value (xtext->adj, new_value); + } + else if (event->direction == GDK_SCROLL_DOWN) /* mouse wheel pageDn */ + { + new_value = xtext->adj->value + (xtext->adj->page_increment / 10); + if (new_value > (xtext->adj->upper - xtext->adj->page_size)) + new_value = xtext->adj->upper - xtext->adj->page_size; + gtk_adjustment_set_value (xtext->adj, new_value); + } + + return FALSE; +} + +static void +gtk_xtext_class_init (GtkXTextClass * class) +{ + GtkObjectClass *object_class; + GtkWidgetClass *widget_class; + GtkXTextClass *xtext_class; + + object_class = (GtkObjectClass *) class; + widget_class = (GtkWidgetClass *) class; + xtext_class = (GtkXTextClass *) class; + + parent_class = gtk_type_class (gtk_widget_get_type ()); + + xtext_signals[WORD_CLICK] = + g_signal_new ("word_click", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_FIRST | G_SIGNAL_ACTION, + G_STRUCT_OFFSET (GtkXTextClass, word_click), + NULL, NULL, + gtk_marshal_VOID__POINTER_POINTER, + G_TYPE_NONE, + 2, G_TYPE_POINTER, G_TYPE_POINTER); + object_class->destroy = gtk_xtext_destroy; + + widget_class->realize = gtk_xtext_realize; + widget_class->unrealize = gtk_xtext_unrealize; + widget_class->size_request = gtk_xtext_size_request; + widget_class->size_allocate = gtk_xtext_size_allocate; + widget_class->button_press_event = gtk_xtext_button_press; + widget_class->button_release_event = gtk_xtext_button_release; + widget_class->motion_notify_event = gtk_xtext_motion_notify; + widget_class->selection_clear_event = (void *)gtk_xtext_selection_kill; + widget_class->selection_get = gtk_xtext_selection_get; + widget_class->expose_event = gtk_xtext_expose; + widget_class->scroll_event = gtk_xtext_scroll; +#ifdef MOTION_MONITOR + widget_class->leave_notify_event = gtk_xtext_leave_notify; +#endif + + xtext_class->word_click = NULL; +} + +GType +gtk_xtext_get_type (void) +{ + static GType xtext_type = 0; + + if (!xtext_type) + { + static const GTypeInfo xtext_info = + { + sizeof (GtkXTextClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) gtk_xtext_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GtkXText), + 0, /* n_preallocs */ + (GInstanceInitFunc) gtk_xtext_init, + }; + + xtext_type = g_type_register_static (GTK_TYPE_WIDGET, "GtkXText", + &xtext_info, 0); + } + + return xtext_type; +} + +/* strip MIRC colors and other attribs. */ + +/* CL: needs to strip hidden when called by gtk_xtext_text_width, but not when copying text */ + +static unsigned char * +gtk_xtext_strip_color (unsigned char *text, int len, unsigned char *outbuf, + int *newlen, int *mb_ret, int strip_hidden) +{ + int i = 0; + int rcol = 0, bgcol = 0; + int hidden = FALSE; + unsigned char *new_str; + int mb = FALSE; + + if (outbuf == NULL) + new_str = malloc (len + 2); + else + new_str = outbuf; + + while (len > 0) + { + if (*text >= 128) + mb = TRUE; + + if (rcol > 0 && (isdigit (*text) || (*text == ',' && isdigit (text[1]) && !bgcol))) + { + if (text[1] != ',') rcol--; + if (*text == ',') + { + rcol = 2; + bgcol = 1; + } + } else + { + rcol = bgcol = 0; + switch (*text) + { + case ATTR_COLOR: + rcol = 2; + break; + case ATTR_BEEP: + case ATTR_RESET: + case ATTR_REVERSE: + case ATTR_BOLD: + case ATTR_UNDERLINE: + case ATTR_ITALICS: + break; + case ATTR_HIDDEN: + hidden = !hidden; + break; + default: + if (!(hidden && strip_hidden)) + new_str[i++] = *text; + } + } + text++; + len--; + } + + new_str[i] = 0; + + if (newlen != NULL) + *newlen = i; + + if (mb_ret != NULL) + *mb_ret = mb; + + return new_str; +} + +/* GeEkMaN: converts mIRC control codes to literal control codes */ + +static char * +gtk_xtext_conv_color (unsigned char *text, int len, int *newlen) +{ + int i, j = 2; + char cchar = 0; + char *new_str; + int mbl; + + for (i = 0; i < len;) + { + switch (text[i]) + { + case ATTR_COLOR: + case ATTR_RESET: + case ATTR_REVERSE: + case ATTR_BOLD: + case ATTR_UNDERLINE: + case ATTR_ITALICS: + case ATTR_HIDDEN: + j += 3; + i++; + break; + default: + mbl = charlen (text + i); + j += mbl; + i += mbl; + } + } + + new_str = malloc (j); + j = 0; + + for (i = 0; i < len;) + { + switch (text[i]) + { + case ATTR_COLOR: + cchar = 'C'; + break; + case ATTR_RESET: + cchar = 'O'; + break; + case ATTR_REVERSE: + cchar = 'R'; + break; + case ATTR_BOLD: + cchar = 'B'; + break; + case ATTR_UNDERLINE: + cchar = 'U'; + break; + case ATTR_ITALICS: + cchar = 'I'; + break; + case ATTR_HIDDEN: + cchar = 'H'; + break; + case ATTR_BEEP: + break; + default: + mbl = charlen (text + i); + if (mbl == 1) + { + new_str[j] = text[i]; + j++; + i++; + } else + { + /* invalid utf8 safe guard */ + if (i + mbl > len) + mbl = len - i; + memcpy (new_str + j, text + i, mbl); + j += mbl; + i += mbl; + } + } + if (cchar != 0) + { + new_str[j++] = '%'; + new_str[j++] = cchar; + cchar = 0; + i++; + } + } + + new_str[j] = 0; + *newlen = j; + + return new_str; +} + +/* gives width of a string, excluding the mIRC codes */ + +static int +gtk_xtext_text_width (GtkXText *xtext, unsigned char *text, int len, + int *mb_ret) +{ + unsigned char *new_buf; + int new_len, mb; + + new_buf = gtk_xtext_strip_color (text, len, xtext->scratch_buffer, + &new_len, &mb, !xtext->ignore_hidden); + + if (mb_ret) + *mb_ret = mb; + + return backend_get_text_width (xtext, new_buf, new_len, mb); +} + +/* actually draw text to screen (one run with the same color/attribs) */ + +static int +gtk_xtext_render_flush (GtkXText * xtext, int x, int y, unsigned char *str, + int len, GdkGC *gc, int is_mb) +{ + int str_width, dofill; + GdkDrawable *pix = NULL; + int dest_x, dest_y; + + if (xtext->dont_render || len < 1 || xtext->hidden) + return 0; + + str_width = backend_get_text_width (xtext, str, len, is_mb); + + if (xtext->dont_render2) + return str_width; + + /* roll-your-own clipping (avoiding XftDrawString is always good!) */ + if (x > xtext->clip_x2 || x + str_width < xtext->clip_x) + return str_width; + if (y - xtext->font->ascent > xtext->clip_y2 || (y - xtext->font->ascent) + xtext->fontsize < xtext->clip_y) + return str_width; + + if (xtext->render_hilights_only) + { + if (!xtext->in_hilight) /* is it a hilight prefix? */ + return str_width; +#ifndef COLOR_HILIGHT + if (!xtext->un_hilight) /* doing a hilight? no need to draw the text */ + goto dounder; +#endif + } + +#ifdef USE_DB +#ifdef WIN32 + if (!xtext->transparent) +#endif + { + pix = gdk_pixmap_new (xtext->draw_buf, str_width, xtext->fontsize, xtext->depth); + if (pix) + { +#ifdef USE_XFT + XftDrawChange (xtext->xftdraw, GDK_WINDOW_XWINDOW (pix)); +#endif + dest_x = x; + dest_y = y - xtext->font->ascent; + + gdk_gc_set_ts_origin (xtext->bgc, xtext->ts_x - x, xtext->ts_y - dest_y); + + x = 0; + y = xtext->font->ascent; + xtext->draw_buf = pix; + } + } +#endif + + dofill = TRUE; + + /* backcolor is always handled by XDrawImageString */ + if (!xtext->backcolor && xtext->pixmap) + { + /* draw the background pixmap behind the text - CAUSES FLICKER HERE!! */ + xtext_draw_bg (xtext, x, y - xtext->font->ascent, str_width, + xtext->fontsize); + dofill = FALSE; /* already drawn the background */ + } + + backend_draw_text (xtext, dofill, gc, x, y, str, len, str_width, is_mb); + +#ifdef USE_DB + if (pix) + { + GdkRectangle clip; + GdkRectangle dest; + + gdk_gc_set_ts_origin (xtext->bgc, xtext->ts_x, xtext->ts_y); + xtext->draw_buf = GTK_WIDGET (xtext)->window; +#ifdef USE_XFT + XftDrawChange (xtext->xftdraw, GDK_WINDOW_XWINDOW (xtext->draw_buf)); +#endif +#if 0 + gdk_draw_drawable (xtext->draw_buf, xtext->bgc, pix, 0, 0, dest_x, + dest_y, str_width, xtext->fontsize); +#else + clip.x = xtext->clip_x; + clip.y = xtext->clip_y; + clip.width = xtext->clip_x2 - xtext->clip_x; + clip.height = xtext->clip_y2 - xtext->clip_y; + + dest.x = dest_x; + dest.y = dest_y; + dest.width = str_width; + dest.height = xtext->fontsize; + + if (gdk_rectangle_intersect (&clip, &dest, &dest)) + /* dump the DB to window, but only within the clip_x/x2/y/y2 */ + gdk_draw_drawable (xtext->draw_buf, xtext->bgc, pix, + dest.x - dest_x, dest.y - dest_y, + dest.x, dest.y, dest.width, dest.height); +#endif + g_object_unref (pix); + } +#endif + + if (xtext->underline) + { +#ifdef USE_XFT + GdkColor col; +#endif + +#ifndef COLOR_HILIGHT +dounder: +#endif + +#ifdef USE_XFT + col.pixel = xtext->xft_fg->pixel; + gdk_gc_set_foreground (gc, &col); +#endif + if (pix) + y = dest_y + xtext->font->ascent + 1; + else + { + y++; + dest_x = x; + } + /* draw directly to window, it's out of the range of our DB */ + gdk_draw_line (xtext->draw_buf, gc, dest_x, y, dest_x + str_width - 1, y); + } + + return str_width; +} + +static void +gtk_xtext_reset (GtkXText * xtext, int mark, int attribs) +{ + if (attribs) + { + xtext->underline = FALSE; + xtext->bold = FALSE; + xtext->italics = FALSE; + xtext->hidden = FALSE; + } + if (!mark) + { + xtext->backcolor = FALSE; + if (xtext->col_fore != XTEXT_FG) + xtext_set_fg (xtext, xtext->fgc, XTEXT_FG); + if (xtext->col_back != XTEXT_BG) + xtext_set_bg (xtext, xtext->fgc, XTEXT_BG); + } + xtext->col_fore = XTEXT_FG; + xtext->col_back = XTEXT_BG; + xtext->parsing_color = FALSE; + xtext->parsing_backcolor = FALSE; + xtext->nc = 0; +} + +/* render a single line, which WONT wrap, and parse mIRC colors */ + +static int +gtk_xtext_render_str (GtkXText * xtext, int y, textentry * ent, + unsigned char *str, int len, int win_width, int indent, + int line, int left_only, int *x_size_ret) +{ + GdkGC *gc; + int i = 0, x = indent, j = 0; + unsigned char *pstr = str; + int col_num, tmp; + int offset; + int mark = FALSE; + int ret = 1; + + xtext->in_hilight = FALSE; + + offset = str - ent->str; + + if (line < 255 && line >= 0) + xtext->buffer->grid_offset[line] = offset; + + gc = xtext->fgc; /* our foreground GC */ + + if (ent->mark_start != -1 && + ent->mark_start <= i + offset && ent->mark_end > i + offset) + { + xtext_set_bg (xtext, gc, XTEXT_MARK_BG); + xtext_set_fg (xtext, gc, XTEXT_MARK_FG); + xtext->backcolor = TRUE; + mark = TRUE; + } +#ifdef MOTION_MONITOR + if (xtext->hilight_ent == ent && + xtext->hilight_start <= i + offset && xtext->hilight_end > i + offset) + { + if (!xtext->un_hilight) + { +#ifdef COLOR_HILIGHT + xtext_set_bg (xtext, gc, 2); +#else + xtext->underline = TRUE; +#endif + } + xtext->in_hilight = TRUE; + } +#endif + + if (!xtext->skip_border_fills && !xtext->dont_render) + { + /* draw background to the left of the text */ + if (str == ent->str && indent > MARGIN && xtext->buffer->time_stamp) + { + /* don't overwrite the timestamp */ + if (indent > xtext->stamp_width) + { + xtext_draw_bg (xtext, xtext->stamp_width, y - xtext->font->ascent, + indent - xtext->stamp_width, xtext->fontsize); + } + } else + { + /* fill the indent area with background gc */ + if (indent >= xtext->clip_x) + { + xtext_draw_bg (xtext, 0, y - xtext->font->ascent, + MIN (indent, xtext->clip_x2), xtext->fontsize); + } + } + } + + if (xtext->jump_in_offset > 0 && offset < xtext->jump_in_offset) + xtext->dont_render2 = TRUE; + + while (i < len) + { + +#ifdef MOTION_MONITOR + if (xtext->hilight_ent == ent && xtext->hilight_start == (i + offset)) + { + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + pstr += j; + j = 0; + if (!xtext->un_hilight) + { +#ifdef COLOR_HILIGHT + xtext_set_bg (xtext, gc, 2); +#else + xtext->underline = TRUE; +#endif + } + + xtext->in_hilight = TRUE; + } +#endif + + if ((xtext->parsing_color && isdigit (str[i]) && xtext->nc < 2) || + (xtext->parsing_color && str[i] == ',' && isdigit (str[i+1]) && xtext->nc < 3 && !xtext->parsing_backcolor)) + { + pstr++; + if (str[i] == ',') + { + xtext->parsing_backcolor = TRUE; + if (xtext->nc) + { + xtext->num[xtext->nc] = 0; + xtext->nc = 0; + col_num = atoi (xtext->num); + if (col_num == 99) /* mIRC lameness */ + col_num = XTEXT_FG; + else + col_num = col_num % XTEXT_MIRC_COLS; + xtext->col_fore = col_num; + if (!mark) + xtext_set_fg (xtext, gc, col_num); + } + } else + { + xtext->num[xtext->nc] = str[i]; + if (xtext->nc < 7) + xtext->nc++; + } + } else + { + if (xtext->parsing_color) + { + xtext->parsing_color = FALSE; + if (xtext->nc) + { + xtext->num[xtext->nc] = 0; + xtext->nc = 0; + col_num = atoi (xtext->num); + if (xtext->parsing_backcolor) + { + if (col_num == 99) /* mIRC lameness */ + col_num = XTEXT_BG; + else + col_num = col_num % XTEXT_MIRC_COLS; + if (col_num == XTEXT_BG) + xtext->backcolor = FALSE; + else + xtext->backcolor = TRUE; + if (!mark) + xtext_set_bg (xtext, gc, col_num); + xtext->col_back = col_num; + } else + { + if (col_num == 99) /* mIRC lameness */ + col_num = XTEXT_FG; + else + col_num = col_num % XTEXT_MIRC_COLS; + if (!mark) + xtext_set_fg (xtext, gc, col_num); + xtext->col_fore = col_num; + } + xtext->parsing_backcolor = FALSE; + } else + { + /* got a \003<non-digit>... i.e. reset colors */ + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + pstr += j; + j = 0; + gtk_xtext_reset (xtext, mark, FALSE); + } + } + + switch (str[i]) + { + case '\n': + /*case ATTR_BEEP:*/ + break; + case ATTR_REVERSE: + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + pstr += j + 1; + j = 0; + tmp = xtext->col_fore; + xtext->col_fore = xtext->col_back; + xtext->col_back = tmp; + if (!mark) + { + xtext_set_fg (xtext, gc, xtext->col_fore); + xtext_set_bg (xtext, gc, xtext->col_back); + } + if (xtext->col_back != XTEXT_BG) + xtext->backcolor = TRUE; + else + xtext->backcolor = FALSE; + break; + case ATTR_BOLD: + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + xtext->bold = !xtext->bold; + pstr += j + 1; + j = 0; + break; + case ATTR_UNDERLINE: + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + xtext->underline = !xtext->underline; + pstr += j + 1; + j = 0; + break; + case ATTR_ITALICS: + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + xtext->italics = !xtext->italics; + pstr += j + 1; + j = 0; + break; + case ATTR_HIDDEN: + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + xtext->hidden = (!xtext->hidden) & (!xtext->ignore_hidden); + pstr += j + 1; + j = 0; + break; + case ATTR_RESET: + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + pstr += j + 1; + j = 0; + gtk_xtext_reset (xtext, mark, !xtext->in_hilight); + break; + case ATTR_COLOR: + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + xtext->parsing_color = TRUE; + pstr += j + 1; + j = 0; + break; + default: + tmp = charlen (str + i); + /* invalid utf8 safe guard */ + if (tmp + i > len) + tmp = len - i; + j += tmp; /* move to the next utf8 char */ + } + } + i += charlen (str + i); /* move to the next utf8 char */ + /* invalid utf8 safe guard */ + if (i > len) + i = len; + + /* Separate the left part, the space and the right part + into separate runs, and reset bidi state inbetween. + Perform this only on the first line of the message. + */ + if (offset == 0) + { + /* we've reached the end of the left part? */ + if ((pstr-str)+j == ent->left_len) + { + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + pstr += j; + j = 0; + } + else if ((pstr-str)+j == ent->left_len+1) + { + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + pstr += j; + j = 0; + } + } + + /* have we been told to stop rendering at this point? */ + if (xtext->jump_out_offset > 0 && xtext->jump_out_offset <= (i + offset)) + { + gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + ret = 0; /* skip the rest of the lines, we're done. */ + j = 0; + break; + } + + if (xtext->jump_in_offset > 0 && xtext->jump_in_offset == (i + offset)) + { + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + pstr += j; + j = 0; + xtext->dont_render2 = FALSE; + } + +#ifdef MOTION_MONITOR + if (xtext->hilight_ent == ent && xtext->hilight_end == (i + offset)) + { + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + pstr += j; + j = 0; +#ifdef COLOR_HILIGHT + if (mark) + { + xtext_set_bg (xtext, gc, XTEXT_MARK_BG); + xtext->backcolor = TRUE; + } else + { + xtext_set_bg (xtext, gc, xtext->col_back); + if (xtext->col_back != XTEXT_BG) + xtext->backcolor = TRUE; + else + xtext->backcolor = FALSE; + } +#else + xtext->underline = FALSE; +#endif + xtext->in_hilight = FALSE; + if (xtext->render_hilights_only) + { + /* stop drawing this ent */ + ret = 0; + break; + } + } +#endif + + if (!mark && ent->mark_start == (i + offset)) + { + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + pstr += j; + j = 0; + xtext_set_bg (xtext, gc, XTEXT_MARK_BG); + xtext_set_fg (xtext, gc, XTEXT_MARK_FG); + xtext->backcolor = TRUE; + mark = TRUE; + } + + if (mark && ent->mark_end == (i + offset)) + { + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + pstr += j; + j = 0; + xtext_set_bg (xtext, gc, xtext->col_back); + xtext_set_fg (xtext, gc, xtext->col_fore); + if (xtext->col_back != XTEXT_BG) + xtext->backcolor = TRUE; + else + xtext->backcolor = FALSE; + mark = FALSE; + } + + } + + if (j) + x += gtk_xtext_render_flush (xtext, x, y, pstr, j, gc, ent->mb); + + if (mark) + { + xtext_set_bg (xtext, gc, xtext->col_back); + xtext_set_fg (xtext, gc, xtext->col_fore); + if (xtext->col_back != XTEXT_BG) + xtext->backcolor = TRUE; + else + xtext->backcolor = FALSE; + } + + /* draw background to the right of the text */ + if (!left_only && !xtext->dont_render) + { + /* draw separator now so it doesn't appear to flicker */ + gtk_xtext_draw_sep (xtext, y - xtext->font->ascent); + if (!xtext->skip_border_fills && xtext->clip_x2 >= x) + { + int xx = MAX (x, xtext->clip_x); + + xtext_draw_bg (xtext, + xx, /* x */ + y - xtext->font->ascent, /* y */ + MIN (xtext->clip_x2 - xx, (win_width + MARGIN) - xx), /* width */ + xtext->fontsize); /* height */ + } + } + + xtext->dont_render2 = FALSE; + + /* return how much we drew in the x direction */ + if (x_size_ret) + *x_size_ret = x - indent; + + return ret; +} + +#ifdef USE_XLIB + +/* get the desktop/root window */ + +static Window desktop_window = None; + +static Window +get_desktop_window (Display *xdisplay, Window the_window) +{ + Atom prop, type; + int format; + unsigned long length, after; + unsigned char *data; + unsigned int nchildren; + Window w, root, *children, parent; + + prop = XInternAtom (xdisplay, "_XROOTPMAP_ID", True); + if (prop == None) + { + prop = XInternAtom (xdisplay, "_XROOTCOLOR_PIXEL", True); + if (prop == None) + return None; + } + + for (w = the_window; w; w = parent) + { + if ((XQueryTree (xdisplay, w, &root, &parent, &children, + &nchildren)) == False) + return None; + + if (nchildren) + XFree (children); + + XGetWindowProperty (xdisplay, w, prop, 0L, 1L, False, + AnyPropertyType, &type, &format, &length, &after, + &data); + if (data) + XFree (data); + + if (type != None) + return (desktop_window = w); + } + + return (desktop_window = None); +} + +/* find the root window (backdrop) Pixmap */ + +static Pixmap +get_pixmap_prop (Display *xdisplay, Window the_window) +{ + Atom type; + int format; + unsigned long length, after; + unsigned char *data; + Pixmap pix = None; + static Atom prop = None; + + if (desktop_window == None) + desktop_window = get_desktop_window (xdisplay, the_window); + if (desktop_window == None) + desktop_window = DefaultRootWindow (xdisplay); + + if (prop == None) + prop = XInternAtom (xdisplay, "_XROOTPMAP_ID", True); + if (prop == None) + return None; + + XGetWindowProperty (xdisplay, desktop_window, prop, 0L, 1L, False, + AnyPropertyType, &type, &format, &length, &after, + &data); + if (data) + { + if (type == XA_PIXMAP) + pix = *((Pixmap *) data); + + XFree (data); + } + + return pix; +} + +/* slow generic routine, for the depths/bpp we don't know about */ + +static void +shade_ximage_generic (GdkVisual *visual, XImage *ximg, int bpl, int w, int h, int rm, int gm, int bm, int bg) +{ + int x, y; + int bgr = (256 - rm) * (bg & visual->red_mask); + int bgg = (256 - gm) * (bg & visual->green_mask); + int bgb = (256 - bm) * (bg & visual->blue_mask); + + for (x = 0; x < w; x++) + { + for (y = 0; y < h; y++) + { + unsigned long pixel = XGetPixel (ximg, x, y); + int r, g, b; + + r = rm * (pixel & visual->red_mask) + bgr; + g = gm * (pixel & visual->green_mask) + bgg; + b = bm * (pixel & visual->blue_mask) + bgb; + + XPutPixel (ximg, x, y, + ((r >> 8) & visual->red_mask) | + ((g >> 8) & visual->green_mask) | + ((b >> 8) & visual->blue_mask)); + } + } +} + +#endif + +/* Fast shading routine. Based on code by Willem Monsuwe <willem@stack.nl> */ + +#define SHADE_IMAGE(bytes, type, rmask, gmask, bmask) \ + unsigned char *ptr; \ + int x, y; \ + int bgr = (256 - rm) * (bg & rmask); \ + int bgg = (256 - gm) * (bg & gmask); \ + int bgb = (256 - bm) * (bg & bmask); \ + ptr = (unsigned char *) data + (w * bytes); \ + for (y = h; --y >= 0;) \ + { \ + for (x = -w; x < 0; x++) \ + { \ + int r, g, b; \ + b = ((type *) ptr)[x]; \ + r = rm * (b & rmask) + bgr; \ + g = gm * (b & gmask) + bgg; \ + b = bm * (b & bmask) + bgb; \ + ((type *) ptr)[x] = ((r >> 8) & rmask) \ + | ((g >> 8) & gmask) \ + | ((b >> 8) & bmask); \ + } \ + ptr += bpl; \ + } + +/* RGB 15 */ +static void +shade_ximage_15 (void *data, int bpl, int w, int h, int rm, int gm, int bm, int bg) +{ + SHADE_IMAGE (2, guint16, 0x7c00, 0x3e0, 0x1f); +} + +/* RGB 16 */ +static void +shade_ximage_16 (void *data, int bpl, int w, int h, int rm, int gm, int bm, int bg) +{ + SHADE_IMAGE (2, guint16, 0xf800, 0x7e0, 0x1f); +} + +/* RGB 24 */ +static void +shade_ximage_24 (void *data, int bpl, int w, int h, int rm, int gm, int bm, int bg) +{ + /* 24 has to be a special case, there's no guint24, or 24bit MOV :) */ + unsigned char *ptr; + int x, y; + int bgr = (256 - rm) * ((bg & 0xff0000) >> 16); + int bgg = (256 - gm) * ((bg & 0xff00) >> 8); + int bgb = (256 - bm) * (bg & 0xff); + + ptr = (unsigned char *) data + (w * 3); + for (y = h; --y >= 0;) + { + for (x = -(w * 3); x < 0; x += 3) + { + int r, g, b; + +#if (G_BYTE_ORDER == G_BIG_ENDIAN) + r = (ptr[x + 0] * rm + bgr) >> 8; + g = (ptr[x + 1] * gm + bgg) >> 8; + b = (ptr[x + 2] * bm + bgb) >> 8; + ptr[x + 0] = r; + ptr[x + 1] = g; + ptr[x + 2] = b; +#else + r = (ptr[x + 2] * rm + bgr) >> 8; + g = (ptr[x + 1] * gm + bgg) >> 8; + b = (ptr[x + 0] * bm + bgb) >> 8; + ptr[x + 2] = r; + ptr[x + 1] = g; + ptr[x + 0] = b; +#endif + } + ptr += bpl; + } +} + +/* RGB 32 */ +static void +shade_ximage_32 (void *data, int bpl, int w, int h, int rm, int gm, int bm, int bg) +{ + SHADE_IMAGE (4, guint32, 0xff0000, 0xff00, 0xff); +} + +static void +shade_image (GdkVisual *visual, void *data, int bpl, int bpp, int w, int h, + int rm, int gm, int bm, int bg, int depth) +{ + int bg_r, bg_g, bg_b; + + bg_r = bg & visual->red_mask; + bg_g = bg & visual->green_mask; + bg_b = bg & visual->blue_mask; + +#ifdef USE_MMX + /* the MMX routines are about 50% faster at 16-bit. */ + /* only use MMX routines with a pure black background */ + if (bg_r == 0 && bg_g == 0 && bg_b == 0 && have_mmx ()) /* do a runtime check too! */ + { + switch (depth) + { + case 15: + shade_ximage_15_mmx (data, bpl, w, h, rm, gm, bm); + break; + case 16: + shade_ximage_16_mmx (data, bpl, w, h, rm, gm, bm); + break; + case 24: + if (bpp != 32) + goto generic; + case 32: + shade_ximage_32_mmx (data, bpl, w, h, rm, gm, bm); + break; + default: + goto generic; + } + } else + { +generic: +#endif + switch (depth) + { + case 15: + shade_ximage_15 (data, bpl, w, h, rm, gm, bm, bg); + break; + case 16: + shade_ximage_16 (data, bpl, w, h, rm, gm, bm, bg); + break; + case 24: + if (bpp != 32) + { + shade_ximage_24 (data, bpl, w, h, rm, gm, bm, bg); + break; + } + case 32: + shade_ximage_32 (data, bpl, w, h, rm, gm, bm, bg); + } +#ifdef USE_MMX + } +#endif +} + +#ifdef USE_XLIB + +#ifdef USE_SHM + +static XImage * +get_shm_image (Display *xdisplay, XShmSegmentInfo *shminfo, int x, int y, + int w, int h, int depth, Pixmap pix) +{ + XImage *ximg; + + shminfo->shmid = -1; + shminfo->shmaddr = (char*) -1; + ximg = XShmCreateImage (xdisplay, 0, depth, ZPixmap, 0, shminfo, w, h); + if (!ximg) + return NULL; + + shminfo->shmid = shmget (IPC_PRIVATE, ximg->bytes_per_line * ximg->height, + IPC_CREAT|0600); + if (shminfo->shmid == -1) + { + XDestroyImage (ximg); + return NULL; + } + + shminfo->readOnly = False; + ximg->data = shminfo->shmaddr = (char *)shmat (shminfo->shmid, 0, 0); + if (shminfo->shmaddr == ((char *)-1)) + { + shmctl (shminfo->shmid, IPC_RMID, 0); + XDestroyImage (ximg); + return NULL; + } + + XShmAttach (xdisplay, shminfo); + XSync (xdisplay, False); + shmctl (shminfo->shmid, IPC_RMID, 0); + XShmGetImage (xdisplay, pix, ximg, x, y, AllPlanes); + + return ximg; +} + +static XImage * +get_image (GtkXText *xtext, Display *xdisplay, XShmSegmentInfo *shminfo, + int x, int y, int w, int h, int depth, Pixmap pix) +{ + XImage *ximg; + + xtext->shm = 1; + ximg = get_shm_image (xdisplay, shminfo, x, y, w, h, depth, pix); + if (!ximg) + { + xtext->shm = 0; + ximg = XGetImage (xdisplay, pix, x, y, w, h, -1, ZPixmap); + } + + return ximg; +} + +#endif + +static GdkPixmap * +shade_pixmap (GtkXText * xtext, Pixmap p, int x, int y, int w, int h) +{ + unsigned int dummy, width, height, depth; + GdkPixmap *shaded_pix; + Window root; + Pixmap tmp; + XImage *ximg; + XGCValues gcv; + GC tgc; + Display *xdisplay = GDK_WINDOW_XDISPLAY (xtext->draw_buf); + +#ifdef USE_SHM + int shm_pixmaps; + shm_pixmaps = have_shm_pixmaps(xdisplay); +#endif + + XGetGeometry (xdisplay, p, &root, &dummy, &dummy, &width, &height, + &dummy, &depth); + + if (width < x + w || height < y + h || x < 0 || y < 0) + { + gcv.subwindow_mode = IncludeInferiors; + gcv.graphics_exposures = False; + tgc = XCreateGC (xdisplay, p, GCGraphicsExposures|GCSubwindowMode, + &gcv); + tmp = XCreatePixmap (xdisplay, p, w, h, depth); + XSetTile (xdisplay, tgc, p); + XSetFillStyle (xdisplay, tgc, FillTiled); + XSetTSOrigin (xdisplay, tgc, -x, -y); + XFillRectangle (xdisplay, tmp, tgc, 0, 0, w, h); + XFreeGC (xdisplay, tgc); + +#ifdef USE_SHM + if (shm_pixmaps) + ximg = get_image (xtext, xdisplay, &xtext->shminfo, 0, 0, w, h, depth, tmp); + else +#endif + ximg = XGetImage (xdisplay, tmp, 0, 0, w, h, -1, ZPixmap); + XFreePixmap (xdisplay, tmp); + } else + { +#ifdef USE_SHM + if (shm_pixmaps) + ximg = get_image (xtext, xdisplay, &xtext->shminfo, x, y, w, h, depth, p); + else +#endif + ximg = XGetImage (xdisplay, p, x, y, w, h, -1, ZPixmap); + } + + if (!ximg) + return NULL; + + if (depth <= 14) + { + shade_ximage_generic (gdk_drawable_get_visual (GTK_WIDGET (xtext)->window), + ximg, ximg->bytes_per_line, w, h, xtext->tint_red, + xtext->tint_green, xtext->tint_blue, + xtext->palette[XTEXT_BG]); + } else + { + shade_image (gdk_drawable_get_visual (GTK_WIDGET (xtext)->window), + ximg->data, ximg->bytes_per_line, ximg->bits_per_pixel, + w, h, xtext->tint_red, xtext->tint_green, xtext->tint_blue, + xtext->palette[XTEXT_BG], depth); + } + + if (xtext->recycle) + shaded_pix = xtext->pixmap; + else + { +#ifdef USE_SHM + if (xtext->shm && shm_pixmaps) + { +#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0) + shaded_pix = gdk_pixmap_foreign_new ( + XShmCreatePixmap (xdisplay, p, ximg->data, &xtext->shminfo, w, h, depth)); +#else + shaded_pix = gdk_pixmap_foreign_new_for_display ( + gdk_drawable_get_display (xtext->draw_buf), + XShmCreatePixmap (xdisplay, p, ximg->data, &xtext->shminfo, w, h, depth)); +#endif + } else +#endif + { + shaded_pix = gdk_pixmap_new (GTK_WIDGET (xtext)->window, w, h, depth); + } + } + +#ifdef USE_SHM + if (!xtext->shm || !shm_pixmaps) +#endif + XPutImage (xdisplay, GDK_WINDOW_XWINDOW (shaded_pix), + GDK_GC_XGC (xtext->fgc), ximg, 0, 0, 0, 0, w, h); + XDestroyImage (ximg); + + return shaded_pix; +} + +#endif /* !USE_XLIB */ + +/* free transparency xtext->pixmap */ +#if defined(USE_XLIB) || defined(WIN32) + +static void +gtk_xtext_free_trans (GtkXText * xtext) +{ + if (xtext->pixmap) + { +#ifdef USE_SHM + if (xtext->shm && have_shm_pixmaps(GDK_WINDOW_XDISPLAY (xtext->draw_buf))) + { + XFreePixmap (GDK_WINDOW_XDISPLAY (xtext->pixmap), + GDK_WINDOW_XWINDOW (xtext->pixmap)); + XShmDetach (GDK_WINDOW_XDISPLAY (xtext->draw_buf), &xtext->shminfo); + shmdt (xtext->shminfo.shmaddr); + } +#endif + g_object_unref (xtext->pixmap); + xtext->pixmap = NULL; + xtext->shm = 0; + } +} + +#endif + +#ifdef WIN32 + +static GdkPixmap * +win32_tint (GtkXText *xtext, GdkImage *img, int width, int height) +{ + guchar *pixelp; + int x, y; + GdkPixmap *pix; + GdkVisual *visual = gdk_drawable_get_visual (GTK_WIDGET (xtext)->window); + guint32 pixel; + int r, g, b; + + if (img->depth <= 14) + { + /* slow generic routine */ + for (y = 0; y < height; y++) + { + for (x = 0; x < width; x++) + { + if (img->depth == 1) + { + pixel = (((guchar *) img->mem)[y * img->bpl + (x >> 3)] & (1 << (7 - (x & 0x7)))) != 0; + goto here; + } + + if (img->depth == 4) + { + pixelp = (guchar *) img->mem + y * img->bpl + (x >> 1); + if (x&1) + { + pixel = (*pixelp) & 0x0F; + goto here; + } + + pixel = (*pixelp) >> 4; + goto here; + } + + pixelp = (guchar *) img->mem + y * img->bpl + x * img->bpp; + + switch (img->bpp) + { + case 1: + pixel = *pixelp; break; + + /* Windows is always LSB, no need to check img->byte_order. */ + case 2: + pixel = pixelp[0] | (pixelp[1] << 8); break; + + case 3: + pixel = pixelp[0] | (pixelp[1] << 8) | (pixelp[2] << 16); break; + + case 4: + pixel = pixelp[0] | (pixelp[1] << 8) | (pixelp[2] << 16); break; + } + +here: + r = (pixel & visual->red_mask) >> visual->red_shift; + g = (pixel & visual->green_mask) >> visual->green_shift; + b = (pixel & visual->blue_mask) >> visual->blue_shift; + + /* actual tinting is only these 3 lines */ + pixel = ((r * xtext->tint_red) >> 8) << visual->red_shift | + ((g * xtext->tint_green) >> 8) << visual->green_shift | + ((b * xtext->tint_blue) >> 8) << visual->blue_shift; + + if (img->depth == 1) + if (pixel & 1) + ((guchar *) img->mem)[y * img->bpl + (x >> 3)] |= (1 << (7 - (x & 0x7))); + else + ((guchar *) img->mem)[y * img->bpl + (x >> 3)] &= ~(1 << (7 - (x & 0x7))); + else if (img->depth == 4) + { + pixelp = (guchar *) img->mem + y * img->bpl + (x >> 1); + + if (x&1) + { + *pixelp &= 0xF0; + *pixelp |= (pixel & 0x0F); + } else + { + *pixelp &= 0x0F; + *pixelp |= (pixel << 4); + } + } else + { + pixelp = (guchar *) img->mem + y * img->bpl + x * img->bpp; + + /* Windows is always LSB, no need to check img->byte_order. */ + switch (img->bpp) + { + case 4: + pixelp[3] = 0; + case 3: + pixelp[2] = ((pixel >> 16) & 0xFF); + case 2: + pixelp[1] = ((pixel >> 8) & 0xFF); + case 1: + pixelp[0] = (pixel & 0xFF); + } + } + } + } + } else + { + shade_image (visual, img->mem, img->bpl, img->bpp, width, height, + xtext->tint_red, xtext->tint_green, xtext->tint_blue, + xtext->palette[XTEXT_BG], visual->depth); + } + + /* no need to dump it to a Pixmap, it's one and the same on win32 */ + pix = (GdkPixmap *)img; + + return pix; +} + +#endif /* !WIN32 */ + +/* grab pixmap from root window and set xtext->pixmap */ +#if defined(USE_XLIB) || defined(WIN32) + +static void +gtk_xtext_load_trans (GtkXText * xtext) +{ +#ifdef WIN32 + GdkImage *img; + int width, height; + HDC hdc; + HWND hwnd; + + /* if not shaded, we paint directly with PaintDesktop() */ + if (!xtext->shaded) + return; + + hwnd = GDK_WINDOW_HWND (GTK_WIDGET (xtext)->window); + hdc = GetDC (hwnd); + PaintDesktop (hdc); + ReleaseDC (hwnd, hdc); + + gdk_window_get_size (GTK_WIDGET (xtext)->window, &width, &height); + img = gdk_image_get (GTK_WIDGET (xtext)->window, 0, 0, width+128, height); + xtext->pixmap = win32_tint (xtext, img, img->width, img->height); + +#else + + Pixmap rootpix; + GtkWidget *widget = GTK_WIDGET (xtext); + int x, y; + + rootpix = get_pixmap_prop (GDK_WINDOW_XDISPLAY (widget->window), GDK_WINDOW_XWINDOW (widget->window)); + if (rootpix == None) + { + if (xtext->error_function) + xtext->error_function (0); + xtext->transparent = FALSE; + return; + } + + gdk_window_get_origin (widget->window, &x, &y); + + if (xtext->shaded) + { + int width, height; + gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &width, &height); + xtext->pixmap = shade_pixmap (xtext, rootpix, x, y, width+105, height); + if (xtext->pixmap == NULL) + { + xtext->shaded = 0; + goto noshade; + } + gdk_gc_set_tile (xtext->bgc, xtext->pixmap); + gdk_gc_set_ts_origin (xtext->bgc, 0, 0); + xtext->ts_x = xtext->ts_y = 0; + } else + { +noshade: +#if (GTK_MAJOR_VERSION == 2) && (GTK_MINOR_VERSION == 0) + xtext->pixmap = gdk_pixmap_foreign_new (rootpix); +#else + xtext->pixmap = gdk_pixmap_foreign_new_for_display (gdk_drawable_get_display (GTK_WIDGET (xtext)->window), rootpix); +#endif + gdk_gc_set_tile (xtext->bgc, xtext->pixmap); + gdk_gc_set_ts_origin (xtext->bgc, -x, -y); + xtext->ts_x = -x; + xtext->ts_y = -y; + } + gdk_gc_set_fill (xtext->bgc, GDK_TILED); +#endif /* !WIN32 */ +} + +#endif /* ! XLIB || WIN32 */ + +/* walk through str until this line doesn't fit anymore */ + +static int +find_next_wrap (GtkXText * xtext, textentry * ent, unsigned char *str, + int win_width, int indent) +{ + unsigned char *last_space = str; + unsigned char *orig_str = str; + int str_width = indent; + int rcol = 0, bgcol = 0; + int hidden = FALSE; + int mbl; + int char_width; + int ret; + int limit_offset = 0; + + /* single liners */ + if (win_width >= ent->str_width + ent->indent) + return ent->str_len; + + /* it does happen! */ + if (win_width < 1) + { + ret = ent->str_len - (str - ent->str); + goto done; + } + + while (1) + { + if (rcol > 0 && (isdigit (*str) || (*str == ',' && isdigit (str[1]) && !bgcol))) + { + if (str[1] != ',') rcol--; + if (*str == ',') + { + rcol = 2; + bgcol = 1; + } + limit_offset++; + str++; + } else + { + rcol = bgcol = 0; + switch (*str) + { + case ATTR_COLOR: + rcol = 2; + case ATTR_BEEP: + case ATTR_RESET: + case ATTR_REVERSE: + case ATTR_BOLD: + case ATTR_UNDERLINE: + case ATTR_ITALICS: + limit_offset++; + str++; + break; + case ATTR_HIDDEN: + if (xtext->ignore_hidden) + goto def; + hidden = !hidden; + limit_offset++; + str++; + break; + default: + def: + char_width = backend_get_char_width (xtext, str, &mbl); + if (!hidden) str_width += char_width; + if (str_width > win_width) + { + if (xtext->wordwrap) + { + if (str - last_space > WORDWRAP_LIMIT + limit_offset) + ret = str - orig_str; /* fall back to character wrap */ + else + { + if (*last_space == ' ') + last_space++; + ret = last_space - orig_str; + if (ret == 0) /* fall back to character wrap */ + ret = str - orig_str; + } + goto done; + } + ret = str - orig_str; + goto done; + } + + /* keep a record of the last space, for wordwrapping */ + if (is_del (*str)) + { + last_space = str; + limit_offset = 0; + } + + /* progress to the next char */ + str += mbl; + + } + } + + if (str >= ent->str + ent->str_len) + { + ret = str - orig_str; + goto done; + } + } + +done: + + /* must make progress */ + if (ret < 1) + ret = 1; + + return ret; +} + +/* find the offset, in bytes, that wrap number 'line' starts at */ + +static int +gtk_xtext_find_subline (GtkXText *xtext, textentry *ent, int line) +{ + int win_width; + unsigned char *str; + int indent, str_pos, line_pos, len; + + if (ent->lines_taken < 2 || line < 1) + return 0; + + /* we record the first 4 lines' wraps, so take a shortcut */ + if (line <= RECORD_WRAPS) + return ent->wrap_offset[line - 1]; + + gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &win_width, 0); + win_width -= MARGIN; + +/* indent = ent->indent; + str = ent->str; + line_pos = str_pos = 0;*/ + + /* start from the last recorded wrap, and move forward */ + indent = xtext->buffer->indent; + str_pos = ent->wrap_offset[RECORD_WRAPS-1]; + str = str_pos + ent->str; + line_pos = RECORD_WRAPS; + + do + { + len = find_next_wrap (xtext, ent, str, win_width, indent); + indent = xtext->buffer->indent; + str += len; + str_pos += len; + line_pos++; + if (line_pos >= line) + return str_pos; + } + while (str < ent->str + ent->str_len); + + return 0; +} + +/* horrible hack for drawing time stamps */ + +static void +gtk_xtext_render_stamp (GtkXText * xtext, textentry * ent, + char *text, int len, int line, int win_width) +{ + textentry tmp_ent; + int jo, ji, hs; + int xsize, y; + + /* trashing ent here, so make a backup first */ + memcpy (&tmp_ent, ent, sizeof (tmp_ent)); + ent->mb = TRUE; /* make non-english days of the week work */ + jo = xtext->jump_out_offset; /* back these up */ + ji = xtext->jump_in_offset; + hs = xtext->hilight_start; + xtext->jump_out_offset = 0; + xtext->jump_in_offset = 0; + xtext->hilight_start = 0xffff; /* temp disable */ + + if (xtext->mark_stamp) + { + /* if this line is marked, mark this stamp too */ + if (ent->mark_start == 0) + { + ent->mark_start = 0; + ent->mark_end = len; + } + else + { + ent->mark_start = -1; + ent->mark_end = -1; + } + ent->str = text; + } + + y = (xtext->fontsize * line) + xtext->font->ascent - xtext->pixel_offset; + gtk_xtext_render_str (xtext, y, ent, text, len, + win_width, 2, line, TRUE, &xsize); + + /* restore everything back to how it was */ + memcpy (ent, &tmp_ent, sizeof (tmp_ent)); + xtext->jump_out_offset = jo; + xtext->jump_in_offset = ji; + xtext->hilight_start = hs; + + /* with a non-fixed-width font, sometimes we don't draw enough + background i.e. when this stamp is shorter than xtext->stamp_width */ + xsize += MARGIN; + if (xsize < xtext->stamp_width) + { + y -= xtext->font->ascent; + xtext_draw_bg (xtext, + xsize, /* x */ + y, /* y */ + xtext->stamp_width - xsize, /* width */ + xtext->fontsize /* height */); + } +} + +/* render a single line, which may wrap to more lines */ + +static int +gtk_xtext_render_line (GtkXText * xtext, textentry * ent, int line, + int lines_max, int subline, int win_width) +{ + unsigned char *str; + int indent, taken, entline, len, y, start_subline; + + entline = taken = 0; + str = ent->str; + indent = ent->indent; + start_subline = subline; + +#ifdef XCHAT + /* draw the timestamp */ + if (xtext->auto_indent && xtext->buffer->time_stamp && + (!xtext->skip_stamp || xtext->mark_stamp || xtext->force_stamp)) + { + char *time_str; + int len; + + len = xtext_get_stamp_str (ent->stamp, &time_str); + gtk_xtext_render_stamp (xtext, ent, time_str, len, line, win_width); + g_free (time_str); + } +#endif + + /* draw each line one by one */ + do + { + /* if it's one of the first 4 wraps, we don't need to calculate it, it's + recorded in ->wrap_offset. This saves us a loop. */ + if (entline < RECORD_WRAPS) + { + if (ent->lines_taken < 2) + len = ent->str_len; + else + { + if (entline > 0) + len = ent->wrap_offset[entline] - ent->wrap_offset[entline-1]; + else + len = ent->wrap_offset[0]; + } + } else + len = find_next_wrap (xtext, ent, str, win_width, indent); + + entline++; + + y = (xtext->fontsize * line) + xtext->font->ascent - xtext->pixel_offset; + if (!subline) + { + if (!gtk_xtext_render_str (xtext, y, ent, str, len, win_width, + indent, line, FALSE, NULL)) + { + /* small optimization */ + gtk_xtext_draw_marker (xtext, ent, y - xtext->fontsize * (taken + start_subline + 1)); + return ent->lines_taken - subline; + } + } else + { + xtext->dont_render = TRUE; + gtk_xtext_render_str (xtext, y, ent, str, len, win_width, + indent, line, FALSE, NULL); + xtext->dont_render = FALSE; + subline--; + line--; + taken--; + } + + indent = xtext->buffer->indent; + line++; + taken++; + str += len; + + if (line >= lines_max) + break; + + } + while (str < ent->str + ent->str_len); + + gtk_xtext_draw_marker (xtext, ent, y - xtext->fontsize * (taken + start_subline)); + + return taken; +} + +void +gtk_xtext_set_palette (GtkXText * xtext, GdkColor palette[]) +{ + int i; + GdkColor col; + + for (i = (XTEXT_COLS-1); i >= 0; i--) + { +#ifdef USE_XFT + xtext->color[i].color.red = palette[i].red; + xtext->color[i].color.green = palette[i].green; + xtext->color[i].color.blue = palette[i].blue; + xtext->color[i].color.alpha = 0xffff; + xtext->color[i].pixel = palette[i].pixel; +#endif + xtext->palette[i] = palette[i].pixel; + } + + if (GTK_WIDGET_REALIZED (xtext)) + { + xtext_set_fg (xtext, xtext->fgc, XTEXT_FG); + xtext_set_bg (xtext, xtext->fgc, XTEXT_BG); + xtext_set_fg (xtext, xtext->bgc, XTEXT_BG); + + col.pixel = xtext->palette[XTEXT_MARKER]; + gdk_gc_set_foreground (xtext->marker_gc, &col); + } + xtext->col_fore = XTEXT_FG; + xtext->col_back = XTEXT_BG; +} + +static void +gtk_xtext_fix_indent (xtext_buffer *buf) +{ + int j; + + /* make indent a multiple of the space width */ + if (buf->indent && buf->xtext->space_width) + { + j = 0; + while (j < buf->indent) + { + j += buf->xtext->space_width; + } + buf->indent = j; + } + + dontscroll (buf); /* force scrolling off */ +} + +static void +gtk_xtext_recalc_widths (xtext_buffer *buf, int do_str_width) +{ + textentry *ent; + + /* since we have a new font, we have to recalc the text widths */ + ent = buf->text_first; + while (ent) + { + if (do_str_width) + { + ent->str_width = gtk_xtext_text_width (buf->xtext, ent->str, + ent->str_len, NULL); + } + if (ent->left_len != -1) + { + ent->indent = + (buf->indent - + gtk_xtext_text_width (buf->xtext, ent->str, + ent->left_len, NULL)) - buf->xtext->space_width; + if (ent->indent < MARGIN) + ent->indent = MARGIN; + } + ent = ent->next; + } + + gtk_xtext_calc_lines (buf, FALSE); +} + +int +gtk_xtext_set_font (GtkXText *xtext, char *name) +{ + int i; + unsigned char c; + + if (xtext->font) + backend_font_close (xtext); + + /* realize now, so that font_open has a XDisplay */ + gtk_widget_realize (GTK_WIDGET (xtext)); + + backend_font_open (xtext, name); + if (xtext->font == NULL) + return FALSE; + + /* measure the width of every char; only the ASCII ones for XFT */ + for (i = 0; i < sizeof(xtext->fontwidth)/sizeof(xtext->fontwidth[0]); i++) + { + c = i; + xtext->fontwidth[i] = backend_get_text_width (xtext, &c, 1, TRUE); + } + xtext->space_width = xtext->fontwidth[' ']; + xtext->fontsize = xtext->font->ascent + xtext->font->descent; + +#ifdef XCHAT + { + char *time_str; + int stamp_size = xtext_get_stamp_str (time(0), &time_str); + xtext->stamp_width = + gtk_xtext_text_width (xtext, time_str, stamp_size, NULL) + MARGIN; + g_free (time_str); + } +#endif + + gtk_xtext_fix_indent (xtext->buffer); + + if (GTK_WIDGET_REALIZED (xtext)) + gtk_xtext_recalc_widths (xtext->buffer, TRUE); + + return TRUE; +} + +void +gtk_xtext_set_background (GtkXText * xtext, GdkPixmap * pixmap, gboolean trans) +{ + GdkGCValues val; + gboolean shaded = FALSE; + + if (trans && (xtext->tint_red != 255 || xtext->tint_green != 255 || xtext->tint_blue != 255)) + shaded = TRUE; + +#if !defined(USE_XLIB) && !defined(WIN32) + shaded = FALSE; + trans = FALSE; +#endif + + if (xtext->pixmap) + { +#if defined(USE_XLIB) || defined(WIN32) + if (xtext->transparent) + gtk_xtext_free_trans (xtext); + else +#endif + g_object_unref (xtext->pixmap); + xtext->pixmap = NULL; + } + + xtext->transparent = trans; + +#if defined(USE_XLIB) || defined(WIN32) + if (trans) + { + xtext->shaded = shaded; + if (GTK_WIDGET_REALIZED (xtext)) + gtk_xtext_load_trans (xtext); + return; + } +#endif + + dontscroll (xtext->buffer); + xtext->pixmap = pixmap; + + if (pixmap != 0) + { + g_object_ref (pixmap); + if (GTK_WIDGET_REALIZED (xtext)) + { + gdk_gc_set_tile (xtext->bgc, pixmap); + gdk_gc_set_ts_origin (xtext->bgc, 0, 0); + xtext->ts_x = xtext->ts_y = 0; + gdk_gc_set_fill (xtext->bgc, GDK_TILED); + } + } else if (GTK_WIDGET_REALIZED (xtext)) + { + g_object_unref (xtext->bgc); + val.subwindow_mode = GDK_INCLUDE_INFERIORS; + val.graphics_exposures = 0; + xtext->bgc = gdk_gc_new_with_values (GTK_WIDGET (xtext)->window, + &val, GDK_GC_EXPOSURES | GDK_GC_SUBWINDOW); + xtext_set_fg (xtext, xtext->bgc, XTEXT_BG); + } +} + +void +gtk_xtext_save (GtkXText * xtext, int fh) +{ + textentry *ent; + int newlen; + char *buf; + + ent = xtext->buffer->text_first; + while (ent) + { + buf = gtk_xtext_strip_color (ent->str, ent->str_len, NULL, + &newlen, NULL, FALSE); + write (fh, buf, newlen); + write (fh, "\n", 1); + free (buf); + ent = ent->next; + } +} + +/* count how many lines 'ent' will take (with wraps) */ + +static int +gtk_xtext_lines_taken (xtext_buffer *buf, textentry * ent) +{ + unsigned char *str; + int indent, taken, len; + int win_width; + + win_width = buf->window_width - MARGIN; + + if (ent->str_width + ent->indent < win_width) + return 1; + + indent = ent->indent; + str = ent->str; + taken = 0; + + do + { + len = find_next_wrap (buf->xtext, ent, str, win_width, indent); + if (taken < RECORD_WRAPS) + ent->wrap_offset[taken] = (str + len) - ent->str; + indent = buf->indent; + taken++; + str += len; + } + while (str < ent->str + ent->str_len); + + return taken; +} + +/* Calculate number of actual lines (with wraps), to set adj->lower. * + * This should only be called when the window resizes. */ + +static void +gtk_xtext_calc_lines (xtext_buffer *buf, int fire_signal) +{ + textentry *ent; + int width; + int height; + int lines; + + gdk_drawable_get_size (GTK_WIDGET (buf->xtext)->window, &width, &height); + width -= MARGIN; + + if (width < 30 || height < buf->xtext->fontsize || width < buf->indent + 30) + return; + + lines = 0; + ent = buf->text_first; + while (ent) + { + ent->lines_taken = gtk_xtext_lines_taken (buf, ent); + lines += ent->lines_taken; + ent = ent->next; + } + + buf->pagetop_ent = NULL; + buf->num_lines = lines; + gtk_xtext_adjustment_set (buf, fire_signal); +} + +/* find the n-th line in the linked list, this includes wrap calculations */ + +static textentry * +gtk_xtext_nth (GtkXText *xtext, int line, int *subline) +{ + int lines = 0; + textentry *ent; + + ent = xtext->buffer->text_first; + + /* -- optimization -- try to make a short-cut using the pagetop ent */ + if (xtext->buffer->pagetop_ent) + { + if (line == xtext->buffer->pagetop_line) + { + *subline = xtext->buffer->pagetop_subline; + return xtext->buffer->pagetop_ent; + } + if (line > xtext->buffer->pagetop_line) + { + /* lets start from the pagetop instead of the absolute beginning */ + ent = xtext->buffer->pagetop_ent; + lines = xtext->buffer->pagetop_line - xtext->buffer->pagetop_subline; + } + else if (line > xtext->buffer->pagetop_line - line) + { + /* move backwards from pagetop */ + ent = xtext->buffer->pagetop_ent; + lines = xtext->buffer->pagetop_line - xtext->buffer->pagetop_subline; + while (1) + { + if (lines <= line) + { + *subline = line - lines; + return ent; + } + ent = ent->prev; + if (!ent) + break; + lines -= ent->lines_taken; + } + return 0; + } + } + /* -- end of optimization -- */ + + while (ent) + { + lines += ent->lines_taken; + if (lines > line) + { + *subline = ent->lines_taken - (lines - line); + return ent; + } + ent = ent->next; + } + return 0; +} + +/* render enta (or an inclusive range enta->entb) */ + +static int +gtk_xtext_render_ents (GtkXText * xtext, textentry * enta, textentry * entb) +{ + textentry *ent, *orig_ent, *tmp_ent; + int line; + int lines_max; + int width; + int height; + int subline; + int drawing = FALSE; + + if (xtext->buffer->indent < MARGIN) + xtext->buffer->indent = MARGIN; /* 2 pixels is our left margin */ + + gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &width, &height); + width -= MARGIN; + + if (width < 32 || height < xtext->fontsize || width < xtext->buffer->indent + 30) + return 0; + + lines_max = ((height + xtext->pixel_offset) / xtext->fontsize) + 1; + line = 0; + orig_ent = xtext->buffer->pagetop_ent; + subline = xtext->buffer->pagetop_subline; + + /* used before a complete page is in buffer */ + if (orig_ent == NULL) + orig_ent = xtext->buffer->text_first; + + /* check if enta is before the start of this page */ + if (entb) + { + tmp_ent = orig_ent; + while (tmp_ent) + { + if (tmp_ent == enta) + break; + if (tmp_ent == entb) + { + drawing = TRUE; + break; + } + tmp_ent = tmp_ent->next; + } + } + + ent = orig_ent; + while (ent) + { + if (entb && ent == enta) + drawing = TRUE; + + if (drawing || ent == entb || ent == enta) + { + gtk_xtext_reset (xtext, FALSE, TRUE); + line += gtk_xtext_render_line (xtext, ent, line, lines_max, + subline, width); + subline = 0; + xtext->jump_in_offset = 0; /* jump_in_offset only for the 1st */ + } else + { + if (ent == orig_ent) + { + line -= subline; + subline = 0; + } + line += ent->lines_taken; + } + + if (ent == entb) + break; + + if (line >= lines_max) + break; + + ent = ent->next; + } + + /* space below last line */ + return (xtext->fontsize * line) - xtext->pixel_offset; +} + +/* render a whole page/window, starting from 'startline' */ + +static void +gtk_xtext_render_page (GtkXText * xtext) +{ + textentry *ent; + int line; + int lines_max; + int width; + int height; + int subline; + int startline = xtext->adj->value; + + if(!GTK_WIDGET_REALIZED(xtext)) + return; + + if (xtext->buffer->indent < MARGIN) + xtext->buffer->indent = MARGIN; /* 2 pixels is our left margin */ + + gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &width, &height); + + if (width < 34 || height < xtext->fontsize || width < xtext->buffer->indent + 32) + return; + +#ifdef SMOOTH_SCROLL + xtext->pixel_offset = (xtext->adj->value - startline) * xtext->fontsize; +#else + xtext->pixel_offset = 0; +#endif + + subline = line = 0; + ent = xtext->buffer->text_first; + + if (startline > 0) + ent = gtk_xtext_nth (xtext, startline, &subline); + + xtext->buffer->pagetop_ent = ent; + xtext->buffer->pagetop_subline = subline; + xtext->buffer->pagetop_line = startline; + +#ifdef SCROLL_HACK +{ + int pos, overlap; + GdkRectangle area; + + if (xtext->buffer->num_lines <= xtext->adj->page_size) + dontscroll (xtext->buffer); + +#ifdef SMOOTH_SCROLL + pos = xtext->adj->value * xtext->fontsize; +#else + pos = startline * xtext->fontsize; +#endif + overlap = xtext->buffer->last_pixel_pos - pos; + xtext->buffer->last_pixel_pos = pos; + +#ifdef USE_DB +#ifdef WIN32 + if (!xtext->transparent && !xtext->pixmap && abs (overlap) < height) +#else + if (!xtext->pixmap && abs (overlap) < height) +#endif +#else + /* dont scroll PageUp/Down without a DB, it looks ugly */ +#ifdef WIN32 + if (!xtext->transparent && !xtext->pixmap && abs (overlap) < height - (3*xtext->fontsize)) +#else + if (!xtext->pixmap && abs (overlap) < height - (3*xtext->fontsize)) +#endif +#endif + { + /* so the obscured regions are exposed */ + gdk_gc_set_exposures (xtext->fgc, TRUE); + if (overlap < 1) /* DOWN */ + { + int remainder; + + gdk_draw_drawable (xtext->draw_buf, xtext->fgc, xtext->draw_buf, + 0, -overlap, 0, 0, width, height + overlap); + remainder = ((height - xtext->font->descent) % xtext->fontsize) + + xtext->font->descent; + area.y = (height + overlap) - remainder; + area.height = remainder - overlap; + } else + { + gdk_draw_drawable (xtext->draw_buf, xtext->fgc, xtext->draw_buf, + 0, 0, 0, overlap, width, height - overlap); + area.y = 0; + area.height = overlap; + } + gdk_gc_set_exposures (xtext->fgc, FALSE); + + if (area.height > 0) + { + area.x = 0; + area.width = width; + gtk_xtext_paint (GTK_WIDGET (xtext), &area); + } + xtext->buffer->grid_dirty = TRUE; + + return; + } +} +#endif + + xtext->buffer->grid_dirty = FALSE; + width -= MARGIN; + lines_max = ((height + xtext->pixel_offset) / xtext->fontsize) + 1; + + while (ent) + { + gtk_xtext_reset (xtext, FALSE, TRUE); + line += gtk_xtext_render_line (xtext, ent, line, lines_max, + subline, width); + subline = 0; + + if (line >= lines_max) + break; + + ent = ent->next; + } + + line = (xtext->fontsize * line) - xtext->pixel_offset; + /* fill any space below the last line with our background GC */ + xtext_draw_bg (xtext, 0, line, width + MARGIN, height - line); + + /* draw the separator line */ + gtk_xtext_draw_sep (xtext, -1); +} + +void +gtk_xtext_refresh (GtkXText * xtext, int do_trans) +{ + if (GTK_WIDGET_REALIZED (GTK_WIDGET (xtext))) + { +#if defined(USE_XLIB) || defined(WIN32) + if (xtext->transparent && do_trans) + { + gtk_xtext_free_trans (xtext); + gtk_xtext_load_trans (xtext); + } +#endif + gtk_xtext_render_page (xtext); + } +} + +static int +gtk_xtext_kill_ent (xtext_buffer *buffer, textentry *ent) +{ + int visible; + + /* Set visible to TRUE if this is the current buffer */ + /* and this ent shows up on the screen now */ + visible = buffer->xtext->buffer == buffer && + gtk_xtext_check_ent_visibility (buffer->xtext, ent, 0); + + if (ent == buffer->pagetop_ent) + buffer->pagetop_ent = NULL; + + if (ent == buffer->last_ent_start) + { + buffer->last_ent_start = ent->next; + buffer->last_offset_start = 0; + } + + if (ent == buffer->last_ent_end) + { + buffer->last_ent_start = NULL; + buffer->last_ent_end = NULL; + } + + if (buffer->marker_pos == ent) buffer->marker_pos = NULL; + + free (ent); + return visible; +} + +/* remove the topline from the list */ + +static void +gtk_xtext_remove_top (xtext_buffer *buffer) +{ + textentry *ent; + + ent = buffer->text_first; + if (!ent) + return; + buffer->num_lines -= ent->lines_taken; + buffer->pagetop_line -= ent->lines_taken; + buffer->last_pixel_pos -= (ent->lines_taken * buffer->xtext->fontsize); + buffer->text_first = ent->next; + if (buffer->text_first) + buffer->text_first->prev = NULL; + else + buffer->text_last = NULL; + + buffer->old_value -= ent->lines_taken; + if (buffer->xtext->buffer == buffer) /* is it the current buffer? */ + { + buffer->xtext->adj->value -= ent->lines_taken; + buffer->xtext->select_start_adj -= ent->lines_taken; + } + + if (gtk_xtext_kill_ent (buffer, ent)) + { + if (!buffer->xtext->add_io_tag) + { + /* remove scrolling events */ + if (buffer->xtext->io_tag) + { + g_source_remove (buffer->xtext->io_tag); + buffer->xtext->io_tag = 0; + } + buffer->xtext->force_render = TRUE; + buffer->xtext->add_io_tag = g_timeout_add (REFRESH_TIMEOUT * 2, + (GSourceFunc) + gtk_xtext_render_page_timeout, + buffer->xtext); + } + } +} + +static void +gtk_xtext_remove_bottom (xtext_buffer *buffer) +{ + textentry *ent; + + ent = buffer->text_last; + if (!ent) + return; + buffer->num_lines -= ent->lines_taken; + buffer->text_last = ent->prev; + if (buffer->text_last) + buffer->text_last->next = NULL; + else + buffer->text_first = NULL; + + gtk_xtext_kill_ent (buffer, ent); +} + +/* If lines=0 => clear all */ + +void +gtk_xtext_clear (xtext_buffer *buf, int lines) +{ + textentry *next; + + if (lines != 0) + { + if (lines < 0) + { + /* delete lines from bottom */ + lines *= -1; + while (lines) + { + gtk_xtext_remove_bottom (buf); + lines--; + } + } + else + { + /* delete lines from top */ + while (lines) + { + gtk_xtext_remove_top (buf); + lines--; + } + } + } + else + { + /* delete all */ + if (buf->xtext->auto_indent) + buf->indent = MARGIN; + buf->scrollbar_down = TRUE; + buf->last_ent_start = NULL; + buf->last_ent_end = NULL; + buf->marker_pos = NULL; + dontscroll (buf); + + while (buf->text_first) + { + next = buf->text_first->next; + free (buf->text_first); + buf->text_first = next; + } + buf->text_last = NULL; + } + + if (buf->xtext->buffer == buf) + { + gtk_xtext_calc_lines (buf, TRUE); + gtk_xtext_refresh (buf->xtext, 0); + } else + { + gtk_xtext_calc_lines (buf, FALSE); + } +} + +static gboolean +gtk_xtext_check_ent_visibility (GtkXText * xtext, textentry *find_ent, int add) +{ + textentry *ent; + int lines_max; + int line = 0; + int width; + int height; + + gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &width, &height); + + lines_max = ((height + xtext->pixel_offset) / xtext->fontsize) + add; + ent = xtext->buffer->pagetop_ent; + + while (ent && line < lines_max) + { + if (find_ent == ent) + return TRUE; + line += ent->lines_taken; + ent = ent->next; + } + + return FALSE; +} + +void +gtk_xtext_check_marker_visibility (GtkXText * xtext) +{ + if (gtk_xtext_check_ent_visibility (xtext, xtext->buffer->marker_pos, 1)) + xtext->buffer->marker_seen = TRUE; +} + +textentry * +gtk_xtext_search (GtkXText * xtext, const gchar *text, textentry *start, gboolean case_match, gboolean backward) +{ + textentry *ent, *fent; + int line; + gchar *str, *nee, *hay; /* needle in haystack */ + + gtk_xtext_selection_clear_full (xtext->buffer); + xtext->buffer->last_ent_start = NULL; + xtext->buffer->last_ent_end = NULL; + + /* set up text comparand for Case Match or Ignore */ + if (case_match) + nee = g_strdup (text); + else + nee = g_utf8_casefold (text, strlen (text)); + + /* Validate that start gives a currently valid ent pointer */ + ent = xtext->buffer->text_first; + while (ent) + { + if (ent == start) + break; + ent = ent->next; + } + if (!ent) + start = NULL; + + /* Choose first ent to look at */ + if (start) + ent = backward? start->prev: start->next; + else + ent = backward? xtext->buffer->text_last: xtext->buffer->text_first; + + /* Search from there to one end or the other until found */ + while (ent) + { + /* If Case Ignore, fold before & free after calling strstr */ + if (case_match) + hay = g_strdup (ent->str); + else + hay = g_utf8_casefold (ent->str, strlen (ent->str)); + /* Try to find the needle in this haystack */ + str = g_strstr_len (hay, strlen (hay), nee); + g_free (hay); + if (str) + break; + ent = backward? ent->prev: ent->next; + } + fent = ent; + + /* Save distance to start, end of found string */ + if (ent) + { + ent->mark_start = str - hay; + ent->mark_end = ent->mark_start + strlen (nee); + + /* is the match visible? Might need to scroll */ + if (!gtk_xtext_check_ent_visibility (xtext, ent, 0)) + { + ent = xtext->buffer->text_first; + line = 0; + while (ent) + { + line += ent->lines_taken; + ent = ent->next; + if (ent == fent) + break; + } + while (line > xtext->adj->upper - xtext->adj->page_size) + line--; + if (backward) + line -= xtext->adj->page_size - ent->lines_taken; + xtext->adj->value = line; + xtext->buffer->scrollbar_down = FALSE; + gtk_adjustment_changed (xtext->adj); + } + } + + g_free (nee); + gtk_widget_queue_draw (GTK_WIDGET (xtext)); + + return fent; +} + +static int +gtk_xtext_render_page_timeout (GtkXText * xtext) +{ + GtkAdjustment *adj = xtext->adj; + + xtext->add_io_tag = 0; + + /* less than a complete page? */ + if (xtext->buffer->num_lines <= adj->page_size) + { + xtext->buffer->old_value = 0; + adj->value = 0; + gtk_xtext_render_page (xtext); + } else if (xtext->buffer->scrollbar_down) + { + g_signal_handler_block (xtext->adj, xtext->vc_signal_tag); + gtk_xtext_adjustment_set (xtext->buffer, FALSE); + gtk_adjustment_set_value (adj, adj->upper - adj->page_size); + g_signal_handler_unblock (xtext->adj, xtext->vc_signal_tag); + xtext->buffer->old_value = adj->value; + gtk_xtext_render_page (xtext); + } else + { + gtk_xtext_adjustment_set (xtext->buffer, TRUE); + if (xtext->force_render) + { + xtext->force_render = FALSE; + gtk_xtext_render_page (xtext); + } + } + + return 0; +} + +/* append a textentry to our linked list */ + +static void +gtk_xtext_append_entry (xtext_buffer *buf, textentry * ent, time_t stamp) +{ + unsigned int mb; + int i; + + /* we don't like tabs */ + i = 0; + while (i < ent->str_len) + { + if (ent->str[i] == '\t') + ent->str[i] = ' '; + i++; + } + + ent->stamp = stamp; + if (stamp == 0) + ent->stamp = time (0); + ent->str_width = gtk_xtext_text_width (buf->xtext, ent->str, ent->str_len, &mb); + ent->mb = FALSE; + if (mb) + ent->mb = TRUE; + ent->mark_start = -1; + ent->mark_end = -1; + ent->next = NULL; + + if (ent->indent < MARGIN) + ent->indent = MARGIN; /* 2 pixels is the left margin */ + + /* append to our linked list */ + if (buf->text_last) + buf->text_last->next = ent; + else + buf->text_first = ent; + ent->prev = buf->text_last; + buf->text_last = ent; + + ent->lines_taken = gtk_xtext_lines_taken (buf, ent); + buf->num_lines += ent->lines_taken; + + if (buf->reset_marker_pos || + ((buf->marker_pos == NULL || buf->marker_seen) && (buf->xtext->buffer != buf || +#if GTK_CHECK_VERSION(2,4,0) + !gtk_window_has_toplevel_focus (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (buf->xtext))))))) +#else + !(GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (buf->xtext))))->has_focus))) +#endif + { + buf->marker_pos = ent; + dontscroll (buf); /* force scrolling off */ + buf->marker_seen = FALSE; + buf->reset_marker_pos = FALSE; + } + + if (buf->xtext->max_lines > 2 && buf->xtext->max_lines < buf->num_lines) + { + gtk_xtext_remove_top (buf); + } + + if (buf->xtext->buffer == buf) + { +#ifdef SCROLL_HACK + /* this could be improved */ + if ((buf->num_lines - 1) <= buf->xtext->adj->page_size) + dontscroll (buf); +#endif + + if (!buf->xtext->add_io_tag) + { + /* remove scrolling events */ + if (buf->xtext->io_tag) + { + g_source_remove (buf->xtext->io_tag); + buf->xtext->io_tag = 0; + } + buf->xtext->add_io_tag = g_timeout_add (REFRESH_TIMEOUT * 2, + (GSourceFunc) + gtk_xtext_render_page_timeout, + buf->xtext); + } + } else if (buf->scrollbar_down) + { + buf->old_value = buf->num_lines - buf->xtext->adj->page_size; + if (buf->old_value < 0) + buf->old_value = 0; + } +} + +/* the main two public functions */ + +void +gtk_xtext_append_indent (xtext_buffer *buf, + unsigned char *left_text, int left_len, + unsigned char *right_text, int right_len, + time_t stamp) +{ + textentry *ent; + unsigned char *str; + int space; + int tempindent; + int left_width; + + if (left_len == -1) + left_len = strlen (left_text); + + if (right_len == -1) + right_len = strlen (right_text); + + if (right_len >= sizeof (buf->xtext->scratch_buffer)) + right_len = sizeof (buf->xtext->scratch_buffer) - 1; + + if (right_text[right_len-1] == '\n') + right_len--; + + ent = malloc (left_len + right_len + 2 + sizeof (textentry)); + str = (unsigned char *) ent + sizeof (textentry); + + memcpy (str, left_text, left_len); + str[left_len] = ' '; + memcpy (str + left_len + 1, right_text, right_len); + str[left_len + 1 + right_len] = 0; + + left_width = gtk_xtext_text_width (buf->xtext, left_text, left_len, NULL); + + ent->left_len = left_len; + ent->str = str; + ent->str_len = left_len + 1 + right_len; + ent->indent = (buf->indent - left_width) - buf->xtext->space_width; + + if (buf->time_stamp) + space = buf->xtext->stamp_width; + else + space = 0; + + /* do we need to auto adjust the separator position? */ + if (buf->xtext->auto_indent && ent->indent < MARGIN + space) + { + tempindent = MARGIN + space + buf->xtext->space_width + left_width; + + if (tempindent > buf->indent) + buf->indent = tempindent; + + if (buf->indent > buf->xtext->max_auto_indent) + buf->indent = buf->xtext->max_auto_indent; + + gtk_xtext_fix_indent (buf); + gtk_xtext_recalc_widths (buf, FALSE); + + ent->indent = (buf->indent - left_width) - buf->xtext->space_width; + buf->xtext->force_render = TRUE; + } + + gtk_xtext_append_entry (buf, ent, stamp); +} + +void +gtk_xtext_append (xtext_buffer *buf, unsigned char *text, int len) +{ + textentry *ent; + + if (len == -1) + len = strlen (text); + + if (text[len-1] == '\n') + len--; + + if (len >= sizeof (buf->xtext->scratch_buffer)) + len = sizeof (buf->xtext->scratch_buffer) - 1; + + ent = malloc (len + 1 + sizeof (textentry)); + ent->str = (unsigned char *) ent + sizeof (textentry); + ent->str_len = len; + if (len) + memcpy (ent->str, text, len); + ent->str[len] = 0; + ent->indent = 0; + ent->left_len = -1; + + gtk_xtext_append_entry (buf, ent, 0); +} + +gboolean +gtk_xtext_is_empty (xtext_buffer *buf) +{ + return buf->text_first == NULL; +} + +int +gtk_xtext_lastlog (xtext_buffer *out, xtext_buffer *search_area, + int (*cmp_func) (char *, void *), void *userdata) +{ + textentry *ent = search_area->text_first; + int matches = 0; + + while (ent) + { + if (cmp_func (ent->str, userdata)) + { + matches++; + /* copy the text over */ + if (search_area->xtext->auto_indent) + gtk_xtext_append_indent (out, ent->str, ent->left_len, + ent->str + ent->left_len + 1, + ent->str_len - ent->left_len - 1, 0); + else + gtk_xtext_append (out, ent->str, ent->str_len); + /* copy the timestamp over */ + out->text_last->stamp = ent->stamp; + } + ent = ent->next; + } + + return matches; +} + +void +gtk_xtext_foreach (xtext_buffer *buf, GtkXTextForeach func, void *data) +{ + textentry *ent = buf->text_first; + + while (ent) + { + (*func) (buf->xtext, ent->str, data); + ent = ent->next; + } +} + +void +gtk_xtext_set_error_function (GtkXText *xtext, void (*error_function) (int)) +{ + xtext->error_function = error_function; +} + +void +gtk_xtext_set_indent (GtkXText *xtext, gboolean indent) +{ + xtext->auto_indent = indent; +} + +void +gtk_xtext_set_max_indent (GtkXText *xtext, int max_auto_indent) +{ + xtext->max_auto_indent = max_auto_indent; +} + +void +gtk_xtext_set_max_lines (GtkXText *xtext, int max_lines) +{ + xtext->max_lines = max_lines; +} + +void +gtk_xtext_set_show_marker (GtkXText *xtext, gboolean show_marker) +{ + xtext->marker = show_marker; +} + +void +gtk_xtext_set_show_separator (GtkXText *xtext, gboolean show_separator) +{ + xtext->separator = show_separator; +} + +void +gtk_xtext_set_thin_separator (GtkXText *xtext, gboolean thin_separator) +{ + xtext->thinline = thin_separator; +} + +void +gtk_xtext_set_time_stamp (xtext_buffer *buf, gboolean time_stamp) +{ + buf->time_stamp = time_stamp; +} + +void +gtk_xtext_set_tint (GtkXText *xtext, int tint_red, int tint_green, int tint_blue) +{ + xtext->tint_red = tint_red; + xtext->tint_green = tint_green; + xtext->tint_blue = tint_blue; + + /*if (xtext->tint_red != 255 || xtext->tint_green != 255 || xtext->tint_blue != 255) + shaded = TRUE;*/ +} + +void +gtk_xtext_set_urlcheck_function (GtkXText *xtext, int (*urlcheck_function) (GtkWidget *, char *, int)) +{ + xtext->urlcheck_function = urlcheck_function; +} + +void +gtk_xtext_set_wordwrap (GtkXText *xtext, gboolean wordwrap) +{ + xtext->wordwrap = wordwrap; +} + +void +gtk_xtext_reset_marker_pos (GtkXText *xtext) +{ + xtext->buffer->marker_pos = NULL; + dontscroll (xtext->buffer); /* force scrolling off */ + gtk_xtext_render_page (xtext); + xtext->buffer->reset_marker_pos = TRUE; +} + +void +gtk_xtext_buffer_show (GtkXText *xtext, xtext_buffer *buf, int render) +{ + int w, h; + + buf->xtext = xtext; + + if (xtext->buffer == buf) + return; + +/*printf("text_buffer_show: xtext=%p buffer=%p\n", xtext, buf);*/ + + if (xtext->add_io_tag) + { + g_source_remove (xtext->add_io_tag); + xtext->add_io_tag = 0; + } + + if (xtext->io_tag) + { + g_source_remove (xtext->io_tag); + xtext->io_tag = 0; + } + + if (!GTK_WIDGET_REALIZED (GTK_WIDGET (xtext))) + gtk_widget_realize (GTK_WIDGET (xtext)); + + gdk_drawable_get_size (GTK_WIDGET (xtext)->window, &w, &h); + + /* after a font change */ + if (buf->needs_recalc) + { + buf->needs_recalc = FALSE; + gtk_xtext_recalc_widths (buf, TRUE); + } + + /* now change to the new buffer */ + xtext->buffer = buf; + dontscroll (buf); /* force scrolling off */ + xtext->adj->value = buf->old_value; + xtext->adj->upper = buf->num_lines; + if (xtext->adj->upper == 0) + xtext->adj->upper = 1; + /* sanity check */ + else if (xtext->adj->value > xtext->adj->upper - xtext->adj->page_size) + { + /*buf->pagetop_ent = NULL;*/ + xtext->adj->value = xtext->adj->upper - xtext->adj->page_size; + if (xtext->adj->value < 0) + xtext->adj->value = 0; + } + + if (render) + { + /* did the window change size since this buffer was last shown? */ + if (buf->window_width != w) + { + buf->window_width = w; + gtk_xtext_calc_lines (buf, FALSE); + if (buf->scrollbar_down) + gtk_adjustment_set_value (xtext->adj, xtext->adj->upper - + xtext->adj->page_size); + } else if (buf->window_height != h) + { + buf->window_height = h; + buf->pagetop_ent = NULL; + gtk_xtext_adjustment_set (buf, FALSE); + } + + gtk_xtext_render_page (xtext); + gtk_adjustment_changed (xtext->adj); + } else + { + /* avoid redoing the transparency */ + xtext->avoid_trans = TRUE; + } +} + +xtext_buffer * +gtk_xtext_buffer_new (GtkXText *xtext) +{ + xtext_buffer *buf; + + buf = malloc (sizeof (xtext_buffer)); + memset (buf, 0, sizeof (xtext_buffer)); + buf->old_value = -1; + buf->xtext = xtext; + buf->scrollbar_down = TRUE; + buf->indent = xtext->space_width * 2; + dontscroll (buf); + + return buf; +} + +void +gtk_xtext_buffer_free (xtext_buffer *buf) +{ + textentry *ent, *next; + + if (buf->xtext->buffer == buf) + buf->xtext->buffer = buf->xtext->orig_buffer; + + if (buf->xtext->selection_buffer == buf) + buf->xtext->selection_buffer = NULL; + + ent = buf->text_first; + while (ent) + { + next = ent->next; + free (ent); + ent = next; + } + + free (buf); +} diff --git a/src/fe-gtk/xtext.h b/src/fe-gtk/xtext.h new file mode 100644 index 00000000..a37ddc32 --- /dev/null +++ b/src/fe-gtk/xtext.h @@ -0,0 +1,275 @@ +#ifndef __XTEXT_H__ +#define __XTEXT_H__ + +#include <gtk/gtkadjustment.h> +#ifdef USE_XFT +#include <X11/Xft/Xft.h> +#endif + +#ifdef USE_SHM +#include <X11/Xlib.h> +#include <sys/ipc.h> +#include <sys/shm.h> +#include <X11/extensions/XShm.h> +#endif + +#define GTK_TYPE_XTEXT (gtk_xtext_get_type ()) +#define GTK_XTEXT(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GTK_TYPE_XTEXT, GtkXText)) +#define GTK_XTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_XTEXT, GtkXTextClass)) +#define GTK_IS_XTEXT(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GTK_TYPE_XTEXT)) +#define GTK_IS_XTEXT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_XTEXT)) +#define GTK_XTEXT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_XTEXT, GtkXTextClass)) + +#define ATTR_BOLD '\002' +#define ATTR_COLOR '\003' +#define ATTR_BLINK '\006' +#define ATTR_BEEP '\007' +#define ATTR_HIDDEN '\010' +#define ATTR_ITALICS2 '\011' +#define ATTR_RESET '\017' +#define ATTR_REVERSE '\026' +#define ATTR_ITALICS '\035' +#define ATTR_UNDERLINE '\037' + +/* these match palette.h */ +#define XTEXT_MIRC_COLS 32 +#define XTEXT_COLS 37 /* 32 plus 5 for extra stuff below */ +#define XTEXT_MARK_FG 32 /* for marking text */ +#define XTEXT_MARK_BG 33 +#define XTEXT_FG 34 +#define XTEXT_BG 35 +#define XTEXT_MARKER 36 /* for marker line */ + +typedef struct _GtkXText GtkXText; +typedef struct _GtkXTextClass GtkXTextClass; +typedef struct textentry textentry; + +typedef struct { + GtkXText *xtext; /* attached to this widget */ + + gfloat old_value; /* last known adj->value */ + textentry *text_first; + textentry *text_last; + guint16 grid_offset[256]; + + textentry *last_ent_start; /* this basically describes the last rendered */ + textentry *last_ent_end; /* selection. */ + int last_offset_start; + int last_offset_end; + + int last_pixel_pos; + + int pagetop_line; + int pagetop_subline; + textentry *pagetop_ent; /* what's at xtext->adj->value */ + + int num_lines; + int indent; /* position of separator (pixels) from left */ + + textentry *marker_pos; + + int window_width; /* window size when last rendered. */ + int window_height; + + unsigned int time_stamp:1; + unsigned int scrollbar_down:1; + unsigned int needs_recalc:1; + unsigned int grid_dirty:1; + unsigned int marker_seen:1; + unsigned int reset_marker_pos:1; +} xtext_buffer; + +struct _GtkXText +{ + GtkWidget widget; + + xtext_buffer *buffer; + xtext_buffer *orig_buffer; + xtext_buffer *selection_buffer; + +#ifdef USE_SHM + XShmSegmentInfo shminfo; +#endif + + GtkAdjustment *adj; + GdkPixmap *pixmap; /* 0 = use palette[19] */ + GdkDrawable *draw_buf; /* points to ->window */ + GdkCursor *hand_cursor; + GdkCursor *resize_cursor; + + int pixel_offset; /* amount of pixels the top line is chopped by */ + + int last_win_x; + int last_win_y; + int last_win_h; + int last_win_w; + + int tint_red; + int tint_green; + int tint_blue; + + GdkGC *bgc; /* backing pixmap */ + GdkGC *fgc; /* text foreground color */ + GdkGC *light_gc; /* sep bar */ + GdkGC *dark_gc; + GdkGC *thin_gc; + GdkGC *marker_gc; + gulong palette[XTEXT_COLS]; + + gint io_tag; /* for delayed refresh events */ + gint add_io_tag; /* "" when adding new text */ + gint scroll_tag; /* marking-scroll timeout */ + gulong vc_signal_tag; /* signal handler for "value_changed" adj */ + + int select_start_adj; /* the adj->value when the selection started */ + int select_start_x; + int select_start_y; + int select_end_x; + int select_end_y; + + int max_lines; + + int col_fore; + int col_back; + + int depth; /* gdk window depth */ + + char num[8]; /* for parsing mirc color */ + int nc; /* offset into xtext->num */ + + textentry *hilight_ent; + int hilight_start; + int hilight_end; + + guint16 fontwidth[128]; /* each char's width, only the ASCII ones */ + +#ifdef USE_XFT + XftColor color[XTEXT_COLS]; + XftColor *xft_fg; + XftColor *xft_bg; /* both point into color[20] */ + XftDraw *xftdraw; + XftFont *font; + XftFont *ifont; /* italics */ +#else + struct pangofont + { + PangoFontDescription *font; + PangoFontDescription *ifont; /* italics */ + int ascent; + int descent; + } *font, pango_font; + PangoLayout *layout; +#endif + + int fontsize; + int space_width; /* width (pixels) of the space " " character */ + int stamp_width; /* width of "[88:88:88]" */ + int max_auto_indent; + + unsigned char scratch_buffer[4096]; + + void (*error_function) (int type); + int (*urlcheck_function) (GtkWidget * xtext, char *word, int len); + + int jump_out_offset; /* point at which to stop rendering */ + int jump_in_offset; /* "" start rendering */ + + int ts_x; /* ts origin for ->bgc GC */ + int ts_y; + + int clip_x; /* clipping (x directions) */ + int clip_x2; /* from x to x2 */ + + int clip_y; /* clipping (y directions) */ + int clip_y2; /* from y to y2 */ + + /* current text states */ + unsigned int bold:1; + unsigned int underline:1; + unsigned int italics:1; + unsigned int hidden:1; + + /* text parsing states */ + unsigned int parsing_backcolor:1; + unsigned int parsing_color:1; + unsigned int backcolor:1; + + /* various state information */ + unsigned int moving_separator:1; + unsigned int word_or_line_select:1; + unsigned int button_down:1; + unsigned int hilighting:1; + unsigned int dont_render:1; + unsigned int dont_render2:1; + unsigned int cursor_hand:1; + unsigned int cursor_resize:1; + unsigned int skip_border_fills:1; + unsigned int skip_stamp:1; + unsigned int mark_stamp:1; /* Cut&Paste with stamps? */ + unsigned int force_stamp:1; /* force redrawing it */ + unsigned int render_hilights_only:1; + unsigned int in_hilight:1; + unsigned int un_hilight:1; + unsigned int recycle:1; + unsigned int avoid_trans:1; + unsigned int force_render:1; + unsigned int shm:1; + unsigned int color_paste:1; /* CTRL was pressed when selection finished */ + + /* settings/prefs */ + unsigned int auto_indent:1; + unsigned int thinline:1; + unsigned int transparent:1; + unsigned int shaded:1; + unsigned int marker:1; + unsigned int separator:1; + unsigned int wordwrap:1; + unsigned int overdraw:1; + unsigned int ignore_hidden:1; /* rawlog uses this */ +}; + +struct _GtkXTextClass +{ + GtkWidgetClass parent_class; + void (*word_click) (GtkXText * xtext, char *word, GdkEventButton * event); +}; + +GtkWidget *gtk_xtext_new (GdkColor palette[], int separator); +void gtk_xtext_append (xtext_buffer *buf, unsigned char *text, int len); +void gtk_xtext_append_indent (xtext_buffer *buf, + unsigned char *left_text, int left_len, + unsigned char *right_text, int right_len, + time_t stamp); +int gtk_xtext_set_font (GtkXText *xtext, char *name); +void gtk_xtext_set_background (GtkXText * xtext, GdkPixmap * pixmap, gboolean trans); +void gtk_xtext_set_palette (GtkXText * xtext, GdkColor palette[]); +void gtk_xtext_clear (xtext_buffer *buf, int lines); +void gtk_xtext_save (GtkXText * xtext, int fh); +void gtk_xtext_refresh (GtkXText * xtext, int do_trans); +int gtk_xtext_lastlog (xtext_buffer *out, xtext_buffer *search_area, int (*cmp_func) (char *, void *userdata), void *userdata); +textentry *gtk_xtext_search (GtkXText * xtext, const gchar *text, textentry *start, gboolean case_match, gboolean backward); +void gtk_xtext_reset_marker_pos (GtkXText *xtext); +void gtk_xtext_check_marker_visibility(GtkXText *xtext); + +gboolean gtk_xtext_is_empty (xtext_buffer *buf); +typedef void (*GtkXTextForeach) (GtkXText *xtext, unsigned char *text, void *data); +void gtk_xtext_foreach (xtext_buffer *buf, GtkXTextForeach func, void *data); + +void gtk_xtext_set_error_function (GtkXText *xtext, void (*error_function) (int)); +void gtk_xtext_set_indent (GtkXText *xtext, gboolean indent); +void gtk_xtext_set_max_indent (GtkXText *xtext, int max_auto_indent); +void gtk_xtext_set_max_lines (GtkXText *xtext, int max_lines); +void gtk_xtext_set_show_marker (GtkXText *xtext, gboolean show_marker); +void gtk_xtext_set_show_separator (GtkXText *xtext, gboolean show_separator); +void gtk_xtext_set_thin_separator (GtkXText *xtext, gboolean thin_separator); +void gtk_xtext_set_time_stamp (xtext_buffer *buf, gboolean timestamp); +void gtk_xtext_set_tint (GtkXText *xtext, int tint_red, int tint_green, int tint_blue); +void gtk_xtext_set_urlcheck_function (GtkXText *xtext, int (*urlcheck_function) (GtkWidget *, char *, int)); +void gtk_xtext_set_wordwrap (GtkXText *xtext, gboolean word_wrap); + +xtext_buffer *gtk_xtext_buffer_new (GtkXText *xtext); +void gtk_xtext_buffer_free (xtext_buffer *buf); +void gtk_xtext_buffer_show (GtkXText *xtext, xtext_buffer *buf, int render); +GType gtk_xtext_get_type (void); + +#endif |