summaryrefslogtreecommitdiffstats
path: root/src/fe-text
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/fe-text/Makefile.am75
-rw-r--r--src/fe-text/Makefile.in899
-rw-r--r--src/fe-text/gui-entry.c1514
-rw-r--r--src/fe-text/gui-entry.h92
-rw-r--r--src/fe-text/gui-expandos.c63
-rw-r--r--src/fe-text/gui-printtext.c429
-rw-r--r--src/fe-text/gui-printtext.h26
-rw-r--r--src/fe-text/gui-readline.c1626
-rw-r--r--src/fe-text/gui-readline.h15
-rw-r--r--src/fe-text/gui-windows.c327
-rw-r--r--src/fe-text/gui-windows.h45
-rw-r--r--src/fe-text/irssi.c402
-rw-r--r--src/fe-text/lastlog.c324
-rw-r--r--src/fe-text/mainwindow-activity.c62
-rw-r--r--src/fe-text/mainwindows-layout.c330
-rw-r--r--src/fe-text/mainwindows.c2004
-rw-r--r--src/fe-text/mainwindows.h74
-rw-r--r--src/fe-text/meson.build69
-rw-r--r--src/fe-text/module-formats.c107
-rw-r--r--src/fe-text/module-formats.h67
-rw-r--r--src/fe-text/module.h8
-rw-r--r--src/fe-text/statusbar-config.c826
-rw-r--r--src/fe-text/statusbar-config.h12
-rw-r--r--src/fe-text/statusbar-item.h17
-rw-r--r--src/fe-text/statusbar-items.c546
-rw-r--r--src/fe-text/statusbar.c1196
-rw-r--r--src/fe-text/statusbar.h117
-rw-r--r--src/fe-text/term-terminfo.c803
-rw-r--r--src/fe-text/term.c208
-rw-r--r--src/fe-text/term.h112
-rw-r--r--src/fe-text/terminfo-core.c740
-rw-r--r--src/fe-text/terminfo-core.h113
-rw-r--r--src/fe-text/textbuffer-commands.c481
-rw-r--r--src/fe-text/textbuffer-formats.c465
-rw-r--r--src/fe-text/textbuffer-formats.h26
-rw-r--r--src/fe-text/textbuffer-view.c1546
-rw-r--r--src/fe-text/textbuffer-view.h172
-rw-r--r--src/fe-text/textbuffer.c345
-rw-r--r--src/fe-text/textbuffer.h106
39 files changed, 16389 insertions, 0 deletions
diff --git a/src/fe-text/Makefile.am b/src/fe-text/Makefile.am
new file mode 100644
index 0000000..d97cbfa
--- /dev/null
+++ b/src/fe-text/Makefile.am
@@ -0,0 +1,75 @@
+bin_PROGRAMS = irssi
+
+AM_CPPFLAGS = \
+ -I$(top_builddir) \
+ $(GLIB_CFLAGS)
+
+irssi_DEPENDENCIES = \
+ @COMMON_LIBS@ \
+ @PERL_LINK_LIBS@ \
+ @PERL_FE_LINK_LIBS@ \
+ @OTR_LINK_LIBS@
+
+irssi_LDFLAGS = -export-dynamic
+
+irssi_LDADD = \
+ @COMMON_LIBS@ \
+ @PERL_LINK_LIBS@ \
+ @PERL_FE_LINK_LIBS@ \
+ @OTR_LINK_LIBS@ \
+ @OTR_LINK_FLAGS@ \
+ @PERL_LINK_FLAGS@ \
+ @PROG_LIBS@ \
+ @TEXTUI_LIBS@
+
+terminfo_sources = \
+ term-terminfo.c \
+ terminfo-core.c
+
+use_term_sources = $(terminfo_sources)
+
+irssi_SOURCES = \
+ gui-entry.c \
+ gui-expandos.c \
+ gui-printtext.c \
+ gui-readline.c \
+ gui-windows.c \
+ lastlog.c \
+ mainwindows.c \
+ mainwindow-activity.c \
+ mainwindows-layout.c \
+ statusbar.c \
+ statusbar-config.c \
+ statusbar-items.c \
+ term.c \
+ $(use_term_sources) \
+ textbuffer.c \
+ textbuffer-commands.c \
+ textbuffer-view.c \
+ textbuffer-formats.c \
+ irssi.c \
+ module-formats.c
+
+pkginc_fe_textdir=$(pkgincludedir)/src/fe-text
+pkginc_fe_text_HEADERS = \
+ gui-printtext.h \
+ gui-windows.h \
+ mainwindows.h \
+ statusbar.h \
+ statusbar-item.h \
+ term.h \
+ textbuffer.h \
+ textbuffer-view.h \
+ textbuffer-formats.h
+
+noinst_HEADERS = \
+ gui-entry.h \
+ gui-readline.h \
+ statusbar-config.h \
+ terminfo-core.h \
+ module.h \
+ module-formats.h
+
+EXTRA_DIST = \
+ $(terminfo_sources) \
+ meson.build
diff --git a/src/fe-text/Makefile.in b/src/fe-text/Makefile.in
new file mode 100644
index 0000000..14e3dde
--- /dev/null
+++ b/src/fe-text/Makefile.in
@@ -0,0 +1,899 @@
+# Makefile.in generated by automake 1.16.5 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2021 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+bin_PROGRAMS = irssi$(EXEEXT)
+subdir = src/fe-text
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/glib-2.0.m4 \
+ $(top_srcdir)/m4/glibtests.m4 $(top_srcdir)/m4/libgcrypt.m4 \
+ $(top_srcdir)/m4/libotr.m4 $(top_srcdir)/m4/libtool.m4 \
+ $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \
+ $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \
+ $(top_srcdir)/m4/pkg.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(noinst_HEADERS) \
+ $(pkginc_fe_text_HEADERS) $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/irssi-config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)" \
+ "$(DESTDIR)$(pkginc_fe_textdir)"
+PROGRAMS = $(bin_PROGRAMS)
+am__objects_1 = term-terminfo.$(OBJEXT) terminfo-core.$(OBJEXT)
+am__objects_2 = $(am__objects_1)
+am_irssi_OBJECTS = gui-entry.$(OBJEXT) gui-expandos.$(OBJEXT) \
+ gui-printtext.$(OBJEXT) gui-readline.$(OBJEXT) \
+ gui-windows.$(OBJEXT) lastlog.$(OBJEXT) mainwindows.$(OBJEXT) \
+ mainwindow-activity.$(OBJEXT) mainwindows-layout.$(OBJEXT) \
+ statusbar.$(OBJEXT) statusbar-config.$(OBJEXT) \
+ statusbar-items.$(OBJEXT) term.$(OBJEXT) $(am__objects_2) \
+ textbuffer.$(OBJEXT) textbuffer-commands.$(OBJEXT) \
+ textbuffer-view.$(OBJEXT) textbuffer-formats.$(OBJEXT) \
+ irssi.$(OBJEXT) module-formats.$(OBJEXT)
+irssi_OBJECTS = $(am_irssi_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+irssi_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(irssi_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES =
+depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/gui-entry.Po \
+ ./$(DEPDIR)/gui-expandos.Po ./$(DEPDIR)/gui-printtext.Po \
+ ./$(DEPDIR)/gui-readline.Po ./$(DEPDIR)/gui-windows.Po \
+ ./$(DEPDIR)/irssi.Po ./$(DEPDIR)/lastlog.Po \
+ ./$(DEPDIR)/mainwindow-activity.Po \
+ ./$(DEPDIR)/mainwindows-layout.Po ./$(DEPDIR)/mainwindows.Po \
+ ./$(DEPDIR)/module-formats.Po ./$(DEPDIR)/statusbar-config.Po \
+ ./$(DEPDIR)/statusbar-items.Po ./$(DEPDIR)/statusbar.Po \
+ ./$(DEPDIR)/term-terminfo.Po ./$(DEPDIR)/term.Po \
+ ./$(DEPDIR)/terminfo-core.Po \
+ ./$(DEPDIR)/textbuffer-commands.Po \
+ ./$(DEPDIR)/textbuffer-formats.Po \
+ ./$(DEPDIR)/textbuffer-view.Po ./$(DEPDIR)/textbuffer.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(irssi_SOURCES)
+DIST_SOURCES = $(irssi_SOURCES)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+HEADERS = $(noinst_HEADERS) $(pkginc_fe_text_HEADERS)
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+am__DIST_COMMON = $(srcdir)/Makefile.in \
+ $(top_srcdir)/build-aux/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CHAT_MODULES = @CHAT_MODULES@
+COMMON_LIBS = @COMMON_LIBS@
+COMMON_NOUI_LIBS = @COMMON_NOUI_LIBS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CSCOPE = @CSCOPE@
+CTAGS = @CTAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+ETAGS = @ETAGS@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+FILECMD = @FILECMD@
+FUZZER_LIBS = @FUZZER_LIBS@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_GENMARSHAL = @GLIB_GENMARSHAL@
+GLIB_LIBS = @GLIB_LIBS@
+GLIB_MKENUMS = @GLIB_MKENUMS@
+GOBJECT_QUERY = @GOBJECT_QUERY@
+GREP = @GREP@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
+LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
+LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBOTR_CFLAGS = @LIBOTR_CFLAGS@
+LIBOTR_LIBS = @LIBOTR_LIBS@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENSSL_CFLAGS = @OPENSSL_CFLAGS@
+OPENSSL_LIBS = @OPENSSL_LIBS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+OTR_CFLAGS = @OTR_CFLAGS@
+OTR_LDFLAGS = @OTR_LDFLAGS@
+OTR_LINK_FLAGS = @OTR_LINK_FLAGS@
+OTR_LINK_LIBS = @OTR_LINK_LIBS@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PERL_CFLAGS = @PERL_CFLAGS@
+PERL_EXTRA_OPTS = @PERL_EXTRA_OPTS@
+PERL_FE_LINK_LIBS = @PERL_FE_LINK_LIBS@
+PERL_LDFLAGS = @PERL_LDFLAGS@
+PERL_LINK_FLAGS = @PERL_LINK_FLAGS@
+PERL_LINK_LIBS = @PERL_LINK_LIBS@
+PERL_MM_OPT = @PERL_MM_OPT@
+PERL_MM_PARAMS = @PERL_MM_PARAMS@
+PERL_STATIC_LIBS = @PERL_STATIC_LIBS@
+PERL_USE_LIB = @PERL_USE_LIB@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PROG_LIBS = @PROG_LIBS@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+TEXTUI_LIBS = @TEXTUI_LIBS@
+VERSION = @VERSION@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+installed_test_metadir = @installed_test_metadir@
+installed_testdir = @installed_testdir@
+irc_MODULES = @irc_MODULES@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+otr_module_lib = @otr_module_lib@
+otr_static_lib = @otr_static_lib@
+pdfdir = @pdfdir@
+perl_module_fe_lib = @perl_module_fe_lib@
+perl_module_lib = @perl_module_lib@
+perl_static_fe_lib = @perl_static_fe_lib@
+perl_static_lib = @perl_static_lib@
+perlpath = @perlpath@
+pkgconfigdir = @pkgconfigdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sedpath = @sedpath@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+AM_CPPFLAGS = \
+ -I$(top_builddir) \
+ $(GLIB_CFLAGS)
+
+irssi_DEPENDENCIES = \
+ @COMMON_LIBS@ \
+ @PERL_LINK_LIBS@ \
+ @PERL_FE_LINK_LIBS@ \
+ @OTR_LINK_LIBS@
+
+irssi_LDFLAGS = -export-dynamic
+irssi_LDADD = \
+ @COMMON_LIBS@ \
+ @PERL_LINK_LIBS@ \
+ @PERL_FE_LINK_LIBS@ \
+ @OTR_LINK_LIBS@ \
+ @OTR_LINK_FLAGS@ \
+ @PERL_LINK_FLAGS@ \
+ @PROG_LIBS@ \
+ @TEXTUI_LIBS@
+
+terminfo_sources = \
+ term-terminfo.c \
+ terminfo-core.c
+
+use_term_sources = $(terminfo_sources)
+irssi_SOURCES = \
+ gui-entry.c \
+ gui-expandos.c \
+ gui-printtext.c \
+ gui-readline.c \
+ gui-windows.c \
+ lastlog.c \
+ mainwindows.c \
+ mainwindow-activity.c \
+ mainwindows-layout.c \
+ statusbar.c \
+ statusbar-config.c \
+ statusbar-items.c \
+ term.c \
+ $(use_term_sources) \
+ textbuffer.c \
+ textbuffer-commands.c \
+ textbuffer-view.c \
+ textbuffer-formats.c \
+ irssi.c \
+ module-formats.c
+
+pkginc_fe_textdir = $(pkgincludedir)/src/fe-text
+pkginc_fe_text_HEADERS = \
+ gui-printtext.h \
+ gui-windows.h \
+ mainwindows.h \
+ statusbar.h \
+ statusbar-item.h \
+ term.h \
+ textbuffer.h \
+ textbuffer-view.h \
+ textbuffer-formats.h
+
+noinst_HEADERS = \
+ gui-entry.h \
+ gui-readline.h \
+ statusbar-config.h \
+ terminfo-core.h \
+ module.h \
+ module-formats.h
+
+EXTRA_DIST = \
+ $(terminfo_sources) \
+ meson.build
+
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign src/fe-text/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign src/fe-text/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-binPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+ @list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+irssi$(EXEEXT): $(irssi_OBJECTS) $(irssi_DEPENDENCIES) $(EXTRA_irssi_DEPENDENCIES)
+ @rm -f irssi$(EXEEXT)
+ $(AM_V_CCLD)$(irssi_LINK) $(irssi_OBJECTS) $(irssi_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gui-entry.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gui-expandos.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gui-printtext.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gui-readline.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/gui-windows.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/irssi.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lastlog.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mainwindow-activity.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mainwindows-layout.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mainwindows.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/module-formats.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/statusbar-config.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/statusbar-items.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/statusbar.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/term-terminfo.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/term.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/terminfo-core.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/textbuffer-commands.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/textbuffer-formats.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/textbuffer-view.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/textbuffer.Po@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+install-pkginc_fe_textHEADERS: $(pkginc_fe_text_HEADERS)
+ @$(NORMAL_INSTALL)
+ @list='$(pkginc_fe_text_HEADERS)'; test -n "$(pkginc_fe_textdir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkginc_fe_textdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkginc_fe_textdir)" || exit 1; \
+ fi; \
+ for p in $$list; do \
+ if test -f "$$p"; then d=; else d="$(srcdir)/"; fi; \
+ echo "$$d$$p"; \
+ done | $(am__base_list) | \
+ while read files; do \
+ echo " $(INSTALL_HEADER) $$files '$(DESTDIR)$(pkginc_fe_textdir)'"; \
+ $(INSTALL_HEADER) $$files "$(DESTDIR)$(pkginc_fe_textdir)" || exit $$?; \
+ done
+
+uninstall-pkginc_fe_textHEADERS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkginc_fe_text_HEADERS)'; test -n "$(pkginc_fe_textdir)" || list=; \
+ files=`for p in $$list; do echo $$p; done | sed -e 's|^.*/||'`; \
+ dir='$(DESTDIR)$(pkginc_fe_textdir)'; $(am__uninstall_files_from_dir)
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS) $(HEADERS)
+installdirs:
+ for dir in "$(DESTDIR)$(bindir)" "$(DESTDIR)$(pkginc_fe_textdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/gui-entry.Po
+ -rm -f ./$(DEPDIR)/gui-expandos.Po
+ -rm -f ./$(DEPDIR)/gui-printtext.Po
+ -rm -f ./$(DEPDIR)/gui-readline.Po
+ -rm -f ./$(DEPDIR)/gui-windows.Po
+ -rm -f ./$(DEPDIR)/irssi.Po
+ -rm -f ./$(DEPDIR)/lastlog.Po
+ -rm -f ./$(DEPDIR)/mainwindow-activity.Po
+ -rm -f ./$(DEPDIR)/mainwindows-layout.Po
+ -rm -f ./$(DEPDIR)/mainwindows.Po
+ -rm -f ./$(DEPDIR)/module-formats.Po
+ -rm -f ./$(DEPDIR)/statusbar-config.Po
+ -rm -f ./$(DEPDIR)/statusbar-items.Po
+ -rm -f ./$(DEPDIR)/statusbar.Po
+ -rm -f ./$(DEPDIR)/term-terminfo.Po
+ -rm -f ./$(DEPDIR)/term.Po
+ -rm -f ./$(DEPDIR)/terminfo-core.Po
+ -rm -f ./$(DEPDIR)/textbuffer-commands.Po
+ -rm -f ./$(DEPDIR)/textbuffer-formats.Po
+ -rm -f ./$(DEPDIR)/textbuffer-view.Po
+ -rm -f ./$(DEPDIR)/textbuffer.Po
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am: install-pkginc_fe_textHEADERS
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ./$(DEPDIR)/gui-entry.Po
+ -rm -f ./$(DEPDIR)/gui-expandos.Po
+ -rm -f ./$(DEPDIR)/gui-printtext.Po
+ -rm -f ./$(DEPDIR)/gui-readline.Po
+ -rm -f ./$(DEPDIR)/gui-windows.Po
+ -rm -f ./$(DEPDIR)/irssi.Po
+ -rm -f ./$(DEPDIR)/lastlog.Po
+ -rm -f ./$(DEPDIR)/mainwindow-activity.Po
+ -rm -f ./$(DEPDIR)/mainwindows-layout.Po
+ -rm -f ./$(DEPDIR)/mainwindows.Po
+ -rm -f ./$(DEPDIR)/module-formats.Po
+ -rm -f ./$(DEPDIR)/statusbar-config.Po
+ -rm -f ./$(DEPDIR)/statusbar-items.Po
+ -rm -f ./$(DEPDIR)/statusbar.Po
+ -rm -f ./$(DEPDIR)/term-terminfo.Po
+ -rm -f ./$(DEPDIR)/term.Po
+ -rm -f ./$(DEPDIR)/terminfo-core.Po
+ -rm -f ./$(DEPDIR)/textbuffer-commands.Po
+ -rm -f ./$(DEPDIR)/textbuffer-formats.Po
+ -rm -f ./$(DEPDIR)/textbuffer-view.Po
+ -rm -f ./$(DEPDIR)/textbuffer.Po
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS uninstall-pkginc_fe_textHEADERS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-binPROGRAMS clean-generic clean-libtool cscopelist-am \
+ ctags ctags-am distclean distclean-compile distclean-generic \
+ distclean-libtool distclean-tags distdir dvi dvi-am html \
+ html-am info info-am install install-am install-binPROGRAMS \
+ install-data install-data-am install-dvi install-dvi-am \
+ install-exec install-exec-am install-html install-html-am \
+ install-info install-info-am install-man install-pdf \
+ install-pdf-am install-pkginc_fe_textHEADERS install-ps \
+ install-ps-am install-strip installcheck installcheck-am \
+ installdirs maintainer-clean maintainer-clean-generic \
+ mostlyclean mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool pdf pdf-am ps ps-am tags tags-am uninstall \
+ uninstall-am uninstall-binPROGRAMS \
+ uninstall-pkginc_fe_textHEADERS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/src/fe-text/gui-entry.c b/src/fe-text/gui-entry.c
new file mode 100644
index 0000000..5050c80
--- /dev/null
+++ b/src/fe-text/gui-entry.c
@@ -0,0 +1,1514 @@
+/*
+ gui-entry.c : irssi
+
+ Copyright (C) 1999 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/utf8.h>
+#include <irssi/src/fe-common/core/formats.h>
+
+#include <irssi/src/fe-text/gui-entry.h>
+#include <irssi/src/fe-text/gui-printtext.h>
+#include <irssi/src/fe-text/term.h>
+#include <irssi/src/core/recode.h>
+
+#undef i_toupper
+#undef i_tolower
+#undef i_isalnum
+
+#define KILL_RING_MAX 10
+
+static unichar i_toupper(unichar c)
+{
+ if (term_type == TERM_TYPE_UTF8)
+ return g_unichar_toupper(c);
+ return c <= 255 ? toupper(c) : c;
+}
+
+static unichar i_tolower(unichar c)
+{
+ if (term_type == TERM_TYPE_UTF8)
+ return g_unichar_tolower(c);
+ return c <= 255 ? tolower(c) : c;
+}
+
+static int i_isalnum(unichar c)
+{
+ if (term_type == TERM_TYPE_UTF8)
+ return (g_unichar_isalnum(c) || i_wcwidth(c) == 0);
+ return c <= 255 ? isalnum(c) : 0;
+}
+
+GUI_ENTRY_REC *active_entry;
+
+static void entry_text_grow(GUI_ENTRY_REC *entry, int grow_size)
+{
+ if (entry->text_len+grow_size < entry->text_alloc)
+ return;
+
+ entry->text_alloc = nearest_power(entry->text_alloc+grow_size);
+ entry->text = g_realloc(entry->text,
+ sizeof(unichar) * entry->text_alloc);
+
+ if (entry->uses_extents)
+ entry->extents = g_realloc(entry->extents,
+ sizeof(char *) * entry->text_alloc);
+}
+
+GUI_ENTRY_REC *gui_entry_create(int xpos, int ypos, int width, int utf8)
+{
+ GUI_ENTRY_REC *rec;
+
+ rec = g_new0(GUI_ENTRY_REC, 1);
+ rec->xpos = xpos;
+ rec->ypos = ypos;
+ rec->width = width;
+ rec->text_alloc = 1024;
+ rec->text = g_new(unichar, rec->text_alloc);
+ rec->extents = NULL;
+ rec->text[0] = '\0';
+ rec->utf8 = utf8;
+ return rec;
+}
+
+static void destroy_extents(GUI_ENTRY_REC *entry)
+{
+ if (entry->uses_extents) {
+ int i;
+ for (i = 0; i < entry->text_alloc; i++) {
+ if (entry->extents[i] != NULL) {
+ g_free(entry->extents[i]);
+ }
+ }
+ }
+ g_free(entry->extents);
+ entry->extents = NULL;
+ entry->uses_extents = FALSE;
+}
+
+void gui_entry_destroy(GUI_ENTRY_REC *entry)
+{
+ GSList *tmp;
+
+ g_return_if_fail(entry != NULL);
+
+ if (active_entry == entry)
+ gui_entry_set_active(NULL);
+
+ for (tmp = entry->kill_ring; tmp != NULL; tmp = tmp->next) {
+ GUI_ENTRY_CUTBUFFER_REC *rec = tmp->data;
+ if (rec != NULL) {
+ g_free(rec->cutbuffer);
+ g_free(rec);
+ }
+ }
+ g_slist_free(entry->kill_ring);
+
+ destroy_extents(entry);
+ g_free(entry->text);
+ g_free(entry->prompt);
+ g_free(entry);
+}
+
+/* big5 functions */
+#define big5_width(ch) ((ch)>0xff ? 2:1)
+
+void unichars_to_big5(const unichar *str, char *out)
+{
+ for (; *str != '\0'; str++) {
+ if (*str > 0xff)
+ *out++ = (*str >> 8) & 0xff;
+ *out++ = *str & 0xff;
+ }
+ *out = '\0';
+}
+
+int strlen_big5(const unsigned char *str)
+{
+ int len=0;
+
+ while (*str != '\0') {
+ if (is_big5(str[0], str[1]))
+ str++;
+ len++;
+ str++;
+ }
+ return len;
+}
+
+void unichars_to_big5_with_pos(const unichar *str, int spos, char *out, int *opos)
+{
+ const unichar *sstart = str;
+ char *ostart = out;
+
+ *opos = 0;
+ while(*str != '\0')
+ {
+ if(*str > 0xff)
+ *out ++ = (*str >> 8) & 0xff;
+ *out ++ = *str & 0xff;
+ str ++;
+ if(str - sstart == spos)
+ *opos = out - ostart;
+ }
+ *out = '\0';
+}
+
+void big5_to_unichars(const char *str, unichar *out)
+{
+ const unsigned char *p = (const unsigned char *) str;
+
+ while (*p != '\0') {
+ if (is_big5(p[0], p[1])) {
+ *out++ = p[0] << 8 | p[1];
+ p += 2;
+ } else {
+ *out++ = *p++;
+ }
+ }
+ *out = '\0';
+}
+
+/* Return screen length of plain string */
+static int scrlen_str(const char *str, int utf8)
+{
+ int len = 0;
+ char *stripped;
+ g_return_val_if_fail(str != NULL, 0);
+
+ stripped = strip_codes(str);
+ len = string_width(stripped, utf8 ? TREAT_STRING_AS_UTF8 : TREAT_STRING_AS_BYTES);
+ g_free(stripped);
+ return len;
+}
+
+/* ----------------------------- */
+
+static int pos2scrpos(GUI_ENTRY_REC *entry, int pos, int cursor)
+{
+ int i;
+ int xpos = 0;
+
+ if (!cursor && pos <= 0)
+ return 0;
+
+ if (entry->uses_extents && entry->extents[0] != NULL) {
+ xpos += scrlen_str(entry->extents[0], entry->utf8);
+ }
+
+ for (i = 0; i < entry->text_len && i < pos; i++) {
+ unichar c = entry->text[i];
+ const char *extent = entry->uses_extents ? entry->extents[i+1] : NULL;
+
+ if (term_type == TERM_TYPE_BIG5)
+ xpos += big5_width(c);
+ else if (entry->utf8)
+ xpos += unichar_isprint(c) ? i_wcwidth(c) : 1;
+ else
+ xpos++;
+
+ if (extent != NULL) {
+ xpos += scrlen_str(extent, entry->utf8);
+ }
+ }
+ return xpos + pos - i;
+}
+
+static int scrpos2pos(GUI_ENTRY_REC *entry, int pos)
+{
+ int i, width, xpos = 0;
+
+ if (entry->uses_extents && entry->extents[0] != NULL) {
+ xpos += scrlen_str(entry->extents[0], entry->utf8);
+ }
+
+ for (i = 0; i < entry->text_len && xpos < pos; i++) {
+ unichar c = entry->text[i];
+ const char *extent = entry->uses_extents ? entry->extents[i+1] : NULL;
+
+ if (term_type == TERM_TYPE_BIG5)
+ width = big5_width(c);
+ else if (entry->utf8)
+ width = unichar_isprint(c) ? i_wcwidth(c) : 1;
+ else
+ width = 1;
+
+ xpos += width;
+
+ if (extent != NULL) {
+ xpos += scrlen_str(extent, entry->utf8);
+ }
+ }
+ return i;
+}
+
+/* Fixes the cursor position in screen */
+static void gui_entry_fix_cursor(GUI_ENTRY_REC *entry)
+{
+ int old_scrstart;
+
+ /* assume prompt len == prompt scrlen */
+ int start = pos2scrpos(entry, entry->scrstart, FALSE);
+ int now = pos2scrpos(entry, entry->pos, TRUE);
+
+ old_scrstart = entry->scrstart;
+ if (now-start < entry->width - 2 - entry->promptlen && now-start > 0) {
+ entry->scrpos = now-start;
+ } else if (now < entry->width - 1 - entry->promptlen) {
+ entry->scrstart = 0;
+ entry->scrpos = now;
+ } else {
+ entry->scrstart = scrpos2pos(entry, now-(entry->width -
+ entry->promptlen)*2/3);
+ start = pos2scrpos(entry, entry->scrstart, FALSE);
+ entry->scrpos = now - start;
+ }
+
+ if (old_scrstart != entry->scrstart)
+ entry->redraw_needed_from = 0;
+}
+
+static char *text_effects_only(const char *p)
+{
+ GString *str;
+
+ str = g_string_sized_new(strlen(p));
+ for (; *p != '\0'; p++) {
+ if (*p == 4 && p[1] != '\0') {
+ if (p[1] >= FORMAT_STYLE_SPECIAL) {
+ g_string_append_len(str, p, 2);
+ p++;
+ continue;
+ }
+
+ /* irssi color */
+ if (p[2] != '\0') {
+#ifdef TERM_TRUECOLOR
+ if (p[1] == FORMAT_COLOR_24) {
+ if (p[3] == '\0') p += 2;
+ else if (p[4] == '\0') p += 3;
+ else if (p[5] == '\0') p += 4;
+ else {
+ g_string_append_len(str, p, 6);
+ p += 5;
+ }
+ } else {
+#endif /* TERM_TRUECOLOR */
+ g_string_append_len(str, p, 3);
+ p += 2;
+#ifdef TERM_TRUECOLOR
+ }
+#endif /* TERM_TRUECOLOR */
+ continue;
+ }
+ }
+ }
+
+ return g_string_free(str, FALSE);
+}
+
+static void gui_entry_draw_from(GUI_ENTRY_REC *entry, int pos)
+{
+ int i, start;
+ int start_xpos, xpos, new_xpos, end_xpos;
+ char *tmp;
+ GString *str;
+
+ start = entry->scrstart + pos;
+
+ start_xpos = xpos = entry->xpos + entry->promptlen +
+ pos2scrpos(entry, start, FALSE) -
+ pos2scrpos(entry, entry->scrstart, FALSE);
+ end_xpos = entry->xpos + entry->width;
+
+ if (xpos > end_xpos)
+ return;
+
+ str = g_string_sized_new(entry->text_alloc);
+
+ term_set_color(root_window, ATTR_RESET);
+ /* term_move(root_window, xpos, entry->ypos); */
+
+ if (entry->uses_extents && entry->extents[0] != NULL) {
+ g_string_append(str, entry->extents[0]);
+ }
+ for (i = 0; i < start && i < entry->text_len; i++) {
+ const char *extent = entry->uses_extents ? entry->extents[i+1] : NULL;
+ if (extent != NULL) {
+ g_string_append(str, extent);
+ }
+ }
+ if (i == 0) {
+ xpos += scrlen_str(str->str, entry->utf8);
+ } else {
+ tmp = text_effects_only(str->str);
+ g_string_assign(str, tmp);
+ g_free(tmp);
+ }
+
+ for (; i < entry->text_len; i++) {
+ unichar c = entry->text[i];
+ const char *extent = entry->uses_extents ? entry->extents[i+1] : NULL;
+ new_xpos = xpos;
+
+ if (entry->hidden)
+ new_xpos++;
+ else if (term_type == TERM_TYPE_BIG5)
+ new_xpos += big5_width(c);
+ else if (entry->utf8)
+ new_xpos += unichar_isprint(c) ? i_wcwidth(c) : 1;
+ else
+ new_xpos++;
+
+ if (new_xpos > end_xpos)
+ break;
+
+ if (entry->hidden) {
+ g_string_append_c(str, ' ');
+ } else if (unichar_isprint(c)) {
+ if (entry->utf8) {
+ g_string_append_unichar(str, c);
+ } else if (term_type == TERM_TYPE_BIG5) {
+ if(c > 0xff)
+ g_string_append_c(str, (c >> 8) & 0xff);
+ g_string_append_c(str, c & 0xff);
+ } else {
+ g_string_append_c(str, c);
+ }
+ } else {
+ g_string_append_c(str, 4);
+ g_string_append_c(str, FORMAT_STYLE_REVERSE);
+ g_string_append_c(str, (c & 127)+'A'-1);
+ g_string_append_c(str, 4);
+ g_string_append_c(str, FORMAT_STYLE_REVERSE);
+ }
+ xpos = new_xpos;
+
+ if (extent != NULL) {
+ new_xpos += scrlen_str(extent, entry->utf8);
+
+ if (new_xpos > end_xpos)
+ break;
+
+ g_string_append(str, extent);
+ xpos = new_xpos;
+ }
+ }
+
+ /* clear the rest of the input line */
+ if (xpos < end_xpos) {
+ if (end_xpos == term_width) {
+ g_string_append_c(str, 4);
+ g_string_append_c(str, FORMAT_STYLE_CLRTOEOL);
+ } else {
+ while (xpos < end_xpos) {
+ g_string_append_c(str, ' ');
+ xpos++;
+ }
+ }
+ }
+
+ gui_printtext_internal(start_xpos, entry->ypos, str->str);
+ g_string_free(str, TRUE);
+}
+
+static void gui_entry_draw(GUI_ENTRY_REC *entry)
+{
+ if (entry->redraw_needed_from >= 0) {
+ gui_entry_draw_from(entry, entry->redraw_needed_from);
+ entry->redraw_needed_from = -1;
+ }
+
+ term_move_cursor(entry->xpos + entry->scrpos + entry->promptlen,
+ entry->ypos);
+ term_refresh(NULL);
+}
+
+static void gui_entry_redraw_from(GUI_ENTRY_REC *entry, int pos)
+{
+ pos -= entry->scrstart;
+ if (pos < 0) pos = 0;
+
+ if (entry->redraw_needed_from == -1 ||
+ entry->redraw_needed_from > pos)
+ entry->redraw_needed_from = pos;
+}
+
+void gui_entry_move(GUI_ENTRY_REC *entry, int xpos, int ypos, int width)
+{
+ int old_width;
+
+ g_return_if_fail(entry != NULL);
+
+ if (entry->xpos != xpos || entry->ypos != ypos) {
+ /* position in screen changed - needs a full redraw */
+ entry->xpos = xpos;
+ entry->ypos = ypos;
+ entry->width = width;
+ gui_entry_redraw(entry);
+ return;
+ }
+
+ if (entry->width == width)
+ return; /* no changes */
+
+ if (width > entry->width) {
+ /* input line grew - need to draw text at the end */
+ old_width = width;
+ entry->width = width;
+ gui_entry_redraw_from(entry, old_width);
+ } else {
+ /* input line shrinked - make sure the cursor
+ is inside the input line */
+ entry->width = width;
+ if (entry->pos - entry->scrstart >
+ entry->width-2 - entry->promptlen) {
+ gui_entry_fix_cursor(entry);
+ }
+ }
+
+ gui_entry_draw(entry);
+}
+
+void gui_entry_set_active(GUI_ENTRY_REC *entry)
+{
+ active_entry = entry;
+
+ if (entry != NULL) {
+ term_move_cursor(entry->xpos + entry->scrpos +
+ entry->promptlen, entry->ypos);
+ term_refresh(NULL);
+ }
+}
+
+void gui_entry_set_prompt(GUI_ENTRY_REC *entry, const char *str)
+{
+ int oldlen;
+
+ g_return_if_fail(entry != NULL);
+
+ oldlen = entry->promptlen;
+ if (str != NULL) {
+ g_free_not_null(entry->prompt);
+ entry->prompt = g_strdup(str);
+ entry->promptlen = scrlen_str(str, entry->utf8);
+ }
+
+ if (entry->prompt != NULL)
+ gui_printtext_internal(entry->xpos, entry->ypos, entry->prompt);
+
+ if (entry->promptlen != oldlen) {
+ gui_entry_fix_cursor(entry);
+ gui_entry_draw(entry);
+ }
+}
+
+void gui_entry_set_hidden(GUI_ENTRY_REC *entry, int hidden)
+{
+ g_return_if_fail(entry != NULL);
+
+ entry->hidden = hidden;
+}
+
+void gui_entry_set_utf8(GUI_ENTRY_REC *entry, int utf8)
+{
+ g_return_if_fail(entry != NULL);
+
+ entry->utf8 = utf8;
+}
+
+void gui_entry_set_text(GUI_ENTRY_REC *entry, const char *str)
+{
+ g_return_if_fail(entry != NULL);
+ g_return_if_fail(str != NULL);
+
+ entry->text_len = 0;
+ entry->pos = 0;
+ entry->text[0] = '\0';
+ destroy_extents(entry);
+
+ gui_entry_insert_text(entry, str);
+}
+
+char *gui_entry_get_text(GUI_ENTRY_REC *entry)
+{
+ char *buf;
+ int i;
+
+ g_return_val_if_fail(entry != NULL, NULL);
+
+ if (entry->utf8)
+ buf = g_ucs4_to_utf8(entry->text, -1, NULL, NULL, NULL);
+ else {
+ buf = g_malloc(entry->text_len*6 + 1);
+ if (term_type == TERM_TYPE_BIG5)
+ unichars_to_big5(entry->text, buf);
+ else
+ for (i = 0; i <= entry->text_len; i++)
+ buf[i] = entry->text[i];
+ }
+ return buf;
+}
+
+char *gui_entry_get_text_and_pos(GUI_ENTRY_REC *entry, int *pos)
+{
+ char *buf;
+ int i;
+
+ g_return_val_if_fail(entry != NULL, NULL);
+
+ if (entry->utf8) {
+ buf = g_ucs4_to_utf8(entry->text, -1, NULL, NULL, NULL);
+ *pos = g_utf8_offset_to_pointer(buf, entry->pos) - buf;
+ } else {
+ buf = g_malloc(entry->text_len*6 + 1);
+ if(term_type==TERM_TYPE_BIG5)
+ unichars_to_big5_with_pos(entry->text, entry->pos, buf, pos);
+ else
+ {
+ for (i = 0; i <= entry->text_len; i++)
+ buf[i] = entry->text[i];
+ *pos = entry->pos;
+ }
+ }
+ return buf;
+}
+
+void gui_entry_insert_text(GUI_ENTRY_REC *entry, const char *str)
+{
+ unichar chr;
+ int i, len;
+ const char *ptr;
+
+ g_return_if_fail(entry != NULL);
+ g_return_if_fail(str != NULL);
+
+ gui_entry_redraw_from(entry, entry->pos);
+
+ if (entry->utf8) {
+ g_utf8_validate(str, -1, &ptr);
+ len = g_utf8_pointer_to_offset(str, ptr);
+ } else if (term_type == TERM_TYPE_BIG5)
+ len = strlen_big5((const unsigned char *)str);
+ else
+ len = strlen(str);
+ entry_text_grow(entry, len);
+
+ /* make space for the string */
+ memmove(entry->text + entry->pos + len, entry->text + entry->pos,
+ (entry->text_len-entry->pos + 1) * sizeof(unichar));
+
+ /* make space for the color */
+ if (entry->uses_extents) {
+ memmove(entry->extents + entry->pos + len + 1, entry->extents + entry->pos + 1,
+ (entry->text_len-entry->pos) * sizeof(char *));
+ for (i = 0; i < len; i++) {
+ entry->extents[entry->pos + i + 1] = NULL;
+ }
+ }
+
+ if (!entry->utf8) {
+ if (term_type == TERM_TYPE_BIG5) {
+ chr = entry->text[entry->pos + len];
+ big5_to_unichars(str, entry->text + entry->pos);
+ entry->text[entry->pos + len] = chr;
+ } else {
+ for (i = 0; i < len; i++)
+ entry->text[entry->pos + i] = str[i];
+ }
+ } else {
+ ptr = str;
+ for (i = 0; i < len; i++) {
+ entry->text[entry->pos + i] = g_utf8_get_char(ptr);
+ ptr = g_utf8_next_char(ptr);
+ }
+ }
+
+ entry->text_len += len;
+ entry->pos += len;
+
+ gui_entry_fix_cursor(entry);
+ gui_entry_draw(entry);
+}
+
+void gui_entry_insert_char(GUI_ENTRY_REC *entry, unichar chr)
+{
+ g_return_if_fail(entry != NULL);
+
+ if (chr == 0 || chr == 13 || chr == 10)
+ return; /* never insert NUL, CR or LF characters */
+
+ if (entry->utf8 && entry->pos == 0 && unichar_isprint(chr) && i_wcwidth(chr) == 0)
+ return;
+
+ gui_entry_redraw_from(entry, entry->pos);
+
+ entry_text_grow(entry, 1);
+
+ /* make space for the string */
+ memmove(entry->text + entry->pos + 1, entry->text + entry->pos,
+ (entry->text_len-entry->pos + 1) * sizeof(unichar));
+
+ if (entry->uses_extents) {
+ memmove(entry->extents + entry->pos + 1 + 1, entry->extents + entry->pos + 1,
+ (entry->text_len-entry->pos) * sizeof(char *));
+ entry->extents[entry->pos + 1] = NULL;
+ }
+
+ entry->text[entry->pos] = chr;
+ entry->text_len++;
+ entry->pos++;
+
+ gui_entry_fix_cursor(entry);
+ gui_entry_draw(entry);
+}
+
+char *gui_entry_get_cutbuffer(GUI_ENTRY_REC *entry)
+{
+ GUI_ENTRY_CUTBUFFER_REC *tmp;
+ char *buf;
+ int i;
+
+ g_return_val_if_fail(entry != NULL, NULL);
+
+ if (entry->kill_ring == NULL || entry->kill_ring->data == NULL)
+ return NULL;
+
+ tmp = entry->kill_ring->data;
+
+ if (tmp->cutbuffer == NULL)
+ return NULL;
+
+ if (entry->utf8)
+ buf = g_ucs4_to_utf8(tmp->cutbuffer, -1, NULL, NULL, NULL);
+ else {
+ buf = g_malloc(tmp->cutbuffer_len*6 + 1);
+ if (term_type == TERM_TYPE_BIG5)
+ unichars_to_big5(tmp->cutbuffer, buf);
+ else
+ for (i = 0; i <= tmp->cutbuffer_len; i++)
+ buf[i] = tmp->cutbuffer[i];
+ }
+ return buf;
+}
+
+char *gui_entry_get_next_cutbuffer(GUI_ENTRY_REC *entry)
+{
+ GUI_ENTRY_CUTBUFFER_REC *tmp;
+
+ g_return_val_if_fail(entry != NULL, NULL);
+
+ if (entry->kill_ring == NULL)
+ return NULL;
+
+ tmp = entry->kill_ring->data;
+
+ entry->kill_ring = g_slist_remove(entry->kill_ring, tmp);
+ entry->kill_ring = g_slist_append(entry->kill_ring, tmp);
+
+ return gui_entry_get_cutbuffer(entry);
+}
+
+void gui_entry_erase_to(GUI_ENTRY_REC *entry, int pos, CUTBUFFER_UPDATE_OP update_cutbuffer)
+{
+ int newpos, size = 0;
+
+ g_return_if_fail(entry != NULL);
+
+ for (newpos = gui_entry_get_pos(entry); newpos > pos; size++)
+ newpos = newpos - 1;
+ gui_entry_erase(entry, size, update_cutbuffer);
+}
+
+static GUI_ENTRY_CUTBUFFER_REC *get_cutbuffer_rec(GUI_ENTRY_REC *entry, CUTBUFFER_UPDATE_OP update_cutbuffer)
+{
+ GUI_ENTRY_CUTBUFFER_REC *tmp;
+
+ g_return_val_if_fail(entry != NULL, NULL);
+
+ if (entry->kill_ring == NULL) {
+ /* no kill ring exists */
+ entry->kill_ring = g_slist_prepend(entry->kill_ring, (void *)NULL);
+ } else {
+ tmp = entry->kill_ring->data;
+
+ if (tmp != NULL && tmp->cutbuffer_len > 0
+ && (!entry->previous_append_next_kill
+ || update_cutbuffer == CUTBUFFER_UPDATE_REPLACE)) {
+ /* a cutbuffer exists and should be replaced */
+ entry->kill_ring = g_slist_prepend(entry->kill_ring, (void *)NULL);
+ }
+ }
+
+ if (g_slist_length(entry->kill_ring) > KILL_RING_MAX) {
+ GUI_ENTRY_CUTBUFFER_REC *rec = g_slist_last(entry->kill_ring)->data;
+ entry->kill_ring = g_slist_remove(entry->kill_ring, rec);
+ if (rec != NULL) g_free(rec->cutbuffer);
+ g_free(rec);
+ }
+
+ if (entry->kill_ring->data == NULL) {
+ entry->kill_ring->data = g_new0(GUI_ENTRY_CUTBUFFER_REC, 1);
+ }
+
+ return entry->kill_ring->data;
+}
+
+void gui_entry_erase(GUI_ENTRY_REC *entry, int size, CUTBUFFER_UPDATE_OP update_cutbuffer)
+{
+ gboolean clear_enabled;
+ size_t i, w = 0;
+
+ g_return_if_fail(entry != NULL);
+ clear_enabled = settings_get_bool("empty_kill_clears_cutbuffer");
+
+ if (entry->pos < size || (size == 0 && !clear_enabled))
+ return;
+
+ if (update_cutbuffer != CUTBUFFER_UPDATE_NOOP) {
+ int cutbuffer_new_size;
+ unichar *tmpcutbuffer;
+ GUI_ENTRY_CUTBUFFER_REC *tmp = get_cutbuffer_rec(entry, update_cutbuffer);
+
+ if (tmp->cutbuffer_len == 0) {
+ update_cutbuffer = CUTBUFFER_UPDATE_REPLACE;
+ }
+
+ cutbuffer_new_size = tmp->cutbuffer_len + size;
+ tmpcutbuffer = tmp->cutbuffer;
+ entry->append_next_kill = TRUE;
+ switch (update_cutbuffer) {
+ case CUTBUFFER_UPDATE_APPEND:
+ tmp->cutbuffer = g_new(unichar, cutbuffer_new_size + 1);
+ memcpy(tmp->cutbuffer, tmpcutbuffer, tmp->cutbuffer_len * sizeof(unichar));
+ memcpy(tmp->cutbuffer + tmp->cutbuffer_len, entry->text + entry->pos - size,
+ size * sizeof(unichar));
+
+ tmp->cutbuffer_len = cutbuffer_new_size;
+ tmp->cutbuffer[cutbuffer_new_size] = '\0';
+ g_free(tmpcutbuffer);
+ break;
+
+ case CUTBUFFER_UPDATE_PREPEND:
+ tmp->cutbuffer = g_new(unichar, cutbuffer_new_size + 1);
+ memcpy(tmp->cutbuffer, entry->text + entry->pos - size,
+ size * sizeof(unichar));
+ memcpy(tmp->cutbuffer + size, tmpcutbuffer,
+ tmp->cutbuffer_len * sizeof(unichar));
+
+ tmp->cutbuffer_len = cutbuffer_new_size;
+ tmp->cutbuffer[cutbuffer_new_size] = '\0';
+ g_free(tmpcutbuffer);
+ break;
+
+ case CUTBUFFER_UPDATE_REPLACE:
+ /* put erased text to cutbuffer */
+ if (tmp->cutbuffer_len < size || tmp->cutbuffer == NULL) {
+ g_free(tmp->cutbuffer);
+ tmp->cutbuffer = g_new(unichar, size + 1);
+ }
+
+ tmp->cutbuffer_len = size;
+ tmp->cutbuffer[size] = '\0';
+ memcpy(tmp->cutbuffer, entry->text + entry->pos - size,
+ size * sizeof(unichar));
+ break;
+
+ case CUTBUFFER_UPDATE_NOOP:
+ /* cannot happen, handled in "if" */
+ break;
+ }
+ }
+
+ if (size == 0) {
+ /* we just wanted to clear the cutbuffer */
+ return;
+ }
+
+ if (entry->utf8)
+ while (entry->pos > size + w && i_wcwidth(entry->text[entry->pos - size - w]) == 0)
+ w++;
+
+ memmove(entry->text + entry->pos - size, entry->text + entry->pos,
+ (entry->text_len-entry->pos+1) * sizeof(unichar));
+
+ if (entry->uses_extents) {
+ for (i = entry->pos - size; i < entry->pos; i++) {
+ if (entry->extents[i+1] != NULL) {
+ g_free(entry->extents[i+1]);
+ }
+ }
+ memmove(entry->extents + entry->pos - size + 1, entry->extents + entry->pos + 1,
+ (entry->text_len - entry->pos) * sizeof(void *)); /* no null terminator here */
+ for (i = 0; i < size; i++) {
+ entry->extents[entry->text_len - i] = NULL;
+ }
+ if (entry->text_len == size && entry->extents[0] != NULL) {
+ g_free(entry->extents[0]);
+ entry->extents[0] = NULL;
+ }
+ }
+
+ entry->pos -= size;
+ entry->text_len -= size;
+
+ gui_entry_redraw_from(entry, entry->pos-w);
+ gui_entry_fix_cursor(entry);
+ gui_entry_draw(entry);
+}
+
+void gui_entry_erase_cell(GUI_ENTRY_REC *entry)
+{
+ int size = 1;
+
+ g_return_if_fail(entry != NULL);
+
+ if (entry->utf8)
+ while (entry->pos+size < entry->text_len &&
+ i_wcwidth(entry->text[entry->pos+size]) == 0) size++;
+
+ memmove(entry->text + entry->pos, entry->text + entry->pos + size,
+ (entry->text_len-entry->pos-size+1) * sizeof(unichar));
+
+ if (entry->uses_extents) {
+ int i;
+ for (i = 0; i < size; i++) {
+ g_free(entry->extents[entry->pos + i + 1]);
+ }
+ memmove(entry->extents + entry->pos + 1, entry->extents + entry->pos + size + 1,
+ (entry->text_len-entry->pos-size) * sizeof(char *));
+ for (i = 0; i < size; i++) {
+ entry->extents[entry->text_len - i] = NULL;
+ }
+ if (entry->text_len == size && entry->extents[0] != NULL) {
+ g_free(entry->extents[0]);
+ entry->extents[0] = NULL;
+ }
+ }
+
+ entry->text_len -= size;
+
+ gui_entry_redraw_from(entry, entry->pos);
+ gui_entry_fix_cursor(entry);
+ gui_entry_draw(entry);
+}
+
+void gui_entry_erase_word(GUI_ENTRY_REC *entry, int to_space, CUTBUFFER_UPDATE_OP cutbuffer_op)
+{
+ int to;
+
+ g_return_if_fail(entry != NULL);
+ if (entry->pos == 0)
+ return;
+
+ to = entry->pos - 1;
+
+ if (to_space) {
+ while (entry->text[to] == ' ' && to > 0)
+ to--;
+ while (entry->text[to] != ' ' && to > 0)
+ to--;
+ } else {
+ while (!i_isalnum(entry->text[to]) && to > 0)
+ to--;
+ while (i_isalnum(entry->text[to]) && to > 0)
+ to--;
+ }
+ if (to > 0) to++;
+
+ gui_entry_erase(entry, entry->pos-to, cutbuffer_op);
+}
+
+void gui_entry_erase_next_word(GUI_ENTRY_REC *entry, int to_space, CUTBUFFER_UPDATE_OP cutbuffer_op)
+{
+ int to, size;
+
+ g_return_if_fail(entry != NULL);
+ if (entry->pos == entry->text_len)
+ return;
+
+ to = entry->pos;
+ if (to_space) {
+ while (entry->text[to] == ' ' && to < entry->text_len)
+ to++;
+ while (entry->text[to] != ' ' && to < entry->text_len)
+ to++;
+ } else {
+ while (!i_isalnum(entry->text[to]) && to < entry->text_len)
+ to++;
+ while (i_isalnum(entry->text[to]) && to < entry->text_len)
+ to++;
+ }
+
+ size = to-entry->pos;
+ entry->pos = to;
+ gui_entry_erase(entry, size, cutbuffer_op);
+}
+
+void gui_entry_transpose_chars(GUI_ENTRY_REC *entry)
+{
+ unichar chr;
+ char *extent;
+
+ if (entry->pos == 0 || entry->text_len < 2)
+ return;
+
+ if (entry->pos == entry->text_len)
+ entry->pos--;
+
+ /* swap chars */
+ chr = entry->text[entry->pos];
+ entry->text[entry->pos] = entry->text[entry->pos-1];
+ entry->text[entry->pos-1] = chr;
+
+ if (entry->uses_extents) {
+ extent = entry->extents[entry->pos+1];
+ entry->extents[entry->pos+1] = entry->extents[entry->pos];
+ entry->extents[entry->pos] = extent;
+ }
+
+ entry->pos++;
+
+ gui_entry_redraw_from(entry, entry->pos-2);
+ gui_entry_fix_cursor(entry);
+ gui_entry_draw(entry);
+}
+
+void gui_entry_transpose_words(GUI_ENTRY_REC *entry)
+{
+ int spos1, epos1, spos2, epos2;
+
+ /* find last position */
+ epos2 = entry->pos;
+ while (epos2 < entry->text_len && !i_isalnum(entry->text[epos2]))
+ epos2++;
+ while (epos2 < entry->text_len && i_isalnum(entry->text[epos2]))
+ epos2++;
+
+ /* find other position */
+ spos2 = epos2;
+ while (spos2 > 0 && !i_isalnum(entry->text[spos2-1]))
+ spos2--;
+ while (spos2 > 0 && i_isalnum(entry->text[spos2-1]))
+ spos2--;
+
+ epos1 = spos2;
+ while (epos1 > 0 && !i_isalnum(entry->text[epos1-1]))
+ epos1--;
+
+ spos1 = epos1;
+ while (spos1 > 0 && i_isalnum(entry->text[spos1-1]))
+ spos1--;
+
+ /* do wordswap if any found */
+ if (spos1 < epos1 && epos1 < spos2 && spos2 < epos2) {
+ unichar *first, *sep, *second;
+ char **first_extent, **sep_extent, **second_extent;
+ int i;
+
+ first = (unichar *) g_malloc( (epos1 - spos1) * sizeof(unichar) );
+ sep = (unichar *) g_malloc( (spos2 - epos1) * sizeof(unichar) );
+ second = (unichar *) g_malloc( (epos2 - spos2) * sizeof(unichar) );
+
+ first_extent = (char **) g_malloc( (epos1 - spos1) * sizeof(char *) );
+ sep_extent = (char **) g_malloc( (spos2 - epos1) * sizeof(char *) );
+ second_extent = (char **) g_malloc( (epos2 - spos2) * sizeof(char *) );
+
+ for (i = spos1; i < epos1; i++) {
+ first[i-spos1] = entry->text[i];
+ if (entry->uses_extents)
+ first_extent[i-spos1] = entry->extents[i+1];
+ }
+ for (i = epos1; i < spos2; i++) {
+ sep[i-epos1] = entry->text[i];
+ if (entry->uses_extents)
+ sep_extent[i-epos1] = entry->extents[i+1];
+ }
+ for (i = spos2; i < epos2; i++) {
+ second[i-spos2] = entry->text[i];
+ if (entry->uses_extents)
+ second_extent[i-spos2] = entry->extents[i+1];
+ }
+
+ entry->pos = spos1;
+ for (i = 0; i < epos2-spos2; i++) {
+ entry->text[entry->pos] = second[i];
+ if (entry->uses_extents)
+ entry->extents[entry->pos+1] = second_extent[i];
+ entry->pos++;
+ }
+ for (i = 0; i < spos2-epos1; i++) {
+ entry->text[entry->pos] = sep[i];
+ if (entry->uses_extents)
+ entry->extents[entry->pos+1] = sep_extent[i];
+ entry->pos++;
+ }
+ for (i = 0; i < epos1-spos1; i++) {
+ entry->text[entry->pos] = first[i];
+ if (entry->uses_extents)
+ entry->extents[entry->pos+1] = first_extent[i];
+ entry->pos++;
+ }
+
+ g_free(first);
+ g_free(sep);
+ g_free(second);
+
+ g_free(first_extent);
+ g_free(sep_extent);
+ g_free(second_extent);
+ }
+
+ gui_entry_redraw_from(entry, spos1);
+ gui_entry_fix_cursor(entry);
+ gui_entry_draw(entry);
+}
+
+void gui_entry_capitalize_word(GUI_ENTRY_REC *entry)
+{
+ int pos = entry->pos;
+ while (pos < entry->text_len && !i_isalnum(entry->text[pos]))
+ pos++;
+
+ if (pos < entry->text_len) {
+ entry->text[pos] = i_toupper(entry->text[pos]);
+ pos++;
+ }
+
+ while (pos < entry->text_len && i_isalnum(entry->text[pos])) {
+ entry->text[pos] = i_tolower(entry->text[pos]);
+ pos++;
+ }
+
+ gui_entry_redraw_from(entry, entry->pos);
+ entry->pos = pos;
+ gui_entry_fix_cursor(entry);
+ gui_entry_draw(entry);
+}
+
+void gui_entry_downcase_word(GUI_ENTRY_REC *entry)
+{
+ int pos = entry->pos;
+ while (pos < entry->text_len && !i_isalnum(entry->text[pos]))
+ pos++;
+
+ while (pos < entry->text_len && i_isalnum(entry->text[pos])) {
+ entry->text[pos] = i_tolower(entry->text[pos]);
+ pos++;
+ }
+
+ gui_entry_redraw_from(entry, entry->pos);
+ entry->pos = pos;
+ gui_entry_fix_cursor(entry);
+ gui_entry_draw(entry);
+}
+
+void gui_entry_upcase_word(GUI_ENTRY_REC *entry)
+{
+ int pos = entry->pos;
+ while (pos < entry->text_len && !i_isalnum(entry->text[pos]))
+ pos++;
+
+ while (pos < entry->text_len && i_isalnum(entry->text[pos])) {
+ entry->text[pos] = i_toupper(entry->text[pos]);
+ pos++;
+ }
+
+ gui_entry_redraw_from(entry, entry->pos);
+ entry->pos = pos;
+ gui_entry_fix_cursor(entry);
+ gui_entry_draw(entry);
+}
+
+int gui_entry_get_pos(GUI_ENTRY_REC *entry)
+{
+ g_return_val_if_fail(entry != NULL, 0);
+
+ return entry->pos;
+}
+
+void gui_entry_set_pos(GUI_ENTRY_REC *entry, int pos)
+{
+ g_return_if_fail(entry != NULL);
+
+ if (pos >= 0 && pos <= entry->text_len)
+ entry->pos = pos;
+
+ gui_entry_fix_cursor(entry);
+ gui_entry_draw(entry);
+}
+
+void gui_entry_set_text_and_pos_bytes(GUI_ENTRY_REC *entry, const char *str, int pos_bytes)
+{
+ int pos, extents_alloc;
+ char **extents;
+ const char *ptr;
+
+ g_return_if_fail(entry != NULL);
+
+ extents = entry->extents;
+ extents_alloc = entry->text_alloc;
+ entry->extents = NULL;
+ entry->uses_extents = FALSE;
+
+ gui_entry_set_text(entry, str);
+
+ if (entry->utf8) {
+ g_utf8_validate(str, pos_bytes, &ptr);
+ pos = g_utf8_pointer_to_offset(str, ptr);
+ } else if (term_type == TERM_TYPE_BIG5)
+ pos = strlen_big5((const unsigned char *)str) - strlen_big5((const unsigned char *)(str + pos_bytes));
+ else
+ pos = pos_bytes;
+
+ if (extents != NULL) {
+ entry->uses_extents = TRUE;
+ entry->extents = extents;
+ if (extents_alloc < entry->text_alloc) {
+ int i;
+ entry->extents = g_realloc(entry->extents,
+ sizeof(char *) * entry->text_alloc);
+ for (i = extents_alloc; i < entry->text_alloc; i++) {
+ entry->extents[i] = NULL;
+ }
+ }
+ }
+ gui_entry_redraw_from(entry, 0);
+ gui_entry_set_pos(entry, pos);
+}
+
+void gui_entry_move_pos(GUI_ENTRY_REC *entry, int pos)
+{
+ g_return_if_fail(entry != NULL);
+
+ if (entry->pos + pos >= 0 && entry->pos + pos <= entry->text_len)
+ entry->pos += pos;
+
+ if (entry->utf8) {
+ int step = pos < 0 ? -1 : 1;
+ while(i_wcwidth(entry->text[entry->pos]) == 0 &&
+ entry->pos + step >= 0 && entry->pos + step <= entry->text_len)
+ entry->pos += step;
+ }
+
+ gui_entry_fix_cursor(entry);
+ gui_entry_draw(entry);
+}
+
+static void gui_entry_move_words_left(GUI_ENTRY_REC *entry, int count, int to_space)
+{
+ int pos;
+
+ pos = entry->pos;
+ while (count > 0 && pos > 0) {
+ if (to_space) {
+ while (pos > 0 && entry->text[pos-1] == ' ')
+ pos--;
+ while (pos > 0 && entry->text[pos-1] != ' ')
+ pos--;
+ } else {
+ while (pos > 0 && !i_isalnum(entry->text[pos-1]))
+ pos--;
+ while (pos > 0 && i_isalnum(entry->text[pos-1]))
+ pos--;
+ }
+ count--;
+ }
+
+ entry->pos = pos;
+}
+
+static void gui_entry_move_words_right(GUI_ENTRY_REC *entry, int count, int to_space)
+{
+ int pos;
+
+ pos = entry->pos;
+ while (count > 0 && pos < entry->text_len) {
+ if (to_space) {
+ while (pos < entry->text_len && entry->text[pos] == ' ')
+ pos++;
+ while (pos < entry->text_len && entry->text[pos] != ' ')
+ pos++;
+ } else {
+ while (pos < entry->text_len && !i_isalnum(entry->text[pos]))
+ pos++;
+ while (pos < entry->text_len && i_isalnum(entry->text[pos]))
+ pos++;
+ }
+ count--;
+ }
+
+ entry->pos = pos;
+}
+
+void gui_entry_move_words(GUI_ENTRY_REC *entry, int count, int to_space)
+{
+ g_return_if_fail(entry != NULL);
+
+ if (count < 0)
+ gui_entry_move_words_left(entry, -count, to_space);
+ else if (count > 0)
+ gui_entry_move_words_right(entry, count, to_space);
+
+ gui_entry_fix_cursor(entry);
+ gui_entry_draw(entry);
+}
+
+void gui_entry_redraw(GUI_ENTRY_REC *entry)
+{
+ g_return_if_fail(entry != NULL);
+
+ gui_entry_set_prompt(entry, NULL);
+ gui_entry_redraw_from(entry, 0);
+ gui_entry_fix_cursor(entry);
+ gui_entry_draw(entry);
+}
+
+static void gui_entry_alloc_extents(GUI_ENTRY_REC *entry)
+{
+ entry->uses_extents = TRUE;
+ entry->extents = g_new0(char *, entry->text_alloc);
+}
+
+void gui_entry_set_extent(GUI_ENTRY_REC *entry, int pos, const char *text)
+{
+ int update = FALSE;
+
+ g_return_if_fail(entry != NULL);
+
+ if (pos < 0 || pos > entry->text_len)
+ return;
+
+ if (text == NULL)
+ return;
+
+ if (!entry->uses_extents) {
+ gui_entry_alloc_extents(entry);
+ }
+
+ if (g_strcmp0(entry->extents[pos], text) != 0) {
+ g_free(entry->extents[pos]);
+ if (*text == '\0') {
+ entry->extents[pos] = NULL;
+ } else {
+ entry->extents[pos] = g_strdup(text);
+ }
+ update = TRUE;
+ }
+
+ if (update) {
+ gui_entry_redraw_from(entry, pos - 1);
+ gui_entry_fix_cursor(entry);
+ gui_entry_draw(entry);
+ }
+}
+
+void gui_entry_set_extents(GUI_ENTRY_REC *entry, int pos, int len, const char *left, const char *right)
+{
+ int end, update = FALSE;
+
+ g_return_if_fail(entry != NULL);
+
+ if (pos < 0 || len < 0 || pos > entry->text_len)
+ return;
+
+ end = pos + len;
+
+ if (end > entry->text_len)
+ end = entry->text_len;
+
+ if (!entry->uses_extents) {
+ gui_entry_alloc_extents(entry);
+ }
+
+ if (g_strcmp0(entry->extents[pos], left) != 0) {
+ g_free(entry->extents[pos]);
+ if (*left == '\0') {
+ entry->extents[pos] = NULL;
+ } else {
+ entry->extents[pos] = g_strdup(left);
+ }
+ update = TRUE;
+ }
+
+ if (pos != end && g_strcmp0(entry->extents[end], right) != 0) {
+ g_free(entry->extents[end]);
+ if (*right == '\0') {
+ entry->extents[end] = NULL;
+ } else {
+ entry->extents[end] = g_strdup(right);
+ }
+ update = TRUE;
+ }
+
+ if (update) {
+ gui_entry_redraw_from(entry, pos - 1);
+ gui_entry_fix_cursor(entry);
+ gui_entry_draw(entry);
+ }
+}
+
+void gui_entry_clear_extents(GUI_ENTRY_REC *entry, int pos, int len)
+{
+ int i, end, update = FALSE;
+
+ g_return_if_fail(entry != NULL);
+
+ if (pos < 0 || len < 0 || pos > entry->text_len)
+ return;
+
+ end = pos + len;
+
+ if (end > entry->text_len)
+ end = entry->text_len;
+
+ if (!entry->uses_extents) {
+ return;
+ }
+
+ for (i = pos; i <= end; i++) {
+ if (entry->extents[i] != NULL) {
+ g_free(entry->extents[i]);
+ entry->extents[i] = NULL;
+ update = TRUE;
+ }
+ }
+
+ if (update) {
+ gui_entry_redraw_from(entry, pos);
+ gui_entry_fix_cursor(entry);
+ gui_entry_draw(entry);
+ }
+}
+
+char *gui_entry_get_extent(GUI_ENTRY_REC *entry, int pos)
+{
+ g_return_val_if_fail(entry != NULL, NULL);
+
+ if (!entry->uses_extents)
+ return NULL;
+
+ if (pos < 0 || pos >= entry->text_len)
+ return NULL;
+
+ return entry->extents[pos];
+}
+
+#define POS_FLAG "%|"
+GSList *gui_entry_get_text_and_extents(GUI_ENTRY_REC *entry)
+{
+ GSList *list = NULL;
+ GString *str;
+ int i;
+
+ g_return_val_if_fail(entry != NULL, NULL);
+
+ if (entry->uses_extents && entry->extents[0] != NULL) {
+ if (entry->pos == 0) {
+ list = g_slist_prepend(list, g_strconcat(entry->extents[0], POS_FLAG, NULL));
+ } else {
+ list = g_slist_prepend(list, g_strdup(entry->extents[0]));
+ }
+ } else {
+ if (entry->pos == 0) {
+ list = g_slist_prepend(list, g_strdup(POS_FLAG));
+ } else {
+ list = g_slist_prepend(list, NULL);
+ }
+ }
+
+ str = g_string_sized_new(entry->text_alloc);
+ for (i = 0; i < entry->text_len; i++) {
+ if (entry->utf8) {
+ g_string_append_unichar(str, entry->text[i]);
+ } else if (term_type == TERM_TYPE_BIG5) {
+ if(entry->text[i] > 0xff)
+ g_string_append_c(str, (entry->text[i] >> 8) & 0xff);
+ g_string_append_c(str, entry->text[i] & 0xff);
+ } else {
+ g_string_append_c(str, entry->text[i]);
+ }
+ if (entry->pos == i+1 || (entry->uses_extents && entry->extents[i+1] != NULL)) {
+ list = g_slist_prepend(list, g_strdup(str->str));
+ g_string_truncate(str, 0);
+ if (entry->uses_extents && entry->extents[i+1] != NULL) {
+ if (entry->pos == i+1) {
+ list = g_slist_prepend(list, g_strconcat(entry->extents[i+1], POS_FLAG, NULL));
+ } else {
+ list = g_slist_prepend(list, g_strdup(entry->extents[i+1]));
+ }
+ } else if (entry->pos == i+1) {
+ list = g_slist_prepend(list, g_strdup(POS_FLAG));
+ }
+ }
+ }
+ if (str->len > 0) {
+ list = g_slist_prepend(list, g_strdup(str->str));
+ }
+ list = g_slist_reverse(list);
+ g_string_free(str, TRUE);
+
+ return list;
+}
+
+void gui_entry_set_text_and_extents(GUI_ENTRY_REC *entry, GSList *list)
+{
+ GSList *tmp;
+ int pos = -1;
+ int is_extent = 1;
+
+ gui_entry_set_text(entry, "");
+ for (tmp = list, is_extent = TRUE; tmp != NULL; tmp = tmp->next, is_extent ^= 1) {
+ if (is_extent) {
+ char *extent;
+ int len;
+
+ if (tmp->data == NULL)
+ continue;
+
+ extent = g_strdup(tmp->data);
+ len = strlen(extent);
+ if (len >= strlen(POS_FLAG) && g_strcmp0(&extent[len-strlen(POS_FLAG)], POS_FLAG) == 0) {
+ char *tmp;
+ tmp = extent;
+ extent = g_strndup(tmp, len - strlen(POS_FLAG));
+ g_free(tmp);
+ pos = entry->pos;
+ }
+
+ if (strlen(extent) > 0) {
+ gui_entry_set_extent(entry, entry->pos, extent);
+ }
+ g_free(extent);
+ } else {
+ gui_entry_insert_text(entry, tmp->data);
+ }
+ }
+ gui_entry_set_pos(entry, pos);
+}
+
+void gui_entry_init(void)
+{
+ settings_add_bool("lookandfeel", "empty_kill_clears_cutbuffer", FALSE);
+}
+
+void gui_entry_deinit(void)
+{
+}
diff --git a/src/fe-text/gui-entry.h b/src/fe-text/gui-entry.h
new file mode 100644
index 0000000..97dcee2
--- /dev/null
+++ b/src/fe-text/gui-entry.h
@@ -0,0 +1,92 @@
+#ifndef IRSSI_FE_TEXT_GUI_ENTRY_H
+#define IRSSI_FE_TEXT_GUI_ENTRY_H
+
+typedef struct {
+ int cutbuffer_len;
+ unichar *cutbuffer;
+} GUI_ENTRY_CUTBUFFER_REC;
+
+typedef struct {
+ int text_len, text_alloc; /* as shorts, not chars */
+ unichar *text;
+ char **extents;
+
+ GSList *kill_ring;
+
+ /* all as shorts, not chars */
+ int xpos, ypos, width; /* entry position in screen */
+ int pos, scrstart, scrpos; /* cursor position */
+ int hidden; /* print the chars as spaces in input line (useful for passwords) */
+
+ int promptlen;
+ char *prompt;
+
+ int redraw_needed_from;
+ unsigned int utf8:1;
+
+ unsigned int previous_append_next_kill:1;
+ unsigned int append_next_kill:1;
+ unsigned int yank_preceded:1;
+ unsigned int uses_extents:1;
+} GUI_ENTRY_REC;
+
+typedef enum {
+ CUTBUFFER_UPDATE_NOOP,
+ CUTBUFFER_UPDATE_REPLACE,
+ CUTBUFFER_UPDATE_APPEND,
+ CUTBUFFER_UPDATE_PREPEND
+} CUTBUFFER_UPDATE_OP;
+
+extern GUI_ENTRY_REC *active_entry;
+
+GUI_ENTRY_REC *gui_entry_create(int xpos, int ypos, int width, int utf8);
+void gui_entry_destroy(GUI_ENTRY_REC *entry);
+
+void gui_entry_move(GUI_ENTRY_REC *entry, int xpos, int ypos, int width);
+void gui_entry_set_active(GUI_ENTRY_REC *entry);
+
+void gui_entry_set_prompt(GUI_ENTRY_REC *entry, const char *str);
+void gui_entry_set_hidden(GUI_ENTRY_REC *entry, int hidden);
+void gui_entry_set_utf8(GUI_ENTRY_REC *entry, int utf8);
+
+void gui_entry_set_text(GUI_ENTRY_REC *entry, const char *str);
+char *gui_entry_get_text(GUI_ENTRY_REC *entry);
+char *gui_entry_get_text_and_pos(GUI_ENTRY_REC *entry, int *pos);
+void gui_entry_set_text_and_pos_bytes(GUI_ENTRY_REC *entry, const char *str, int pos_bytes);
+
+void gui_entry_insert_text(GUI_ENTRY_REC *entry, const char *str);
+void gui_entry_insert_char(GUI_ENTRY_REC *entry, unichar chr);
+
+char *gui_entry_get_cutbuffer(GUI_ENTRY_REC *entry);
+char *gui_entry_get_next_cutbuffer(GUI_ENTRY_REC *entry);
+void gui_entry_erase_to(GUI_ENTRY_REC *entry, int pos, CUTBUFFER_UPDATE_OP update_cutbuffer);
+void gui_entry_erase(GUI_ENTRY_REC *entry, int size, CUTBUFFER_UPDATE_OP update_cutbuffer);
+void gui_entry_erase_cell(GUI_ENTRY_REC *entry);
+void gui_entry_erase_word(GUI_ENTRY_REC *entry, int to_space, CUTBUFFER_UPDATE_OP cutbuffer_op);
+void gui_entry_erase_next_word(GUI_ENTRY_REC *entry, int to_space, CUTBUFFER_UPDATE_OP cutbuffer_op);
+
+void gui_entry_transpose_chars(GUI_ENTRY_REC *entry);
+void gui_entry_transpose_words(GUI_ENTRY_REC *entry);
+
+void gui_entry_capitalize_word(GUI_ENTRY_REC *entry);
+void gui_entry_downcase_word(GUI_ENTRY_REC *entry);
+void gui_entry_upcase_word(GUI_ENTRY_REC *entry);
+
+int gui_entry_get_pos(GUI_ENTRY_REC *entry);
+void gui_entry_set_pos(GUI_ENTRY_REC *entry, int pos);
+void gui_entry_move_pos(GUI_ENTRY_REC *entry, int pos);
+void gui_entry_move_words(GUI_ENTRY_REC *entry, int count, int to_space);
+
+void gui_entry_redraw(GUI_ENTRY_REC *entry);
+
+void gui_entry_set_extent(GUI_ENTRY_REC *entry, int pos, const char *text);
+void gui_entry_set_extents(GUI_ENTRY_REC *entry, int pos, int len, const char *left, const char *right);
+void gui_entry_clear_extents(GUI_ENTRY_REC *entry, int pos, int len);
+char *gui_entry_get_extent(GUI_ENTRY_REC *entry, int pos);
+GSList *gui_entry_get_text_and_extents(GUI_ENTRY_REC *entry);
+void gui_entry_set_text_and_extents(GUI_ENTRY_REC *entry, GSList *list);
+
+void gui_entry_init(void);
+void gui_entry_deinit(void);
+
+#endif
diff --git a/src/fe-text/gui-expandos.c b/src/fe-text/gui-expandos.c
new file mode 100644
index 0000000..a6b1141
--- /dev/null
+++ b/src/fe-text/gui-expandos.c
@@ -0,0 +1,63 @@
+/*
+ gui-expandos.c : irssi
+
+ Copyright (C) 2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/expandos.h>
+
+#include <irssi/src/fe-text/gui-entry.h>
+#include <irssi/src/fe-text/gui-readline.h>
+
+/* idle time */
+static char *expando_idletime(SERVER_REC *server, void *item, int *free_ret)
+{
+ int diff;
+
+ *free_ret = TRUE;
+ diff = (int) (time(NULL) - get_idle_time());
+ return g_strdup_printf("%d", diff);
+}
+
+/* current contents of the input line */
+static char *expando_inputline(SERVER_REC *server, void *item, int *free_ret)
+{
+ *free_ret = TRUE;
+ return gui_entry_get_text(active_entry);
+}
+
+/* value of cutbuffer */
+static char *expando_cutbuffer(SERVER_REC *server, void *item, int *free_ret)
+{
+ *free_ret = TRUE;
+ return gui_entry_get_cutbuffer(active_entry);
+}
+
+void gui_expandos_init(void)
+{
+ expando_create("E", expando_idletime, NULL);
+ expando_create("L", expando_inputline, NULL);
+ expando_create("U", expando_cutbuffer, NULL);
+}
+
+void gui_expandos_deinit(void)
+{
+ expando_destroy("E", expando_idletime);
+ expando_destroy("L", expando_inputline);
+ expando_destroy("U", expando_cutbuffer);
+}
diff --git a/src/fe-text/gui-printtext.c b/src/fe-text/gui-printtext.c
new file mode 100644
index 0000000..d317456
--- /dev/null
+++ b/src/fe-text/gui-printtext.c
@@ -0,0 +1,429 @@
+/*
+ gui-printtext.c : irssi
+
+ Copyright (C) 1999 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/signals.h>
+
+#include <irssi/src/fe-common/core/formats.h>
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/fe-common/core/themes.h>
+
+#include <irssi/src/fe-text/term.h>
+#include <irssi/src/fe-text/gui-printtext.h>
+#include <irssi/src/fe-text/gui-windows.h>
+
+/* Terminal indexed colour map */
+int mirc_colors[] = { 15, 0, 1, 2, 12, 4, 5, 6, 14, 10, 3, 11, 9, 13, 8, 7,
+ /* 16-27 */ 52, 94, 100, 58, 22, 29, 23, 24, 17, 54, 53, 89,
+ /* 28-39 */ 88, 130, 142, 64, 28, 35, 30, 25, 18, 91, 90, 125,
+ /* 40-51 */ 124, 166, 184, 106, 34, 49, 37, 33, 19, 129, 127, 161,
+ /* 52-63 */ 196, 208, 226, 154, 46, 86, 51, 75, 21, 171, 201, 198,
+ /* 64-75 */ 203, 215, 227, 191, 83, 122, 87, 111, 63, 177, 207, 205,
+ /* 76-87 */ 217, 223, 229, 193, 157, 158, 159, 153, 147, 183, 219, 212,
+ /* 88-98 */ 16, 233, 235, 237, 239, 241, 244, 247, 250, 254, 231, -1 };
+
+/* RGB colour map */
+int mirc_colors24[] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ /* 16-27 */ 0x470000, 0x472100, 0x474700, 0x324700, 0x004700, 0x00472c, 0x004747, 0x002747, 0x000047, 0x2e0047, 0x470047, 0x47002a,
+ /* 28-39 */ 0x740000, 0x743a00, 0x747400, 0x517400, 0x007400, 0x007449, 0x007474, 0x004074, 0x000074, 0x4b0074, 0x740074, 0x740045,
+ /* 40-51 */ 0xb50000, 0xb56300, 0xb5b500, 0x7db500, 0x00b500, 0x00b571, 0x00b5b5, 0x0063b5, 0x0000b5, 0x7500b5, 0xb500b5, 0xb5006b,
+ /* 52-63 */ 0xff0000, 0xff8c00, 0xffff00, 0xb2ff00, 0x00ff00, 0x00ffa0, 0x00ffff, 0x008cff, 0x0000ff, 0xa500ff, 0xff00ff, 0xff0098,
+ /* 64-75 */ 0xff5959, 0xffb459, 0xffff71, 0xcfff60, 0x6fff6f, 0x65ffc9, 0x6dffff, 0x59b4ff, 0x5959ff, 0xc459ff, 0xff66ff, 0xff59bc,
+ /* 76-87 */ 0xff9c9c, 0xffd39c, 0xffff9c, 0xe2ff9c, 0x9cff9c, 0x9cffdb, 0x9cffff, 0x9cd3ff, 0x9c9cff, 0xdc9cff, 0xff9cff, 0xff94d3,
+ /* 88-98 */ 0x000000, 0x131313, 0x282828, 0x363636, 0x4d4d4d, 0x656565, 0x818181, 0x9f9f9f, 0xbcbcbc, 0xe2e2e2, 0xffffff, -1 };
+
+static int scrollback_lines, scrollback_time, scrollback_burst_remove;
+/*
+ * If positive, remove lines older than scrollback_max_age seconds.
+ * Deletion is triggered by the "gui print text finished" signal (i.e. a message
+ * for the window). Note: "Day changed to" message also cause messages to be
+ * deleted within 1 day latency.
+ */
+static int scrollback_max_age;
+
+static int next_xpos, next_ypos;
+
+static GHashTable *indent_functions;
+static INDENT_FUNC default_indent_func;
+
+void gui_register_indent_func(const char *name, INDENT_FUNC func)
+{
+ gpointer key, value;
+ GSList *list;
+
+ if (g_hash_table_lookup_extended(indent_functions, name, &key, &value)) {
+ list = value;
+ g_hash_table_remove(indent_functions, key);
+ } else {
+ key = g_strdup(name);
+ list = NULL;
+ }
+
+ list = g_slist_append(list, (void *) func);
+ g_hash_table_insert(indent_functions, key, list);
+}
+
+void gui_unregister_indent_func(const char *name, INDENT_FUNC func)
+{
+ gpointer key, value;
+ GSList *list;
+
+ if (g_hash_table_lookup_extended(indent_functions, name, &key, &value)) {
+ list = value;
+
+ list = g_slist_remove(list, (void *) func);
+ g_hash_table_remove(indent_functions, key);
+ if (list == NULL)
+ g_free(key);
+ else
+ g_hash_table_insert(indent_functions, key, list);
+ }
+
+ if (default_indent_func == func)
+ gui_set_default_indent(NULL);
+
+ textbuffer_views_unregister_indent_func(func);
+}
+
+void gui_set_default_indent(const char *name)
+{
+ GSList *list;
+
+ list = name == NULL ? NULL :
+ g_hash_table_lookup(indent_functions, name);
+ default_indent_func = list == NULL ? NULL :
+ (INDENT_FUNC) list->data;
+ gui_windows_reset_settings();
+}
+
+INDENT_FUNC get_default_indent_func(void)
+{
+ return default_indent_func;
+}
+
+void gui_printtext(int xpos, int ypos, const char *str)
+{
+ next_xpos = xpos;
+ next_ypos = ypos;
+
+ printtext_gui(str);
+
+ next_xpos = next_ypos = -1;
+}
+
+void gui_printtext_internal(int xpos, int ypos, const char *str)
+{
+ next_xpos = xpos;
+ next_ypos = ypos;
+
+ printtext_gui_internal(str);
+
+ next_xpos = next_ypos = -1;
+}
+
+static void view_add_eol(TEXT_BUFFER_VIEW_REC *view, LINE_REC **line);
+
+void gui_printtext_after_time(TEXT_DEST_REC *dest, LINE_REC *prev, const char *str, time_t time)
+{
+ GUI_WINDOW_REC *gui;
+
+ gui = WINDOW_GUI(dest->window);
+
+ if (prev == NULL && !gui->view->buffer->last_eol) {
+ /* we have an unfinished line in the buffer still */
+ view_add_eol(gui->view, &gui->insert_after);
+ }
+
+ gui->use_insert_after = TRUE;
+ gui->insert_after = prev;
+ gui->insert_after_time = time;
+ format_send_to_gui(dest, str);
+ gui->use_insert_after = FALSE;
+ signal_emit("gui print text after finished", 4, dest->window, gui->insert_after, prev,
+ dest);
+}
+
+void gui_printtext_after(TEXT_DEST_REC *dest, LINE_REC *prev, const char *str)
+{
+ gui_printtext_after_time(dest, prev, str, 0);
+}
+
+void gui_printtext_window_border(int x, int y)
+{
+ char *v0, *v1;
+ int len;
+ if (current_theme != NULL) {
+ v1 = theme_format_expand(current_theme, "{window_border} ");
+ len = format_real_length(v1, 1);
+ v1[len] = '\0';
+ }
+ else {
+ v1 = g_strdup(" ");
+ }
+
+ if (*v1 == '\0') {
+ g_free(v1);
+ v1 = g_strdup(" ");
+ }
+
+ if (clrtoeol_info->color != NULL) {
+ char *color = g_strdup(clrtoeol_info->color);
+ len = format_real_length(color, 0);
+ color[len] = '\0';
+ v0 = g_strconcat(color, v1, NULL);
+ g_free(color);
+ g_free(v1);
+ } else {
+ v0 = v1;
+ }
+
+ gui_printtext(x, y, v0);
+ g_free(v0);
+}
+
+static void remove_old_lines(TEXT_BUFFER_VIEW_REC *view)
+{
+ LINE_REC *line;
+ time_t cur_time = time(NULL);
+ time_t old_time;
+
+ old_time = cur_time - scrollback_time + 1;
+ if (view->buffer->lines_count >=
+ scrollback_lines+scrollback_burst_remove) {
+ /* remove lines by line count */
+ while (view->buffer->lines_count > scrollback_lines) {
+ line = view->buffer->first_line;
+ if (line->info.time >= old_time ||
+ scrollback_lines == 0) {
+ /* too new line, don't remove yet - also
+ if scrollback_lines is 0, we want to check
+ only scrollback_time setting. */
+ break;
+ }
+ textbuffer_view_remove_line(view, line);
+ }
+ }
+
+ if (scrollback_max_age > 0) {
+ old_time = cur_time - scrollback_max_age;
+ while (view->buffer->lines_count > 0) {
+ line = view->buffer->first_line;
+ if (line->info.time >= old_time) {
+ /*
+ * The first line is newer than the threshold
+ * time -> no need to remove more lines.
+ */
+ break;
+ }
+ textbuffer_view_remove_line(view, line);
+ }
+ }
+}
+
+void gui_printtext_get_colors(int *flags, int *fg, int *bg, int *attr)
+{
+ *attr = 0;
+ if (*flags & GUI_PRINT_FLAG_MIRC_COLOR) {
+ /* mirc colors - extended colours proposal */
+ gboolean use_24_map = FALSE;
+#ifdef TERM_TRUECOLOR
+ use_24_map = settings_get_bool("colors_ansi_24bit");
+#endif
+ if (*bg >= 0) {
+ if (use_24_map && mirc_colors24[*bg % 100] != -1) {
+ *bg = mirc_colors24[*bg % 100];
+ *flags |= GUI_PRINT_FLAG_COLOR_24_BG;
+ } else {
+ *bg = mirc_colors[*bg % 100];
+ *flags &= ~GUI_PRINT_FLAG_COLOR_24_BG;
+ /* ignore mirc color 99 = -1 (reset) */
+ if (*bg != -1 && settings_get_bool("mirc_blink_fix")) {
+ if (*bg < 16) /* ansi bit flip :-( */
+ *bg = (*bg&8) | (*bg&4)>>2 | (*bg&2) | (*bg&1)<<2;
+ *bg = term_color256map[*bg&0xff] & 7;
+ }
+ }
+ }
+ if (*fg >= 0) {
+ if (use_24_map && mirc_colors24[*fg % 100] != -1) {
+ *fg = mirc_colors24[*fg % 100];
+ *flags |= GUI_PRINT_FLAG_COLOR_24_FG;
+ } else {
+ *fg = mirc_colors[*fg % 100];
+ *flags &= ~GUI_PRINT_FLAG_COLOR_24_FG;
+ }
+ }
+ }
+
+ if (*flags & GUI_PRINT_FLAG_COLOR_24_FG)
+ *attr |= ATTR_FGCOLOR24;
+ else if (*fg < 0 || *fg > 255) {
+ *fg = -1;
+ *attr |= ATTR_RESETFG;
+ }
+ else
+ *attr |= *fg;
+
+ if (*flags & GUI_PRINT_FLAG_COLOR_24_BG)
+ *attr |= ATTR_BGCOLOR24;
+ else if (*bg < 0 || *bg > 255) {
+ *bg = -1;
+ *attr |= ATTR_RESETBG;
+ }
+ else
+ *attr |= (*bg << BG_SHIFT);
+
+ if (*flags & GUI_PRINT_FLAG_REVERSE) *attr |= ATTR_REVERSE;
+ if (*flags & GUI_PRINT_FLAG_ITALIC) *attr |= ATTR_ITALIC;
+ if (*flags & GUI_PRINT_FLAG_BOLD) *attr |= ATTR_BOLD;
+ if (*flags & GUI_PRINT_FLAG_UNDERLINE) *attr |= ATTR_UNDERLINE;
+ if (*flags & GUI_PRINT_FLAG_BLINK) *attr |= ATTR_BLINK;
+}
+
+static void view_add_eol(TEXT_BUFFER_VIEW_REC *view, LINE_REC **line)
+{
+ static const unsigned char eol[] = { 0, LINE_CMD_EOL };
+
+ *line = textbuffer_insert(view->buffer, *line, eol, 2, NULL);
+ textbuffer_view_insert_line(view, *line);
+}
+
+static void print_text_no_window(int flags, int fg, int bg, int attr, const char *str)
+{
+ g_return_if_fail(next_xpos != -1);
+
+ term_set_color2(root_window, attr, fg, bg);
+
+ term_move(root_window, next_xpos, next_ypos);
+ if (flags & GUI_PRINT_FLAG_CLRTOEOL) {
+ if (clrtoeol_info->window != NULL) {
+ term_window_clrtoeol_abs(clrtoeol_info->window, next_ypos);
+ } else {
+ term_clrtoeol(root_window);
+ }
+ }
+ next_xpos += term_addstr(root_window, str);
+}
+
+static void sig_gui_print_text(WINDOW_REC *window, void *fgcolor,
+ void *bgcolor, void *pflags,
+ const char *str, TEXT_DEST_REC *dest)
+{
+ GUI_WINDOW_REC *gui;
+ TEXT_BUFFER_VIEW_REC *view;
+ LINE_REC *insert_after;
+ LINE_INFO_REC lineinfo = { 0 };
+ int fg, bg, flags, attr;
+
+ flags = GPOINTER_TO_INT(pflags);
+ fg = GPOINTER_TO_INT(fgcolor);
+ bg = GPOINTER_TO_INT(bgcolor);
+ gui_printtext_get_colors(&flags, &fg, &bg, &attr);
+
+ if (window == NULL) {
+ print_text_no_window(flags, fg, bg, attr, str);
+ return;
+ }
+
+ if (dest != NULL && dest->flags & PRINT_FLAG_FORMAT) {
+ return;
+ }
+
+ gui = WINDOW_GUI(window);
+ view = gui->view;
+
+ lineinfo.level = dest == NULL ? 0 : dest->level;
+ lineinfo.time =
+ (gui->use_insert_after && gui->insert_after_time) ? gui->insert_after_time : time(NULL);
+ lineinfo.format =
+ dest != NULL && dest->flags & PRINT_FLAG_FORMAT ? LINE_INFO_FORMAT_SET : NULL;
+
+ insert_after = gui->use_insert_after ?
+ gui->insert_after : view->buffer->cur_line;
+
+ if (flags & GUI_PRINT_FLAG_NEWLINE) {
+ view_add_eol(view, &insert_after);
+ }
+ textbuffer_line_add_colors(view->buffer, &insert_after, fg, bg, flags);
+
+ /* for historical reasons, the \n will set
+ GUI_PRINT_FLAG_NEWLINE and print an empty string. in this
+ special case, ignore the empty string which would otherwise
+ start another new line */
+ if (~flags & GUI_PRINT_FLAG_NEWLINE || *str != '\0') {
+ insert_after = textbuffer_insert(view->buffer, insert_after, (unsigned char *) str,
+ strlen(str), &lineinfo);
+ }
+
+ if (gui->use_insert_after)
+ gui->insert_after = insert_after;
+}
+
+static void sig_gui_printtext_finished(WINDOW_REC *window, TEXT_DEST_REC *dest)
+{
+ TEXT_BUFFER_VIEW_REC *view;
+ LINE_REC *insert_after;
+
+ view = WINDOW_GUI(window)->view;
+ insert_after = WINDOW_GUI(window)->use_insert_after ?
+ WINDOW_GUI(window)->insert_after : view->buffer->cur_line;
+
+ if (insert_after != NULL)
+ view_add_eol(view, &insert_after);
+ remove_old_lines(view);
+}
+
+static void read_settings(void)
+{
+ scrollback_lines = settings_get_int("scrollback_lines");
+ scrollback_time = settings_get_time("scrollback_time")/1000;
+ scrollback_max_age = settings_get_time("scrollback_max_age")/1000;
+ scrollback_burst_remove = settings_get_int("scrollback_burst_remove");
+}
+
+void gui_printtext_init(void)
+{
+ next_xpos = next_ypos = -1;
+ default_indent_func = NULL;
+ indent_functions = g_hash_table_new((GHashFunc) g_str_hash,
+ (GCompareFunc) g_str_equal);
+
+ settings_add_int("history", "scrollback_lines", 500);
+ settings_add_time("history", "scrollback_time", "1day");
+ settings_add_time("history", "scrollback_max_age", "0");
+ settings_add_int("history", "scrollback_burst_remove", 10);
+
+ signal_add("gui print text", (SIGNAL_FUNC) sig_gui_print_text);
+ signal_add("gui print text finished", (SIGNAL_FUNC) sig_gui_printtext_finished);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+
+ read_settings();
+}
+
+void gui_printtext_deinit(void)
+{
+ g_hash_table_destroy(indent_functions);
+
+ signal_remove("gui print text", (SIGNAL_FUNC) sig_gui_print_text);
+ signal_remove("gui print text finished", (SIGNAL_FUNC) sig_gui_printtext_finished);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
diff --git a/src/fe-text/gui-printtext.h b/src/fe-text/gui-printtext.h
new file mode 100644
index 0000000..0acf4d3
--- /dev/null
+++ b/src/fe-text/gui-printtext.h
@@ -0,0 +1,26 @@
+#ifndef IRSSI_FE_TEXT_GUI_PRINTTEXT_H
+#define IRSSI_FE_TEXT_GUI_PRINTTEXT_H
+
+#include <irssi/src/fe-text/gui-windows.h>
+#include <irssi/src/fe-text/textbuffer-view.h>
+#include <irssi/src/fe-common/core/formats.h>
+
+extern int mirc_colors[];
+
+void gui_printtext_init(void);
+void gui_printtext_deinit(void);
+
+void gui_register_indent_func(const char *name, INDENT_FUNC func);
+void gui_unregister_indent_func(const char *name, INDENT_FUNC func);
+
+void gui_set_default_indent(const char *name);
+INDENT_FUNC get_default_indent_func(void);
+
+void gui_printtext(int xpos, int ypos, const char *str);
+void gui_printtext_internal(int xpos, int ypos, const char *str);
+void gui_printtext_after(TEXT_DEST_REC *dest, LINE_REC *prev, const char *str);
+void gui_printtext_after_time(TEXT_DEST_REC *dest, LINE_REC *prev, const char *str, time_t time);
+void gui_printtext_window_border(int xpos, int ypos);
+void gui_printtext_get_colors(int *flags, int *fg, int *bg, int *attr);
+
+#endif
diff --git a/src/fe-text/gui-readline.c b/src/fe-text/gui-readline.c
new file mode 100644
index 0000000..a53e415
--- /dev/null
+++ b/src/fe-text/gui-readline.c
@@ -0,0 +1,1626 @@
+/*
+ gui-readline.c : irssi
+
+ Copyright (C) 1999 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-text/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/special-vars.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/servers.h>
+
+#include <irssi/src/fe-common/core/completion.h>
+#include <irssi/src/fe-common/core/command-history.h>
+#include <irssi/src/fe-common/core/keyboard.h>
+#include <irssi/src/fe-common/core/printtext.h>
+
+#include <irssi/src/fe-text/term.h>
+#include <irssi/src/fe-text/gui-entry.h>
+#include <irssi/src/fe-text/gui-windows.h>
+#include <irssi/src/core/utf8.h>
+
+#include <string.h>
+#include <signal.h>
+
+/* After LINE_SPLIT_LIMIT characters, the message will be split into multiple lines */
+#define LINE_SPLIT_LIMIT 400
+
+typedef void (*ENTRY_REDIRECT_KEY_FUNC) (int key, void *data, SERVER_REC *server, WI_ITEM_REC *item);
+typedef void (*ENTRY_REDIRECT_ENTRY_FUNC) (const char *line, void *data, SERVER_REC *server, WI_ITEM_REC *item);
+
+typedef struct {
+ SIGNAL_FUNC func;
+ int flags;
+ void *data;
+} ENTRY_REDIRECT_REC;
+
+static KEYBOARD_REC *keyboard;
+static ENTRY_REDIRECT_REC *redir;
+static int escape_next_key;
+
+static int readtag;
+static unichar prev_key;
+static gint64 last_keypress;
+
+static int paste_detect_time, paste_verify_line_count;
+static char *paste_entry;
+static int paste_entry_pos;
+static GArray *paste_buffer;
+static GArray *paste_buffer_rest;
+
+static char *paste_old_prompt;
+static int paste_prompt, paste_line_count;
+static int paste_join_multiline;
+static int paste_ignore_first_nl;
+static int paste_timeout_id;
+static int paste_use_bracketed_mode;
+static int paste_bracketed_mode;
+static int paste_was_bracketed_mode;
+static int previous_yank_preceded;
+
+/* Terminal sequences that surround the input when the terminal has the
+ * bracketed paste mode active. Fror more details see
+ * https://cirw.in/blog/bracketed-paste */
+static const unichar bp_start[] = { 0x1b, '[', '2', '0', '0', '~' };
+static const unichar bp_end[] = { 0x1b, '[', '2', '0', '1', '~' };
+
+#define BRACKETED_PASTE_TIMEOUT (5 * 1000) // ms
+
+#if GLIB_CHECK_VERSION(2, 62, 0)
+/* nothing */
+#else
+/* compatibility code for old GLib */
+GArray *g_array_copy(GArray *array)
+{
+ GArray *out;
+ guint elt_size;
+
+ elt_size = g_array_get_element_size(array);
+ out = g_array_sized_new(FALSE, FALSE, elt_size, array->len);
+ memcpy(out->data, array->data, array->len * elt_size);
+ out->len = array->len;
+
+ return out;
+}
+#endif
+
+static void sig_input(void);
+
+void input_listen_init(int handle)
+{
+ readtag = i_input_add_poll(handle, G_PRIORITY_HIGH, I_INPUT_READ,
+ (GInputFunction) sig_input, NULL);
+}
+
+void input_listen_deinit(void)
+{
+ g_source_remove(readtag);
+ readtag = -1;
+}
+
+static void handle_key_redirect(int key)
+{
+ ENTRY_REDIRECT_KEY_FUNC func;
+ void *data;
+
+ func = (ENTRY_REDIRECT_KEY_FUNC) redir->func;
+ data = redir->data;
+ g_free_and_null(redir);
+
+ gui_entry_set_prompt(active_entry, "");
+
+ if (func != NULL)
+ func(key, data, active_win->active_server, active_win->active);
+}
+
+static void handle_entry_redirect(const char *line)
+{
+ ENTRY_REDIRECT_ENTRY_FUNC func;
+ void *data;
+
+ gui_entry_set_hidden(active_entry, FALSE);
+
+ func = (ENTRY_REDIRECT_ENTRY_FUNC) redir->func;
+ data = redir->data;
+ g_free_and_null(redir);
+
+ gui_entry_set_prompt(active_entry, "");
+
+ if (func != NULL) {
+ func(line, data, active_win->active_server,
+ active_win->active);
+ }
+}
+
+static int get_scroll_count(void)
+{
+ const char *str;
+ double count;
+
+ str = settings_get_str("scroll_page_count");
+ count = atof(str + (*str == '/'));
+ if (count == 0)
+ count = 1;
+ else if (count < 0)
+ count = active_mainwin->height-active_mainwin->statusbar_lines+count;
+ else if (count < 1)
+ count = 1.0/count;
+
+ if (*str == '/' || *str == '.') {
+ count = (active_mainwin->height-active_mainwin->statusbar_lines)/count;
+ }
+ return (int)count;
+}
+
+static void window_prev_page(void)
+{
+ gui_window_scroll(active_win, -get_scroll_count());
+}
+
+static void window_next_page(void)
+{
+ gui_window_scroll(active_win, get_scroll_count());
+}
+
+#define isnewline(x) ((x) == '\n' || (x) == '\r')
+static void paste_buffer_join_lines(GArray *buf)
+{
+ unsigned int i, count, indent, line_len;
+ unichar *arr, *dest, *last_lf_pos;
+ int last_lf;
+
+ /* first check if we actually want to join anything. This is assuming
+ that we only want to join lines if
+
+ a) first line doesn't begin with whitespace
+ b) subsequent lines begin with same amount of whitespace
+ c) whenever there's no whitespace, goto a)
+
+ For example:
+
+ line 1
+ line 2
+ line 3
+ line 4
+ line 5
+ line 6
+
+ ->
+
+ line1 line2 line 3
+ line4
+ line5 line 6
+ */
+ if (buf->len == 0)
+ return;
+
+ arr = (unichar *)buf->data;
+
+ /* first line */
+ if (isblank(arr[0]))
+ return;
+
+ /* find the first beginning of indented line */
+ for (i = 1; i < buf->len; i++) {
+ if (isnewline(arr[i-1]) && isblank(arr[i]))
+ break;
+ }
+ if (i == buf->len)
+ return;
+
+ /* get how much indentation we have.. */
+ for (indent = 0; i < buf->len; i++, indent++) {
+ if (!isblank(arr[i]))
+ break;
+ }
+ if (i == buf->len)
+ return;
+
+ /* now, enforce these to all subsequent lines */
+ count = indent; last_lf = TRUE;
+ for (; i < buf->len; i++) {
+ if (last_lf) {
+ if (isblank(arr[i]))
+ count++;
+ else {
+ last_lf = FALSE;
+ if (count != 0 && count != indent)
+ return;
+ count = 0;
+ }
+ }
+ if (isnewline(arr[i]))
+ last_lf = TRUE;
+ }
+
+ /* all looks fine - now remove the whitespace, but don't let lines
+ get longer than LINE_SPLIT_LIMIT chars */
+ dest = arr; last_lf = TRUE; last_lf_pos = NULL; line_len = 0;
+ for (i = 0; i < buf->len; i++) {
+ if (last_lf && isblank(arr[i])) {
+ /* whitespace, ignore */
+ } else if (isnewline(arr[i])) {
+ if (!last_lf && i+1 != buf->len &&
+ isblank(arr[i+1])) {
+ last_lf_pos = dest;
+ if (i != 0 && !isblank(arr[i-1]))
+ *dest++ = ' ';
+ } else {
+ *dest++ = '\n'; /* double-LF */
+ line_len = 0;
+ last_lf_pos = NULL;
+ }
+ last_lf = TRUE;
+ } else {
+ last_lf = FALSE;
+ if (++line_len >= LINE_SPLIT_LIMIT && last_lf_pos != NULL) {
+ memmove(last_lf_pos+1, last_lf_pos,
+ (dest - last_lf_pos) * sizeof(unichar));
+ *last_lf_pos = '\n'; last_lf_pos = NULL;
+ line_len = 0;
+ dest++;
+ }
+ *dest++ = arr[i];
+ }
+ }
+ g_array_set_size(buf, dest - arr);
+}
+
+static void paste_send_line(char *text)
+{
+ /* we need to get the current history every time because it might change between calls */
+ command_history_add(command_history_current(active_win), text);
+
+ signal_emit("send command", 3, text, active_win->active_server, active_win->active);
+}
+
+static void paste_send(void)
+{
+ unichar *arr;
+ GString *str;
+ char out[10], *text;
+ unsigned int i;
+
+ if (paste_join_multiline)
+ paste_buffer_join_lines(paste_buffer);
+
+ arr = (unichar *) paste_buffer->data;
+ if (active_entry->text_len == 0)
+ i = 0;
+ else {
+ /* first line has to be kludged kind of to get pasting in the
+ middle of line right.. */
+ for (i = 0; i < paste_buffer->len; i++) {
+ if (isnewline(arr[i])) {
+ i++;
+ break;
+ }
+
+ gui_entry_insert_char(active_entry, arr[i]);
+ }
+
+ text = gui_entry_get_text(active_entry);
+ paste_send_line(text);
+ g_free(text);
+ }
+
+ /* rest of the lines */
+ str = g_string_new(NULL);
+ for (; i < paste_buffer->len; i++) {
+ if (isnewline(arr[i])) {
+ paste_send_line(str->str);
+ g_string_truncate(str, 0);
+ } else if (active_entry->utf8) {
+ out[g_unichar_to_utf8(arr[i], out)] = '\0';
+ g_string_append(str, out);
+ } else if (term_type == TERM_TYPE_BIG5) {
+ if (arr[i] > 0xff)
+ g_string_append_c(str, (arr[i] >> 8) & 0xff);
+ g_string_append_c(str, arr[i] & 0xff);
+ } else {
+ g_string_append_c(str, arr[i]);
+ }
+ }
+
+ if (paste_was_bracketed_mode) {
+ /* the text before the bracket end should be sent along with the rest */
+ paste_send_line(str->str);
+ gui_entry_set_text(active_entry, "");
+ } else {
+ gui_entry_set_text(active_entry, str->str);
+ }
+
+ g_string_free(str, TRUE);
+}
+
+static void paste_flush(void (*send)(void))
+{
+ if (paste_prompt) {
+ gui_entry_set_text(active_entry, paste_entry);
+ gui_entry_set_pos(active_entry, paste_entry_pos);
+ g_free_and_null(paste_entry);
+ }
+
+ if (send != NULL)
+ send();
+ g_array_set_size(paste_buffer, 0);
+
+ /* re-add anything that may have been after the bracketed paste end */
+ if (paste_buffer_rest->len > 0) {
+ g_array_append_vals(paste_buffer, paste_buffer_rest->data, paste_buffer_rest->len);
+ g_array_set_size(paste_buffer_rest, 0);
+ }
+
+ gui_entry_set_prompt(active_entry,
+ paste_old_prompt == NULL ? "" : paste_old_prompt);
+ g_free(paste_old_prompt); paste_old_prompt = NULL;
+ paste_prompt = FALSE;
+
+ paste_line_count = 0;
+
+ gui_entry_redraw(active_entry);
+}
+
+static void paste_print_line(const char *str)
+{
+ printformat_window(active_win, MSGLEVEL_CLIENTCRAP, TXT_PASTE_CONTENT, str);
+}
+
+static void paste_print(void)
+{
+ GArray *garr;
+ unichar *arr;
+ GString *str;
+ char out[10];
+ unsigned int i;
+ gboolean free_garr;
+
+ if (paste_join_multiline) {
+ garr = g_array_copy(paste_buffer);
+ paste_buffer_join_lines(garr);
+ free_garr = TRUE;
+ } else {
+ garr = paste_buffer;
+ free_garr = FALSE;
+ }
+
+ arr = &g_array_index(garr, unichar, 0);
+
+ str = g_string_new(NULL);
+ for (i = 0; i < garr->len; i++) {
+ if (isnewline(arr[i])) {
+ paste_print_line(str->str);
+ g_string_truncate(str, 0);
+ } else if (active_entry->utf8) {
+ out[g_unichar_to_utf8(arr[i], out)] = '\0';
+ g_string_append(str, out);
+ } else if (term_type == TERM_TYPE_BIG5) {
+ if (arr[i] > 0xff)
+ g_string_append_c(str, (arr[i] >> 8) & 0xff);
+ g_string_append_c(str, arr[i] & 0xff);
+ } else {
+ g_string_append_c(str, arr[i]);
+ }
+ }
+
+ if (str->len)
+ paste_print_line(str->str);
+
+ g_string_free(str, TRUE);
+ if (free_garr)
+ g_array_free(garr, TRUE);
+}
+
+static void paste_event(const char *arg)
+{
+ GArray *garr;
+ unichar *arr;
+ GString *str;
+ char out[10];
+ unsigned int i;
+ gboolean free_garr;
+
+ if (paste_join_multiline) {
+ garr = g_array_copy(paste_buffer);
+ paste_buffer_join_lines(garr);
+ free_garr = TRUE;
+ } else {
+ garr = paste_buffer;
+ free_garr = FALSE;
+ }
+
+ arr = &g_array_index(garr, unichar, 0);
+ str = g_string_new(NULL);
+ for (i = 0; i < garr->len; i++) {
+ if (isnewline(arr[i])) {
+ g_string_append_c(str, '\n');
+ } else if (active_entry->utf8) {
+ out[g_unichar_to_utf8(arr[i], out)] = '\0';
+ g_string_append(str, out);
+ } else if (term_type == TERM_TYPE_BIG5) {
+ if (arr[i] > 0xff)
+ g_string_append_c(str, (arr[i] >> 8) & 0xff);
+ g_string_append_c(str, arr[i] & 0xff);
+ } else {
+ g_string_append_c(str, arr[i]);
+ }
+ }
+
+ if (signal_emit("paste event", 2, str->str, arg)) {
+ paste_flush(NULL);
+ }
+
+ g_string_free(str, TRUE);
+ if (free_garr)
+ g_array_free(garr, TRUE);
+}
+
+static void paste_insert_edit(void)
+{
+ unichar *arr;
+ unsigned int i;
+
+ if (paste_join_multiline)
+ paste_buffer_join_lines(paste_buffer);
+
+ arr = &g_array_index(paste_buffer, unichar, 0);
+ for (i = 0; i < paste_buffer->len; i++) {
+ if (isnewline(arr[i])) {
+ gui_entry_insert_char(active_entry, '\\');
+ gui_entry_insert_char(active_entry, 'n');
+ } else if (arr[i] == 9) {
+ gui_entry_insert_char(active_entry, '\\');
+ gui_entry_insert_char(active_entry, 't');
+ } else if (arr[i] == 27) {
+ gui_entry_insert_char(active_entry, '\\');
+ gui_entry_insert_char(active_entry, 'e');
+ } else if (arr[i] == '\\') {
+ gui_entry_insert_char(active_entry, '\\');
+ gui_entry_insert_char(active_entry, '\\');
+ } else {
+ gui_entry_insert_char(active_entry, arr[i]);
+ }
+ }
+}
+
+static void insert_paste_prompt(void)
+{
+ char *str;
+
+ /* The actual number of lines that will show up post-line-split */
+ int actual_line_count = paste_line_count;
+ int split_lines = paste_buffer->len / LINE_SPLIT_LIMIT;
+
+ /* in case this prompt is happening due to line-splitting, calculate the
+ number of lines obtained from this. The number isn't entirely accurate;
+ we just choose the greater of the two since the exact value isn't
+ important */
+ if (split_lines > paste_verify_line_count &&
+ split_lines > paste_line_count) {
+ actual_line_count = split_lines;
+ }
+
+ paste_prompt = TRUE;
+ paste_old_prompt = g_strdup(active_entry->prompt);
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_PASTE_WARNING,
+ actual_line_count,
+ active_win->active == NULL ? "window" :
+ active_win->active->visible_name);
+
+ str = format_get_text(MODULE_NAME, active_win, NULL, NULL,
+ TXT_PASTE_PROMPT, 0, 0);
+ gui_entry_set_prompt(active_entry, str);
+ paste_entry = gui_entry_get_text(active_entry);
+ paste_entry_pos = gui_entry_get_pos(active_entry);
+ gui_entry_set_text(active_entry, "");
+ g_free(str);
+}
+
+static void sig_gui_key_pressed(gpointer keyp)
+{
+ gint64 now;
+ unichar key;
+ char str[20];
+ int ret;
+
+ key = GPOINTER_TO_INT(keyp);
+
+ if (redir != NULL && redir->flags & ENTRY_REDIRECT_FLAG_HOTKEY) {
+ handle_key_redirect(key);
+ return;
+ }
+
+ now = g_get_real_time();
+
+ if (key < 32) {
+ /* control key */
+ str[0] = '^';
+ str[1] = (char)key+'@';
+ str[2] = '\0';
+ } else if (key == 127) {
+ str[0] = '^';
+ str[1] = '?';
+ str[2] = '\0';
+ } else if (!active_entry->utf8) {
+ if (key <= 0xff) {
+ str[0] = (char)key;
+ str[1] = '\0';
+ } else {
+ str[0] = (char) (key >> 8);
+ str[1] = (char) (key & 0xff);
+ str[2] = '\0';
+ }
+ } else {
+ /* need to convert to utf8 */
+ str[g_unichar_to_utf8(key, str)] = '\0';
+ }
+
+ if (g_strcmp0(str, "^") == 0) {
+ /* change it as ^-, that is an invalid control char */
+ str[1] = '-';
+ str[2] = '\0';
+ }
+
+ if (escape_next_key) {
+ escape_next_key = FALSE;
+ gui_entry_insert_char(active_entry, key);
+ ret = 1;
+ } else {
+ previous_yank_preceded = active_entry->yank_preceded;
+ active_entry->yank_preceded = FALSE;
+ active_entry->previous_append_next_kill = active_entry->append_next_kill;
+ active_entry->append_next_kill = FALSE;
+ ret = key_pressed(keyboard, str);
+ if (ret < 0) {
+ /* key wasn't used for anything, print it */
+ gui_entry_insert_char(active_entry, key);
+ }
+ if (ret == 0) {
+ /* combo not complete, ignore append_next_kill and yank_preceded */
+ active_entry->append_next_kill = active_entry->previous_append_next_kill;
+ active_entry->yank_preceded = previous_yank_preceded;
+ }
+ }
+
+ /* ret = 0 : some key create multiple characters - we're in the middle
+ of one. try to detect the keycombo as a single keypress rather than
+ multiple small onces to avoid incorrect paste detection.
+
+ don't count repeated keys so paste detection won't go on when
+ you're holding some key down */
+ if (ret != 0 && key != prev_key) {
+ last_keypress = now;
+ }
+ prev_key = key;
+}
+
+static void key_send_line(void)
+{
+ HISTORY_REC *history;
+ char *str;
+ int add_history;
+
+ str = gui_entry_get_text(active_entry);
+
+ /* we can't use gui_entry_get_text() later, since the entry might
+ have been destroyed after we get back */
+ add_history = *str != '\0';
+ history = command_history_current(active_win);
+
+ if (redir != NULL && redir->flags & ENTRY_REDIRECT_FLAG_HIDDEN)
+ add_history = 0;
+
+ if (add_history && history != NULL) {
+ command_history_add(history, str);
+ }
+
+ if (redir == NULL) {
+ signal_emit("send command", 3, str,
+ active_win->active_server,
+ active_win->active);
+ } else {
+ handle_entry_redirect(str);
+ }
+
+ if (active_entry != NULL)
+ gui_entry_set_text(active_entry, "");
+ command_history_clear_pos(active_win);
+
+ g_free(str);
+}
+
+static void key_combo(void)
+{
+}
+
+static void key_backward_history(void)
+{
+ const char *text;
+ char *line;
+
+ line = gui_entry_get_text(active_entry);
+ text = command_history_prev(active_win, line);
+ gui_entry_set_text(active_entry, text);
+ g_free(line);
+}
+
+static void key_forward_history(void)
+{
+ const char *text;
+ char *line;
+
+ line = gui_entry_get_text(active_entry);
+ text = command_history_next(active_win, line);
+ gui_entry_set_text(active_entry, text);
+ g_free(line);
+}
+
+static void key_backward_global_history(void)
+{
+ const char *text;
+ char *line;
+
+ line = gui_entry_get_text(active_entry);
+ text = command_global_history_prev(active_win, line);
+ gui_entry_set_text(active_entry, text);
+ g_free(line);
+}
+
+static void key_forward_global_history(void)
+{
+ const char *text;
+ char *line;
+
+ line = gui_entry_get_text(active_entry);
+ text = command_global_history_next(active_win, line);
+ gui_entry_set_text(active_entry, text);
+ g_free(line);
+}
+
+static void key_erase_history_entry(void)
+{
+ const char *text;
+ char *line;
+
+ line = gui_entry_get_text(active_entry);
+ text = command_history_delete_current(active_win, line);
+ gui_entry_set_text(active_entry, text);
+ g_free(line);
+}
+
+static void key_beginning_of_line(void)
+{
+ gui_entry_set_pos(active_entry, 0);
+}
+
+static void key_end_of_line(void)
+{
+ gui_entry_set_pos(active_entry, active_entry->text_len);
+}
+
+static void key_backward_character(void)
+{
+ gui_entry_move_pos(active_entry, -1);
+}
+
+static void key_forward_character(void)
+{
+ gui_entry_move_pos(active_entry, 1);
+}
+
+static void key_backward_word(void)
+{
+ gui_entry_move_words(active_entry, -1, FALSE);
+}
+
+static void key_forward_word(void)
+{
+ gui_entry_move_words(active_entry, 1, FALSE);
+}
+
+static void key_backward_to_space(void)
+{
+ gui_entry_move_words(active_entry, -1, TRUE);
+}
+
+static void key_forward_to_space(void)
+{
+ gui_entry_move_words(active_entry, 1, TRUE);
+}
+
+static void key_erase_line(void)
+{
+ gui_entry_set_pos(active_entry, active_entry->text_len);
+ gui_entry_erase(active_entry, active_entry->text_len, CUTBUFFER_UPDATE_REPLACE);
+}
+
+static void key_erase_to_beg_of_line(void)
+{
+ int pos;
+
+ pos = gui_entry_get_pos(active_entry);
+ gui_entry_erase(active_entry, pos, CUTBUFFER_UPDATE_PREPEND);
+}
+
+static void key_erase_to_end_of_line(void)
+{
+ int pos;
+
+ pos = gui_entry_get_pos(active_entry);
+ gui_entry_set_pos(active_entry, active_entry->text_len);
+ gui_entry_erase(active_entry, active_entry->text_len - pos, CUTBUFFER_UPDATE_APPEND);
+}
+
+static void key_yank_from_cutbuffer(void)
+{
+ char *cutbuffer;
+
+ cutbuffer = gui_entry_get_cutbuffer(active_entry);
+ if (cutbuffer != NULL) {
+ gui_entry_insert_text(active_entry, cutbuffer);
+ active_entry->yank_preceded = TRUE;
+ g_free(cutbuffer);
+ }
+}
+
+static void key_yank_next_cutbuffer(void)
+{
+ GUI_ENTRY_CUTBUFFER_REC *rec;
+ guint length = 0;
+ char *cutbuffer;
+
+ if (!previous_yank_preceded)
+ return;
+
+ if (active_entry->kill_ring == NULL)
+ return;
+
+ rec = active_entry->kill_ring->data;
+ if (rec != NULL) length = rec->cutbuffer_len;
+
+ cutbuffer = gui_entry_get_next_cutbuffer(active_entry);
+ if (cutbuffer != NULL) {
+ gui_entry_erase(active_entry, length, CUTBUFFER_UPDATE_NOOP);
+ gui_entry_insert_text(active_entry, cutbuffer);
+ active_entry->yank_preceded = TRUE;
+ g_free(cutbuffer);
+ }
+}
+
+static void key_transpose_characters(void)
+{
+ gui_entry_transpose_chars(active_entry);
+}
+
+static void key_transpose_words(void)
+{
+ gui_entry_transpose_words(active_entry);
+}
+
+static void key_capitalize_word(void)
+{
+ gui_entry_capitalize_word(active_entry);
+}
+
+static void key_downcase_word(void)
+{
+ gui_entry_downcase_word(active_entry);
+}
+static void key_upcase_word(void)
+{
+ gui_entry_upcase_word(active_entry);
+}
+
+static void key_delete_character(void)
+{
+ if (gui_entry_get_pos(active_entry) < active_entry->text_len) {
+ gui_entry_erase_cell(active_entry);
+ }
+}
+
+static void key_backspace(void)
+{
+ gui_entry_erase(active_entry, 1, CUTBUFFER_UPDATE_NOOP);
+}
+
+static void key_delete_previous_word(void)
+{
+ gui_entry_erase_word(active_entry, FALSE, CUTBUFFER_UPDATE_PREPEND);
+}
+
+static void key_delete_next_word(void)
+{
+ gui_entry_erase_next_word(active_entry, FALSE, CUTBUFFER_UPDATE_APPEND);
+}
+
+static void key_delete_to_previous_space(void)
+{
+ gui_entry_erase_word(active_entry, TRUE, CUTBUFFER_UPDATE_PREPEND);
+}
+
+static void key_delete_to_next_space(void)
+{
+ gui_entry_erase_next_word(active_entry, TRUE, CUTBUFFER_UPDATE_APPEND);
+}
+
+static void key_append_next_kill(void)
+{
+ active_entry->append_next_kill = TRUE;
+}
+
+static gboolean paste_timeout(gpointer data)
+{
+ int split_lines;
+ paste_was_bracketed_mode = paste_bracketed_mode;
+
+ if (paste_ignore_first_nl && paste_line_count == 1) {
+ unichar last_char;
+
+ last_char = g_array_index(paste_buffer, unichar, paste_buffer->len - 1);
+
+ if (isnewline(last_char)) {
+ g_array_set_size(paste_buffer, paste_buffer->len - 1);
+ paste_line_count--;
+ }
+ }
+
+ /* number of lines after splitting extra-long messages */
+ split_lines = paste_buffer->len / LINE_SPLIT_LIMIT;
+
+ /* Take into account the fact that a line may be split every LINE_SPLIT_LIMIT characters */
+ if (paste_line_count == 0 && split_lines <= paste_verify_line_count) {
+ int i;
+
+ for (i = 0; i < paste_buffer->len; i++) {
+ unichar key = g_array_index(paste_buffer, unichar, i);
+ signal_emit("gui key pressed", 1, GINT_TO_POINTER(key));
+ }
+ g_array_set_size(paste_buffer, 0);
+ } else if (paste_verify_line_count > 0 &&
+ (paste_line_count >= paste_verify_line_count ||
+ split_lines > paste_verify_line_count) &&
+ active_win->active != NULL)
+ insert_paste_prompt();
+ else
+ paste_flush(paste_send);
+ paste_timeout_id = -1;
+ return FALSE;
+}
+
+static void paste_bracketed_end(int i, gboolean rest)
+{
+ unichar last_char;
+
+ /* if there's stuff after the end bracket, save it for later */
+ if (rest) {
+ unichar *start = ((unichar *) paste_buffer->data) + i + G_N_ELEMENTS(bp_end);
+ int len = paste_buffer->len - i - G_N_ELEMENTS(bp_end);
+
+ g_array_set_size(paste_buffer_rest, 0);
+ g_array_append_vals(paste_buffer_rest, start, len);
+ }
+
+ /* remove the rest, including the trailing sequence chars */
+ g_array_set_size(paste_buffer, i);
+
+ last_char = g_array_index(paste_buffer, unichar, i - 1);
+
+ if (paste_line_count > 0 && !isnewline(last_char)) {
+ /* there are newlines, but there's also stuff after the newline
+ * adjust line count to reflect this */
+ paste_line_count++;
+ }
+
+ /* decide what to do with the buffer */
+ if (paste_timeout_id != -1)
+ g_source_remove(paste_timeout_id);
+ paste_timeout(NULL);
+
+ paste_bracketed_mode = FALSE;
+}
+
+static void paste_bracketed_middle(void)
+{
+ int i;
+ int marklen = G_N_ELEMENTS(bp_end);
+ int len = paste_buffer->len - marklen;
+ unichar *ptr = (unichar *) paste_buffer->data;
+
+ if (len < 0) {
+ return;
+ }
+
+ for (i = 0; i <= len; i++, ptr++) {
+ if (ptr[0] == bp_end[0] && memcmp(ptr, bp_end, sizeof(bp_end)) == 0) {
+
+ /* if there are at least 6 bytes after the end,
+ * check for another start marker right afterwards */
+ if (i <= (len - marklen) &&
+ memcmp(ptr + marklen, bp_start, sizeof(bp_start)) == 0) {
+
+ /* remove both markers*/
+ g_array_remove_range(paste_buffer, i, marklen * 2);
+ len -= marklen * 2;
+
+ /* go one step back */
+ i--;
+ ptr--;
+ continue;
+ }
+ paste_bracketed_end(i, i != len);
+ break;
+ }
+ }
+}
+
+static void sig_input(void)
+{
+ if (!active_entry) {
+ /* no active entry yet - wait until we have it */
+ return;
+ }
+
+ if (paste_prompt) {
+ GArray *buffer = g_array_new(FALSE, FALSE, sizeof(unichar));
+ int line_count = 0;
+ unichar key;
+ term_gets(buffer, &line_count);
+ key = g_array_index(buffer, unichar, 0);
+ /* Either Ctrl-k or Ctrl-c is pressed */
+ if (key < 32 && key != 13 /* CR */ && key != 10 /* LF */ && key != 27 /* Esc */) {
+ key_pressed(keyboard, "paste");
+ signal_emit("gui key pressed", 1, GINT_TO_POINTER(key));
+ }
+ g_array_free(buffer, TRUE);
+ } else {
+ term_gets(paste_buffer, &paste_line_count);
+
+ /* use the bracketed paste mode to detect when the user pastes
+ * some text into the entry */
+ if (paste_bracketed_mode) {
+ paste_bracketed_middle();
+
+ } else if (!paste_use_bracketed_mode && paste_detect_time > 0 && paste_buffer->len >= 3) {
+ if (paste_timeout_id != -1)
+ g_source_remove(paste_timeout_id);
+ paste_timeout_id = g_timeout_add(paste_detect_time, paste_timeout, NULL);
+ } else if (!paste_bracketed_mode) {
+ int i;
+
+ for (i = 0; i < paste_buffer->len; i++) {
+ unichar key = g_array_index(paste_buffer, unichar, i);
+ signal_emit("gui key pressed", 1, GINT_TO_POINTER(key));
+
+ if (paste_bracketed_mode) {
+ /* just enabled by the signal, remove what was processed so far */
+ g_array_remove_range(paste_buffer, 0, i + 1);
+
+ /* handle single-line / small pastes here */
+ paste_bracketed_middle();
+ return;
+ }
+ }
+ g_array_set_size(paste_buffer, 0);
+ paste_line_count = 0;
+ }
+ }
+}
+
+static void key_paste_start(void)
+{
+ if (paste_use_bracketed_mode) {
+ paste_bracketed_mode = TRUE;
+ if (paste_timeout_id != -1)
+ g_source_remove(paste_timeout_id);
+ paste_timeout_id = g_timeout_add(BRACKETED_PASTE_TIMEOUT, paste_timeout, NULL);
+ }
+}
+
+static void key_paste_cancel(void)
+{
+ if (paste_prompt) {
+ paste_flush(NULL);
+ }
+}
+
+static void key_paste_print(void)
+{
+ if (paste_prompt) {
+ paste_print();
+ }
+}
+
+static void key_paste_send(void)
+{
+ if (paste_prompt) {
+ paste_flush(paste_send);
+ }
+}
+
+static void key_paste_edit(void)
+{
+ if (paste_prompt) {
+ paste_flush(paste_insert_edit);
+ }
+}
+
+static void key_paste_event(const char *arg)
+{
+ if (paste_prompt) {
+ paste_event(arg);
+ }
+}
+
+time_t get_idle_time(void)
+{
+ return last_keypress / G_TIME_SPAN_SECOND;
+}
+
+static void key_scroll_backward(void)
+{
+ window_prev_page();
+}
+
+static void key_scroll_forward(void)
+{
+ window_next_page();
+}
+
+static void key_scroll_start(void)
+{
+ signal_emit("command scrollback home", 3, NULL, active_win->active_server, active_win->active);
+}
+
+static void key_scroll_end(void)
+{
+ signal_emit("command scrollback end", 3, NULL, active_win->active_server, active_win->active);
+}
+
+static void key_change_window(const char *data)
+{
+ signal_emit("command window goto", 3, data, active_win->active_server, active_win->active);
+}
+
+static void key_completion(int erase, int backward)
+{
+ char *text, *line;
+ int pos;
+
+ text = gui_entry_get_text_and_pos(active_entry, &pos);
+ line = word_complete(active_win, text, &pos, erase, backward);
+ g_free(text);
+
+ if (line != NULL) {
+ gui_entry_set_text_and_pos_bytes(active_entry, line, pos);
+ g_free(line);
+ }
+}
+
+static void key_word_completion_backward(void)
+{
+ key_completion(FALSE, TRUE);
+}
+
+static void key_word_completion(void)
+{
+ key_completion(FALSE, FALSE);
+}
+
+static void key_erase_completion(void)
+{
+ key_completion(TRUE, FALSE);
+}
+
+static void key_check_replaces(void)
+{
+ char *text, *line;
+ int pos;
+
+ text = gui_entry_get_text_and_pos(active_entry, &pos);
+ line = auto_word_complete(text, &pos);
+ g_free(text);
+
+ if (line != NULL) {
+ gui_entry_set_text_and_pos_bytes(active_entry, line, pos);
+ g_free(line);
+ }
+}
+
+static void key_previous_window(void)
+{
+ signal_emit("command window previous", 3, "", active_win->active_server, active_win->active);
+}
+
+static void key_next_window(void)
+{
+ signal_emit("command window next", 3, "", active_win->active_server, active_win->active);
+}
+
+static void key_left_window(void)
+{
+ signal_emit("command window left", 3, "", active_win->active_server, active_win->active);
+}
+
+static void key_right_window(void)
+{
+ signal_emit("command window right", 3, "", active_win->active_server, active_win->active);
+}
+
+static void key_upper_window(void)
+{
+ signal_emit("command window up", 3, "", active_win->active_server, active_win->active);
+}
+
+static void key_lower_window(void)
+{
+ signal_emit("command window down", 3, "", active_win->active_server, active_win->active);
+}
+
+static void key_active_window(void)
+{
+ signal_emit("command window goto", 3, "active", active_win->active_server, active_win->active);
+}
+
+static SERVER_REC *get_prev_server(SERVER_REC *current)
+{
+ int pos;
+
+ if (current == NULL) {
+ return servers != NULL ? g_slist_last(servers)->data :
+ lookup_servers != NULL ?
+ g_slist_last(lookup_servers)->data : NULL;
+ }
+
+ /* connect2 -> connect1 -> server2 -> server1 -> connect2 -> .. */
+
+ pos = g_slist_index(servers, current);
+ if (pos != -1) {
+ if (pos > 0)
+ return g_slist_nth(servers, pos-1)->data;
+ if (lookup_servers != NULL)
+ return g_slist_last(lookup_servers)->data;
+ return g_slist_last(servers)->data;
+ }
+
+ pos = g_slist_index(lookup_servers, current);
+ g_assert(pos >= 0);
+
+ if (pos > 0)
+ return g_slist_nth(lookup_servers, pos-1)->data;
+ if (servers != NULL)
+ return g_slist_last(servers)->data;
+ return g_slist_last(lookup_servers)->data;
+}
+
+static SERVER_REC *get_next_server(SERVER_REC *current)
+{
+ GSList *pos;
+
+ if (current == NULL) {
+ return servers != NULL ? servers->data :
+ lookup_servers != NULL ? lookup_servers->data : NULL;
+ }
+
+ /* server1 -> server2 -> connect1 -> connect2 -> server1 -> .. */
+
+ pos = g_slist_find(servers, current);
+ if (pos != NULL) {
+ if (pos->next != NULL)
+ return pos->next->data;
+ if (lookup_servers != NULL)
+ return lookup_servers->data;
+ return servers->data;
+ }
+
+ pos = g_slist_find(lookup_servers, current);
+ g_assert(pos != NULL);
+
+ if (pos->next != NULL)
+ return pos->next->data;
+ if (servers != NULL)
+ return servers->data;
+ return lookup_servers->data;
+}
+
+static void key_previous_window_item(void)
+{
+ SERVER_REC *server;
+
+ if (active_win->items != NULL) {
+ signal_emit("command window item prev", 3, "",
+ active_win->active_server, active_win->active);
+ } else if (servers != NULL || lookup_servers != NULL) {
+ /* change server */
+ server = active_win->active_server;
+ if (server == NULL)
+ server = active_win->connect_server;
+ server = get_prev_server(server);
+ signal_emit("command window server", 3, server->tag,
+ active_win->active_server, active_win->active);
+ }
+}
+
+static void key_next_window_item(void)
+{
+ SERVER_REC *server;
+
+ if (active_win->items != NULL) {
+ signal_emit("command window item next", 3, "",
+ active_win->active_server, active_win->active);
+ } else if (servers != NULL || lookup_servers != NULL) {
+ /* change server */
+ server = active_win->active_server;
+ if (server == NULL)
+ server = active_win->connect_server;
+ server = get_next_server(server);
+ signal_emit("command window server", 3, server->tag,
+ active_win->active_server, active_win->active);
+ }
+}
+
+static void key_escape(void)
+{
+ escape_next_key = TRUE;
+}
+
+static void key_insert_text(const char *data)
+{
+ char *str;
+
+ str = parse_special_string(data, active_win->active_server,
+ active_win->active, "", NULL, 0);
+ gui_entry_insert_text(active_entry, str);
+ g_free(str);
+}
+
+static void key_sig_stop(void)
+{
+ term_stop();
+}
+
+static void sig_window_auto_changed(void)
+{
+ char *text;
+
+ if (active_entry == NULL)
+ return;
+
+ text = gui_entry_get_text(active_entry);
+ command_history_next(active_win, text);
+ gui_entry_set_text(active_entry, "");
+ g_free(text);
+}
+
+static void sig_gui_entry_redirect(SIGNAL_FUNC func, const char *entry,
+ void *flags, void *data)
+{
+ redir = g_new0(ENTRY_REDIRECT_REC, 1);
+ redir->func = func;
+ redir->flags = GPOINTER_TO_INT(flags);
+ redir->data = data;
+
+ if (redir->flags & ENTRY_REDIRECT_FLAG_HIDDEN)
+ gui_entry_set_hidden(active_entry, TRUE);
+ gui_entry_set_prompt(active_entry, entry);
+}
+
+static void setup_changed(void)
+{
+ paste_detect_time = settings_get_time("paste_detect_time");
+
+ paste_verify_line_count = settings_get_int("paste_verify_line_count");
+ paste_join_multiline = settings_get_bool("paste_join_multiline");
+ paste_ignore_first_nl = settings_get_bool("paste_ignore_first_nl");
+ paste_use_bracketed_mode = settings_get_bool("paste_use_bracketed_mode");
+
+ term_set_appkey_mode(settings_get_bool("term_appkey_mode"));
+ /* Enable the bracketed paste mode on demand */
+ term_set_bracketed_paste_mode(paste_use_bracketed_mode);
+ if (!paste_use_bracketed_mode)
+ paste_bracketed_mode = FALSE;
+}
+
+void gui_readline_init(void)
+{
+ static char changekeys[] = "1234567890qwertyuio";
+ char *key, data[MAX_INT_STRLEN];
+ int n;
+
+ escape_next_key = FALSE;
+ redir = NULL;
+ paste_entry = NULL;
+ paste_entry_pos = 0;
+ paste_buffer = g_array_new(FALSE, FALSE, sizeof(unichar));
+ paste_buffer_rest = g_array_new(FALSE, FALSE, sizeof(unichar));
+ paste_old_prompt = NULL;
+ paste_timeout_id = -1;
+ paste_bracketed_mode = FALSE;
+ last_keypress = g_get_real_time();
+ input_listen_init(STDIN_FILENO);
+
+ settings_add_bool("lookandfeel", "term_appkey_mode", TRUE);
+ settings_add_str("history", "scroll_page_count", "/2");
+ settings_add_time("misc", "paste_detect_time", "5msecs");
+ settings_add_bool("misc", "paste_use_bracketed_mode", FALSE);
+ /* NOTE: function keys can generate at least 5 characters long
+ keycodes. this must be larger to allow them to work. */
+ settings_add_int("misc", "paste_verify_line_count", 5);
+ settings_add_bool("misc", "paste_join_multiline", TRUE);
+ settings_add_bool("misc", "paste_ignore_first_nl", FALSE);
+ setup_changed();
+
+ keyboard = keyboard_create(NULL);
+ key_configure_freeze();
+
+ key_bind("key", NULL, " ", "space", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "^M", "return", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "^J", "return", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "^H", "backspace", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "^?", "backspace", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "^I", "tab", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-Z", "stab", (SIGNAL_FUNC) key_combo);
+
+ /* meta */
+ key_bind("key", NULL, "^[", "meta", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta-[", "meta2", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta-O", "meta2", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta-[O", "meta2", (SIGNAL_FUNC) key_combo);
+
+ /* arrow keys */
+ key_bind("key", NULL, "meta2-A", "up", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-B", "down", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-C", "right", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-D", "left", (SIGNAL_FUNC) key_combo);
+
+ key_bind("key", NULL, "meta2-1~", "home", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-7~", "home", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-H", "home", (SIGNAL_FUNC) key_combo);
+
+ key_bind("key", NULL, "meta2-4~", "end", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-8~", "end", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-F", "end", (SIGNAL_FUNC) key_combo);
+
+ key_bind("key", NULL, "meta2-5~", "prior", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-I", "prior", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-6~", "next", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-G", "next", (SIGNAL_FUNC) key_combo);
+
+ key_bind("key", NULL, "meta2-2~", "insert", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-3~", "delete", (SIGNAL_FUNC) key_combo);
+
+ key_bind("key", NULL, "meta2-d", "cleft", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-c", "cright", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-5D", "cleft", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-5C", "cright", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-1;5D", "cleft", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-1;5C", "cright", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-1;5A", "cup", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-1;5B", "cdown", (SIGNAL_FUNC) key_combo);
+
+ key_bind("key", NULL, "meta2-1;3A", "mup", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-1;3B", "mdown", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-1;3D", "mleft", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-1;3C", "mright", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta-up", "mup", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta-down", "mdown", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta-left", "mleft", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta-right", "mright", (SIGNAL_FUNC) key_combo);
+
+ key_bind("key", NULL, "meta2-1;5~", "chome", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-7;5~", "chome", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-5H", "chome", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-1;5H", "chome", (SIGNAL_FUNC) key_combo);
+
+ key_bind("key", NULL, "meta2-4;5~", "cend", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-8;5~", "cend", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-5F", "cend", (SIGNAL_FUNC) key_combo);
+ key_bind("key", NULL, "meta2-1;5F", "cend", (SIGNAL_FUNC) key_combo);
+
+ key_bind("key", NULL, "meta-O-M", "return", (SIGNAL_FUNC) key_combo);
+
+ /* clang-format off */
+ key_bind("paste_start", "Bracketed paste start", "^[[200~", "paste_start", (SIGNAL_FUNC) key_paste_start);
+ key_bind("paste_cancel", "Cancel paste", "paste-^C", NULL, (SIGNAL_FUNC) key_paste_cancel);
+ key_bind("paste_print", "Print paste to screen", "paste-^P", NULL, (SIGNAL_FUNC) key_paste_print);
+ key_bind("paste_send", "Send paste to target", "paste-^K", NULL, (SIGNAL_FUNC) key_paste_send);
+ key_bind("paste_edit", "Insert paste to input line", "paste-^E", NULL, (SIGNAL_FUNC) key_paste_edit);
+ key_bind("paste_event", "Send paste to event", "paste-^U", NULL, (SIGNAL_FUNC) key_paste_event);
+
+ /* cursor movement */
+ key_bind("backward_character", "Move the cursor a character backward", "left", NULL, (SIGNAL_FUNC) key_backward_character);
+ key_bind("forward_character", "Move the cursor a character forward", "right", NULL, (SIGNAL_FUNC) key_forward_character);
+ key_bind("backward_word", "Move the cursor a word backward", "cleft", NULL, (SIGNAL_FUNC) key_backward_word);
+ key_bind("backward_word", NULL, "meta-b", NULL, (SIGNAL_FUNC) key_backward_word);
+ key_bind("forward_word", "Move the cursor a word forward", "cright", NULL, (SIGNAL_FUNC) key_forward_word);
+ key_bind("forward_word", NULL, "meta-f", NULL, (SIGNAL_FUNC) key_forward_word);
+ key_bind("backward_to_space", "Move the cursor backward to a space", NULL, NULL, (SIGNAL_FUNC) key_backward_to_space);
+ key_bind("forward_to_space", "Move the cursor forward to a space", NULL, NULL, (SIGNAL_FUNC) key_forward_to_space);
+ key_bind("beginning_of_line", "Move the cursor to the beginning of the line", "home", NULL, (SIGNAL_FUNC) key_beginning_of_line);
+ key_bind("beginning_of_line", NULL, "^A", NULL, (SIGNAL_FUNC) key_beginning_of_line);
+ key_bind("end_of_line", "Move the cursor to the end of the line", "end", NULL, (SIGNAL_FUNC) key_end_of_line);
+ key_bind("end_of_line", NULL, "^E", NULL, (SIGNAL_FUNC) key_end_of_line);
+
+ /* history */
+ key_bind("backward_history", "Go back one line in the history", "up", NULL, (SIGNAL_FUNC) key_backward_history);
+ key_bind("forward_history", "Go forward one line in the history", "down", NULL, (SIGNAL_FUNC) key_forward_history);
+ key_bind("backward_global_history", "Go back one line in the global history", "cup", NULL, (SIGNAL_FUNC) key_backward_global_history);
+ key_bind("forward_global_history", "Go forward one line in the global history", "cdown", NULL, (SIGNAL_FUNC) key_forward_global_history);
+ key_bind("erase_history_entry", "Erase the currently active entry from the history", NULL, NULL, (SIGNAL_FUNC) key_erase_history_entry);
+
+ /* line editing */
+ key_bind("backspace", "Delete the previous character", "backspace", NULL, (SIGNAL_FUNC) key_backspace);
+ key_bind("delete_character", "Delete the current character", "delete", NULL, (SIGNAL_FUNC) key_delete_character);
+ key_bind("delete_character", NULL, "^D", NULL, (SIGNAL_FUNC) key_delete_character);
+ key_bind("delete_next_word", "Delete the word after the cursor", "meta-d", NULL, (SIGNAL_FUNC) key_delete_next_word);
+ key_bind("delete_previous_word", "Delete the word before the cursor", "meta-backspace", NULL, (SIGNAL_FUNC) key_delete_previous_word);
+ key_bind("delete_to_previous_space", "Delete up to the previous space", "^W", NULL, (SIGNAL_FUNC) key_delete_to_previous_space);
+ key_bind("delete_to_next_space", "Delete up to the next space", "", NULL, (SIGNAL_FUNC) key_delete_to_next_space);
+ key_bind("erase_line", "Erase the whole input line", "^U", NULL, (SIGNAL_FUNC) key_erase_line);
+ key_bind("erase_to_beg_of_line", "Erase everything before the cursor", NULL, NULL, (SIGNAL_FUNC) key_erase_to_beg_of_line);
+ key_bind("erase_to_end_of_line", "Erase everything after the cursor", "^K", NULL, (SIGNAL_FUNC) key_erase_to_end_of_line);
+ key_bind("yank_from_cutbuffer", "\"Undelete\", paste the last deleted text", "^Y", NULL, (SIGNAL_FUNC) key_yank_from_cutbuffer);
+ key_bind("yank_next_cutbuffer", "Revert to the previous last deleted text", NULL, NULL, (SIGNAL_FUNC) key_yank_next_cutbuffer);
+ key_bind("append_next_kill", "Append next deletion", NULL, NULL, (SIGNAL_FUNC) key_append_next_kill);
+ key_bind("transpose_characters", "Swap current and previous character", "^T", NULL, (SIGNAL_FUNC) key_transpose_characters);
+ key_bind("transpose_words", "Swap current and previous word", NULL, NULL, (SIGNAL_FUNC) key_transpose_words);
+ key_bind("capitalize_word", "Capitalize the current word", NULL, NULL, (SIGNAL_FUNC) key_capitalize_word);
+ key_bind("downcase_word", "Downcase the current word", NULL, NULL, (SIGNAL_FUNC) key_downcase_word);
+ key_bind("upcase_word", "Upcase the current word", NULL, NULL, (SIGNAL_FUNC) key_upcase_word);
+
+ /* line transmitting */
+ key_bind("send_line", "Execute the input line", "return", NULL, (SIGNAL_FUNC) key_send_line);
+ key_bind("word_completion_backward", "Choose previous completion suggestion", "stab", NULL, (SIGNAL_FUNC) key_word_completion_backward);
+ key_bind("word_completion", "Complete the current word", "tab", NULL, (SIGNAL_FUNC) key_word_completion);
+ key_bind("erase_completion", "Remove the completion added by word_completion", "meta-k", NULL, (SIGNAL_FUNC) key_erase_completion);
+ key_bind("check_replaces", "Check word replaces", NULL, NULL, (SIGNAL_FUNC) key_check_replaces);
+
+ /* window managing */
+ key_bind("previous_window", "Go to the previous window", "^P", NULL, (SIGNAL_FUNC) key_previous_window);
+ key_bind("next_window", "Go to the next window", "^N", NULL, (SIGNAL_FUNC) key_next_window);
+ key_bind("upper_window", "Go to the split window above", "mup", NULL, (SIGNAL_FUNC) key_upper_window);
+ key_bind("lower_window", "Go to the split window below", "mdown", NULL, (SIGNAL_FUNC) key_lower_window);
+ key_bind("left_window", "Go to the previous window in the current split window", "mleft", NULL, (SIGNAL_FUNC) key_left_window);
+ key_bind("right_window", "Go to the next window in the current split window", "mright", NULL, (SIGNAL_FUNC) key_right_window);
+ key_bind("active_window", "Go to next window with the highest activity", "meta-a", NULL, (SIGNAL_FUNC) key_active_window);
+ key_bind("next_window_item", "Go to the next channel/query. In empty windows change to the next server", "^X", NULL, (SIGNAL_FUNC) key_next_window_item);
+ key_bind("previous_window_item", "Go to the previous channel/query. In empty windows change to the previous server", NULL, NULL, (SIGNAL_FUNC) key_previous_window_item);
+
+ key_bind("refresh_screen", "Redraw screen", "^L", NULL, (SIGNAL_FUNC) irssi_redraw);
+ key_bind("scroll_backward", "Scroll to previous page", "prior", NULL, (SIGNAL_FUNC) key_scroll_backward);
+ key_bind("scroll_backward", NULL, "meta-p", NULL, (SIGNAL_FUNC) key_scroll_backward);
+ key_bind("scroll_forward", "Scroll to next page", "next", NULL, (SIGNAL_FUNC) key_scroll_forward);
+ key_bind("scroll_forward", NULL, "meta-n", NULL, (SIGNAL_FUNC) key_scroll_forward);
+ key_bind("scroll_start", "Scroll to the beginning of the window", "chome", NULL, (SIGNAL_FUNC) key_scroll_start);
+ key_bind("scroll_end", "Scroll to the end of the window", "cend", NULL, (SIGNAL_FUNC) key_scroll_end);
+
+ /* inserting special input characters to line.. */
+ key_bind("escape_char", "Insert the next character exactly as-is to input line", NULL, NULL, (SIGNAL_FUNC) key_escape);
+ key_bind("insert_text", "Append text to line", NULL, NULL, (SIGNAL_FUNC) key_insert_text);
+ /* clang-format on */
+
+ /* autoreplaces */
+ key_bind("multi", NULL, "return", "check_replaces;send_line", NULL);
+ key_bind("multi", NULL, "space", "check_replaces;insert_text ", NULL);
+
+ /* moving between windows */
+ for (n = 0; changekeys[n] != '\0'; n++) {
+ key = g_strdup_printf("meta-%c", changekeys[n]);
+ ltoa(data, n+1);
+ key_bind("change_window", "Change window", key, data, (SIGNAL_FUNC) key_change_window);
+ g_free(key);
+ }
+
+ /* misc */
+ key_bind("stop_irc", "Send SIGSTOP to client", "^Z", NULL, (SIGNAL_FUNC) key_sig_stop);
+
+ key_configure_thaw();
+
+ signal_add("window changed automatic", (SIGNAL_FUNC) sig_window_auto_changed);
+ signal_add("gui entry redirect", (SIGNAL_FUNC) sig_gui_entry_redirect);
+ signal_add("gui key pressed", (SIGNAL_FUNC) sig_gui_key_pressed);
+ signal_add("setup changed", (SIGNAL_FUNC) setup_changed);
+}
+
+void gui_readline_deinit(void)
+{
+ input_listen_deinit();
+
+ key_configure_freeze();
+
+ key_unbind("paste_start", (SIGNAL_FUNC) key_paste_start);
+ key_unbind("paste_cancel", (SIGNAL_FUNC) key_paste_cancel);
+ key_unbind("paste_print", (SIGNAL_FUNC) key_paste_print);
+ key_unbind("paste_send", (SIGNAL_FUNC) key_paste_send);
+ key_unbind("paste_edit", (SIGNAL_FUNC) key_paste_edit);
+ key_unbind("paste_event", (SIGNAL_FUNC) key_paste_event);
+
+ key_unbind("backward_character", (SIGNAL_FUNC) key_backward_character);
+ key_unbind("forward_character", (SIGNAL_FUNC) key_forward_character);
+ key_unbind("backward_word", (SIGNAL_FUNC) key_backward_word);
+ key_unbind("forward_word", (SIGNAL_FUNC) key_forward_word);
+ key_unbind("backward_to_space", (SIGNAL_FUNC) key_backward_to_space);
+ key_unbind("forward_to_space", (SIGNAL_FUNC) key_forward_to_space);
+ key_unbind("beginning_of_line", (SIGNAL_FUNC) key_beginning_of_line);
+ key_unbind("end_of_line", (SIGNAL_FUNC) key_end_of_line);
+
+ key_unbind("backward_history", (SIGNAL_FUNC) key_backward_history);
+ key_unbind("forward_history", (SIGNAL_FUNC) key_forward_history);
+ key_unbind("backward_global_history", (SIGNAL_FUNC) key_backward_global_history);
+ key_unbind("forward_global_history", (SIGNAL_FUNC) key_forward_global_history);
+ key_unbind("erase_history_entry", (SIGNAL_FUNC) key_erase_history_entry);
+
+ key_unbind("backspace", (SIGNAL_FUNC) key_backspace);
+ key_unbind("delete_character", (SIGNAL_FUNC) key_delete_character);
+ key_unbind("delete_next_word", (SIGNAL_FUNC) key_delete_next_word);
+ key_unbind("delete_previous_word", (SIGNAL_FUNC) key_delete_previous_word);
+ key_unbind("delete_to_next_space", (SIGNAL_FUNC) key_delete_to_next_space);
+ key_unbind("delete_to_previous_space", (SIGNAL_FUNC) key_delete_to_previous_space);
+ key_unbind("erase_line", (SIGNAL_FUNC) key_erase_line);
+ key_unbind("erase_to_beg_of_line", (SIGNAL_FUNC) key_erase_to_beg_of_line);
+ key_unbind("erase_to_end_of_line", (SIGNAL_FUNC) key_erase_to_end_of_line);
+ key_unbind("yank_from_cutbuffer", (SIGNAL_FUNC) key_yank_from_cutbuffer);
+ key_unbind("yank_next_cutbuffer", (SIGNAL_FUNC) key_yank_next_cutbuffer);
+ key_unbind("append_next_kill", (SIGNAL_FUNC) key_append_next_kill);
+ key_unbind("transpose_characters", (SIGNAL_FUNC) key_transpose_characters);
+ key_unbind("transpose_words", (SIGNAL_FUNC) key_transpose_words);
+
+ key_unbind("capitalize_word", (SIGNAL_FUNC) key_capitalize_word);
+ key_unbind("downcase_word", (SIGNAL_FUNC) key_downcase_word);
+ key_unbind("upcase_word", (SIGNAL_FUNC) key_upcase_word);
+
+ key_unbind("send_line", (SIGNAL_FUNC) key_send_line);
+ key_unbind("word_completion_backward", (SIGNAL_FUNC) key_word_completion_backward);
+ key_unbind("word_completion", (SIGNAL_FUNC) key_word_completion);
+ key_unbind("erase_completion", (SIGNAL_FUNC) key_erase_completion);
+ key_unbind("check_replaces", (SIGNAL_FUNC) key_check_replaces);
+
+ key_unbind("previous_window", (SIGNAL_FUNC) key_previous_window);
+ key_unbind("next_window", (SIGNAL_FUNC) key_next_window);
+ key_unbind("upper_window", (SIGNAL_FUNC) key_upper_window);
+ key_unbind("lower_window", (SIGNAL_FUNC) key_lower_window);
+ key_unbind("left_window", (SIGNAL_FUNC) key_left_window);
+ key_unbind("right_window", (SIGNAL_FUNC) key_right_window);
+ key_unbind("active_window", (SIGNAL_FUNC) key_active_window);
+ key_unbind("next_window_item", (SIGNAL_FUNC) key_next_window_item);
+ key_unbind("previous_window_item", (SIGNAL_FUNC) key_previous_window_item);
+
+ key_unbind("refresh_screen", (SIGNAL_FUNC) irssi_redraw);
+ key_unbind("scroll_backward", (SIGNAL_FUNC) key_scroll_backward);
+ key_unbind("scroll_forward", (SIGNAL_FUNC) key_scroll_forward);
+ key_unbind("scroll_start", (SIGNAL_FUNC) key_scroll_start);
+ key_unbind("scroll_end", (SIGNAL_FUNC) key_scroll_end);
+
+ key_unbind("escape_char", (SIGNAL_FUNC) key_escape);
+ key_unbind("insert_text", (SIGNAL_FUNC) key_insert_text);
+ key_unbind("change_window", (SIGNAL_FUNC) key_change_window);
+ key_unbind("stop_irc", (SIGNAL_FUNC) key_sig_stop);
+ keyboard_destroy(keyboard);
+ g_array_free(paste_buffer, TRUE);
+ g_array_free(paste_buffer_rest, TRUE);
+
+ key_configure_thaw();
+
+ signal_remove("window changed automatic", (SIGNAL_FUNC) sig_window_auto_changed);
+ signal_remove("gui entry redirect", (SIGNAL_FUNC) sig_gui_entry_redirect);
+ signal_remove("gui key pressed", (SIGNAL_FUNC) sig_gui_key_pressed);
+ signal_remove("setup changed", (SIGNAL_FUNC) setup_changed);
+}
diff --git a/src/fe-text/gui-readline.h b/src/fe-text/gui-readline.h
new file mode 100644
index 0000000..dc497b8
--- /dev/null
+++ b/src/fe-text/gui-readline.h
@@ -0,0 +1,15 @@
+#ifndef IRSSI_FE_TEXT_GUI_READLINE_H
+#define IRSSI_FE_TEXT_GUI_READLINE_H
+
+extern char *cutbuffer;
+
+void input_listen_init(int handle);
+void input_listen_deinit(void);
+
+void readline(void);
+time_t get_idle_time(void);
+
+void gui_readline_init(void);
+void gui_readline_deinit(void);
+
+#endif
diff --git a/src/fe-text/gui-windows.c b/src/fe-text/gui-windows.c
new file mode 100644
index 0000000..46be239
--- /dev/null
+++ b/src/fe-text/gui-windows.c
@@ -0,0 +1,327 @@
+/*
+ gui-windows.c : irssi
+
+ Copyright (C) 1999-2001 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/special-vars.h>
+#include <irssi/src/core/levels.h>
+
+#include <irssi/src/fe-text/term.h>
+#include <irssi/src/fe-text/gui-entry.h>
+#include <irssi/src/fe-text/gui-windows.h>
+#include <irssi/src/fe-text/gui-printtext.h>
+
+static int window_create_override;
+static int wcwidth_impl;
+
+static GUI_WINDOW_REC *gui_window_init(WINDOW_REC *window,
+ MAIN_WINDOW_REC *parent)
+{
+ GUI_WINDOW_REC *gui;
+
+ window->width = parent->width;
+ window->height = MAIN_WINDOW_TEXT_HEIGHT(parent);
+
+ gui = g_new0(GUI_WINDOW_REC, 1);
+ gui->parent = parent;
+ gui->view =
+ textbuffer_view_create(textbuffer_create(window), window->width, window->height,
+ settings_get_bool("scroll"), term_type == TERM_TYPE_UTF8);
+ textbuffer_view_set_default_indent(gui->view,
+ settings_get_int("indent"),
+ !settings_get_bool("indent_always"),
+ get_default_indent_func());
+ textbuffer_view_set_break_wide(gui->view, settings_get_bool("break_wide"));
+ wcwidth_impl = settings_get_choice("wcwidth_implementation");
+ textbuffer_view_set_hidden_level(gui->view, settings_get_level("window_default_hidelevel"));
+ if (parent->active == window)
+ textbuffer_view_set_window(gui->view, parent->screen_win);
+ return gui;
+}
+
+static void gui_window_deinit(GUI_WINDOW_REC *gui)
+{
+ textbuffer_view_destroy(gui->view);
+ g_free(gui);
+}
+
+static void sig_window_create_override(gpointer tab)
+{
+ window_create_override = GPOINTER_TO_INT(tab);
+}
+
+static void gui_window_created(WINDOW_REC *window, void *automatic)
+{
+ MAIN_WINDOW_REC *parent;
+ int new_parent;
+
+ g_return_if_fail(window != NULL);
+
+ new_parent = window_create_override == MAIN_WINDOW_TYPE_DEFAULT ||
+ window_create_override == MAIN_WINDOW_TYPE_SPLIT ||
+ window_create_override == MAIN_WINDOW_TYPE_RSPLIT ||
+ active_win == NULL || WINDOW_GUI(active_win) == NULL;
+ parent = !new_parent ? WINDOW_MAIN(active_win) : mainwindow_create(window_create_override == MAIN_WINDOW_TYPE_RSPLIT);
+ if (parent == NULL) {
+ /* not enough space for new window, but we really can't
+ abort creation of the window anymore, so create hidden
+ window instead. */
+ parent = WINDOW_MAIN(active_win);
+ }
+ window_create_override = MAIN_WINDOW_TYPE_NONE;
+
+ if (parent->active == NULL) parent->active = window;
+ window->gui_data = gui_window_init(window, parent);
+
+ /* set only non-automatic windows sticky so that the windows
+ irssi creates at startup wont get sticky. */
+ if (automatic == NULL &&
+ (parent->sticky_windows ||
+ (new_parent && settings_get_bool("autostick_split_windows"))))
+ gui_window_set_sticky(window);
+
+ signal_emit("gui window created", 1, window);
+}
+
+static void gui_window_destroyed(WINDOW_REC *window)
+{
+ MAIN_WINDOW_REC *parent;
+ GUI_WINDOW_REC *gui;
+
+ g_return_if_fail(window != NULL);
+
+ gui = WINDOW_GUI(window);
+ parent = gui->parent;
+
+ gui_window_set_unsticky(window);
+
+ signal_emit("gui window destroyed", 1, window);
+
+ gui_window_deinit(gui);
+ window->gui_data = NULL;
+
+ if (parent->active == window)
+ mainwindow_change_active(parent, window);
+}
+
+void gui_window_resize(WINDOW_REC *window, int width, int height)
+{
+ GUI_WINDOW_REC *gui;
+
+ if (window->width == width && window->height == height)
+ return;
+
+ gui = WINDOW_GUI(window);
+
+ irssi_set_dirty();
+ WINDOW_MAIN(window)->dirty = TRUE;
+
+ window->width = width;
+ window->height = height;
+ textbuffer_view_resize(gui->view, width, height);
+}
+
+void gui_window_scroll(WINDOW_REC *window, int lines)
+{
+ g_return_if_fail(window != NULL);
+
+ textbuffer_view_scroll(WINDOW_GUI(window)->view, lines);
+ signal_emit("gui page scrolled", 1, window);
+}
+
+void gui_window_scroll_line(WINDOW_REC *window, LINE_REC *line)
+{
+ g_return_if_fail(window != NULL);
+ g_return_if_fail(line != NULL);
+
+ textbuffer_view_scroll_line(WINDOW_GUI(window)->view, line);
+ signal_emit("gui page scrolled", 1, window);
+}
+
+void gui_window_set_sticky(WINDOW_REC *window)
+{
+ GUI_WINDOW_REC *gui = WINDOW_GUI(window);
+
+ if (!gui->sticky) {
+ gui->sticky = TRUE;
+ gui->parent->sticky_windows++;
+ }
+}
+
+void gui_window_set_unsticky(WINDOW_REC *window)
+{
+ GUI_WINDOW_REC *gui = WINDOW_GUI(window);
+
+ if (gui->sticky) {
+ gui->sticky = FALSE;
+ gui->parent->sticky_windows--;
+ }
+}
+
+void gui_window_reparent(WINDOW_REC *window, MAIN_WINDOW_REC *parent)
+{
+ MAIN_WINDOW_REC *oldparent;
+
+ oldparent = WINDOW_MAIN(window);
+ if (oldparent == parent)
+ return;
+
+ gui_window_set_unsticky(window);
+ textbuffer_view_set_window(WINDOW_GUI(window)->view, NULL);
+
+ WINDOW_MAIN(window) = parent;
+ if (parent->sticky_windows)
+ gui_window_set_sticky(window);
+
+ if (MAIN_WINDOW_TEXT_HEIGHT(parent) !=
+ MAIN_WINDOW_TEXT_HEIGHT(oldparent) ||
+ parent->width != oldparent->width) {
+ gui_window_resize(window, parent->width,
+ MAIN_WINDOW_TEXT_HEIGHT(parent));
+ }
+}
+
+void gui_windows_reset_settings(void)
+{
+ GSList *tmp;
+ int old_wcwidth_impl = wcwidth_impl;
+
+ wcwidth_impl = settings_get_choice("wcwidth_implementation");
+
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+ GUI_WINDOW_REC *gui = WINDOW_GUI(rec);
+
+ if (old_wcwidth_impl != wcwidth_impl) {
+ textbuffer_view_reset_cache(gui->view);
+ }
+
+ textbuffer_view_set_break_wide(gui->view, settings_get_bool("break_wide"));
+
+ textbuffer_view_set_default_indent(gui->view,
+ settings_get_int("indent"),
+ !settings_get_bool("indent_always"),
+ get_default_indent_func());
+
+ textbuffer_view_set_scroll(gui->view,
+ gui->use_scroll ? gui->scroll :
+ settings_get_bool("scroll"));
+
+ if (old_wcwidth_impl != wcwidth_impl) {
+ textbuffer_view_redraw(gui->view);
+ }
+ }
+}
+
+static MAIN_WINDOW_REC *mainwindow_find_unsticky(void)
+{
+ GSList *tmp;
+
+ for (tmp = mainwindows; tmp != NULL; tmp = tmp->next) {
+ MAIN_WINDOW_REC *rec = tmp->data;
+
+ if (!rec->sticky_windows)
+ return rec;
+ }
+
+ /* all windows are sticky, fallback to active window */
+ return active_mainwin;
+}
+
+static void signal_window_changed(WINDOW_REC *window)
+{
+ MAIN_WINDOW_REC *parent;
+ WINDOW_REC *old_window;
+
+ g_return_if_fail(window != NULL);
+
+ if (quitting) return;
+
+ parent = WINDOW_MAIN(window);
+ if (is_window_visible(window)) {
+ /* already visible */
+ active_mainwin = parent;
+ } else if (active_mainwin == NULL) {
+ /* no main window set yet */
+ active_mainwin = parent;
+ } else if (WINDOW_GUI(window)->sticky) {
+ /* window is sticky, switch to correct main window */
+ if (parent != active_mainwin)
+ active_mainwin = parent;
+ } else {
+ /* move window to active main window */
+ if (active_mainwin->sticky_windows) {
+ /* active mainwindow is sticky, we'll need to
+ set the window active somewhere else */
+ active_mainwin = mainwindow_find_unsticky();
+ }
+ gui_window_reparent(window, active_mainwin);
+ }
+
+ old_window = active_mainwin->active;
+ if (old_window != NULL && old_window != window)
+ textbuffer_view_set_window(WINDOW_GUI(old_window)->view, NULL);
+
+ active_mainwin->active = window;
+
+ textbuffer_view_set_window(WINDOW_GUI(window)->view,
+ active_mainwin->screen_win);
+ if (WINDOW_GUI(window)->view->dirty)
+ active_mainwin->dirty = TRUE;
+}
+
+static void read_settings(void)
+{
+ gui_windows_reset_settings();
+}
+
+void gui_windows_init(void)
+{
+ settings_add_bool("lookandfeel", "autostick_split_windows", FALSE);
+ settings_add_bool("lookandfeel", "autounstick_windows", TRUE);
+ settings_add_int("lookandfeel", "indent", 10);
+ settings_add_bool("lookandfeel", "indent_always", FALSE);
+ settings_add_bool("lookandfeel", "break_wide", FALSE);
+ settings_add_bool("lookandfeel", "scroll", TRUE);
+ settings_add_level("lookandfeel", "window_default_hidelevel", "HIDDEN");
+
+ window_create_override = MAIN_WINDOW_TYPE_NONE;
+
+ read_settings();
+ signal_add("gui window create override", (SIGNAL_FUNC) sig_window_create_override);
+ signal_add("window created", (SIGNAL_FUNC) gui_window_created);
+ signal_add("window destroyed", (SIGNAL_FUNC) gui_window_destroyed);
+ signal_add_first("window changed", (SIGNAL_FUNC) signal_window_changed);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void gui_windows_deinit(void)
+{
+ while (windows != NULL)
+ window_destroy(windows->data);
+
+ signal_remove("gui window create override", (SIGNAL_FUNC) sig_window_create_override);
+ signal_remove("window created", (SIGNAL_FUNC) gui_window_created);
+ signal_remove("window destroyed", (SIGNAL_FUNC) gui_window_destroyed);
+ signal_remove("window changed", (SIGNAL_FUNC) signal_window_changed);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
diff --git a/src/fe-text/gui-windows.h b/src/fe-text/gui-windows.h
new file mode 100644
index 0000000..52a64ce
--- /dev/null
+++ b/src/fe-text/gui-windows.h
@@ -0,0 +1,45 @@
+#ifndef IRSSI_FE_TEXT_GUI_WINDOWS_H
+#define IRSSI_FE_TEXT_GUI_WINDOWS_H
+
+#include <irssi/src/fe-text/mainwindows.h>
+#include <irssi/src/fe-text/textbuffer-view.h>
+
+#define WINDOW_GUI(a) ((GUI_WINDOW_REC *) ((a)->gui_data))
+#define WINDOW_MAIN(a) (WINDOW_GUI(a)->parent)
+
+#define is_window_visible(win) \
+ (WINDOW_GUI(win)->parent->active == (win))
+
+typedef struct {
+ MAIN_WINDOW_REC *parent;
+ TEXT_BUFFER_VIEW_REC *view;
+
+ unsigned int scroll:1;
+ unsigned int use_scroll:1;
+
+ unsigned int sticky:1;
+ unsigned int use_insert_after:1;
+ LINE_REC *insert_after;
+ time_t insert_after_time;
+} GUI_WINDOW_REC;
+
+void gui_windows_init(void);
+void gui_windows_deinit(void);
+
+WINDOW_REC *gui_window_create(MAIN_WINDOW_REC *parent);
+
+void gui_window_resize(WINDOW_REC *window, int width, int height);
+void gui_window_reparent(WINDOW_REC *window, MAIN_WINDOW_REC *parent);
+
+#define gui_window_redraw(window) \
+ textbuffer_view_redraw(WINDOW_GUI(window)->view)
+
+void gui_window_scroll(WINDOW_REC *window, int lines);
+void gui_window_scroll_line(WINDOW_REC *window, LINE_REC *line);
+
+void gui_window_set_sticky(WINDOW_REC *window);
+void gui_window_set_unsticky(WINDOW_REC *window);
+
+void gui_windows_reset_settings(void);
+
+#endif
diff --git a/src/fe-text/irssi.c b/src/fe-text/irssi.c
new file mode 100644
index 0000000..fbaa303
--- /dev/null
+++ b/src/fe-text/irssi.c
@@ -0,0 +1,402 @@
+/*
+ irssi.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-text/module-formats.h>
+#include <irssi/src/core/modules-load.h>
+#include <irssi/src/core/args.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/core.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/session.h>
+#include <irssi/src/core/servers.h>
+
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/fe-common/core/fe-common-core.h>
+#include <irssi/src/fe-common/core/fe-settings.h>
+#include <irssi/src/fe-common/core/themes.h>
+
+#include <irssi/src/fe-text/term.h>
+#include <irssi/src/fe-text/gui-entry.h>
+#include <irssi/src/fe-text/mainwindows.h>
+#include <irssi/src/fe-text/gui-printtext.h>
+#include <irssi/src/fe-text/gui-readline.h>
+#include <irssi/src/fe-text/statusbar.h>
+#include <irssi/src/fe-text/gui-windows.h>
+#include <irssi/irssi-version.h>
+
+#include <signal.h>
+#include <locale.h>
+
+#ifdef HAVE_STATIC_PERL
+void perl_core_init(void);
+void perl_core_deinit(void);
+
+void fe_perl_init(void);
+void fe_perl_deinit(void);
+#endif
+
+#ifdef HAVE_STATIC_OTR
+void otr_core_init(void);
+void otr_core_deinit(void);
+#endif
+
+void irc_init(void);
+void irc_deinit(void);
+
+void fe_common_irc_init(void);
+void fe_common_irc_deinit(void);
+
+void gui_expandos_init(void);
+void gui_expandos_deinit(void);
+
+void textbuffer_commands_init(void);
+void textbuffer_commands_deinit(void);
+
+void textbuffer_formats_init(void);
+void textbuffer_formats_deinit(void);
+
+void lastlog_init(void);
+void lastlog_deinit(void);
+
+void mainwindow_activity_init(void);
+void mainwindow_activity_deinit(void);
+
+void mainwindows_layout_init(void);
+void mainwindows_layout_deinit(void);
+
+static int dirty, full_redraw;
+
+static GMainLoop *main_loop;
+int quitting;
+
+static int display_firsttimer = FALSE;
+static unsigned int user_settings_changed = 0;
+
+
+static void sig_exit(void)
+{
+ quitting = TRUE;
+}
+
+static void sig_settings_userinfo_changed(gpointer changedp)
+{
+ user_settings_changed = GPOINTER_TO_UINT(changedp);
+}
+
+static void sig_autoload_modules(void)
+{
+ char **list, **module;
+ list = g_strsplit_set(settings_get_str("autoload_modules"), " ,", -1);
+ for (module = list; *module != NULL; module++) {
+ char *tmp;
+ if ((tmp = strchr(*module, ':')) != NULL)
+ *tmp = ' ';
+ tmp = g_strdup_printf("-silent %s", *module);
+ signal_emit("command load", 1, tmp);
+ g_free(tmp);
+ }
+ g_strfreev(list);
+}
+
+/* redraw irssi's screen.. */
+void irssi_redraw(void)
+{
+ dirty = TRUE;
+ full_redraw = TRUE;
+}
+
+void irssi_set_dirty(void)
+{
+ dirty = TRUE;
+}
+
+static void dirty_check(void)
+{
+ if (!dirty)
+ return;
+
+ term_resize_dirty();
+
+ if (full_redraw) {
+ full_redraw = FALSE;
+
+ /* first clear the screen so curses will be
+ forced to redraw the screen */
+ term_clear();
+ term_refresh(NULL);
+
+ mainwindows_redraw();
+ statusbar_redraw(NULL, TRUE);
+ }
+
+ mainwindows_redraw_dirty();
+ statusbar_redraw_dirty();
+ term_refresh(NULL);
+
+ dirty = FALSE;
+}
+
+static void textui_init(void)
+{
+#ifdef SIGTRAP
+ struct sigaction act;
+
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ act.sa_handler = SIG_IGN;
+ sigaction(SIGTRAP, &act, NULL);
+#endif
+
+ irssi_gui = IRSSI_GUI_TEXT;
+ core_init();
+ irc_init();
+ fe_common_core_init();
+ fe_common_irc_init();
+
+ theme_register(gui_text_formats);
+ signal_add("settings userinfo changed", (SIGNAL_FUNC) sig_settings_userinfo_changed);
+ signal_add("module autoload", (SIGNAL_FUNC) sig_autoload_modules);
+ signal_add_last("gui exit", (SIGNAL_FUNC) sig_exit);
+}
+
+static int critical_fatal_section_begin(void)
+{
+ return g_log_set_always_fatal(G_LOG_FATAL_MASK | G_LOG_LEVEL_CRITICAL);
+}
+
+static void critical_fatal_section_end(int loglev)
+{
+ g_log_set_always_fatal(loglev);
+}
+
+static void textui_finish_init(void)
+{
+ int loglev;
+ quitting = FALSE;
+
+ term_refresh_freeze();
+ textbuffer_init();
+ textbuffer_view_init();
+ textbuffer_commands_init();
+ textbuffer_formats_init();
+ gui_expandos_init();
+ gui_printtext_init();
+ gui_readline_init();
+ gui_entry_init();
+ lastlog_init();
+ mainwindows_init();
+ mainwindow_activity_init();
+ mainwindows_layout_init();
+ gui_windows_init();
+ /* Temporarily raise the fatal level to abort on config errors. */
+ loglev = critical_fatal_section_begin();
+ statusbar_init();
+ critical_fatal_section_end(loglev);
+
+ settings_check();
+
+ module_register("core", "fe-text");
+
+#ifdef HAVE_STATIC_PERL
+ perl_core_init();
+ fe_perl_init();
+#endif
+
+#ifdef HAVE_STATIC_OTR
+ otr_core_init();
+#endif
+
+ dirty_check();
+
+ /* Temporarily raise the fatal level to abort on config errors. */
+ loglev = critical_fatal_section_begin();
+ fe_common_core_finish_init();
+ critical_fatal_section_end(loglev);
+ term_refresh_thaw();
+
+ signal_emit("irssi init finished", 0);
+ statusbar_redraw(NULL, TRUE);
+
+ if (servers == NULL && lookup_servers == NULL) {
+ printformat(NULL, NULL, MSGLEVEL_CRAP|MSGLEVEL_NO_ACT, TXT_IRSSI_BANNER);
+ }
+
+ if (display_firsttimer) {
+ printformat(NULL, NULL, MSGLEVEL_CRAP|MSGLEVEL_NO_ACT, TXT_WELCOME_FIRSTTIME);
+ }
+
+ /* see irc-servers-setup.c:init_userinfo */
+ if (user_settings_changed)
+ printformat(NULL, NULL, MSGLEVEL_CLIENTNOTICE, TXT_WELCOME_INIT_SETTINGS);
+ if (user_settings_changed & USER_SETTINGS_REAL_NAME)
+ fe_settings_set_print("real_name");
+ if (user_settings_changed & USER_SETTINGS_USER_NAME)
+ fe_settings_set_print("user_name");
+ if (user_settings_changed & USER_SETTINGS_NICK)
+ fe_settings_set_print("nick");
+ if (user_settings_changed & USER_SETTINGS_HOSTNAME)
+ fe_settings_set_print("hostname");
+
+ term_environment_check();
+}
+
+static void textui_deinit(void)
+{
+ signal(SIGINT, SIG_DFL);
+
+ term_refresh_freeze();
+ while (modules != NULL)
+ module_unload(modules->data);
+
+#ifdef HAVE_STATIC_PERL
+ perl_core_deinit();
+ fe_perl_deinit();
+#endif
+
+#ifdef HAVE_STATIC_OTR
+ otr_core_deinit();
+#endif
+
+ dirty_check(); /* one last time to print any quit messages */
+ signal_remove("settings userinfo changed", (SIGNAL_FUNC) sig_settings_userinfo_changed);
+ signal_remove("module autoload", (SIGNAL_FUNC) sig_autoload_modules);
+ signal_remove("gui exit", (SIGNAL_FUNC) sig_exit);
+
+ lastlog_deinit();
+ statusbar_deinit();
+ gui_entry_deinit();
+ gui_printtext_deinit();
+ gui_readline_deinit();
+ gui_windows_deinit();
+ mainwindows_layout_deinit();
+ mainwindow_activity_deinit();
+ mainwindows_deinit();
+ gui_expandos_deinit();
+ textbuffer_formats_deinit();
+ textbuffer_commands_deinit();
+ textbuffer_view_deinit();
+ textbuffer_deinit();
+
+ term_refresh_thaw();
+ term_deinit();
+
+ theme_unregister();
+
+ fe_common_irc_deinit();
+ fe_common_core_deinit();
+ irc_deinit();
+ core_deinit();
+}
+
+static void check_files(void)
+{
+ struct stat statbuf;
+
+ if (stat(get_irssi_dir(), &statbuf) != 0) {
+ /* ~/.irssi doesn't exist, first time running irssi */
+ display_firsttimer = TRUE;
+ }
+}
+
+int main(int argc, char **argv)
+{
+ static int version = 0;
+ static GOptionEntry options[] = {
+ { "version", 'v', 0, G_OPTION_ARG_NONE, &version, "Display Irssi version", NULL },
+ { NULL }
+ };
+ int loglev;
+
+ core_register_options();
+ fe_common_core_register_options();
+ args_register(options);
+ args_execute(argc, argv);
+
+ if (version) {
+ printf(PACKAGE_TARNAME" " PACKAGE_VERSION" (%d %04d)\n",
+ IRSSI_VERSION_DATE, IRSSI_VERSION_TIME);
+ return 0;
+ }
+
+ srand(time(NULL));
+
+ quitting = FALSE;
+ core_preinit(argv[0]);
+
+ check_files();
+
+#ifdef HAVE_SOCKS
+ SOCKSinit(argv[0]);
+#endif
+
+ /* setlocale() must be called at the beginning before any calls that
+ affect it, especially regexps seem to break if they're generated
+ before this call.
+
+ locales aren't actually used for anything else than autodetection
+ of UTF-8 currently..
+
+ furthermore to get the users's charset with g_get_charset() properly
+ you have to call setlocale(LC_ALL, "") */
+ setlocale(LC_ALL, "");
+
+ /* Temporarily raise the fatal level to abort on config errors. */
+ loglev = critical_fatal_section_begin();
+ textui_init();
+
+ if (!term_init()) {
+ fprintf(stderr, "Can't initialize screen handling.\n");
+ return 1;
+ }
+
+ critical_fatal_section_end(loglev);
+
+ textui_finish_init();
+ main_loop = g_main_loop_new(NULL, TRUE);
+
+ /* Does the same as g_main_run(main_loop), except we
+ can call our dirty-checker after each iteration */
+ while (!quitting) {
+ if (sighup_received) {
+ sighup_received = FALSE;
+
+ if (settings_get_bool("quit_on_hup")) {
+ signal_emit("gui exit", 0);
+ }
+ else {
+ signal_emit("command reload", 1, "");
+ }
+ }
+
+ dirty_check();
+
+ term_refresh_freeze();
+ g_main_context_iteration(NULL, TRUE);
+ term_refresh_thaw();
+ }
+
+ g_main_loop_unref(main_loop);
+ textui_deinit();
+
+ session_upgrade(); /* if we /UPGRADEd, start the new process */
+ return 0;
+}
diff --git a/src/fe-text/lastlog.c b/src/fe-text/lastlog.c
new file mode 100644
index 0000000..89ee30a
--- /dev/null
+++ b/src/fe-text/lastlog.c
@@ -0,0 +1,324 @@
+/*
+ lastlog.c : irssi
+
+ Copyright (C) 1999-2001 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/fe-text/module-formats.h>
+#include <irssi/src/fe-common/core/printtext.h>
+
+#include <irssi/src/fe-text/gui-windows.h>
+#include <irssi/src/fe-text/gui-printtext.h>
+
+#define DEFAULT_LASTLOG_BEFORE 3
+#define DEFAULT_LASTLOG_AFTER 3
+#define MAX_LINES_WITHOUT_FORCE 1000
+
+/* Only unknown keys in `optlist' should be levels.
+ Returns -1 if unknown option was given. */
+int cmd_options_get_level(const char *cmd, GHashTable *optlist)
+{
+ GList *list;
+ int level, retlevel;
+
+ list = optlist_remove_known(cmd, optlist);
+
+ retlevel = 0;
+ while (list != NULL) {
+ level = level_get(list->data);
+ if (level == 0) {
+ /* unknown option */
+ signal_emit("error command", 2,
+ GINT_TO_POINTER(CMDERR_OPTION_UNKNOWN),
+ list->data);
+ retlevel = -1;
+ break;
+ }
+
+ retlevel |= level;
+ list = g_list_remove(list, list->data);
+ }
+
+ return retlevel;
+}
+
+static void prepend_date(WINDOW_REC *window, LINE_REC *rec, GString *line)
+{
+ THEME_REC *theme = NULL;
+ TEXT_DEST_REC dest = {0};
+ char *format = NULL, datestamp[20] = {0};
+ struct tm *tm = localtime(&rec->info.time);
+ int ret = 0;
+
+ theme = window->theme != NULL ? window->theme : current_theme;
+ format_create_dest(&dest, NULL, NULL, MSGLEVEL_LASTLOG, window);
+ format = format_get_text_theme(theme, MODULE_NAME, &dest, TXT_LASTLOG_DATE);
+
+ ret = strftime(datestamp, sizeof(datestamp), format, tm);
+ g_free(format);
+ if (ret <= 0) return;
+
+ g_string_prepend(line, datestamp);
+}
+
+static void show_lastlog(const char *searchtext, GHashTable *optlist,
+ int start, int count, FILE *fhandle)
+{
+ WINDOW_REC *window;
+ LINE_REC *startline;
+ TEXT_BUFFER_VIEW_REC *view;
+ TEXT_BUFFER_REC *buffer;
+ GSList *texts, *tmp;
+ GList *list, *tmp2;
+ char *str;
+ int level, before, after, len, date = FALSE;
+
+ level = cmd_options_get_level("lastlog", optlist);
+ if (level == -1) return; /* error in options */
+ if (level == 0) level = MSGLEVEL_ALL;
+
+ view = WINDOW_GUI(active_win)->view;
+ if (g_hash_table_lookup(optlist, "clear") != NULL) {
+ textbuffer_view_remove_lines_by_level(view, MSGLEVEL_LASTLOG);
+ if (*searchtext == '\0')
+ return;
+ }
+
+ /* which window's lastlog to look at? */
+ window = active_win;
+ str = g_hash_table_lookup(optlist, "window");
+ if (str != NULL) {
+ window = is_numeric(str, '\0') ?
+ window_find_refnum(atoi(str)) :
+ window_find_item(NULL, str);
+ if (window == NULL) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_REFNUM_NOT_FOUND, str);
+ return;
+ }
+ }
+ view = WINDOW_GUI(window)->view;
+
+ if (g_hash_table_lookup(optlist, "new") != NULL)
+ startline = textbuffer_view_get_bookmark(view, "lastlog_last_check");
+ else if (g_hash_table_lookup(optlist, "away") != NULL)
+ startline = textbuffer_view_get_bookmark(view, "lastlog_last_away");
+ else
+ startline = NULL;
+
+ if (startline == NULL)
+ startline = textbuffer_view_get_lines(view);
+
+ str = g_hash_table_lookup(optlist, "#");
+ if (str != NULL) {
+ before = after = atoi(str);
+ } else {
+ str = g_hash_table_lookup(optlist, "before");
+ before = str == NULL ? 0 : *str != '\0' ?
+ atoi(str) : DEFAULT_LASTLOG_BEFORE;
+
+ str = g_hash_table_lookup(optlist, "after");
+ if (str == NULL) str = g_hash_table_lookup(optlist, "a");
+ after = str == NULL ? 0 : *str != '\0' ?
+ atoi(str) : DEFAULT_LASTLOG_AFTER;
+ }
+
+ if (g_hash_table_lookup(optlist, "date") != NULL)
+ date = TRUE;
+
+ buffer = view->buffer;
+ list = textbuffer_find_text(buffer, startline, level, MSGLEVEL_LASTLOG, searchtext, before,
+ after, g_hash_table_lookup(optlist, "regexp") != NULL,
+ g_hash_table_lookup(optlist, "word") != NULL,
+ g_hash_table_lookup(optlist, "case") != NULL);
+
+ len = g_list_length(list);
+ if (count <= 0)
+ tmp2 = list;
+ else {
+ int pos = len-count-start;
+ if (pos < 0) pos = 0;
+
+ tmp2 = pos > len ? NULL : g_list_nth(list, pos);
+ len = g_list_length(tmp2);
+ }
+
+ if (g_hash_table_lookup(optlist, "count") != NULL) {
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_LASTLOG_COUNT, len);
+ g_list_free(list);
+ return;
+ }
+
+ if (len > MAX_LINES_WITHOUT_FORCE && fhandle == NULL &&
+ g_hash_table_lookup(optlist, "force") == NULL) {
+ printformat_window(active_win,
+ MSGLEVEL_CLIENTNOTICE|MSGLEVEL_LASTLOG,
+ TXT_LASTLOG_TOO_LONG, len);
+ g_list_free(list);
+ return;
+ }
+
+ /* collect the line texts */
+ texts = NULL;
+ for (; tmp2 != NULL && (count < 0 || count > 0); tmp2 = tmp2->next) {
+ GString *line;
+ LINE_REC *rec = tmp2->data;
+
+ if (rec == NULL) {
+ if (tmp2->next == NULL)
+ break;
+ texts = g_slist_prepend(texts, NULL);
+ continue;
+ }
+
+ line = g_string_new(NULL);
+ textbuffer_line2text(buffer, rec, fhandle == NULL, line);
+ if (!settings_get_bool("timestamps")) {
+ struct tm *tm = localtime(&rec->info.time);
+ char timestamp[10];
+
+ g_snprintf(timestamp, sizeof(timestamp),
+ "%02d:%02d ",
+ tm->tm_hour, tm->tm_min);
+ g_string_prepend(line, timestamp);
+ }
+
+ if (date == TRUE)
+ prepend_date(window, rec, line);
+
+ texts = g_slist_prepend(texts, line);
+
+ count--;
+ }
+ texts = g_slist_reverse(texts);
+
+ if (fhandle == NULL && g_hash_table_lookup(optlist, "-") == NULL)
+ printformat(NULL, NULL, MSGLEVEL_LASTLOG, TXT_LASTLOG_START);
+
+ for (tmp = texts; tmp != NULL; tmp = tmp->next) {
+ GString *line = tmp->data;
+
+ if (line == NULL) {
+ if (tmp->next == NULL)
+ break;
+ if (fhandle != NULL) {
+ fwrite("--\n", 3, 1, fhandle);
+ } else {
+ printformat_window(active_win, MSGLEVEL_LASTLOG,
+ TXT_LASTLOG_SEPARATOR);
+ }
+ continue;
+ }
+
+ /* write to file/window */
+ if (fhandle != NULL) {
+ fwrite(line->str, line->len, 1, fhandle);
+ fputc('\n', fhandle);
+ } else {
+ printtext_window(active_win, MSGLEVEL_LASTLOG,
+ "%s", line->str);
+ }
+ g_string_free(line, TRUE);
+ }
+
+ if (fhandle == NULL && g_hash_table_lookup(optlist, "-") == NULL)
+ printformat(NULL, NULL, MSGLEVEL_LASTLOG, TXT_LASTLOG_END);
+
+ textbuffer_view_set_bookmark_bottom(view, "lastlog_last_check");
+
+ g_slist_free(texts);
+ g_list_free(list);
+}
+
+/* SYNTAX: LASTLOG [-] [-file <filename>] [-window <ref#|name>] [-new | -away]
+ [-<level> -<level...>] [-clear] [-count] [-case] [-date]
+ [-regexp | -word] [-before [<#>]] [-after [<#>]]
+ [-<# before+after>] [<pattern>] [<count> [<start>]] */
+static void cmd_lastlog(const char *data)
+{
+ GHashTable *optlist;
+ char *text, *countstr, *start, *fname;
+ void *free_arg;
+ int count, fd;
+ FILE *fhandle;
+
+ g_return_if_fail(data != NULL);
+
+ if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_OPTIONS |
+ PARAM_FLAG_UNKNOWN_OPTIONS, "lastlog", &optlist,
+ &text, &countstr, &start))
+ return;
+
+ if (*start == '\0' && is_numeric(text, 0) && *text != '0' &&
+ (*countstr == '\0' || is_numeric(countstr, 0))) {
+ start = countstr;
+ countstr = text;
+ text = "";
+ }
+ count = atoi(countstr);
+ if (count == 0) count = -1;
+
+ /* target where to print it */
+ fhandle = NULL;
+ fname = g_hash_table_lookup(optlist, "file");
+ if (fname != NULL) {
+ fname = convert_home(fname);
+ fd = open(fname, O_WRONLY | O_APPEND | O_CREAT,
+ octal2dec(settings_get_int("log_create_mode")));
+ if (fd != -1) {
+ fhandle = fdopen(fd, "a");
+ if (fhandle == NULL)
+ close(fd);
+ }
+ g_free(fname);
+ }
+
+ if (fname != NULL && fhandle == NULL) {
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ "Could not open lastlog: %s", g_strerror(errno));
+ } else {
+ show_lastlog(text, optlist, atoi(start), count, fhandle);
+ if (fhandle != NULL) {
+ if (ferror(fhandle))
+ printtext(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ "Could not write lastlog: %s", g_strerror(errno));
+ fclose(fhandle);
+ }
+ }
+
+ cmd_params_free(free_arg);
+}
+
+void lastlog_init(void)
+{
+ command_bind("lastlog", NULL, (SIGNAL_FUNC) cmd_lastlog);
+
+ command_set_options("lastlog", "!- # force clear -file -window new away word regexp case count date @a @after @before");
+}
+
+void lastlog_deinit(void)
+{
+ command_unbind("lastlog", (SIGNAL_FUNC) cmd_lastlog);
+}
diff --git a/src/fe-text/mainwindow-activity.c b/src/fe-text/mainwindow-activity.c
new file mode 100644
index 0000000..d3759ac
--- /dev/null
+++ b/src/fe-text/mainwindow-activity.c
@@ -0,0 +1,62 @@
+/*
+ mainwindow-activity.c : irssi
+
+ Copyright (C) 2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/fe-text/gui-windows.h>
+
+/* Don't send window activity if window is already visible in
+ another mainwindow */
+static void sig_activity(WINDOW_REC *window)
+{
+ GSList *tmp;
+
+ if (!is_window_visible(window) || window->data_level == 0)
+ return;
+
+ if (!settings_get_bool("activity_hide_visible"))
+ return;
+
+ window->data_level = 0;
+ g_free_and_null(window->hilight_color);
+
+ for (tmp = window->items; tmp != NULL; tmp = tmp->next) {
+ WI_ITEM_REC *item = tmp->data;
+
+ item->data_level = 0;
+ g_free_and_null(item->hilight_color);
+ }
+ signal_stop();
+}
+
+void mainwindow_activity_init(void)
+{
+ settings_add_bool("lookandfeel", "activity_hide_visible", TRUE);
+ signal_add_first("window hilight", (SIGNAL_FUNC) sig_activity);
+ signal_add_first("window activity", (SIGNAL_FUNC) sig_activity);
+}
+
+void mainwindow_activity_deinit(void)
+{
+ signal_remove("window hilight", (SIGNAL_FUNC) sig_activity);
+ signal_remove("window activity", (SIGNAL_FUNC) sig_activity);
+}
diff --git a/src/fe-text/mainwindows-layout.c b/src/fe-text/mainwindows-layout.c
new file mode 100644
index 0000000..ef13892
--- /dev/null
+++ b/src/fe-text/mainwindows-layout.c
@@ -0,0 +1,330 @@
+/*
+ mainwindows-layout.c : irssi
+
+ Copyright (C) 2001 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/lib-config/iconfig.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/levels.h>
+
+#include <irssi/src/fe-text/mainwindows.h>
+#include <irssi/src/fe-text/gui-windows.h>
+#include <irssi/src/fe-text/textbuffer-view.h>
+
+static void sig_layout_window_save(WINDOW_REC *window, CONFIG_NODE *node)
+{
+ WINDOW_REC *active;
+ GUI_WINDOW_REC *gui;
+
+ gui = WINDOW_GUI(window);
+ if (gui->sticky) {
+ iconfig_node_set_bool(node, "sticky", TRUE);
+ active = gui->parent->active;
+ if (window != active)
+ iconfig_node_set_int(node, "parent", active->refnum);
+ }
+
+ if (gui->view->hidden_level != settings_get_level("window_default_hidelevel")) {
+ char *level = bits2level(gui->view->hidden_level);
+ iconfig_node_set_str(node, "hidelevel", level);
+ g_free(level);
+ } else {
+ iconfig_node_set_str(node, "hidelevel", NULL);
+ }
+
+ if (gui->use_scroll)
+ iconfig_node_set_bool(node, "scroll", gui->scroll);
+}
+
+static void sig_layout_window_restore(WINDOW_REC *window, CONFIG_NODE *node)
+{
+ WINDOW_REC *parent;
+ GUI_WINDOW_REC *gui;
+ const char *default_hidelevel = settings_get_str("window_default_hidelevel");
+
+ gui = WINDOW_GUI(window);
+
+ parent = window_find_refnum(config_node_get_int(node, "parent", -1));
+ if (parent != NULL)
+ gui_window_reparent(window, WINDOW_MAIN(parent));
+
+ if (config_node_get_bool(node, "sticky", FALSE))
+ gui_window_set_sticky(window);
+
+ textbuffer_view_set_hidden_level(gui->view, level2bits(config_node_get_str(node, "hidelevel", default_hidelevel), NULL));
+
+ if (config_node_get_str(node, "scroll", NULL) != NULL) {
+ gui->use_scroll = TRUE;
+ gui->scroll = config_node_get_bool(node, "scroll", TRUE);
+ textbuffer_view_set_scroll(gui->view, gui->scroll);
+ }
+}
+
+static void main_window_save(MAIN_WINDOW_REC *window, CONFIG_NODE *node)
+{
+ char num[MAX_INT_STRLEN];
+
+ ltoa(num, window->active->refnum);
+ node = iconfig_node_section(node, num, NODE_TYPE_BLOCK);
+
+ iconfig_node_set_int(node, "first_line", window->first_line);
+ iconfig_node_set_int(node, "lines", window->height);
+ iconfig_node_set_int(node, "first_column", window->first_column);
+ iconfig_node_set_int(node, "columns", window->width);
+}
+
+static void sig_layout_save(void)
+{
+ CONFIG_NODE *node;
+
+ iconfig_set_str(NULL, "mainwindows", NULL);
+ node = iconfig_node_traverse("mainwindows", TRUE);
+
+ g_slist_foreach(mainwindows, (GFunc) main_window_save, node);
+}
+
+static int window_node_cmp(CONFIG_NODE *n1, CONFIG_NODE *n2)
+{
+ return (config_node_get_int(n1, "first_line", 0) ==
+ config_node_get_int(n2, "first_line", 0)
+ &&
+ config_node_get_int(n1, "first_column", 0) >
+ config_node_get_int(n2, "first_column", 0)
+ ) ||
+ config_node_get_int(n1, "first_line", 0) >
+ config_node_get_int(n2, "first_line", 0)
+ ? -1
+ : 1;
+}
+
+/* Returns list of mainwindow nodes sorted by first_line
+ (lowest in screen first) */
+static GSList *get_sorted_windows_config(CONFIG_NODE *node)
+{
+ GSList *tmp, *output;
+
+ output = NULL;
+ tmp = config_node_first(node->value);
+ for (; tmp != NULL; tmp = config_node_next(tmp)) {
+ output = g_slist_insert_sorted(output, tmp->data,
+ (GCompareFunc) window_node_cmp);
+ }
+
+ return output;
+}
+
+static GSList *get_windows_config_filter_line(GSList *in)
+{
+ GSList *tmp, *output;
+
+ output = NULL;
+ for (tmp = in; tmp != NULL; tmp = tmp->next) {
+ CONFIG_NODE *node = tmp->data;
+ if (config_node_get_int(node, "first_column", 0) == 0)
+ output = g_slist_append(output, node);
+ }
+
+ return output;
+}
+
+static GSList *get_windows_config_filter_column(GSList *in, int first_line, int last_line)
+{
+ GSList *tmp, *output;
+
+ output = NULL;
+ for (tmp = in; tmp != NULL; tmp = tmp->next) {
+ int l1, l2;
+ CONFIG_NODE *node = tmp->data;
+ l1 = config_node_get_int(node, "first_line", -1);
+ l2 = l1 + config_node_get_int(node, "lines", 0) - 1;
+ if (l1 >= first_line && l2 <= last_line)
+ output = g_slist_prepend(output, node);
+ }
+
+ return output;
+}
+
+static void sig_layout_restore(void)
+{
+ MAIN_WINDOW_REC *lower_window;
+ WINDOW_REC *window, *first;
+ CONFIG_NODE *node;
+ GSList *tmp, *sorted_config, *lines_config;
+ int avail_height, height, *heights, *widths, max_wins_line;
+ int i, lower_size, lines_count, columns_count, diff;
+
+ node = iconfig_node_traverse("mainwindows", FALSE);
+ if (node == NULL) return;
+
+ sorted_config = get_sorted_windows_config(node);
+ if (sorted_config == NULL) return;
+
+ lines_config = get_windows_config_filter_line(sorted_config);
+ lines_count = g_slist_length(lines_config);
+
+ /* calculate the saved terminal height */
+ avail_height = term_height -
+ screen_reserved_top - screen_reserved_bottom;
+ height = 0;
+ heights = g_new0(int, lines_count);
+ for (i = 0, tmp = lines_config; tmp != NULL; tmp = tmp->next, i++) {
+ CONFIG_NODE *node = tmp->data;
+
+ heights[i] = config_node_get_int(node, "lines", 0);
+ height += heights[i];
+ }
+
+ max_wins_line = (term_width + 1) / (NEW_WINDOW_WIDTH + 1);
+ if (max_wins_line < 1)
+ max_wins_line = 1;
+
+ if (avail_height <= (WINDOW_MIN_SIZE*2)+1) {
+ /* we can fit only one window to screen -
+ give it all the height we can */
+ lines_count = 1;
+ heights[0] = avail_height;
+ } else if (height != avail_height) {
+ /* Terminal's height is different from the saved one.
+ Resize the windows so they fit to screen. */
+ while (height > avail_height &&
+ lines_count*(WINDOW_MIN_SIZE+1) > avail_height) {
+ /* all windows can't fit into screen,
+ remove the lowest ones */
+ lines_count--;
+ }
+
+ /* try to keep the windows' size about the same in percents */
+ for (i = 0; i < lines_count; i++) {
+ int size = avail_height*heights[i]/height;
+ if (size < WINDOW_MIN_SIZE+1)
+ size = WINDOW_MIN_SIZE+1;
+ heights[i] = size;
+ }
+
+ /* give/remove the last bits */
+ height = 0;
+ for (i = 0; i < lines_count; i++)
+ height += heights[i];
+
+ diff = height < avail_height ? 1 : -1;
+ for (i = 0; height != avail_height; i++) {
+ if (i == lines_count)
+ i = 0;
+
+ if (heights[i] > WINDOW_MIN_SIZE+1) {
+ height += diff;
+ heights[i] += diff;
+ }
+ }
+ }
+
+ /* create all the visible windows with correct size */
+ lower_window = NULL; lower_size = 0; first = NULL;
+ for (i = 0, tmp = lines_config; i < lines_count; tmp = tmp->next, i++) {
+ GSList *tmp2, *columns_config, *line;
+ int j, l1, l2;
+ CONFIG_NODE *node = tmp->data;
+ if (node->key == NULL) continue;
+
+ l1 = config_node_get_int(node, "first_line", -1);
+ l2 = l1 + config_node_get_int(node, "lines", 0) - 1;
+ columns_config = get_windows_config_filter_column(sorted_config, l1, l2);
+
+ window = NULL; columns_count = 0;
+ widths = g_new0(int, max_wins_line);
+ for (j = 0, tmp2 = columns_config; j < max_wins_line && tmp2 != NULL; tmp2 = tmp2->next, j++) {
+ int width;
+ WINDOW_REC *new_win;
+ CONFIG_NODE *node2 = tmp2->data;
+ if (node2->key == NULL) continue;
+
+ /* create a new window + mainwindow */
+ signal_emit("gui window create override", 1,
+ GINT_TO_POINTER(window == NULL ? MAIN_WINDOW_TYPE_SPLIT : MAIN_WINDOW_TYPE_RSPLIT));
+
+ new_win = window_create(NULL, TRUE);
+
+ window_set_refnum(new_win, atoi(node2->key));
+ width = config_node_get_int(node2, "columns", NEW_WINDOW_WIDTH);
+ widths[j] = width;
+ columns_count += width + (window == NULL ? 0 : 1);
+
+ if (window == NULL)
+ window = new_win;
+ if (first == NULL)
+ first = new_win;
+
+ window_set_active(new_win);
+ active_mainwin = WINDOW_MAIN(new_win);
+ }
+ if (window == NULL)
+ continue;
+ line = g_slist_reverse(mainwindows_get_line(WINDOW_MAIN(window)));
+ for (j = g_slist_length(line), tmp2 = line; tmp2 != NULL; tmp2 = tmp2->next, j--) {
+ int width = MAX(NEW_WINDOW_WIDTH, widths[j-1] * term_width / columns_count);
+ MAIN_WINDOW_REC *rec = tmp2->data;
+ mainwindow_set_rsize(rec, width);
+ }
+ g_slist_free(line);
+ g_free(widths);
+
+ if (lower_size > 0)
+ mainwindow_set_size(lower_window, lower_size, FALSE);
+
+ lower_window = WINDOW_MAIN(window);
+ lower_size = heights[i];
+ if (lower_size < WINDOW_MIN_SIZE+1)
+ lower_size = WINDOW_MIN_SIZE+1;
+ }
+ g_slist_free(sorted_config);
+ g_free(heights);
+
+ if (lower_size > 0)
+ mainwindow_set_size(lower_window, lower_size, FALSE);
+
+ if (first != NULL) {
+ window_set_active(first);
+ active_mainwin = WINDOW_MAIN(first);
+ }
+}
+
+static void sig_layout_reset(void)
+{
+ iconfig_set_str(NULL, "mainwindows", NULL);
+}
+
+void mainwindows_layout_init(void)
+{
+ signal_add("layout save window", (SIGNAL_FUNC) sig_layout_window_save);
+ signal_add("layout restore window", (SIGNAL_FUNC) sig_layout_window_restore);
+ signal_add("layout save", (SIGNAL_FUNC) sig_layout_save);
+ signal_add_first("layout restore", (SIGNAL_FUNC) sig_layout_restore);
+ signal_add("layout reset", (SIGNAL_FUNC) sig_layout_reset);
+}
+
+void mainwindows_layout_deinit(void)
+{
+ signal_remove("layout save window", (SIGNAL_FUNC) sig_layout_window_save);
+ signal_remove("layout restore window", (SIGNAL_FUNC) sig_layout_window_restore);
+ signal_remove("layout save", (SIGNAL_FUNC) sig_layout_save);
+ signal_remove("layout restore", (SIGNAL_FUNC) sig_layout_restore);
+ signal_remove("layout reset", (SIGNAL_FUNC) sig_layout_reset);
+}
diff --git a/src/fe-text/mainwindows.c b/src/fe-text/mainwindows.c
new file mode 100644
index 0000000..26b9424
--- /dev/null
+++ b/src/fe-text/mainwindows.c
@@ -0,0 +1,2004 @@
+/*
+ mainwindows.c : irssi
+
+ Copyright (C) 1999-2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-text/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/fe-common/core/printtext.h>
+
+#include <irssi/src/fe-text/term.h>
+#include <irssi/src/fe-text/gui-windows.h>
+
+#define NEW_WINDOW_SIZE (WINDOW_MIN_SIZE + 1)
+
+GSList *mainwindows;
+MAIN_WINDOW_REC *active_mainwin;
+MAIN_WINDOW_BORDER_REC *clrtoeol_info;
+
+int screen_reserved_top, screen_reserved_bottom;
+int screen_reserved_left, screen_reserved_right;
+static int screen_width, screen_height;
+
+#define mainwindow_create_screen(window) \
+ term_window_create((window)->first_column + (window)->statusbar_columns_left, \
+ (window)->first_line + (window)->statusbar_lines_top, \
+ (window)->width - (window)->statusbar_columns, \
+ (window)->height - (window)->statusbar_lines)
+
+#define mainwindow_set_screen_size(window) \
+ term_window_move((window)->screen_win, \
+ (window)->first_column + (window)->statusbar_columns_left, \
+ (window)->first_line + (window)->statusbar_lines_top, \
+ (window)->width - (window)->statusbar_columns, \
+ (window)->height - (window)->statusbar_lines);
+
+
+static MAIN_WINDOW_REC *find_window_with_room()
+{
+ MAIN_WINDOW_REC *biggest_rec;
+ GSList *tmp;
+ int space, biggest;
+
+ biggest = 0; biggest_rec = NULL;
+ for (tmp = mainwindows; tmp != NULL; tmp = tmp->next) {
+ MAIN_WINDOW_REC *rec = tmp->data;
+
+ space = MAIN_WINDOW_TEXT_HEIGHT(rec);
+ if (space >= WINDOW_MIN_SIZE+NEW_WINDOW_SIZE && space > biggest) {
+ biggest = space;
+ biggest_rec = rec;
+ }
+ }
+
+ return biggest_rec;
+}
+
+static MAIN_WINDOW_REC *find_window_with_room_right(void)
+{
+ MAIN_WINDOW_REC *biggest_rec;
+ GSList *tmp;
+ int space, biggest;
+
+ biggest = 0; biggest_rec = NULL;
+ for (tmp = mainwindows; tmp != NULL; tmp = tmp->next) {
+ MAIN_WINDOW_REC *rec = tmp->data;
+
+ space = MAIN_WINDOW_TEXT_WIDTH(rec);
+ if (space >= 2 * NEW_WINDOW_WIDTH && space > biggest) {
+ biggest = space;
+ biggest_rec = rec;
+ }
+ }
+
+ return biggest_rec;
+}
+
+#define window_size_equals(window, mainwin) \
+ ((window)->width == MAIN_WINDOW_TEXT_WIDTH(mainwin) && \
+ (window)->height == MAIN_WINDOW_TEXT_HEIGHT(mainwin))
+
+static void mainwindow_resize_windows(MAIN_WINDOW_REC *window)
+{
+ GSList *tmp;
+ int resized;
+
+ mainwindow_set_screen_size(window);
+
+ resized = FALSE;
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ if (rec->gui_data != NULL &&
+ WINDOW_GUI(rec)->parent == window &&
+ !window_size_equals(rec, window)) {
+ resized = TRUE;
+ gui_window_resize(rec, MAIN_WINDOW_TEXT_WIDTH(window),
+ MAIN_WINDOW_TEXT_HEIGHT(window));
+ }
+ }
+
+ if (resized)
+ signal_emit("mainwindow resized", 1, window);
+}
+
+static void mainwindow_resize(MAIN_WINDOW_REC *window, int xdiff, int ydiff)
+{
+ int height, width;
+ if (quitting || (xdiff == 0 && ydiff == 0))
+ return;
+
+ height = window->height + ydiff;
+ width = window->width + xdiff;
+ window->width = window->last_column-window->first_column+1;
+ window->height = window->last_line-window->first_line+1;
+ if (height != window->height || width != window->width) {
+ g_warning("Resizing window %p W:%d expected:%d H:%d expected:%d",
+ window, window->width, width, window->height, height);
+ }
+ window->size_dirty = TRUE;
+}
+
+static GSList *get_sticky_windows_sorted(MAIN_WINDOW_REC *mainwin)
+{
+ GSList *tmp, *list;
+
+ list = NULL;
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ if (WINDOW_GUI(rec)->sticky && WINDOW_MAIN(rec) == mainwin) {
+ list = g_slist_insert_sorted(list, rec, (GCompareFunc)
+ window_refnum_cmp);
+ }
+ }
+
+ return list;
+}
+
+void mainwindow_change_active(MAIN_WINDOW_REC *mainwin,
+ WINDOW_REC *skip_window)
+{
+ WINDOW_REC *window, *other;
+ GSList *tmp;
+
+ mainwin->active = NULL;
+ if (mainwin->sticky_windows) {
+ /* sticky window */
+ tmp = get_sticky_windows_sorted(mainwin);
+ window = tmp->data;
+ if (window == skip_window) {
+ window = tmp->next == NULL ? NULL :
+ tmp->next->data;
+ }
+ g_slist_free(tmp);
+
+ if (window != NULL) {
+ window_set_active(window);
+ return;
+ }
+ }
+
+ other = NULL;
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ if (rec != skip_window) {
+ other = rec;
+ break;
+ }
+ }
+
+ window_set_active(other);
+ if (mainwindows->next != NULL)
+ mainwindow_destroy(mainwin);
+}
+
+void mainwindows_recreate(void)
+{
+ GSList *tmp;
+
+ for (tmp = mainwindows; tmp != NULL; tmp = tmp->next) {
+ MAIN_WINDOW_REC *rec = tmp->data;
+
+ rec->screen_win = mainwindow_create_screen(rec);
+ rec->dirty = TRUE;
+ textbuffer_view_set_window(WINDOW_GUI(rec->active)->view,
+ rec->screen_win);
+ }
+}
+
+MAIN_WINDOW_REC *mainwindow_create(int right)
+{
+ MAIN_WINDOW_REC *rec, *parent;
+ int space;
+
+ rec = g_new0(MAIN_WINDOW_REC, 1);
+ rec->dirty = TRUE;
+
+ if (mainwindows == NULL) {
+ active_mainwin = rec;
+
+ rec->first_line = screen_reserved_top;
+ rec->last_line = term_height-1 - screen_reserved_bottom;
+ rec->height = rec->last_line-rec->first_line+1;
+ rec->first_column = screen_reserved_left;
+ rec->last_column = screen_width-1 - screen_reserved_right;
+ rec->width = rec->last_column-rec->first_column+1;
+ } else {
+ parent = WINDOW_MAIN(active_win);
+
+ if (!right) {
+ GSList *tmp, *line;
+ if (MAIN_WINDOW_TEXT_HEIGHT(parent) <
+ WINDOW_MIN_SIZE+NEW_WINDOW_SIZE)
+ parent = find_window_with_room();
+ if (parent == NULL)
+ return NULL; /* not enough space */
+
+ space = parent->height / 2;
+ rec->first_line = parent->first_line;
+ rec->last_line = rec->first_line + space;
+ rec->height = rec->last_line-rec->first_line+1;
+ rec->first_column = screen_reserved_left;
+ rec->last_column = screen_width-1 - screen_reserved_right;
+ rec->width = rec->last_column-rec->first_column+1;
+
+ line = mainwindows_get_line(parent);
+ for (tmp = line; tmp != NULL; tmp = tmp->next) {
+ MAIN_WINDOW_REC *rec = tmp->data;
+ rec->first_line += space+1;
+ mainwindow_resize(rec, 0, -space-1);
+ }
+ g_slist_free(line);
+ } else {
+ if (MAIN_WINDOW_TEXT_WIDTH(parent) < 2 * NEW_WINDOW_WIDTH) {
+ parent = find_window_with_room_right();
+ }
+ if (parent == NULL)
+ return NULL; /* not enough space */
+
+ space = parent->width / 2;
+ rec->first_line = parent->first_line;
+ rec->last_line = parent->last_line;
+ rec->height = parent->height;
+ rec->first_column = parent->last_column - space + 1;
+ rec->last_column = parent->last_column;
+ rec->width = rec->last_column-rec->first_column+1;
+
+ parent->last_column -= space+1;
+ mainwindow_resize(parent, -space-1, 0);
+ }
+ }
+
+ rec->screen_win = mainwindow_create_screen(rec);
+ term_refresh(NULL);
+
+ mainwindows = g_slist_append(mainwindows, rec);
+ signal_emit("mainwindow created", 1, rec);
+ return rec;
+}
+
+static MAIN_WINDOW_REC *mainwindows_find_lower(MAIN_WINDOW_REC *window)
+{
+ int last_line;
+ MAIN_WINDOW_REC *best;
+ GSList *tmp;
+
+ /* unfortunate special case: if the window has been resized
+ and there is not enough room, the last_line could become
+ smaller than the first_line, sending us in an infinite
+ loop */
+ if (window != NULL)
+ last_line =
+ window->last_line > window->first_line ? window->last_line : window->first_line;
+ else
+ last_line = -1;
+
+ best = NULL;
+ for (tmp = mainwindows; tmp != NULL; tmp = tmp->next) {
+ MAIN_WINDOW_REC *rec = tmp->data;
+
+ if (rec->first_line > last_line &&
+ (best == NULL || rec->first_line < best->first_line))
+ best = rec;
+ }
+
+ return best;
+}
+
+static MAIN_WINDOW_REC *mainwindows_find_right(MAIN_WINDOW_REC *window, int find_first)
+{
+ int first_line, last_line, last_column;
+ MAIN_WINDOW_REC *best;
+ GSList *tmp;
+
+ if (window != NULL) {
+ first_line = window->first_line;
+ last_line = window->last_line;
+ last_column = window->last_column;
+ } else {
+ first_line = last_line = last_column = -1;
+ }
+
+ if (find_first)
+ last_column = -1;
+
+ best = NULL;
+ for (tmp = mainwindows; tmp != NULL; tmp = tmp->next) {
+ MAIN_WINDOW_REC *rec = tmp->data;
+
+ if (rec->first_line >= first_line &&
+ rec->last_line <= last_line &&
+ rec->first_column > last_column &&
+ (best == NULL || rec->first_column < best->first_column))
+ best = rec;
+ }
+
+ return best;
+}
+
+static MAIN_WINDOW_REC *mainwindows_find_lower_right(MAIN_WINDOW_REC *window)
+{
+ MAIN_WINDOW_REC *best;
+
+ best = mainwindows_find_right(window, FALSE);
+ if (best == NULL)
+ best = mainwindows_find_lower(window);
+
+ return best;
+}
+
+static MAIN_WINDOW_REC *mainwindows_find_upper(MAIN_WINDOW_REC *window)
+{
+ int first_line;
+ MAIN_WINDOW_REC *best;
+ GSList *tmp;
+
+ if (window != NULL)
+ first_line = window->first_line;
+ else
+ first_line = screen_height;
+
+ best = NULL;
+ for (tmp = mainwindows; tmp != NULL; tmp = tmp->next) {
+ MAIN_WINDOW_REC *rec = tmp->data;
+
+ if (rec->last_line < first_line &&
+ (best == NULL || rec->last_line > best->last_line))
+ best = rec;
+ }
+
+ return best;
+}
+
+static MAIN_WINDOW_REC *mainwindows_find_left(MAIN_WINDOW_REC *window, int find_last)
+{
+ int first_line, last_line, first_column;
+ MAIN_WINDOW_REC *best;
+ GSList *tmp;
+
+ if (window != NULL) {
+ first_line = window->first_line;
+ last_line = window->last_line;
+ first_column = window->first_column;
+ } else {
+ first_line = last_line = screen_height;
+ first_column = screen_width;
+ }
+
+ if (find_last)
+ first_column = screen_width;
+
+ best = NULL;
+ for (tmp = mainwindows; tmp != NULL; tmp = tmp->next) {
+ MAIN_WINDOW_REC *rec = tmp->data;
+
+ if (rec->first_line >= first_line &&
+ rec->last_line <= last_line &&
+ rec->last_column < first_column &&
+ (best == NULL || rec->last_column > best->last_column))
+ best = rec;
+ }
+
+ return best;
+}
+
+static MAIN_WINDOW_REC *mainwindows_find_upper_left(MAIN_WINDOW_REC *window)
+{
+ MAIN_WINDOW_REC *best;
+
+ best = mainwindows_find_left(window, FALSE);
+ if (best == NULL)
+ best = mainwindows_find_upper(window);
+
+ return best;
+}
+
+static MAIN_WINDOW_REC *mainwindows_find_left_upper(MAIN_WINDOW_REC *window)
+{
+ MAIN_WINDOW_REC *best;
+
+ best = mainwindows_find_left(window, FALSE);
+ if (best == NULL)
+ best = mainwindows_find_left(mainwindows_find_upper(window), TRUE);
+
+ return best;
+}
+
+GSList *mainwindows_get_line(MAIN_WINDOW_REC *rec)
+{
+ MAIN_WINDOW_REC *win;
+ GSList *list;
+
+ list = NULL;
+
+ for (win = mainwindows_find_left(rec, FALSE);
+ win != NULL;
+ win = mainwindows_find_left(win, FALSE)) {
+ list = g_slist_append(list, win);
+ }
+
+ if (rec != NULL)
+ list = g_slist_append(list, rec);
+
+ for (win = mainwindows_find_right(rec, FALSE);
+ win != NULL;
+ win = mainwindows_find_right(win, FALSE)) {
+ list = g_slist_append(list, win);
+ }
+
+ return list;
+}
+
+/* add back the space which was occupied by destroyed mainwindow first_line .. last_line */
+static void mainwindows_add_space(MAIN_WINDOW_REC *destroy_win)
+{
+ MAIN_WINDOW_REC *rec;
+ int size, rsize;
+
+ if (destroy_win->last_line < destroy_win->first_line)
+ return;
+
+ if (destroy_win->last_column < destroy_win->first_column)
+ return;
+
+ rsize = destroy_win->last_column-destroy_win->first_column+1;
+ rec = mainwindows_find_left(destroy_win, FALSE);
+ if (rec != NULL) {
+ rec->last_column = destroy_win->last_column;
+ mainwindow_resize(rec, rsize+1, 0);
+ return;
+ }
+
+ rec = mainwindows_find_right(destroy_win, FALSE);
+ if (rec != NULL) {
+ rec->first_column = destroy_win->first_column;
+ mainwindow_resize(rec, rsize+1, 0);
+ return;
+ }
+
+ size = destroy_win->last_line-destroy_win->first_line+1;
+
+ rec = mainwindows_find_lower(destroy_win);
+ if (rec != NULL) {
+ GSList *tmp, *list;
+ list = mainwindows_get_line(rec);
+
+ for (tmp = list; tmp != NULL; tmp = tmp->next) {
+ MAIN_WINDOW_REC *rec = tmp->data;
+ rec->first_line = destroy_win->first_line;
+ mainwindow_resize(rec, 0, size);
+ }
+
+ g_slist_free(list);
+ return;
+ }
+
+ rec = mainwindows_find_upper(destroy_win);
+ if (rec != NULL) {
+ GSList *tmp, *list;
+ list = mainwindows_get_line(rec);
+
+ for (tmp = list; tmp != NULL; tmp = tmp->next) {
+ MAIN_WINDOW_REC *rec = tmp->data;
+ rec->last_line = destroy_win->last_line;
+ mainwindow_resize(rec, 0, size);
+ }
+
+ g_slist_free(list);
+ return;
+ }
+}
+
+static void gui_windows_remove_parent(MAIN_WINDOW_REC *window)
+{
+ MAIN_WINDOW_REC *new_parent;
+ GSList *tmp;
+
+ new_parent = mainwindows->data;
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ if (rec->gui_data != NULL && WINDOW_MAIN(rec) == window)
+ gui_window_reparent(rec, new_parent);
+ }
+}
+
+static void mainwindow_destroy_full(MAIN_WINDOW_REC *window, int respace)
+{
+ g_return_if_fail(window != NULL);
+
+ mainwindows = g_slist_remove(mainwindows, window);
+ signal_emit("mainwindow destroyed", 1, window);
+
+ term_window_destroy(window->screen_win);
+
+ if (mainwindows != NULL) {
+ gui_windows_remove_parent(window);
+ if (respace) {
+ mainwindows_add_space(window);
+ mainwindows_redraw();
+ }
+ }
+
+ g_free(window);
+
+ if (active_mainwin == window) active_mainwin = NULL;
+}
+
+void mainwindow_destroy(MAIN_WINDOW_REC *window)
+{
+ mainwindow_destroy_full(window, !quitting);
+}
+
+void mainwindow_destroy_half(MAIN_WINDOW_REC *window)
+{
+ mainwindow_destroy_full(window, FALSE);
+}
+
+void mainwindows_redraw(void)
+{
+ GSList *tmp;
+
+ irssi_set_dirty();
+ for (tmp = mainwindows; tmp != NULL; tmp = tmp->next) {
+ MAIN_WINDOW_REC *rec = tmp->data;
+
+ rec->dirty = TRUE;
+ }
+}
+
+static int mainwindows_compare(MAIN_WINDOW_REC *w1, MAIN_WINDOW_REC *w2)
+{
+ return w1->first_line < w2->first_line ? -1
+ : w1->first_line > w2->first_line ? 1
+ : w1->first_column < w2->first_column ? -1
+ : w1->first_column > w2->first_column ? 1
+ : 0;
+}
+
+static int mainwindows_compare_reverse(MAIN_WINDOW_REC *w1, MAIN_WINDOW_REC *w2)
+{
+ return w1->first_line < w2->first_line ? 1
+ : w1->first_line > w2->first_line ? -1
+ : w1->first_column < w2->first_column ? 1
+ : w1->first_column > w2->first_column ? -1
+ : 0;
+}
+
+GSList *mainwindows_get_sorted(int reverse)
+{
+ GSList *tmp, *list;
+
+ list = NULL;
+ for (tmp = mainwindows; tmp != NULL; tmp = tmp->next) {
+ list = g_slist_insert_sorted(list, tmp->data, (GCompareFunc)
+ (reverse ? mainwindows_compare_reverse : mainwindows_compare));
+ }
+
+ return list;
+}
+
+static void mainwindows_resize_smaller(int ydiff)
+{
+ MAIN_WINDOW_REC *rec;
+ GSList *sorted, *tmp;
+ int space;
+
+ sorted = NULL;
+ for (rec = mainwindows_find_lower(NULL);
+ rec != NULL;
+ rec = mainwindows_find_lower(rec)) {
+ sorted = g_slist_prepend(sorted, rec);
+ }
+ if (sorted == NULL)
+ return;
+
+ for (;;) {
+ int skip_active = FALSE;
+ space = 0;
+ /* for each line of windows, calculate the space that can be reduced still */
+ for (tmp = sorted; tmp != NULL; tmp = tmp->next) {
+ int min;
+ GSList *line, *ltmp;
+ rec = tmp->data;
+ line = mainwindows_get_line(rec);
+ min = screen_height - ydiff;
+ for (ltmp = line; ltmp != NULL; ltmp = ltmp->next) {
+ int lmin;
+ MAIN_WINDOW_REC *win = ltmp->data;
+ if (win == active_mainwin && tmp == sorted)
+ skip_active = TRUE;
+
+ lmin = MAIN_WINDOW_TEXT_HEIGHT(win)-WINDOW_MIN_SIZE;
+ if (lmin < min)
+ min = lmin;
+ }
+ g_slist_free(line);
+ space += min;
+ }
+
+ if (space >= -ydiff)
+ break;
+
+ rec = sorted->data;
+ if (skip_active && sorted->next != NULL)
+ rec = sorted->next->data;
+ sorted = g_slist_remove(sorted, rec);
+
+ if (sorted != NULL) {
+ /* terminal is too small - destroy the
+ uppest window and try again */
+ GSList *line, *ltmp;
+ line = mainwindows_get_line(rec);
+ for (ltmp = line; ltmp != NULL; ltmp = ltmp->next) {
+ MAIN_WINDOW_REC *win = ltmp->data;
+ mainwindow_destroy(win);
+ }
+ g_slist_free(line);
+ } else {
+ /* only one line of window in screen.. just force the resize */
+ GSList *line, *ltmp;
+ line = mainwindows_get_line(rec);
+ for (ltmp = line; ltmp != NULL; ltmp = ltmp->next) {
+ MAIN_WINDOW_REC *win = ltmp->data;
+ win->last_line += ydiff;
+ mainwindow_resize(win, 0, ydiff);
+ }
+ g_slist_free(line);
+ return;
+ }
+ }
+
+ /* resize windows that have space */
+ for (tmp = sorted; tmp != NULL && ydiff < 0; tmp = tmp->next) {
+ int min;
+ GSList *line, *ltmp;
+
+ rec = tmp->data;
+ line = mainwindows_get_line(rec);
+ min = screen_height - ydiff;
+ for (ltmp = line; ltmp != NULL; ltmp = ltmp->next) {
+ int lmin;
+ MAIN_WINDOW_REC *win = ltmp->data;
+ lmin = MAIN_WINDOW_TEXT_HEIGHT(win)-WINDOW_MIN_SIZE;
+ if (lmin < min)
+ min = lmin;
+ }
+ space = min;
+
+ if (space == 0) {
+ /* move the line */
+ for (ltmp = line; ltmp != NULL; ltmp = ltmp->next) {
+ MAIN_WINDOW_REC *win = ltmp->data;
+ mainwindow_resize(win, 0, 0);
+ win->size_dirty = TRUE;
+ win->first_line += ydiff;
+ win->last_line += ydiff;
+ signal_emit("mainwindow moved", 1, win);
+ }
+ } else {
+ if (space > -ydiff) space = -ydiff;
+ for (ltmp = line; ltmp != NULL; ltmp = ltmp->next) {
+ MAIN_WINDOW_REC *win = ltmp->data;
+ win->last_line += ydiff;
+ win->first_line += ydiff + space;
+
+ mainwindow_resize(win, 0, -space);
+ }
+ ydiff += space;
+ }
+ g_slist_free(line);
+ }
+
+ g_slist_free(sorted);
+}
+
+static void mainwindows_rresize_line(int xdiff, MAIN_WINDOW_REC *win)
+{
+ int windows, i, extra_width, next_column, shrunk;
+ int *widths;
+ GSList *line, *tmp;
+
+ line = mainwindows_get_line(win);
+ windows = g_slist_length(line);
+ widths = g_new0(int, windows);
+
+ extra_width = screen_width - windows + 1;
+ for (tmp = line, i = 0; tmp != NULL; tmp = tmp->next, i++) {
+ MAIN_WINDOW_REC *rec = tmp->data;
+ widths[i] = (MAIN_WINDOW_TEXT_WIDTH(rec) * (screen_width - windows + 1)) / (screen_width - xdiff - windows + 1);
+ extra_width -= widths[i] + rec->statusbar_columns;
+ }
+ shrunk = FALSE;
+ for (i = windows; extra_width < 0; i = i > 1 ? i - 1 : windows) {
+ if (widths[i-1] > NEW_WINDOW_WIDTH || (i == 1 && !shrunk)) {
+ widths[i-1]--;
+ extra_width++;
+ shrunk = i == 1;
+ }
+ }
+
+ next_column = 0;
+
+#define extra ( (i >= screen_width % windows && i < extra_width + (screen_width % windows)) \
+ || i + windows < extra_width + (screen_width % windows) ? 1 : 0 )
+
+ for (tmp = line, i = 0; tmp != NULL; tmp = tmp->next, i++) {
+ MAIN_WINDOW_REC *rec = tmp->data;
+ rec->first_column = next_column;
+ rec->last_column = rec->first_column + widths[i] + rec->statusbar_columns + extra - 1;
+ next_column = rec->last_column + 2;
+ mainwindow_resize(rec, widths[i] + rec->statusbar_columns + extra - rec->width, 0);
+ rec->size_dirty = TRUE;
+ }
+#undef extra
+
+ g_free(widths);
+ g_slist_free(line);
+}
+
+void mainwindows_resize(int width, int height)
+{
+ int xdiff, ydiff;
+
+ xdiff = width-screen_width;
+ ydiff = height-screen_height;
+ screen_width = width;
+ screen_height = height;
+
+ if (ydiff > 0) {
+ /* algorithm: enlarge bottom window */
+ MAIN_WINDOW_REC *rec;
+ GSList *line, *tmp;
+ line = mainwindows_get_line(mainwindows_find_upper(NULL));
+ for (tmp = line; tmp != NULL; tmp = tmp->next) {
+ rec = tmp->data;
+ rec->last_line += ydiff;
+ mainwindow_resize(rec, 0, ydiff);
+ }
+ g_slist_free(line);
+ }
+
+ if (xdiff > 0) {
+ /* algorithm: distribute new space on each line */
+ MAIN_WINDOW_REC *win;
+
+ for (win = mainwindows_find_lower(NULL);
+ win != NULL;
+ win = mainwindows_find_lower(win)) {
+ mainwindows_rresize_line(xdiff, win);
+ }
+ }
+
+ if (xdiff < 0) {
+ /* algorithm: shrink each window,
+ destroy windows on the right if no room */
+ MAIN_WINDOW_REC *win;
+
+ for (win = mainwindows_find_lower(NULL);
+ win != NULL;
+ win = mainwindows_find_lower(win)) {
+ int max_windows, i, last_column;
+ GSList *line, *tmp;
+
+ line = mainwindows_get_line(win);
+ max_windows = (screen_width + 1) / (NEW_WINDOW_WIDTH + 1);
+ if (max_windows < 1)
+ max_windows = 1;
+ last_column = screen_width - 1;
+ for (tmp = line, i = 0; tmp != NULL; tmp = tmp->next, i++) {
+ MAIN_WINDOW_REC *rec = tmp->data;
+ if (i >= max_windows)
+ mainwindow_destroy_half(rec);
+ else
+ last_column = rec->last_column;
+ }
+ win = line->data;
+ g_slist_free(line);
+
+ mainwindows_rresize_line(screen_width - last_column + 1, win);
+ }
+ }
+
+ if (ydiff < 0) {
+ /* algorithm: shrink windows starting from bottom,
+ destroy windows starting from top if no room */
+ mainwindows_resize_smaller(ydiff);
+ }
+
+ /* if we lost our active mainwin, get a new one */
+ if (active_mainwin == NULL && active_win != NULL && !quitting) {
+ active_mainwin = WINDOW_MAIN(active_win);
+ window_set_active(active_mainwin->active);
+ }
+
+ signal_emit("terminal resized", 0);
+
+ irssi_redraw();
+}
+
+int mainwindows_reserve_lines(int top, int bottom)
+{
+ MAIN_WINDOW_REC *window;
+ int ret;
+
+ ret = -1;
+ if (top != 0) {
+ GSList *list, *tmp;
+ g_return_val_if_fail(top > 0 || screen_reserved_top > top, -1);
+
+ ret = screen_reserved_top;
+ screen_reserved_top += top;
+
+ list = mainwindows_get_line(mainwindows_find_lower(NULL));
+ for (tmp = list; tmp != NULL; tmp = tmp->next) {
+ window = tmp->data;
+ window->first_line += top;
+ mainwindow_resize(window, 0, -top);
+ }
+ g_slist_free(list);
+ }
+
+ if (bottom != 0) {
+ GSList *list, *tmp;
+ g_return_val_if_fail(bottom > 0 || screen_reserved_bottom > bottom, -1);
+
+ ret = screen_reserved_bottom;
+ screen_reserved_bottom += bottom;
+
+ list = mainwindows_get_line(mainwindows_find_upper(NULL));
+ for (tmp = list; tmp != NULL; tmp = tmp->next) {
+ window = tmp->data;
+ window->last_line -= bottom;
+ mainwindow_resize(window, 0, -bottom);
+ }
+ g_slist_free(list);
+ }
+
+ return ret;
+}
+
+int mainwindow_set_statusbar_lines(MAIN_WINDOW_REC *window,
+ int top, int bottom)
+{
+ int ret;
+
+ ret = -1;
+ if (top != 0) {
+ ret = window->statusbar_lines_top;
+ window->statusbar_lines_top += top;
+ window->statusbar_lines += top;
+ }
+
+ if (bottom != 0) {
+ ret = window->statusbar_lines_bottom;
+ window->statusbar_lines_bottom += bottom;
+ window->statusbar_lines += bottom;
+ }
+
+ if (top+bottom != 0)
+ window->size_dirty = TRUE;
+
+ return ret;
+}
+
+static void mainwindows_resize_two(GSList *grow_list,
+ GSList *shrink_list, int count)
+{
+ GSList *tmp;
+ MAIN_WINDOW_REC *win;
+
+ irssi_set_dirty();
+
+ for (tmp = shrink_list; tmp != NULL; tmp = tmp->next) {
+ win = tmp->data;
+ mainwindow_resize(win, 0, -count);
+ win->dirty = TRUE;
+ }
+ for (tmp = grow_list; tmp != NULL; tmp = tmp->next) {
+ win = tmp->data;
+ mainwindow_resize(win, 0, count);
+ win->dirty = TRUE;
+ }
+}
+
+static int try_shrink_lower(MAIN_WINDOW_REC *window, int count)
+{
+ MAIN_WINDOW_REC *shrink_win;
+
+ g_return_val_if_fail(count >= 0, FALSE);
+
+ shrink_win = mainwindows_find_lower(window);
+ if (shrink_win != NULL) {
+ int ok;
+ GSList *shrink_list, *tmp;
+ MAIN_WINDOW_REC *win;
+
+ ok = TRUE;
+ shrink_list = mainwindows_get_line(shrink_win);
+
+ for (tmp = shrink_list; tmp != NULL; tmp = tmp->next) {
+ win = tmp->data;
+ if (MAIN_WINDOW_TEXT_HEIGHT(win)-count < WINDOW_MIN_SIZE) {
+ ok = FALSE;
+ break;
+ }
+ }
+ if (ok) {
+ GSList *grow_list;
+ grow_list = mainwindows_get_line(window);
+
+ for (tmp = shrink_list; tmp != NULL; tmp = tmp->next) {
+ win = tmp->data;
+ win->first_line += count;
+ }
+ for (tmp = grow_list; tmp != NULL; tmp = tmp->next) {
+ win = tmp->data;
+ win->last_line += count;
+ }
+
+ mainwindows_resize_two(grow_list, shrink_list, count);
+ g_slist_free(grow_list);
+ }
+
+ g_slist_free(shrink_list);
+ return ok;
+ }
+
+ return FALSE;
+}
+
+static int try_shrink_upper(MAIN_WINDOW_REC *window, int count)
+{
+ MAIN_WINDOW_REC *shrink_win;
+
+ g_return_val_if_fail(count >= 0, FALSE);
+
+ shrink_win = mainwindows_find_upper(window);
+ if (shrink_win != NULL) {
+ int ok;
+ GSList *shrink_list, *tmp;
+ MAIN_WINDOW_REC *win;
+
+ ok = TRUE;
+ shrink_list = mainwindows_get_line(shrink_win);
+
+ for (tmp = shrink_list; tmp != NULL; tmp = tmp->next) {
+ win = tmp->data;
+ if (MAIN_WINDOW_TEXT_HEIGHT(win)-count < WINDOW_MIN_SIZE) {
+ ok = FALSE;
+ break;
+ }
+ }
+ if (ok) {
+ GSList *grow_list;
+ grow_list = mainwindows_get_line(window);
+ for (tmp = grow_list; tmp != NULL; tmp = tmp->next) {
+ win = tmp->data;
+ win->first_line -= count;
+ }
+ for (tmp = shrink_list; tmp != NULL; tmp = tmp->next) {
+ win = tmp->data;
+ win->last_line -= count;
+ }
+ mainwindows_resize_two(grow_list, shrink_list, count);
+ g_slist_free(grow_list);
+ }
+ g_slist_free(shrink_list);
+ return ok;
+ }
+
+ return FALSE;
+}
+
+static int mainwindow_grow(MAIN_WINDOW_REC *window, int count,
+ int resize_lower)
+{
+ if (!resize_lower || !try_shrink_lower(window, count)) {
+ if (!try_shrink_upper(window, count)) {
+ if (resize_lower || !try_shrink_lower(window, count))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static int try_grow_lower(MAIN_WINDOW_REC *window, int count)
+{
+ MAIN_WINDOW_REC *grow_win;
+
+ grow_win = mainwindows_find_lower(window);
+ if (grow_win != NULL) {
+ MAIN_WINDOW_REC *win;
+ GSList *grow_list, *shrink_list, *tmp;
+ grow_list = mainwindows_get_line(grow_win);
+ shrink_list = mainwindows_get_line(window);
+ for (tmp = grow_list; tmp != NULL; tmp = tmp->next) {
+ win = tmp->data;
+ win->first_line -= count;
+ }
+ for (tmp = shrink_list; tmp != NULL; tmp = tmp->next) {
+ win = tmp->data;
+ win->last_line -= count;
+ }
+ mainwindows_resize_two(grow_list, shrink_list, count);
+ g_slist_free(shrink_list);
+ g_slist_free(grow_list);
+ }
+
+ return grow_win != NULL;
+}
+
+static int try_grow_upper(MAIN_WINDOW_REC *window, int count)
+{
+ MAIN_WINDOW_REC *grow_win;
+
+ grow_win = mainwindows_find_upper(window);
+ if (grow_win != NULL) {
+ MAIN_WINDOW_REC *win;
+ GSList *grow_list, *shrink_list, *tmp;
+ grow_list = mainwindows_get_line(grow_win);
+ shrink_list = mainwindows_get_line(window);
+ for (tmp = grow_list; tmp != NULL; tmp = tmp->next) {
+ win = tmp->data;
+ win->last_line += count;
+ }
+ for (tmp = shrink_list; tmp != NULL; tmp = tmp->next) {
+ win = tmp->data;
+ win->first_line += count;
+ }
+ mainwindows_resize_two(grow_list, shrink_list, count);
+ g_slist_free(shrink_list);
+ g_slist_free(grow_list);
+ }
+
+ return grow_win != NULL;
+}
+
+static int mainwindow_shrink(MAIN_WINDOW_REC *window, int count, int resize_lower)
+{
+ g_return_val_if_fail(count >= 0, FALSE);
+
+ if (MAIN_WINDOW_TEXT_HEIGHT(window)-count < WINDOW_MIN_SIZE)
+ return FALSE;
+
+ if (!resize_lower || !try_grow_lower(window, count)) {
+ if (!try_grow_upper(window, count)) {
+ if (resize_lower || !try_grow_lower(window, count))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+static void mainwindows_rresize_two(MAIN_WINDOW_REC *grow_win,
+ MAIN_WINDOW_REC *shrink_win, int count)
+{
+ irssi_set_dirty();
+
+ mainwindow_resize(grow_win, count, 0);
+ mainwindow_resize(shrink_win, -count, 0);
+ grow_win->dirty = TRUE;
+ shrink_win->dirty = TRUE;
+}
+
+static int try_shrink_right(MAIN_WINDOW_REC *window, int count)
+{
+ MAIN_WINDOW_REC *shrink_win;
+
+ g_return_val_if_fail(count >= 0, FALSE);
+
+ shrink_win = mainwindows_find_right(window, FALSE);
+ if (shrink_win != NULL) {
+ if (MAIN_WINDOW_TEXT_WIDTH(shrink_win)-count < NEW_WINDOW_WIDTH) {
+ return FALSE;
+ }
+
+ shrink_win->first_column += count;
+ window->last_column += count;
+
+ mainwindows_rresize_two(window, shrink_win, count);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static int try_shrink_left(MAIN_WINDOW_REC *window, int count)
+{
+ MAIN_WINDOW_REC *shrink_win;
+
+ g_return_val_if_fail(count >= 0, FALSE);
+
+ shrink_win = mainwindows_find_left(window, FALSE);
+ if (shrink_win != NULL) {
+ if (MAIN_WINDOW_TEXT_WIDTH(shrink_win)-count < NEW_WINDOW_WIDTH) {
+ return FALSE;
+ }
+ window->first_column -= count;
+ shrink_win->last_column -= count;
+
+ mainwindows_rresize_two(window, shrink_win, count);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static int mainwindow_grow_right(MAIN_WINDOW_REC *window, int count)
+{
+ if (!try_shrink_right(window, count)) {
+ if (!try_shrink_left(window, count))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static int try_grow_right(MAIN_WINDOW_REC *window, int count)
+{
+ MAIN_WINDOW_REC *grow_win;
+
+ grow_win = mainwindows_find_right(window, FALSE);
+ if (grow_win != NULL) {
+ grow_win->first_column -= count;
+ window->last_column -= count;
+ mainwindows_rresize_two(grow_win, window, count);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static int try_grow_left(MAIN_WINDOW_REC *window, int count)
+{
+ MAIN_WINDOW_REC *grow_win;
+
+ grow_win = mainwindows_find_left(window, FALSE);
+ if (grow_win != NULL) {
+ grow_win->last_column += count;
+ window->first_column += count;
+ mainwindows_rresize_two(grow_win, window, count);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static int mainwindow_shrink_right(MAIN_WINDOW_REC *window, int count)
+{
+ g_return_val_if_fail(count >= 0, FALSE);
+
+ if (MAIN_WINDOW_TEXT_WIDTH(window)-count < NEW_WINDOW_WIDTH)
+ return FALSE;
+
+ if (!try_grow_right(window, count)) {
+ if (!try_grow_left(window, count))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/* Change the window height - the height includes the lines needed for
+ statusbars. If resize_lower is TRUE, the lower window is first tried
+ to be resized instead of upper window. */
+void mainwindow_set_size(MAIN_WINDOW_REC *window, int height, int resize_lower)
+{
+ height -= window->height;
+ if (height < 0)
+ mainwindow_shrink(window, -height, resize_lower);
+ else
+ mainwindow_grow(window, height, resize_lower);
+}
+
+void mainwindow_set_rsize(MAIN_WINDOW_REC *window, int width)
+{
+ width -= window->width;
+ if (width < 0)
+ mainwindow_shrink_right(window, -width);
+ else
+ mainwindow_grow_right(window, width);
+}
+
+void mainwindows_redraw_dirty(void)
+{
+ GSList *tmp;
+
+ for (tmp = mainwindows; tmp != NULL; tmp = tmp->next) {
+ MAIN_WINDOW_REC *rec = tmp->data;
+
+ if (rec->size_dirty) {
+ rec->size_dirty = FALSE;
+ mainwindow_resize_windows(rec);
+ }
+ if (rec->dirty) {
+ rec->dirty = FALSE;
+ gui_window_redraw(rec->active);
+ } else if (WINDOW_GUI(rec->active)->view->dirty) {
+ gui_window_redraw(rec->active);
+ }
+ }
+}
+
+static void mainwindow_grow_int(int count)
+{
+ if (count == 0) {
+ return;
+ } else if (count < 0) {
+ if (!mainwindow_shrink(WINDOW_MAIN(active_win), -count, FALSE)) {
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, TXT_WINDOW_TOO_SMALL);
+ }
+ } else {
+ if (!mainwindow_grow(WINDOW_MAIN(active_win), count, FALSE)) {
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, TXT_WINDOW_TOO_SMALL);
+ }
+ }
+}
+
+static void window_balance_vertical(void)
+{
+ GSList *sorted, *stmp, *line, *ltmp;
+ int avail_size, unit_size, bigger_units;
+ int windows, last_line, old_size;
+ MAIN_WINDOW_REC *win;
+
+ windows = g_slist_length(mainwindows);
+ if (windows == 1) return;
+
+ sorted = NULL;
+ windows = 0;
+ for (win = mainwindows_find_lower(NULL);
+ win != NULL;
+ win = mainwindows_find_lower(win)) {
+ windows++;
+ sorted = g_slist_append(sorted, win);
+ }
+
+ avail_size = term_height - screen_reserved_top-screen_reserved_bottom;
+ unit_size = avail_size/windows;
+ bigger_units = avail_size%windows;
+
+ last_line = screen_reserved_top;
+ for (stmp = sorted; stmp != NULL; stmp = stmp->next) {
+ win = stmp->data;
+ line = mainwindows_get_line(win);
+
+ for (ltmp = line; ltmp != NULL; ltmp = ltmp->next) {
+ MAIN_WINDOW_REC *rec = ltmp->data;
+ old_size = rec->height;
+ rec->first_line = last_line;
+ rec->last_line = rec->first_line + unit_size-1;
+
+ if (bigger_units > 0) {
+ rec->last_line++;
+ }
+
+ mainwindow_resize(rec, 0, rec->last_line - rec->first_line + 1 - old_size);
+ }
+ if (line != NULL && bigger_units > 0) {
+ bigger_units--;
+ }
+ last_line = win->last_line+1;
+
+ g_slist_free(line);
+ }
+ g_slist_free(sorted);
+
+ mainwindows_redraw();
+}
+
+/* SYNTAX: WINDOW HIDE [<number>|<name>] */
+static void cmd_window_hide(const char *data)
+{
+ WINDOW_REC *window;
+
+ if (mainwindows->next == NULL) {
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_CANT_HIDE_LAST);
+ return;
+ }
+
+ if (*data == '\0')
+ window = active_win;
+ else if (is_numeric(data, 0)) {
+ window = window_find_refnum(atoi(data));
+ if (window == NULL) {
+ printformat_window(active_win, MSGLEVEL_CLIENTERROR,
+ TXT_REFNUM_NOT_FOUND, data);
+ }
+ } else {
+ window = window_find_item(active_win->active_server, data);
+ }
+
+ if (window == NULL || !is_window_visible(window))
+ return;
+
+ if (WINDOW_MAIN(window)->sticky_windows) {
+ if (!settings_get_bool("autounstick_windows")) {
+ printformat_window(active_win, MSGLEVEL_CLIENTERROR,
+ TXT_CANT_HIDE_STICKY_WINDOWS);
+ return;
+ }
+ }
+
+ mainwindow_destroy(WINDOW_MAIN(window));
+
+ if (active_mainwin == NULL) {
+ active_mainwin = WINDOW_MAIN(active_win);
+ window_set_active(active_mainwin->active);
+ }
+}
+
+/* SYNTAX: WINDOW SHOW [-right] <number>|<name> */
+static void cmd_window_show(const char *data)
+{
+ GHashTable *optlist;
+ MAIN_WINDOW_REC *parent;
+ WINDOW_REC *window;
+ char *args;
+ void *free_arg;
+ int right;
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
+ "window show", &optlist, &args))
+ return;
+
+ right = g_hash_table_lookup(optlist, "right") != NULL;
+
+ if (*args == '\0') {
+ cmd_params_free(free_arg);
+ cmd_return_error(CMDERR_NOT_ENOUGH_PARAMS);
+ }
+
+ if (is_numeric(args, '\0')) {
+ window = window_find_refnum(atoi(args));
+ if (window == NULL) {
+ printformat_window(active_win, MSGLEVEL_CLIENTERROR,
+ TXT_REFNUM_NOT_FOUND, args);
+ }
+ } else {
+ window = window_find_item(active_win->active_server, args);
+ }
+
+ cmd_params_free(free_arg);
+
+ if (window == NULL || is_window_visible(window))
+ return;
+
+ if (WINDOW_GUI(window)->sticky) {
+ if (!settings_get_bool("autounstick_windows")) {
+ printformat_window(active_win, MSGLEVEL_CLIENTERROR,
+ TXT_CANT_SHOW_STICKY_WINDOWS);
+ return;
+ }
+ }
+
+ parent = mainwindow_create(right);
+ if (parent == NULL) {
+ printformat_window(active_win, MSGLEVEL_CLIENTERROR, TXT_WINDOW_TOO_SMALL);
+ return;
+ }
+
+ parent->active = window;
+ gui_window_reparent(window, parent);
+
+ if (settings_get_bool("autostick_split_windows"))
+ gui_window_set_sticky(window);
+
+ active_mainwin = NULL;
+ window_set_active(window);
+}
+
+static void mainwindow_grow_right_int(int count)
+{
+ if (count == 0) {
+ return;
+ } else if (count < 0) {
+ if (!mainwindow_shrink_right(WINDOW_MAIN(active_win), -count)) {
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, TXT_WINDOW_TOO_SMALL);
+ }
+ } else {
+ if (!mainwindow_grow_right(WINDOW_MAIN(active_win), count)) {
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE, TXT_WINDOW_TOO_SMALL);
+ }
+ }
+}
+
+
+/* SYNTAX: WINDOW GROW [-right] [<lines>|<columns>] */
+static void cmd_window_grow(const char *data)
+{
+ GHashTable *optlist;
+ char *args;
+ void *free_arg;
+ int count;
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
+ "window grow", &optlist, &args))
+ return;
+
+ count = *data == '\0' ? 1 : atoi(args);
+
+ if (g_hash_table_lookup(optlist, "right") != NULL) {
+ mainwindow_grow_right_int(count);
+ } else {
+ mainwindow_grow_int(count);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: WINDOW SHRINK [-right] [<lines>|<columns>] */
+static void cmd_window_shrink(const char *data)
+{
+ GHashTable *optlist;
+ char *args;
+ void *free_arg;
+ int count;
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
+ "window shrink", &optlist, &args))
+ return;
+
+ count = *data == '\0' ? 1 : atoi(args);
+ if (count < -INT_MAX) count = -INT_MAX;
+
+ if (g_hash_table_lookup(optlist, "right") != NULL) {
+ mainwindow_grow_right_int(-count);
+ } else {
+ mainwindow_grow_int(-count);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: WINDOW SIZE [-right] <lines>|<columns> */
+static void cmd_window_size(const char *data)
+{
+ GHashTable *optlist;
+ char *args;
+ void *free_arg;
+ int size;
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
+ "window size", &optlist, &args))
+ return;
+
+ if (!is_numeric(args, 0)) {
+ cmd_params_free(free_arg);
+ return;
+ }
+ size = atoi(data);
+
+ if (g_hash_table_lookup(optlist, "right") != NULL) {
+ size -= MAIN_WINDOW_TEXT_WIDTH(WINDOW_MAIN(active_win));
+
+ mainwindow_grow_right_int(size);
+ } else {
+ size -= WINDOW_MAIN(active_win)->height -
+ WINDOW_MAIN(active_win)->statusbar_lines;
+ if (size < -INT_MAX) size = -INT_MAX;
+
+ mainwindow_grow_int(size);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+static void window_balance_horizontal(void)
+{
+ GSList *line, *ltmp;
+ int avail_width, unit_width, bigger_units;
+ int windows, last_column, old_width;
+ MAIN_WINDOW_REC *win;
+
+ line = mainwindows_get_line(WINDOW_MAIN(active_win));
+ windows = g_slist_length(line);
+ if (windows == 1) {
+ g_slist_free(line);
+ return;
+ }
+
+ avail_width = term_width - screen_reserved_left-screen_reserved_right - windows + 1;
+ unit_width = avail_width/windows;
+ bigger_units = avail_width%windows;
+
+ last_column = screen_reserved_left;
+ for (ltmp = line; ltmp != NULL; ltmp = ltmp->next) {
+ win = ltmp->data;
+ old_width = win->width;
+ win->first_column = last_column;
+ win->last_column = win->first_column + unit_width-1;
+
+ if (bigger_units > 0) {
+ win->last_column++;
+ bigger_units--;
+ }
+
+ mainwindow_resize(win, win->last_column - win->first_column + 1 - old_width, 0);
+ last_column = win->last_column+2;
+ }
+ g_slist_free(line);
+
+ mainwindows_redraw();
+}
+
+/* SYNTAX: WINDOW BALANCE [-right] */
+static void cmd_window_balance(const char *data)
+{
+ GHashTable *optlist;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
+ "window balance", &optlist))
+ return;
+
+ if (g_hash_table_lookup(optlist, "right") != NULL) {
+ window_balance_horizontal();
+ } else {
+ window_balance_vertical();
+ }
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: WINDOW UP [-directional] */
+static void cmd_window_up(const char *data)
+{
+ MAIN_WINDOW_REC *rec;
+ GHashTable *optlist;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
+ "window up", &optlist))
+ return;
+
+ if (g_hash_table_lookup(optlist, "directional") != NULL) {
+ rec = mainwindows_find_upper(active_mainwin);
+ if (rec == NULL)
+ rec = mainwindows_find_upper(NULL);
+ } else {
+ rec = mainwindows_find_left_upper(active_mainwin);
+ if (rec == NULL)
+ rec = mainwindows_find_left_upper(NULL);
+ }
+ if (rec != NULL)
+ window_set_active(rec->active);
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: WINDOW DOWN [-directional] */
+static void cmd_window_down(const char *data)
+{
+ MAIN_WINDOW_REC *rec;
+ GHashTable *optlist;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
+ "window down", &optlist))
+ return;
+
+ if (g_hash_table_lookup(optlist, "directional") != NULL) {
+ rec = mainwindows_find_lower(active_mainwin);
+ if (rec == NULL)
+ rec = mainwindows_find_lower(NULL);
+ } else {
+ rec = mainwindows_find_lower_right(active_mainwin);
+ if (rec == NULL)
+ rec = mainwindows_find_lower_right(NULL);
+ }
+
+ if (rec != NULL)
+ window_set_active(rec->active);
+
+ cmd_params_free(free_arg);
+}
+
+#define WINDOW_STICKY_MATCH(window, sticky_parent) \
+ ((!WINDOW_GUI(window)->sticky && (sticky_parent) == NULL) || \
+ (WINDOW_GUI(window)->sticky && \
+ WINDOW_MAIN(window) == (sticky_parent)))
+
+static int window_refnum_left(int refnum, int wrap)
+{
+ MAIN_WINDOW_REC *find_sticky;
+ WINDOW_REC *window;
+ int start_refnum = refnum;
+
+ window = window_find_refnum(refnum);
+ g_return_val_if_fail(window != NULL, -1);
+
+ find_sticky = WINDOW_MAIN(window)->sticky_windows ?
+ WINDOW_MAIN(window) : NULL;
+
+ do {
+ refnum = window_refnum_prev(refnum, wrap);
+ if (refnum < 0 || refnum == start_refnum)
+ break;
+
+ window = window_find_refnum(refnum);
+ } while (!WINDOW_STICKY_MATCH(window, find_sticky) || is_window_visible(window));
+
+ return refnum;
+}
+
+static int window_refnum_right(int refnum, int wrap)
+{
+ MAIN_WINDOW_REC *find_sticky;
+ WINDOW_REC *window;
+ int start_refnum = refnum;
+
+ window = window_find_refnum(refnum);
+ g_return_val_if_fail(window != NULL, -1);
+
+ find_sticky = WINDOW_MAIN(window)->sticky_windows ?
+ WINDOW_MAIN(window) : NULL;
+
+ do {
+ refnum = window_refnum_next(refnum, wrap);
+ if (refnum < 0 || refnum == start_refnum)
+ break;
+
+ window = window_find_refnum(refnum);
+ } while (!WINDOW_STICKY_MATCH(window, find_sticky) || is_window_visible(window));
+
+ return refnum;
+}
+
+/* SYNTAX: WINDOW LEFT [-directional] */
+static void cmd_window_left(const char *data, SERVER_REC *server, void *item)
+{
+ GHashTable *optlist;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
+ "window left", &optlist))
+ return;
+
+ if (g_hash_table_lookup(optlist, "directional") != NULL) {
+ MAIN_WINDOW_REC *rec;
+
+ rec = mainwindows_find_left(active_mainwin, FALSE);
+ if (rec == NULL)
+ rec = mainwindows_find_left(active_mainwin, TRUE);
+ if (rec != NULL)
+ window_set_active(rec->active);
+ } else {
+ int refnum;
+
+ refnum = window_refnum_left(active_win->refnum, TRUE);
+ if (refnum != -1)
+ window_set_active(window_find_refnum(refnum));
+ }
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: WINDOW RIGHT [-directional] */
+static void cmd_window_right(const char *data)
+{
+ GHashTable *optlist;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
+ "window right", &optlist))
+ return;
+
+ if (g_hash_table_lookup(optlist, "directional") != NULL) {
+ MAIN_WINDOW_REC *rec;
+
+ rec = mainwindows_find_right(active_mainwin, FALSE);
+ if (rec == NULL)
+ rec = mainwindows_find_right(active_mainwin, TRUE);
+ if (rec != NULL)
+ window_set_active(rec->active);
+ } else {
+ int refnum;
+
+ refnum = window_refnum_right(active_win->refnum, TRUE);
+ if (refnum != -1)
+ window_set_active(window_find_refnum(refnum));
+ }
+
+ cmd_params_free(free_arg);
+}
+
+static void window_reparent(WINDOW_REC *win, MAIN_WINDOW_REC *mainwin)
+{
+ MAIN_WINDOW_REC *old_mainwin;
+
+ old_mainwin = WINDOW_MAIN(win);
+
+ if (old_mainwin != mainwin) {
+ gui_window_set_unsticky(win);
+
+ if (old_mainwin->active == win) {
+ mainwindow_change_active(old_mainwin, win);
+ if (active_mainwin == NULL) {
+ active_mainwin = mainwin;
+ window_set_active(mainwin->active);
+ }
+ }
+
+ gui_window_reparent(win, mainwin);
+ window_set_active(win);
+ }
+}
+
+/* SYNTAX: WINDOW STICK [<ref#>] [ON|OFF] */
+static void cmd_window_stick(const char *data)
+{
+ MAIN_WINDOW_REC *mainwin;
+ WINDOW_REC *win;
+
+ mainwin = active_mainwin;
+ win = active_mainwin->active;
+
+ if (is_numeric(data, ' ')) {
+ /* ref# specified */
+ win = window_find_refnum(atoi(data));
+ if (win == NULL) {
+ printformat_window(active_win, MSGLEVEL_CLIENTERROR,
+ TXT_REFNUM_NOT_FOUND, data);
+ return;
+ }
+
+ while (*data != ' ' && *data != '\0') data++;
+ while (*data == ' ') data++;
+ }
+
+ if (g_ascii_strncasecmp(data, "OF", 2) == 0 || i_toupper(*data) == 'N') {
+ /* unset sticky */
+ if (!WINDOW_GUI(win)->sticky) {
+ printformat_window(win, MSGLEVEL_CLIENTERROR,
+ TXT_WINDOW_NOT_STICKY);
+ } else {
+ gui_window_set_unsticky(win);
+ printformat_window(win, MSGLEVEL_CLIENTNOTICE,
+ TXT_WINDOW_UNSET_STICKY);
+ }
+ } else {
+ /* set sticky */
+ window_reparent(win, mainwin);
+ gui_window_set_sticky(win);
+
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_WINDOW_SET_STICKY);
+ }
+}
+
+/* SYNTAX: WINDOW MOVE LEFT [-directional] */
+static void cmd_window_move_left(const char *data)
+{
+ GHashTable *optlist;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
+ "window move left", &optlist))
+ return;
+
+ if (g_hash_table_lookup(optlist, "directional") != NULL) {
+ MAIN_WINDOW_REC *rec;
+
+ rec = mainwindows_find_left(active_mainwin, FALSE);
+ if (rec == NULL)
+ rec = mainwindows_find_left(active_mainwin, TRUE);
+ if (rec != NULL)
+ window_reparent(active_win, rec);
+ } else {
+ int refnum;
+
+ refnum = window_refnum_left(active_win->refnum, TRUE);
+ if (refnum != -1)
+ window_set_refnum(active_win, refnum);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: WINDOW MOVE RIGHT [-directional] */
+static void cmd_window_move_right(const char *data)
+{
+ GHashTable *optlist;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
+ "window move right", &optlist))
+ return;
+
+ if (g_hash_table_lookup(optlist, "directional") != NULL) {
+ MAIN_WINDOW_REC *rec;
+
+ rec = mainwindows_find_right(active_mainwin, FALSE);
+ if (rec == NULL)
+ rec = mainwindows_find_right(active_mainwin, TRUE);
+ if (rec != NULL)
+ window_reparent(active_win, rec);
+ } else {
+ int refnum;
+
+ refnum = window_refnum_right(active_win->refnum, TRUE);
+ if (refnum != -1)
+ window_set_refnum(active_win, refnum);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: WINDOW MOVE UP [-directional] */
+static void cmd_window_move_up(const char *data)
+{
+ MAIN_WINDOW_REC *rec;
+ GHashTable *optlist;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
+ "window move up", &optlist))
+ return;
+
+ if (g_hash_table_lookup(optlist, "directional") != NULL) {
+ rec = mainwindows_find_upper(active_mainwin);
+ } else {
+ rec = mainwindows_find_upper_left(active_mainwin);
+ }
+
+ if (rec != NULL)
+ window_reparent(active_win, rec);
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: WINDOW MOVE DOWN [-directional] */
+static void cmd_window_move_down(const char *data)
+{
+ MAIN_WINDOW_REC *rec;
+ GHashTable *optlist;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, PARAM_FLAG_GETREST | PARAM_FLAG_OPTIONS,
+ "window move down", &optlist))
+ return;
+
+ if (g_hash_table_lookup(optlist, "directional") != NULL) {
+ rec = mainwindows_find_lower(active_mainwin);
+ } else {
+ rec = mainwindows_find_lower_right(active_mainwin);
+ }
+
+ if (rec != NULL)
+ window_reparent(active_win, rec);
+
+ cmd_params_free(free_arg);
+}
+
+static void windows_print_sticky(WINDOW_REC *win)
+{
+ MAIN_WINDOW_REC *mainwin;
+ GSList *tmp, *list;
+ GString *str;
+
+ mainwin = WINDOW_MAIN(win);
+
+ /* convert to string */
+ str = g_string_new(NULL);
+ list = get_sticky_windows_sorted(mainwin);
+ for (tmp = list; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ g_string_append_printf(str, "#%d, ", rec->refnum);
+ }
+ g_string_truncate(str, str->len-2);
+ g_slist_free(list);
+
+ printformat_window(win, MSGLEVEL_CLIENTCRAP,
+ TXT_WINDOW_INFO_STICKY, str->str);
+ g_string_free(str, TRUE);
+}
+
+static void sig_window_print_info(WINDOW_REC *win)
+{
+ GUI_WINDOW_REC *gui;
+
+ gui = WINDOW_GUI(win);
+ if (gui->use_scroll) {
+ printformat_window(win, MSGLEVEL_CLIENTCRAP,
+ TXT_WINDOW_INFO_SCROLL,
+ gui->scroll ? "yes" : "no");
+ }
+
+ if (WINDOW_MAIN(win)->sticky_windows)
+ windows_print_sticky(win);
+}
+
+void mainwindows_init(void)
+{
+ screen_width = term_width;
+ screen_height = term_height;
+
+ mainwindows = NULL;
+ active_mainwin = NULL;
+ clrtoeol_info = g_new0(MAIN_WINDOW_BORDER_REC, 1);
+ screen_reserved_top = screen_reserved_bottom = 0;
+ screen_reserved_left = screen_reserved_right = 0;
+
+ command_bind("window grow", NULL, (SIGNAL_FUNC) cmd_window_grow);
+ command_bind("window shrink", NULL, (SIGNAL_FUNC) cmd_window_shrink);
+ command_bind("window size", NULL, (SIGNAL_FUNC) cmd_window_size);
+ command_bind("window balance", NULL, (SIGNAL_FUNC) cmd_window_balance);
+ command_bind("window hide", NULL, (SIGNAL_FUNC) cmd_window_hide);
+ command_bind("window show", NULL, (SIGNAL_FUNC) cmd_window_show);
+ command_bind("window up", NULL, (SIGNAL_FUNC) cmd_window_up);
+ command_bind("window down", NULL, (SIGNAL_FUNC) cmd_window_down);
+ command_bind("window left", NULL, (SIGNAL_FUNC) cmd_window_left);
+ command_bind("window right", NULL, (SIGNAL_FUNC) cmd_window_right);
+ command_bind("window stick", NULL, (SIGNAL_FUNC) cmd_window_stick);
+ command_bind("window move left", NULL, (SIGNAL_FUNC) cmd_window_move_left);
+ command_bind("window move right", NULL, (SIGNAL_FUNC) cmd_window_move_right);
+ command_bind("window move up", NULL, (SIGNAL_FUNC) cmd_window_move_up);
+ command_bind("window move down", NULL, (SIGNAL_FUNC) cmd_window_move_down);
+ signal_add("window print info", (SIGNAL_FUNC) sig_window_print_info);
+
+ command_set_options("window show", "right");
+ command_set_options("window grow", "right");
+ command_set_options("window shrink", "right");
+ command_set_options("window size", "right");
+ command_set_options("window balance", "right");
+ command_set_options("window up", "directional");
+ command_set_options("window down", "directional");
+ command_set_options("window left", "directional");
+ command_set_options("window right", "directional");
+ command_set_options("window move left", "directional");
+ command_set_options("window move right", "directional");
+ command_set_options("window move up", "directional");
+ command_set_options("window move down", "directional");
+}
+
+void mainwindows_deinit(void)
+{
+ while (mainwindows != NULL)
+ mainwindow_destroy(mainwindows->data);
+ g_free(clrtoeol_info);
+
+ command_unbind("window grow", (SIGNAL_FUNC) cmd_window_grow);
+ command_unbind("window shrink", (SIGNAL_FUNC) cmd_window_shrink);
+ command_unbind("window size", (SIGNAL_FUNC) cmd_window_size);
+ command_unbind("window balance", (SIGNAL_FUNC) cmd_window_balance);
+ command_unbind("window hide", (SIGNAL_FUNC) cmd_window_hide);
+ command_unbind("window show", (SIGNAL_FUNC) cmd_window_show);
+ command_unbind("window up", (SIGNAL_FUNC) cmd_window_up);
+ command_unbind("window down", (SIGNAL_FUNC) cmd_window_down);
+ command_unbind("window left", (SIGNAL_FUNC) cmd_window_left);
+ command_unbind("window right", (SIGNAL_FUNC) cmd_window_right);
+ command_unbind("window stick", (SIGNAL_FUNC) cmd_window_stick);
+ command_unbind("window move left", (SIGNAL_FUNC) cmd_window_move_left);
+ command_unbind("window move right", (SIGNAL_FUNC) cmd_window_move_right);
+ command_unbind("window move up", (SIGNAL_FUNC) cmd_window_move_up);
+ command_unbind("window move down", (SIGNAL_FUNC) cmd_window_move_down);
+ signal_remove("window print info", (SIGNAL_FUNC) sig_window_print_info);
+}
diff --git a/src/fe-text/mainwindows.h b/src/fe-text/mainwindows.h
new file mode 100644
index 0000000..52cf2df
--- /dev/null
+++ b/src/fe-text/mainwindows.h
@@ -0,0 +1,74 @@
+#ifndef IRSSI_FE_TEXT_MAINWINDOWS_H
+#define IRSSI_FE_TEXT_MAINWINDOWS_H
+
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-text/term.h>
+
+#define WINDOW_MIN_SIZE 2
+#define NEW_WINDOW_WIDTH 20 /* must be >= MIN_SCREEN_WIDTH defined in term.c */
+
+#define MAIN_WINDOW_TEXT_HEIGHT(window) \
+ ((window)->height-(window)->statusbar_lines)
+
+#define MAIN_WINDOW_TEXT_WIDTH(window) \
+ ((window)->width-(window)->statusbar_columns)
+
+typedef struct {
+ WINDOW_REC *active;
+
+ TERM_WINDOW *screen_win;
+ int sticky_windows; /* number of sticky windows */
+
+ int first_line, last_line; /* first/last line used by this window (0..x) (includes statusbars) */
+ int first_column, last_column; /* first/last column used by this window (0..x) (includes statusbars) */
+ int width, height; /* width/height of the window (includes statusbars) */
+
+ GSList *statusbars;
+ int statusbar_lines_top, statusbar_lines_bottom;
+ int statusbar_lines; /* top+bottom */
+ int statusbar_columns_left, statusbar_columns_right;
+ int statusbar_columns; /* left+right */
+
+ unsigned int dirty:1; /* This window needs a redraw */
+ unsigned int size_dirty:1; /* We'll need to resize the window, but haven't got around doing it just yet. */
+} MAIN_WINDOW_REC;
+
+typedef struct {
+ char *color;
+ TERM_WINDOW *window;
+} MAIN_WINDOW_BORDER_REC;
+
+extern GSList *mainwindows;
+extern MAIN_WINDOW_REC *active_mainwin;
+extern MAIN_WINDOW_BORDER_REC *clrtoeol_info;
+extern int screen_reserved_top, screen_reserved_bottom;
+
+void mainwindows_init(void);
+void mainwindows_deinit(void);
+
+MAIN_WINDOW_REC *mainwindow_create(int);
+void mainwindow_destroy(MAIN_WINDOW_REC *window);
+
+void mainwindows_redraw(void);
+void mainwindows_recreate(void);
+
+/* Change the window height - the height includes the lines needed for
+ statusbars. If resize_lower is TRUE, the lower window is first tried
+ to be resized instead of upper window. */
+void mainwindow_set_size(MAIN_WINDOW_REC *window, int height,
+ int resize_lower);
+void mainwindow_set_rsize(MAIN_WINDOW_REC *window, int width);
+void mainwindows_resize(int width, int height);
+
+void mainwindow_change_active(MAIN_WINDOW_REC *mainwin,
+ WINDOW_REC *skip_window);
+
+int mainwindows_reserve_lines(int top, int bottom);
+int mainwindow_set_statusbar_lines(MAIN_WINDOW_REC *window,
+ int top, int bottom);
+void mainwindows_redraw_dirty(void);
+
+GSList *mainwindows_get_sorted(int reverse);
+GSList *mainwindows_get_line(MAIN_WINDOW_REC *rec);
+
+#endif
diff --git a/src/fe-text/meson.build b/src/fe-text/meson.build
new file mode 100644
index 0000000..3ceeda1
--- /dev/null
+++ b/src/fe-text/meson.build
@@ -0,0 +1,69 @@
+# this file is part of irssi
+
+executable('irssi',
+ files(
+ #### terminfo_sources ####
+ 'term-terminfo.c',
+ 'terminfo-core.c',
+
+ #### irssi sources ####
+ 'gui-entry.c',
+ 'gui-expandos.c',
+ 'gui-printtext.c',
+ 'gui-readline.c',
+ 'gui-windows.c',
+ 'irssi.c',
+ 'lastlog.c',
+ 'mainwindow-activity.c',
+ 'mainwindows-layout.c',
+ 'mainwindows.c',
+ 'module-formats.c',
+ 'statusbar-config.c',
+ 'statusbar-items.c',
+ 'statusbar.c',
+ 'term.c',
+ 'textbuffer-commands.c',
+ 'textbuffer-formats.c',
+ 'textbuffer-view.c',
+ 'textbuffer.c',
+ )
+ + [ irssi_version_h ],
+ include_directories : rootinc,
+ implicit_include_directories : false,
+ export_dynamic : true,
+ link_with : [
+ libconfig_a,
+ libcore_a,
+ libfe_common_core_a,
+ libirc_a,
+ libfe_common_irc_a,
+ libfe_irc_dcc_a,
+ libfe_irc_notifylist_a,
+ ],
+ install : true,
+ dependencies : dep
+ + textui_dep
+)
+
+install_headers(
+ files(
+ 'gui-printtext.h',
+ 'gui-windows.h',
+ 'mainwindows.h',
+ 'statusbar-item.h',
+ 'statusbar.h',
+ 'term.h',
+ 'textbuffer-formats.h',
+ 'textbuffer-view.h',
+ 'textbuffer.h',
+ ),
+ subdir : incdir / 'src' / 'fe-text')
+
+# noinst_headers = files(
+# 'gui-entry.h',
+# 'gui-readline.h',
+# 'module-formats.h'
+# 'module.h',
+# 'statusbar-config.h',
+# 'terminfo-core.h',
+# )
diff --git a/src/fe-text/module-formats.c b/src/fe-text/module-formats.c
new file mode 100644
index 0000000..6c2ba0a
--- /dev/null
+++ b/src/fe-text/module-formats.c
@@ -0,0 +1,107 @@
+/*
+ module-formats.c : irssi
+
+ Copyright (C) 2000 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-common/core/formats.h>
+
+FORMAT_REC gui_text_formats[] = {
+ /* clang-format off */
+ { MODULE_NAME, "Text user interface", 0 },
+
+ /* ---- */
+ { NULL, "Lastlog", 0 },
+
+ { "lastlog_too_long", "/LASTLOG would print $0 lines. If you really want to print all these lines use -force option.", 1, { 1 } },
+ { "lastlog_count", "{hilight Lastlog}: $0 lines", 1, { 1 } },
+ { "lastlog_start", "{hilight Lastlog}:", 0 },
+ { "lastlog_end", "{hilight End of Lastlog}", 0 },
+ { "lastlog_separator", "--", 0 },
+ { "lastlog_date", "%%F ", 0 },
+
+ /* ---- */
+ { NULL, "Windows", 0 },
+
+ { "refnum_not_found", "Window number $0 not found", 1, { 0 } },
+ { "window_too_small", "Not enough room to resize this window", 0 },
+ { "cant_hide_last", "You can't hide the last window", 0 },
+ { "cant_hide_sticky_windows", "You can't hide sticky windows (use /SET autounstick_windows ON)", 0 },
+ { "cant_show_sticky_windows", "You can't show sticky windows (use /SET autounstick_windows ON)", 0 },
+ { "window_not_sticky", "Window is not sticky", 0 },
+ { "window_set_sticky", "Window set sticky", 0 },
+ { "window_unset_sticky", "Window is not sticky anymore", 0 },
+ { "window_info_sticky", "%#Sticky : $0", 1, { 0 } },
+ { "window_info_scroll", "%#Scroll : $0", 1, { 0 } },
+ { "window_scroll", "Window scroll mode is now $0", 1, { 0 } },
+ { "window_scroll_unknown", "Unknown scroll mode $0, must be ON, OFF or DEFAULT", 1, { 0 } },
+ { "window_hidelevel", "Window hidden level is now $0", 1, { 0 } },
+
+ /* ---- */
+ { NULL, "Statusbars", 0 },
+
+ { "statusbar_list_header", "%#Name Type Placement Position Visible", 0 },
+ { "statusbar_list_footer", "", 0 },
+ { "statusbar_list", "%#$[30]0 $[6]1 $[9]2 $[8]3 $4", 5, { 0, 0, 0, 1, 0 } },
+ { "statusbar_info_name", "%#Statusbar: {hilight $0}", 1, { 0 } },
+ { "statusbar_info_type", "%#Type : $0", 1, { 0 } },
+ { "statusbar_info_placement", "%#Placement: $0", 1, { 0 } },
+ { "statusbar_info_position", "%#Position : $0", 1, { 1 } },
+ { "statusbar_info_visible", "%#Visible : $0", 1, { 0 } },
+ { "statusbar_info_item_header", "%#Items : Name Priority Alignment", 0 },
+ { "statusbar_info_item_footer", "", 0 },
+ { "statusbar_info_item_name", "%# : $[35]0 $[9]1 $2", 3, { 0, 1, 0 } },
+ { "statusbar_not_found", "Statusbar doesn't exist: $0", 1, { 0 } },
+ { "statusbar_not_found", "Statusbar is disabled: $0", 1, { 0 } },
+ { "statusbar_item_not_found", "Statusbar item doesn't exist: $0", 1, { 0 } },
+ { "statusbar_unknown_command", "Unknown statusbar command: $0", 1, { 0 } },
+ { "statusbar_unknown_type", "Statusbar type must be 'window' or 'root'", 1, { 0 } },
+ { "statusbar_unknown_placement", "Statusbar placement must be 'top' or 'bottom'", 1, { 0 } },
+ { "statusbar_unknown_visibility", "Statusbar visibility must be 'always', 'active' or 'inactive'", 1, { 0 } },
+
+ /* ---- */
+ { NULL, "Pasting", 0 },
+
+ { "paste_warning", "Pasting $0 lines to $1. Press Ctrl-K if you wish to do this or Ctrl-C to cancel. Ctrl-P to print the paste content, Ctrl-E to insert the paste in the input line, Ctrl-U to pass the paste to a signal handler.", 2, { 1, 0 } },
+ { "paste_prompt", "Hit Ctrl-K to paste, Ctrl-C to abort?", 0 },
+ { "paste_content", "%_>%_ $0", 1, { 0 } },
+
+ /* ---- */
+ { NULL, "Welcome", 0 },
+
+ { "irssi_banner",
+ " ___ _%:"
+ "|_ _|_ _ _____(_)%:"
+ " | || '_(_-<_-< |%:"
+ "|___|_| /__/__/_|%:"
+ "Irssi v$J - https://irssi.org", 0 },
+ { "welcome_firsttime",
+ "- - - - - - - - - - - - - - - - - - - - - - - - - - - -\n"
+ "Hi there! If this is your first time using Irssi, you%:"
+ "might want to go to our website and read the startup%:"
+ "documentation to get you going.%:%:"
+ "Our community and staff are available to assist you or%:"
+ "to answer any questions you may have.%:%:"
+ "Use the /HELP command to get detailed information about%:"
+ "the available commands.%:"
+ "- - - - - - - - - - - - - - - - - - - - - - - - - - - -", 0 },
+ { "welcome_init_settings", "The following settings were initialized", 0 },
+
+ { NULL, NULL, 0 }
+ /* clang-format on */
+};
diff --git a/src/fe-text/module-formats.h b/src/fe-text/module-formats.h
new file mode 100644
index 0000000..5392705
--- /dev/null
+++ b/src/fe-text/module-formats.h
@@ -0,0 +1,67 @@
+#include <irssi/src/fe-common/core/formats.h>
+
+enum {
+ TXT_MODULE_NAME,
+
+ TXT_FILL_1,
+
+ TXT_LASTLOG_TOO_LONG,
+ TXT_LASTLOG_COUNT,
+ TXT_LASTLOG_START,
+ TXT_LASTLOG_END,
+ TXT_LASTLOG_SEPARATOR,
+ TXT_LASTLOG_DATE,
+
+ TXT_FILL_2,
+
+ TXT_REFNUM_NOT_FOUND,
+ TXT_WINDOW_TOO_SMALL,
+ TXT_CANT_HIDE_LAST,
+ TXT_CANT_HIDE_STICKY_WINDOWS,
+ TXT_CANT_SHOW_STICKY_WINDOWS,
+ TXT_WINDOW_NOT_STICKY,
+ TXT_WINDOW_SET_STICKY,
+ TXT_WINDOW_UNSET_STICKY,
+ TXT_WINDOW_INFO_STICKY,
+ TXT_WINDOW_INFO_SCROLL,
+ TXT_WINDOW_SCROLL,
+ TXT_WINDOW_SCROLL_UNKNOWN,
+ TXT_WINDOW_HIDELEVEL,
+
+ TXT_FILL_3,
+
+ TXT_STATUSBAR_LIST_HEADER,
+ TXT_STATUSBAR_LIST_FOOTER,
+ TXT_STATUSBAR_LIST,
+ TXT_STATUSBAR_INFO_NAME,
+ TXT_STATUSBAR_INFO_TYPE,
+ TXT_STATUSBAR_INFO_PLACEMENT,
+ TXT_STATUSBAR_INFO_POSITION,
+ TXT_STATUSBAR_INFO_VISIBLE,
+ TXT_STATUSBAR_INFO_ITEM_HEADER,
+ TXT_STATUSBAR_INFO_ITEM_FOOTER,
+ TXT_STATUSBAR_INFO_ITEM_NAME,
+ TXT_STATUSBAR_NOT_FOUND,
+ TXT_STATUSBAR_NOT_ENABLED,
+ TXT_STATUSBAR_ITEM_NOT_FOUND,
+ TXT_STATUSBAR_UNKNOWN_COMMAND,
+ TXT_STATUSBAR_UNKNOWN_TYPE,
+ TXT_STATUSBAR_UNKNOWN_PLACEMENT,
+ TXT_STATUSBAR_UNKNOWN_VISIBILITY,
+
+ TXT_FILL_4,
+
+ TXT_PASTE_WARNING,
+ TXT_PASTE_PROMPT,
+ TXT_PASTE_CONTENT,
+
+ TXT_FILL_5, /* Welcome */
+
+ TXT_IRSSI_BANNER,
+ TXT_WELCOME_FIRSTTIME,
+ TXT_WELCOME_INIT_SETTINGS,
+
+ TXT_COUNT
+};
+
+extern FORMAT_REC gui_text_formats[TXT_COUNT+1];
diff --git a/src/fe-text/module.h b/src/fe-text/module.h
new file mode 100644
index 0000000..b1eeeb0
--- /dev/null
+++ b/src/fe-text/module.h
@@ -0,0 +1,8 @@
+#include <irssi/src/common.h>
+#include <irssi/src/fe-text/term.h>
+
+#define MODULE_NAME "fe-text"
+
+extern int quitting;
+void irssi_redraw(void);
+void irssi_set_dirty(void);
diff --git a/src/fe-text/statusbar-config.c b/src/fe-text/statusbar-config.c
new file mode 100644
index 0000000..3cfa537
--- /dev/null
+++ b/src/fe-text/statusbar-config.c
@@ -0,0 +1,826 @@
+/*
+ statusbar-config.c : irssi
+
+ Copyright (C) 2001 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/fe-text/module-formats.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/lib-config/iconfig.h>
+#include <irssi/src/core/misc.h>
+
+#include <irssi/src/fe-text/statusbar.h>
+#include <irssi/src/fe-common/core/printtext.h>
+
+static void read_statusbar_config_from_node(CONFIG_NODE *node);
+
+static STATUSBAR_CONFIG_REC *
+statusbar_config_create(STATUSBAR_GROUP_REC *group, const char *name)
+{
+ STATUSBAR_CONFIG_REC *bar;
+
+ g_return_val_if_fail(group != NULL, NULL);
+ g_return_val_if_fail(name != NULL, NULL);
+
+ bar = g_new0(STATUSBAR_CONFIG_REC, 1);
+ group->config_bars = g_slist_append(group->config_bars, bar);
+
+ bar->name = g_strdup(name);
+ return bar;
+}
+
+static SBAR_ITEM_CONFIG_REC *
+statusbar_item_config_create(STATUSBAR_CONFIG_REC *bar, const char *name,
+ int priority, int right_alignment)
+{
+ SBAR_ITEM_CONFIG_REC *rec;
+
+ g_return_val_if_fail(bar != NULL, NULL);
+ g_return_val_if_fail(name != NULL, NULL);
+
+ rec = g_new0(SBAR_ITEM_CONFIG_REC, 1);
+ bar->items = g_slist_append(bar->items, rec);
+
+ rec->name = g_strdup(name);
+ rec->priority = priority;
+ rec->right_alignment = right_alignment;
+
+ return rec;
+}
+
+static void statusbar_config_item_destroy(STATUSBAR_CONFIG_REC *barconfig,
+ SBAR_ITEM_CONFIG_REC *itemconfig)
+{
+ barconfig->items = g_slist_remove(barconfig->items, itemconfig);
+
+ g_free(itemconfig->name);
+ g_free(itemconfig);
+}
+
+void statusbar_config_destroy(STATUSBAR_GROUP_REC *group,
+ STATUSBAR_CONFIG_REC *config)
+{
+ group->config_bars = g_slist_remove(group->config_bars, config);
+
+ while (config->items != NULL)
+ statusbar_config_item_destroy(config, config->items->data);
+
+ g_free(config->name);
+ g_free(config);
+}
+
+static STATUSBAR_CONFIG_REC *
+statusbar_config_find(STATUSBAR_GROUP_REC *group, const char *name)
+{
+ GSList *tmp;
+
+ for (tmp = group->config_bars; tmp != NULL; tmp = tmp->next) {
+ STATUSBAR_CONFIG_REC *config = tmp->data;
+
+ if ((config->name == NULL || name == NULL) ?
+ config->name == name :
+ g_ascii_strcasecmp(config->name, name) == 0)
+ return config;
+ }
+
+ return NULL;
+}
+
+static void statusbar_reset_defaults(void)
+{
+ CONFIG_REC *config;
+ CONFIG_NODE *node;
+
+ while (statusbar_groups != NULL)
+ statusbar_group_destroy(statusbar_groups->data);
+ active_statusbar_group = NULL;
+
+ /* read the default statusbar settings from internal config */
+ config = config_open(NULL, -1);
+ config_parse_data(config, default_config, "internal");
+ node = config_node_traverse(config, "statusbar", FALSE);
+ if (node != NULL)
+ read_statusbar_config_from_node(node);
+ config_close(config);
+}
+
+static void statusbar_read_items(CONFIG_NODE *items)
+{
+ GSList *tmp;
+
+ tmp = config_node_first(items->value);
+ for (; tmp != NULL; tmp = config_node_next(tmp)) {
+ CONFIG_NODE *node = tmp->data;
+
+ statusbar_item_register(node->key, node->value, NULL);
+ }
+}
+
+static void statusbar_read_item(STATUSBAR_CONFIG_REC *bar, CONFIG_NODE *node)
+{
+ int priority, right_alignment;
+
+ priority = config_node_get_int(node, "priority", 0);
+ right_alignment = g_strcmp0(config_node_get_str(node, "alignment", ""), "right") == 0;
+ statusbar_item_config_create(bar, node->key,
+ priority, right_alignment);
+}
+
+static void statusbar_read(STATUSBAR_GROUP_REC *group, CONFIG_NODE *node)
+{
+ STATUSBAR_CONFIG_REC *bar;
+ GSList *tmp;
+ const char *visible_str;
+
+ g_return_if_fail(is_node_list(node));
+ g_return_if_fail(node->key != NULL);
+
+ bar = statusbar_config_find(group, node->key);
+ if (config_node_get_bool(node, "disabled", FALSE)) {
+ /* disabled, destroy it if it already exists */
+ if (bar != NULL)
+ statusbar_config_destroy(group, bar);
+ return;
+ }
+
+ if (bar == NULL) {
+ bar = statusbar_config_create(group, node->key);
+ bar->type = STATUSBAR_TYPE_ROOT;
+ bar->placement = STATUSBAR_BOTTOM;
+ bar->position = 0;
+ }
+
+ visible_str = config_node_get_str(node, "visible", "");
+ if (g_ascii_strcasecmp(visible_str, "active") == 0)
+ bar->visible = STATUSBAR_VISIBLE_ACTIVE;
+ else if (g_ascii_strcasecmp(visible_str, "inactive") == 0)
+ bar->visible = STATUSBAR_VISIBLE_INACTIVE;
+ else
+ bar->visible = STATUSBAR_VISIBLE_ALWAYS;
+
+ if (g_ascii_strcasecmp(config_node_get_str(node, "type", ""), "window") == 0)
+ bar->type = STATUSBAR_TYPE_WINDOW;
+ if (g_ascii_strcasecmp(config_node_get_str(node, "placement", ""), "top") == 0)
+ bar->placement = STATUSBAR_TOP;
+ bar->position = config_node_get_int(node, "position", 0);
+
+ node = iconfig_node_section(node, "items", -1);
+ if (node != NULL) {
+ /* we're overriding the items - destroy the old */
+ while (bar->items != NULL)
+ statusbar_config_item_destroy(bar, bar->items->data);
+
+ tmp = config_node_first(node->value);
+ for (; tmp != NULL; tmp = config_node_next(tmp))
+ statusbar_read_item(bar, tmp->data);
+ }
+}
+
+#define skip_corrupt_config(parent, node, index, format, ...) \
+ if ((node)->type != NODE_TYPE_BLOCK) { \
+ if ((node)->key == NULL) { \
+ g_critical("Expected %s node at `.." format "/%s[%d]' was of %s type. Corrupt config?", \
+ "block", ##__VA_ARGS__, (parent)->key, (index), \
+ (node)->type == NODE_TYPE_LIST ? "list" : "scalar"); \
+ } else { \
+ g_critical("Expected %s node at `.." format "/%s/%s' was of %s type. Corrupt config?", \
+ "block", ##__VA_ARGS__, (parent)->key, (node)->key, \
+ (node)->type == NODE_TYPE_LIST ? "list" : "scalar"); \
+ } \
+ continue; \
+ } \
+
+
+static void statusbar_read_group(CONFIG_NODE *node)
+{
+ STATUSBAR_GROUP_REC *group;
+ GSList *tmp;
+ int i;
+
+ g_return_if_fail(is_node_list(node));
+
+ group = statusbar_group_find(node->key);
+ if (group == NULL) {
+ group = statusbar_group_create(node->key);
+ if (active_statusbar_group == NULL)
+ active_statusbar_group = group;
+ }
+
+ for (tmp = config_node_first(node->value), i = 0; tmp != NULL; tmp = config_node_next(tmp), i++) {
+ CONFIG_NODE *value = tmp->data;
+ skip_corrupt_config(node, value, i, "statusbar");
+ statusbar_read(group, value);
+ }
+}
+
+static void create_root_statusbars(void)
+{
+ STATUSBAR_REC *bar;
+ GSList *tmp;
+
+ tmp = active_statusbar_group->config_bars;
+ for (; tmp != NULL; tmp = tmp->next) {
+ STATUSBAR_CONFIG_REC *rec = tmp->data;
+
+ if (rec->type == STATUSBAR_TYPE_ROOT) {
+ bar = statusbar_create(active_statusbar_group, rec, NULL);
+ statusbar_redraw(bar, TRUE);
+ }
+ }
+}
+
+static void read_statusbar_config_from_node(CONFIG_NODE *node)
+{
+ CONFIG_NODE *items, *group;
+ GSList *tmp;
+ int i;
+
+ items = iconfig_node_section(node, "items", -1);
+ if (items != NULL)
+ statusbar_read_items(items);
+
+ for (tmp = config_node_first(node->value), i = 0; tmp != NULL; tmp = config_node_next(tmp), i++) {
+ group = tmp->data;
+ if (group != items) {
+ skip_corrupt_config(node, group, i, "");
+ statusbar_read_group(group);
+ }
+ }
+}
+
+static void read_statusbar_config(void)
+{
+ CONFIG_NODE *node;
+
+ statusbar_reset_defaults();
+
+ node = iconfig_node_traverse("statusbar", FALSE);
+ if (node != NULL)
+ read_statusbar_config_from_node(node);
+
+ create_root_statusbars();
+ statusbars_create_window_bars();
+}
+
+static const char *sbar_get_type(STATUSBAR_CONFIG_REC *rec)
+{
+ return rec->type == STATUSBAR_TYPE_ROOT ? "root" :
+ rec->type == STATUSBAR_TYPE_WINDOW ? "window" : "??";
+}
+
+static const char *sbar_get_placement(STATUSBAR_CONFIG_REC *rec)
+{
+ return rec->placement == STATUSBAR_TOP ? "top" :
+ rec->placement == STATUSBAR_BOTTOM ? "bottom" : "??";
+}
+
+static const char *sbar_get_visibility(STATUSBAR_CONFIG_REC *rec)
+{
+ return rec->visible == STATUSBAR_VISIBLE_ALWAYS ? "always" :
+ rec->visible == STATUSBAR_VISIBLE_ACTIVE ? "active" :
+ rec->visible == STATUSBAR_VISIBLE_INACTIVE ? "inactive" : "??";
+}
+
+#define iconfig_sbar_node(a, b) config_sbar_node(mainconfig, a, b)
+static CONFIG_NODE *config_sbar_node(CONFIG_REC *config, const char *name, gboolean create)
+{
+ CONFIG_NODE *node;
+
+ node = config_node_traverse(config, "statusbar", create);
+ if (node != NULL) {
+ node = config_node_section(config, node, active_statusbar_group->name,
+ create ? NODE_TYPE_BLOCK : -1);
+ }
+
+ if (node != NULL) {
+ node = config_node_section(config, node, name, create ? NODE_TYPE_BLOCK : -1);
+ }
+
+ return node;
+}
+
+static CONFIG_NODE *sbar_node(const char *name, gboolean create)
+{
+ STATUSBAR_CONFIG_REC *rec = statusbar_config_find(active_statusbar_group, name);
+ if (rec != NULL) {
+ name = rec->name;
+ }
+
+ /* lookup/create the statusbar node */
+ return iconfig_sbar_node(name, create);
+}
+
+static gboolean sbar_node_isdefault(const char *name)
+{
+ CONFIG_REC *config;
+ CONFIG_NODE *node;
+
+ /* read the default statusbar settings from internal config */
+ config = config_open(NULL, -1);
+ config_parse_data(config, default_config, "internal");
+
+ node = config_sbar_node(config, name, FALSE);
+
+ config_close(config);
+
+ return node != NULL ? TRUE : FALSE;
+}
+
+static void statusbar_list_items(STATUSBAR_CONFIG_REC *bar)
+{
+ GSList *tmp;
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ TXT_STATUSBAR_INFO_ITEM_HEADER);
+
+ for (tmp = bar->items; tmp != NULL; tmp = tmp->next) {
+ SBAR_ITEM_CONFIG_REC *rec = tmp->data;
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ TXT_STATUSBAR_INFO_ITEM_NAME,
+ rec->name, rec->priority,
+ rec->right_alignment ? "right" : "left");
+ }
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ TXT_STATUSBAR_INFO_ITEM_FOOTER);
+}
+
+static void statusbar_print(STATUSBAR_CONFIG_REC *rec)
+{
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ TXT_STATUSBAR_INFO_NAME, rec->name);
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ TXT_STATUSBAR_INFO_TYPE, sbar_get_type(rec));
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ TXT_STATUSBAR_INFO_PLACEMENT,
+ sbar_get_placement(rec));
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ TXT_STATUSBAR_INFO_POSITION, rec->position);
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ TXT_STATUSBAR_INFO_VISIBLE,
+ sbar_get_visibility(rec));
+
+ if (rec->items != NULL)
+ statusbar_list_items(rec);
+}
+
+static void cmd_statusbar_list(void)
+{
+ GSList *tmp;
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_STATUSBAR_LIST_HEADER);
+
+ tmp = active_statusbar_group->config_bars;
+ for (; tmp != NULL; tmp = tmp->next) {
+ STATUSBAR_CONFIG_REC *rec = tmp->data;
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ TXT_STATUSBAR_LIST, rec->name, sbar_get_type(rec),
+ sbar_get_placement(rec), rec->position,
+ sbar_get_visibility(rec));
+ }
+
+ printformat(NULL, NULL, MSGLEVEL_CLIENTCRAP, TXT_STATUSBAR_LIST_FOOTER);
+}
+
+static void cmd_statusbar_print_info(const char *name)
+{
+ STATUSBAR_CONFIG_REC *rec = statusbar_config_find(active_statusbar_group, name);
+
+ if (rec != NULL) {
+ statusbar_print(rec);
+ return;
+ }
+
+ if (sbar_node(name, FALSE) != NULL || sbar_node_isdefault(name))
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_STATUSBAR_NOT_ENABLED, name);
+ else
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_STATUSBAR_NOT_FOUND, name);
+}
+
+/* SYNTAX: STATUSBAR ADD|MODIFY [-disable | -nodisable] [-type window|root]
+ [-placement top|bottom] [-position #] [-visible always|active|inactive] <statusbar> */
+static void cmd_statusbar_add_modify(const char *data, void *server, void *witem)
+{
+ GHashTable *optlist;
+ CONFIG_NODE *node;
+ char *name, *type, *placement, *visible;
+ void *free_arg;
+ int error;
+ int add = GPOINTER_TO_INT(signal_get_user_data());
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS | PARAM_FLAG_STRIP_TRAILING_WS,
+ "statusbar add", &optlist, &name))
+ return;
+
+ if (*name == '\0') {
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+ }
+
+ error = 0;
+
+ type = NULL;
+ data = g_hash_table_lookup(optlist, "type");
+ if (data != NULL) {
+ if (g_ascii_strcasecmp(data, "window") == 0)
+ type = "window";
+ else if (g_ascii_strcasecmp(data, "root") == 0)
+ type = "root";
+ else {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_STATUSBAR_UNKNOWN_TYPE,
+ data);
+ error++;
+ }
+ }
+
+ placement = NULL;
+ data = g_hash_table_lookup(optlist, "placement");
+ if (data != NULL) {
+ if (g_ascii_strcasecmp(data, "top") == 0)
+ placement = "top";
+ else if (g_ascii_strcasecmp(data, "bottom") == 0)
+ placement = "bottom";
+ else {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_STATUSBAR_UNKNOWN_PLACEMENT, data);
+ error++;
+ }
+ }
+
+ visible = NULL;
+ data = g_hash_table_lookup(optlist, "visible");
+ if (data != NULL) {
+ if (g_ascii_strcasecmp(data, "always") == 0)
+ visible = "always";
+ else if (g_ascii_strcasecmp(data, "active") == 0)
+ visible = "active";
+ else if (g_ascii_strcasecmp(data, "inactive") == 0)
+ visible = "inactive";
+ else {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_STATUSBAR_UNKNOWN_VISIBILITY, data);
+ error++;
+ }
+ }
+
+ if (!error) {
+ node = sbar_node(name, add);
+ if (node == NULL && !add && sbar_node_isdefault(name)) {
+ /* If this node is a default status bar, we need to create it in the config
+ * to configure it */
+ node = sbar_node(name, TRUE);
+ }
+
+ if (node == NULL) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_STATUSBAR_NOT_FOUND, name);
+ error++;
+ }
+ }
+
+ if (error) {
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ if (g_hash_table_lookup(optlist, "nodisable"))
+ iconfig_node_set_str(node, "disabled", NULL);
+ if (g_hash_table_lookup(optlist, "disable"))
+ iconfig_node_set_bool(node, "disabled", TRUE);
+ if (type != NULL)
+ iconfig_node_set_str(node, "type", type);
+ if (placement != NULL)
+ iconfig_node_set_str(node, "placement", placement);
+ data = g_hash_table_lookup(optlist, "position");
+ if (data != NULL)
+ iconfig_node_set_int(node, "position", atoi(data));
+ if (visible != NULL)
+ iconfig_node_set_str(node, "visible", visible);
+
+ read_statusbar_config();
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: STATUSBAR RESET <statusbar> */
+static void cmd_statusbar_reset(const char *data, void *server, void *witem)
+{
+ CONFIG_NODE *node, *parent;
+ char *name;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_STRIP_TRAILING_WS, &name))
+ return;
+
+ if (*name == '\0') {
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+ }
+
+ node = sbar_node(name, FALSE);
+ if (node == NULL && !sbar_node_isdefault(name)) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_STATUSBAR_NOT_FOUND, name);
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ parent = iconfig_node_traverse("statusbar", FALSE);
+ if (parent != NULL) {
+ parent = iconfig_node_section(parent, active_statusbar_group->name, -1);
+ }
+
+ if (parent != NULL && node != NULL) {
+ iconfig_node_set_str(parent, node->key, NULL);
+ }
+
+ read_statusbar_config();
+ cmd_params_free(free_arg);
+}
+
+#define iconfig_sbar_items_section(a, b) config_sbar_items_section(mainconfig, a, b)
+static CONFIG_NODE *config_sbar_items_section(CONFIG_REC *config, CONFIG_NODE *parent,
+ gboolean create)
+{
+ return config_node_section(config, parent, "items", create ? NODE_TYPE_BLOCK : -1);
+}
+
+static CONFIG_NODE *statusbar_copy_config(CONFIG_REC *config, CONFIG_NODE *source,
+ CONFIG_NODE *parent)
+{
+ GSList *tmp;
+
+ g_return_val_if_fail(parent != NULL, NULL);
+
+ parent = iconfig_sbar_items_section(parent, TRUE);
+
+ /* since items list in config file overrides defaults,
+ we'll need to copy the whole list. */
+ for (tmp = config_node_first(source->value); tmp != NULL; tmp = config_node_next(tmp)) {
+ int priority, right_alignment;
+ CONFIG_NODE *node, *snode;
+
+ snode = tmp->data;
+
+ priority = config_node_get_int(snode, "priority", 0);
+ right_alignment =
+ g_strcmp0(config_node_get_str(snode, "alignment", ""), "right") == 0;
+
+ /* create new item */
+ node = iconfig_node_section(parent, snode->key, NODE_TYPE_BLOCK);
+
+ if (priority != 0)
+ iconfig_node_set_int(node, "priority", priority);
+ if (right_alignment)
+ iconfig_node_set_str(node, "alignment", "right");
+ }
+
+ return parent;
+}
+
+static CONFIG_NODE *sbar_find_item_with_defaults(const char *statusbar, const char *item,
+ gboolean create)
+{
+ CONFIG_REC *config, *close_config;
+ CONFIG_NODE *node;
+
+ close_config = NULL;
+ config = mainconfig;
+ node = sbar_node(statusbar, FALSE);
+
+ if (node == NULL) {
+ /* we are looking up defaults from the internal config */
+ close_config = config = config_open(NULL, -1);
+ config_parse_data(config, default_config, "internal");
+ node = config_sbar_node(config, statusbar, FALSE);
+ }
+
+ if (node == NULL) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_STATUSBAR_NOT_FOUND, statusbar);
+ if (close_config != NULL)
+ config_close(close_config);
+ return NULL;
+ }
+
+ node = config_sbar_items_section(config, node, create);
+
+ if (node == NULL || (!create && config_node_section(config, node, item, -1) == NULL)) {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR, TXT_STATUSBAR_ITEM_NOT_FOUND, item);
+ if (close_config != NULL)
+ config_close(close_config);
+ return NULL;
+ }
+
+ if (config != mainconfig) {
+ /* we need to copy default to user config */
+ node = statusbar_copy_config(config, node, sbar_node(statusbar, TRUE));
+ }
+
+ if (close_config != NULL)
+ config_close(close_config);
+
+ return node;
+}
+
+/* SYNTAX: STATUSBAR ADDITEM|MODIFYITEM [-before | -after <item>]
+ [-priority #] [-alignment left|right] <item> <statusbar> */
+static void cmd_statusbar_additem_modifyitem(const char *data, void *server, void *witem)
+{
+ CONFIG_NODE *node;
+ GHashTable *optlist;
+ char *item, *statusbar, *value;
+ void *free_arg;
+ int index;
+ int additem = GPOINTER_TO_INT(signal_get_user_data());
+
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_OPTIONS | PARAM_FLAG_STRIP_TRAILING_WS,
+ "statusbar additem", &optlist, &item, &statusbar))
+ return;
+
+ if (*statusbar == '\0') {
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+ }
+
+ node = sbar_find_item_with_defaults(statusbar, item, additem);
+ if (node == NULL) {
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ /* get the index */
+ index = -1;
+ value = g_hash_table_lookup(optlist, "before");
+ if (value != NULL)
+ index = config_node_index(node, value);
+ value = g_hash_table_lookup(optlist, "after");
+ if (value != NULL)
+ index = config_node_index(node, value) + 1;
+
+ /* create/move item */
+ node = iconfig_node_section_index(node, item, index, NODE_TYPE_BLOCK);
+
+ /* set the options */
+ value = g_hash_table_lookup(optlist, "priority");
+ if (value != NULL) iconfig_node_set_int(node, "priority", atoi(value));
+
+ value = g_hash_table_lookup(optlist, "alignment");
+ if (value != NULL) {
+ iconfig_node_set_str(node, "alignment",
+ g_ascii_strcasecmp(value, "right") == 0 ?
+ "right" : NULL);
+ }
+
+ read_statusbar_config();
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: STATUSBAR REMOVEITEM <item> <statusbar> */
+static void cmd_statusbar_removeitem(const char *data, void *server, void *witem)
+{
+ CONFIG_NODE *node;
+ char *item, *statusbar;
+ void *free_arg;
+ if (!cmd_get_params(data, &free_arg, 2 | PARAM_FLAG_STRIP_TRAILING_WS, &item, &statusbar))
+ return;
+
+ if (*statusbar == '\0') {
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+ }
+
+ node = sbar_find_item_with_defaults(statusbar, item, FALSE);
+
+ if (node != NULL)
+ iconfig_node_set_str(node, item, NULL);
+
+ read_statusbar_config();
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: STATUSBAR INFO <statusbar> */
+static void cmd_statusbar_info(const char *data)
+{
+ void *free_arg;
+ char *name;
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_STRIP_TRAILING_WS, &name))
+ return;
+
+ if (*name == '\0') {
+ cmd_param_error(CMDERR_NOT_ENOUGH_PARAMS);
+ }
+
+ /* print statusbar info */
+ cmd_statusbar_print_info(name);
+ cmd_params_free(free_arg);
+ return;
+}
+
+static void cmd_statusbar(const char *data)
+{
+ char *arg1, *arg2, *params, *oldcmd;
+ void *free_arg;
+
+ if (!cmd_get_params(data, &free_arg, 3 | PARAM_FLAG_GETREST | PARAM_FLAG_STRIP_TRAILING_WS,
+ &arg1, &arg2, &params))
+ return;
+
+ /* backward compatibility layer */
+ oldcmd = NULL;
+ if (*arg1 == '\0') {
+ oldcmd = g_strdup("list");
+ } else if (g_ascii_strcasecmp(arg2, "enable") == 0) {
+ oldcmd = g_strdup_printf("add -nodisable %s %s", arg1, params);
+ } else if (g_ascii_strcasecmp(arg2, "disable") == 0) {
+ oldcmd = g_strdup_printf("add -disable %s %s", arg1, params);
+ } else if (g_ascii_strcasecmp(arg2, "reset") == 0) {
+ oldcmd = g_strdup_printf("reset %s", arg1);
+ } else if (g_ascii_strcasecmp(arg2, "type") == 0) {
+ oldcmd = g_strdup_printf("add -type %s %s", params, arg1);
+ } else if (g_ascii_strcasecmp(arg2, "placement") == 0) {
+ oldcmd = g_strdup_printf("add -placement %s %s", params, arg1);
+ } else if (g_ascii_strcasecmp(arg2, "position") == 0) {
+ oldcmd = g_strdup_printf("add -position %s %s", params, arg1);
+ } else if (g_ascii_strcasecmp(arg2, "visible") == 0) {
+ oldcmd = g_strdup_printf("add -visible %s %s", params, arg1);
+ } else if (g_ascii_strcasecmp(arg2, "add") == 0) {
+ oldcmd = g_strdup_printf("additem %s %s", params, arg1);
+ } else if (g_ascii_strcasecmp(arg2, "remove") == 0) {
+ oldcmd = g_strdup_printf("removeitem %s %s", params, arg1);
+ } else if (*arg2 == '\0') {
+ oldcmd = g_strdup_printf("statusbar %s", arg1);
+ if (command_find(oldcmd) == NULL) {
+ g_free(oldcmd);
+ oldcmd = g_strdup_printf("info %s", arg1);
+ } else {
+ g_free(oldcmd);
+ oldcmd = NULL;
+ }
+ }
+
+ cmd_params_free(free_arg);
+ if (oldcmd) {
+ command_runsub("statusbar", oldcmd, NULL, NULL);
+ g_free(oldcmd);
+ } else {
+ command_runsub("statusbar", data, NULL, NULL);
+ }
+
+ return;
+}
+
+void statusbar_config_init(void)
+{
+ read_statusbar_config();
+ signal_add_last("setup reread", (SIGNAL_FUNC) read_statusbar_config);
+ signal_add("theme changed", (SIGNAL_FUNC) read_statusbar_config);
+
+ command_bind("statusbar", NULL, (SIGNAL_FUNC) cmd_statusbar);
+ command_bind("statusbar list", NULL, (SIGNAL_FUNC) cmd_statusbar_list);
+ command_bind_data("statusbar add", NULL, (SIGNAL_FUNC) cmd_statusbar_add_modify, GINT_TO_POINTER(TRUE));
+ command_bind_data("statusbar modify", NULL, (SIGNAL_FUNC) cmd_statusbar_add_modify, GINT_TO_POINTER(FALSE));
+ command_bind("statusbar reset", NULL, (SIGNAL_FUNC) cmd_statusbar_reset);
+ command_bind("statusbar info", NULL, (SIGNAL_FUNC) cmd_statusbar_info);
+ command_bind_data("statusbar additem", NULL, (SIGNAL_FUNC) cmd_statusbar_additem_modifyitem, GINT_TO_POINTER(TRUE));
+ command_bind_data("statusbar modifyitem", NULL, (SIGNAL_FUNC) cmd_statusbar_additem_modifyitem, GINT_TO_POINTER(FALSE));
+ command_bind("statusbar removeitem", NULL, (SIGNAL_FUNC) cmd_statusbar_removeitem);
+
+ command_set_options("statusbar additem", "+before +after +priority +alignment");
+ command_set_options("statusbar modifyitem", "+before +after +priority +alignment");
+ command_set_options("statusbar add",
+ "disable nodisable +type +placement +position +visible");
+ command_set_options("statusbar modify",
+ "disable nodisable +type +placement +position +visible");
+}
+
+void statusbar_config_deinit(void)
+{
+ signal_remove("setup reread", (SIGNAL_FUNC) read_statusbar_config);
+ signal_remove("theme changed", (SIGNAL_FUNC) read_statusbar_config);
+
+ command_unbind("statusbar", (SIGNAL_FUNC) cmd_statusbar);
+ command_unbind("statusbar list", (SIGNAL_FUNC) cmd_statusbar_list);
+ command_unbind_full("statusbar add", (SIGNAL_FUNC) cmd_statusbar_add_modify, GINT_TO_POINTER(TRUE));
+ command_unbind_full("statusbar modify", (SIGNAL_FUNC) cmd_statusbar_add_modify, GINT_TO_POINTER(FALSE));
+ command_unbind("statusbar reset", (SIGNAL_FUNC) cmd_statusbar_reset);
+ command_unbind("statusbar info", (SIGNAL_FUNC) cmd_statusbar_info);
+ command_unbind_full("statusbar additem", (SIGNAL_FUNC) cmd_statusbar_additem_modifyitem, GINT_TO_POINTER(TRUE));
+ command_unbind_full("statusbar modifyitem", (SIGNAL_FUNC) cmd_statusbar_additem_modifyitem, GINT_TO_POINTER(FALSE));
+ command_unbind("statusbar removeitem", (SIGNAL_FUNC) cmd_statusbar_removeitem);
+}
diff --git a/src/fe-text/statusbar-config.h b/src/fe-text/statusbar-config.h
new file mode 100644
index 0000000..239918d
--- /dev/null
+++ b/src/fe-text/statusbar-config.h
@@ -0,0 +1,12 @@
+#ifndef IRSSI_FE_TEXT_STATUSBAR_CONFIG_H
+#define IRSSI_FE_TEXT_STATUSBAR_CONFIG_H
+
+#include <irssi/src/fe-text/statusbar.h>
+
+void statusbar_config_destroy(STATUSBAR_GROUP_REC *group,
+ STATUSBAR_CONFIG_REC *config);
+
+void statusbar_config_init(void);
+void statusbar_config_deinit(void);
+
+#endif
diff --git a/src/fe-text/statusbar-item.h b/src/fe-text/statusbar-item.h
new file mode 100644
index 0000000..ce9011e
--- /dev/null
+++ b/src/fe-text/statusbar-item.h
@@ -0,0 +1,17 @@
+#ifndef IRSSI_STATUSBAR_ITEM_H
+#define IRSSI_STATUSBAR_ITEM_H
+
+struct SBAR_ITEM_REC;
+
+typedef void (*STATUSBAR_FUNC) (struct SBAR_ITEM_REC *item, int get_size_only);
+
+void statusbar_item_register(const char *name, const char *value,
+ STATUSBAR_FUNC func);
+void statusbar_item_unregister(const char *name);
+void statusbar_item_set_size(struct SBAR_ITEM_REC *item, int min_size, int max_size);
+void statusbar_item_default_handler(struct SBAR_ITEM_REC *item, int get_size_only,
+ const char *str, const char *data,
+ int escape_vars);
+void statusbar_items_redraw(const char *name);
+
+#endif
diff --git a/src/fe-text/statusbar-items.c b/src/fe-text/statusbar-items.c
new file mode 100644
index 0000000..6e7cf8a
--- /dev/null
+++ b/src/fe-text/statusbar-items.c
@@ -0,0 +1,546 @@
+/*
+ statusbar-items.c : irssi
+
+ Copyright (C) 1999-2001 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/servers.h>
+
+#include <irssi/src/fe-common/core/themes.h>
+#include <irssi/src/fe-text/statusbar.h>
+#include <irssi/src/fe-text/gui-entry.h>
+#include <irssi/src/fe-text/gui-windows.h>
+
+/* how often to redraw lagging time (seconds) */
+#define LAG_REFRESH_TIME 10
+
+static GList *activity_list;
+static guint8 actlist_sort;
+static char *actlist_separator;
+static GSList *more_visible; /* list of MAIN_WINDOW_RECs which have --more-- */
+static GHashTable *input_entries;
+static int last_lag, last_lag_unknown, lag_timeout_tag;
+
+static void item_window_active(SBAR_ITEM_REC *item, int get_size_only)
+{
+ WINDOW_REC *window;
+
+ window = active_win;
+ if (item->bar->parent_window != NULL)
+ window = item->bar->parent_window->active;
+
+ if (window != NULL && window->active != NULL) {
+ statusbar_item_default_handler(item, get_size_only,
+ NULL, "", TRUE);
+ } else if (get_size_only) {
+ item->min_size = item->max_size = 0;
+ }
+}
+
+static void item_window_empty(SBAR_ITEM_REC *item, int get_size_only)
+{
+ WINDOW_REC *window;
+
+ window = active_win;
+ if (item->bar->parent_window != NULL)
+ window = item->bar->parent_window->active;
+
+ if (window != NULL && window->active == NULL) {
+ statusbar_item_default_handler(item, get_size_only,
+ NULL, "", TRUE);
+ } else if (get_size_only) {
+ item->min_size = item->max_size = 0;
+ }
+}
+
+static char *get_activity_list(MAIN_WINDOW_REC *window, int normal, int hilight)
+{
+ THEME_REC *theme;
+ GString *str;
+ GString *format;
+ GList *tmp;
+ char *ret, *name, *value;
+ int is_det;
+ int add_name = settings_get_bool("actlist_names");
+ int pref_name = settings_get_bool("actlist_prefer_window_name");
+
+ str = g_string_new(NULL);
+ format = g_string_new(NULL);
+
+ theme = window != NULL && window->active != NULL &&
+ window->active->theme != NULL ?
+ window->active->theme : current_theme;
+
+ for (tmp = activity_list; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *window = tmp->data;
+
+ is_det = window->data_level >= DATA_LEVEL_HILIGHT;
+ if ((!is_det && !normal) || (is_det && !hilight))
+ continue;
+
+ /* comma separator */
+ if (str->len > 0) {
+ g_string_printf(format, "{sb_act_sep %s}", actlist_separator);
+ value = theme_format_expand(theme, format->str);
+ g_string_append(str, value);
+ g_free(value);
+ }
+
+ switch (window->data_level) {
+ case DATA_LEVEL_NONE:
+ case DATA_LEVEL_TEXT:
+ name = "{sb_act_text %d";
+ break;
+ case DATA_LEVEL_MSG:
+ name = "{sb_act_msg %d";
+ break;
+ default:
+ if (window->hilight_color == NULL)
+ name = "{sb_act_hilight %d";
+ else
+ name = NULL;
+ break;
+ }
+
+ if (name != NULL)
+ g_string_printf(format, name, window->refnum);
+ else
+ g_string_printf(format, "{sb_act_hilight_color %s %d",
+ window->hilight_color,
+ window->refnum);
+
+ if (add_name && window->active != NULL)
+ g_string_append_printf(format, ":%s",
+ pref_name == 1 && window->name != NULL ?
+ window->name : window->active->visible_name);
+ g_string_append_c(format, '}');
+
+ value = theme_format_expand(theme, format->str);
+ g_string_append(str, value);
+ g_free(value);
+ }
+
+ ret = str->len == 0 ? NULL : str->str;
+ g_string_free(str, ret == NULL);
+ g_string_free(format, TRUE);
+ return ret;
+}
+
+/* redraw activity, FIXME: if we didn't get enough size, this gets buggy.
+ At least "Det:" isn't printed properly. also we should rearrange the
+ act list so that the highest priority items comes first. */
+static void item_act(SBAR_ITEM_REC *item, int get_size_only)
+{
+ char *actlist;
+ int max_size;
+
+ if (get_size_only) {
+ if (activity_list == NULL)
+ item->min_size = item->max_size = 0;
+ /* Skip activity calculation on regular trigger, only
+ set dirty */
+ return;
+ }
+
+ actlist = get_activity_list(item->bar->parent_window, TRUE, TRUE);
+ if (actlist == NULL) {
+ return;
+ }
+
+ max_size = item->max_size;
+ statusbar_item_default_handler(item, TRUE,
+ NULL, actlist, FALSE);
+ statusbar_item_default_handler(item, FALSE,
+ NULL, actlist, FALSE);
+ if (max_size != item->max_size) {
+ /* Due to above hack of skipping the calculation, we
+ need to manually trigger the redraw process now or
+ we won't see the item */
+ item->bar->dirty = item->dirty = TRUE;
+ statusbar_redraw(item->bar, TRUE);
+ statusbar_redraw_dirty();
+ }
+
+ g_free_not_null(actlist);
+}
+
+static int window_level_recent_cmp(WINDOW_REC *w1, WINDOW_REC *w2)
+{
+ if (w1->data_level >= w2->data_level)
+ return -1;
+ else
+ return 1;
+}
+
+static int window_level_cmp(WINDOW_REC *w1, WINDOW_REC *w2)
+{
+ if (w1->data_level > w2->data_level ||
+ (w1->data_level == w2->data_level && w1->refnum < w2->refnum))
+ return -1;
+ else
+ return 1;
+}
+
+static void sig_statusbar_activity_hilight(WINDOW_REC *window, gpointer oldlevel)
+{
+ GList *node;
+
+ g_return_if_fail(window != NULL);
+
+ node = g_list_find(activity_list, window);
+
+ if (actlist_sort == 1) {
+ /* Move the window to the first in the activity list */
+ if (node != NULL)
+ activity_list = g_list_delete_link(activity_list, node);
+ if (window->data_level != 0)
+ activity_list = g_list_prepend(activity_list, window);
+ statusbar_items_redraw("act");
+ return;
+ }
+
+ if (actlist_sort == 2) {
+ if (node != NULL) {
+ if (window->data_level == GPOINTER_TO_INT(oldlevel)) {
+ if (window->hilight_color != 0)
+ statusbar_items_redraw("act");
+ return;
+ }
+ activity_list = g_list_delete_link(activity_list, node);
+ }
+ if (window->data_level != 0)
+ activity_list = g_list_insert_sorted(activity_list, window, (GCompareFunc)
+ window_level_cmp);
+ statusbar_items_redraw("act");
+ return;
+ }
+
+ if (actlist_sort == 3) {
+ if (node != NULL)
+ activity_list = g_list_delete_link(activity_list, node);
+ if (window->data_level != 0)
+ activity_list = g_list_insert_sorted(activity_list, window, (GCompareFunc)
+ window_level_recent_cmp);
+ statusbar_items_redraw("act");
+ return;
+ }
+
+ if (node != NULL) {
+ /* already in activity list */
+ if (window->data_level == 0) {
+ /* remove from activity list */
+ activity_list = g_list_delete_link(activity_list, node);
+ statusbar_items_redraw("act");
+ } else if (window->data_level != GPOINTER_TO_INT(oldlevel) ||
+ window->hilight_color != 0) {
+ /* different level as last time (or maybe different
+ hilight color?), just redraw it. */
+ statusbar_items_redraw("act");
+ }
+ return;
+ }
+
+ if (window->data_level == 0)
+ return;
+
+ /* add window to activity list .. */
+ activity_list = g_list_insert_sorted(activity_list, window, (GCompareFunc)
+ window_refnum_cmp);
+
+ statusbar_items_redraw("act");
+}
+
+static void sig_statusbar_activity_window_destroyed(WINDOW_REC *window)
+{
+ GList *node;
+
+ g_return_if_fail(window != NULL);
+
+ node = g_list_find(activity_list, window);
+ if (node != NULL)
+ activity_list = g_list_delete_link(activity_list, node);
+ statusbar_items_redraw("act");
+}
+
+static void sig_statusbar_activity_updated(void)
+{
+ statusbar_items_redraw("act");
+}
+
+static void item_more(SBAR_ITEM_REC *item, int get_size_only)
+{
+ MAIN_WINDOW_REC *mainwin;
+ int visible;
+
+ if (active_win == NULL) {
+ mainwin = NULL;
+ visible = FALSE;
+ } else {
+ mainwin = WINDOW_MAIN(active_win);
+ visible = WINDOW_GUI(active_win)->view->more_text;
+ }
+
+ if (!visible) {
+ if (mainwin != NULL)
+ more_visible = g_slist_remove(more_visible, mainwin);
+ if (get_size_only)
+ item->min_size = item->max_size = 0;
+ return;
+ }
+
+ more_visible = g_slist_prepend(more_visible, mainwin);
+ statusbar_item_default_handler(item, get_size_only, NULL, "", FALSE);
+}
+
+static void sig_statusbar_more_updated(void)
+{
+ int visible;
+
+ /* no active window, for example during /window hide */
+ if (active_win == NULL)
+ return;
+
+ visible = g_slist_find(more_visible, WINDOW_MAIN(active_win)) != NULL;
+ if (WINDOW_GUI(active_win)->view->more_text != visible)
+ statusbar_items_redraw("more");
+}
+
+/* Returns the lag in milliseconds. If we haven't been able to ask the lag
+ for a while, unknown is set to TRUE. */
+static int get_lag(SERVER_REC *server, int *unknown)
+{
+ long lag;
+
+ *unknown = FALSE;
+
+ if (server == NULL || server->lag_last_check == 0) {
+ /* lag has not been asked even once yet */
+ return 0;
+ }
+
+ if (server->lag_sent == 0) {
+ /* no lag queries going on currently */
+ return server->lag;
+ }
+
+ /* we're not sure about our current lag.. */
+ *unknown = TRUE;
+
+ lag = (long) (time(NULL) - (server->lag_sent / G_TIME_SPAN_SECOND));
+ if (server->lag/1000 > lag) {
+ /* we've been waiting the lag reply less time than
+ what last known lag was -> use the last known lag */
+ return server->lag;
+ }
+
+ /* return how long we have been waiting for lag reply */
+ return lag*1000;
+}
+
+static void item_lag(SBAR_ITEM_REC *item, int get_size_only)
+{
+ SERVER_REC *server;
+ char str[MAX_INT_STRLEN+10];
+ int lag, lag_unknown;
+
+ server = active_win == NULL ? NULL : active_win->active_server;
+ lag = get_lag(server, &lag_unknown);
+
+ if (lag <= 0 || lag < settings_get_time("lag_min_show")) {
+ /* don't print the lag item */
+ if (get_size_only)
+ item->min_size = item->max_size = 0;
+ return;
+ }
+
+ lag /= 10;
+ last_lag = lag;
+ last_lag_unknown = lag_unknown;
+
+ if (lag_unknown) {
+ /* "??)" in C becomes ']'
+ See: https://en.wikipedia.org/wiki/Digraphs_and_trigraphs#C */
+ g_snprintf(str, sizeof(str), "%d (?""?)", lag / 100);
+ } else {
+ if (lag % 100 == 0)
+ g_snprintf(str, sizeof(str), "%d", lag / 100);
+ else
+ g_snprintf(str, sizeof(str), "%d.%02d", lag / 100, lag % 100);
+ }
+
+ statusbar_item_default_handler(item, get_size_only,
+ NULL, str, TRUE);
+}
+
+static void lag_check_update(void)
+{
+ SERVER_REC *server;
+ int lag, lag_unknown;
+
+ server = active_win == NULL ? NULL : active_win->active_server;
+ lag = get_lag(server, &lag_unknown)/10;
+
+ if (lag < settings_get_time("lag_min_show"))
+ lag = 0;
+ else
+ lag /= 10;
+
+ if (lag != last_lag || (lag > 0 && lag_unknown != last_lag_unknown))
+ statusbar_items_redraw("lag");
+}
+
+static void sig_server_lag_updated(SERVER_REC *server)
+{
+ if (active_win != NULL && active_win->active_server == server)
+ lag_check_update();
+}
+
+static int sig_lag_timeout(void)
+{
+ lag_check_update();
+ return 1;
+}
+
+static void item_input(SBAR_ITEM_REC *item, int get_size_only)
+{
+ GUI_ENTRY_REC *rec;
+
+ rec = g_hash_table_lookup(input_entries, item->bar->config->name);
+ if (rec == NULL) {
+ rec = gui_entry_create(ITEM_WINDOW_REAL_XPOS(item), item->bar->real_ypos,
+ item->size, term_type == TERM_TYPE_UTF8);
+ gui_entry_set_active(rec);
+ g_hash_table_insert(input_entries,
+ g_strdup(item->bar->config->name), rec);
+ }
+
+ if (get_size_only) {
+ int max_width;
+ WINDOW_REC *window;
+
+ window = item->bar->parent_window != NULL
+ ? item->bar->parent_window->active
+ : NULL;
+
+ max_width = window != NULL ? window->width : term_width;
+
+ item->min_size = 2+max_width/10;
+ item->max_size = max_width;
+ return;
+ }
+
+ gui_entry_move(rec, ITEM_WINDOW_REAL_XPOS(item), item->bar->real_ypos,
+ item->size);
+ gui_entry_redraw(rec); /* FIXME: this is only necessary with ^L.. */
+}
+
+static void read_settings(void)
+{
+ const char *sep;
+ if (active_entry != NULL)
+ gui_entry_set_utf8(active_entry, term_type == TERM_TYPE_UTF8);
+
+ actlist_sort = settings_get_choice("actlist_sort");
+
+ sep = settings_get_str("actlist_separator");
+ if (g_strcmp0(actlist_separator, sep) != 0) {
+ g_free(actlist_separator);
+ actlist_separator = g_strdup(sep);
+ statusbar_items_redraw("act");
+ }
+}
+
+void statusbar_items_init(void)
+{
+ settings_add_time("misc", "lag_min_show", "1sec");
+ settings_add_choice("lookandfeel", "actlist_sort", 0, "refnum;recent;level;level,recent");
+ settings_add_bool("lookandfeel", "actlist_names", FALSE);
+ settings_add_str("lookandfeel", "actlist_separator", ",");
+ settings_add_bool("lookandfeel", "actlist_prefer_window_name", FALSE);
+
+ statusbar_item_register("window", NULL, item_window_active);
+ statusbar_item_register("window_empty", NULL, item_window_empty);
+ statusbar_item_register("prompt", NULL, item_window_active);
+ statusbar_item_register("prompt_empty", NULL, item_window_empty);
+ statusbar_item_register("topic", NULL, item_window_active);
+ statusbar_item_register("topic_empty", NULL, item_window_empty);
+ statusbar_item_register("lag", NULL, item_lag);
+ statusbar_item_register("act", NULL, item_act);
+ statusbar_item_register("more", NULL, item_more);
+ statusbar_item_register("input", NULL, item_input);
+
+ /* activity */
+ activity_list = NULL;
+ signal_add("window activity", (SIGNAL_FUNC) sig_statusbar_activity_hilight);
+ signal_add("window destroyed", (SIGNAL_FUNC) sig_statusbar_activity_window_destroyed);
+ signal_add("window refnum changed", (SIGNAL_FUNC) sig_statusbar_activity_updated);
+
+ /* more */
+ more_visible = NULL;
+ signal_add("gui page scrolled", (SIGNAL_FUNC) sig_statusbar_more_updated);
+ signal_add("window changed", (SIGNAL_FUNC) sig_statusbar_more_updated);
+ signal_add_last("gui print text finished", (SIGNAL_FUNC) sig_statusbar_more_updated);
+ signal_add_last("command clear", (SIGNAL_FUNC) sig_statusbar_more_updated);
+ signal_add_last("command scrollback", (SIGNAL_FUNC) sig_statusbar_more_updated);
+
+ /* lag */
+ last_lag = 0; last_lag_unknown = FALSE;
+ signal_add("server lag", (SIGNAL_FUNC) sig_server_lag_updated);
+ signal_add("window changed", (SIGNAL_FUNC) lag_check_update);
+ signal_add("window server changed", (SIGNAL_FUNC) lag_check_update);
+ lag_timeout_tag = g_timeout_add(5000, (GSourceFunc) sig_lag_timeout, NULL);
+
+ /* input */
+ input_entries = g_hash_table_new((GHashFunc) g_str_hash,
+ (GCompareFunc) g_str_equal);
+
+ read_settings();
+ signal_add_last("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void statusbar_items_deinit(void)
+{
+ /* activity */
+ signal_remove("window activity", (SIGNAL_FUNC) sig_statusbar_activity_hilight);
+ signal_remove("window destroyed", (SIGNAL_FUNC) sig_statusbar_activity_window_destroyed);
+ signal_remove("window refnum changed", (SIGNAL_FUNC) sig_statusbar_activity_updated);
+ g_list_free(activity_list);
+ activity_list = NULL;
+
+ /* more */
+ g_slist_free(more_visible);
+ signal_remove("gui page scrolled", (SIGNAL_FUNC) sig_statusbar_more_updated);
+ signal_remove("window changed", (SIGNAL_FUNC) sig_statusbar_more_updated);
+ signal_remove("gui print text finished", (SIGNAL_FUNC) sig_statusbar_more_updated);
+ signal_remove("command clear", (SIGNAL_FUNC) sig_statusbar_more_updated);
+ signal_remove("command scrollback", (SIGNAL_FUNC) sig_statusbar_more_updated);
+
+ /* lag */
+ signal_remove("server lag", (SIGNAL_FUNC) sig_server_lag_updated);
+ signal_remove("window changed", (SIGNAL_FUNC) lag_check_update);
+ signal_remove("window server changed", (SIGNAL_FUNC) lag_check_update);
+ g_source_remove(lag_timeout_tag);
+
+ /* input */
+ g_hash_table_foreach(input_entries, (GHFunc) g_free, NULL);
+ g_hash_table_destroy(input_entries);
+
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
diff --git a/src/fe-text/statusbar.c b/src/fe-text/statusbar.c
new file mode 100644
index 0000000..2439d10
--- /dev/null
+++ b/src/fe-text/statusbar.c
@@ -0,0 +1,1196 @@
+/*
+ statusbar.c : irssi
+
+ Copyright (C) 1999-2001 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/expandos.h>
+#include <irssi/src/core/special-vars.h>
+
+#include <irssi/src/fe-common/core/themes.h>
+
+#include <irssi/src/fe-text/statusbar.h>
+#include <irssi/src/fe-text/statusbar-config.h>
+#include <irssi/src/fe-text/gui-windows.h>
+#include <irssi/src/fe-text/gui-printtext.h>
+
+void statusbar_items_init(void);
+void statusbar_items_deinit(void);
+
+GSList *statusbar_groups;
+STATUSBAR_GROUP_REC *active_statusbar_group;
+
+/*
+ sbar_item_defs: char *name => char *value
+ sbar_item_funcs: char *name => STATUSBAR_FUNC func
+ sbar_signal_items: int signal_id => GSList *(SBAR_ITEM_REC *items)
+ sbar_item_signals: SBAR_ITEM_REC *item => GSList *(int *signal_ids)
+ named_sbar_items: const char *name => GSList *(SBAR_ITEM_REC *items)
+*/
+static GHashTable *sbar_item_defs, *sbar_item_funcs;
+static GHashTable *sbar_signal_items, *sbar_item_signals;
+static GHashTable *named_sbar_items;
+static int statusbar_need_recreate_items;
+
+void statusbar_item_register(const char *name, const char *value,
+ STATUSBAR_FUNC func)
+{
+ gpointer hkey, hvalue;
+
+ statusbar_need_recreate_items = TRUE;
+ if (value != NULL) {
+ if (g_hash_table_lookup_extended(sbar_item_defs,
+ name, &hkey, &hvalue)) {
+ g_hash_table_remove(sbar_item_defs, name);
+ g_free(hkey);
+ g_free(hvalue);
+ }
+ g_hash_table_insert(sbar_item_defs,
+ g_strdup(name), g_strdup(value));
+ }
+
+ if (func != NULL) {
+ if (g_hash_table_lookup(sbar_item_funcs, name) == NULL) {
+ g_hash_table_insert(sbar_item_funcs,
+ g_strdup(name), (void *) func);
+ }
+ }
+}
+
+void statusbar_item_unregister(const char *name)
+{
+ gpointer key, value;
+
+ statusbar_need_recreate_items = TRUE;
+ if (g_hash_table_lookup_extended(sbar_item_defs,
+ name, &key, &value)) {
+ g_hash_table_remove(sbar_item_defs, key);
+ g_free(key);
+ g_free(value);
+ }
+
+ if (g_hash_table_lookup_extended(sbar_item_funcs,
+ name, &key, &value)) {
+ g_hash_table_remove(sbar_item_funcs, key);
+ g_free(key);
+ }
+}
+
+void statusbar_item_set_size(struct SBAR_ITEM_REC *item, int min_size, int max_size)
+{
+ item->min_size = min_size;
+ item->max_size = max_size;
+}
+
+STATUSBAR_GROUP_REC *statusbar_group_create(const char *name)
+{
+ STATUSBAR_GROUP_REC *rec;
+
+ rec = g_new0(STATUSBAR_GROUP_REC, 1);
+ rec->name = g_strdup(name);
+
+ statusbar_groups = g_slist_append(statusbar_groups, rec);
+ return rec;
+}
+
+void statusbar_group_destroy(STATUSBAR_GROUP_REC *rec)
+{
+ statusbar_groups = g_slist_remove(statusbar_groups, rec);
+
+ while (rec->bars != NULL)
+ statusbar_destroy(rec->bars->data);
+ while (rec->config_bars != NULL)
+ statusbar_config_destroy(rec, rec->config_bars->data);
+
+ g_free(rec->name);
+ g_free(rec);
+}
+
+STATUSBAR_GROUP_REC *statusbar_group_find(const char *name)
+{
+ GSList *tmp;
+
+ for (tmp = statusbar_groups; tmp != NULL; tmp = tmp->next) {
+ STATUSBAR_GROUP_REC *rec = tmp->data;
+
+ if (g_strcmp0(rec->name, name) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static int sbar_item_cmp(SBAR_ITEM_REC *item1, SBAR_ITEM_REC *item2)
+{
+ return item1->config->priority == item2->config->priority ? 0 :
+ item1->config->priority < item2->config->priority ? -1 : 1;
+}
+
+static int sbar_cmp_position(STATUSBAR_REC *bar1, STATUSBAR_REC *bar2)
+{
+ return bar1->config->position < bar2->config->position ? -1 : 1;
+}
+
+/* Shink all items in statusbar to their minimum requested size.
+ The items list should be sorted by priority, highest first. */
+static int statusbar_shrink_to_min(GSList *items, int size, int max_width)
+{
+ GSList *tmp;
+
+ for (tmp = items; tmp != NULL; tmp = tmp->next) {
+ SBAR_ITEM_REC *rec = tmp->data;
+
+ size -= (rec->max_size-rec->min_size);
+ rec->size = rec->min_size;
+
+ if (size <= max_width) {
+ rec->size += max_width-size;
+ break;
+ }
+
+ if (rec->size == 0) {
+ /* min_size was 0, item removed.
+ remove the marginal too */
+ size--;
+ }
+ }
+
+ return size;
+}
+
+/* shink the items in statusbar, even if their size gets smaller than
+ their minimum requested size. The items list should be sorted by
+ priority, highest first. */
+static void statusbar_shrink_forced(GSList *items, int size, int max_width)
+{
+ GSList *tmp;
+
+ for (tmp = items; tmp != NULL; tmp = tmp->next) {
+ SBAR_ITEM_REC *rec = tmp->data;
+
+ if (size-rec->size > max_width) {
+ /* remove the whole item */
+ size -= rec->size;
+ rec->size = 0;
+ } else {
+ /* shrink the item */
+ rec->size -= size-max_width;
+ break;
+ }
+ }
+}
+
+static void statusbar_resize_items(STATUSBAR_REC *bar, int max_width)
+{
+ GSList *tmp, *prior_sorted;
+ int width;
+
+ /* first give items their max. size */
+ prior_sorted = NULL;
+ width = 0;
+ for (tmp = bar->items; tmp != NULL; tmp = tmp->next) {
+ SBAR_ITEM_REC *rec = tmp->data;
+
+ rec->func(rec, TRUE);
+ rec->size = rec->max_size;
+
+ if (rec->size > 0) {
+ width += rec->max_size;
+
+ prior_sorted = g_slist_insert_sorted(prior_sorted, rec,
+ (GCompareFunc)
+ sbar_item_cmp);
+ }
+ }
+
+ if (width > max_width) {
+ /* too big, start shrinking from items with lowest priority
+ and shrink until everything fits or until we've shrinked
+ all items. */
+ width = statusbar_shrink_to_min(prior_sorted, width,
+ max_width);
+ if (width > max_width) {
+ /* still need to shrink, remove the items with lowest
+ priority until everything fits to screen */
+ statusbar_shrink_forced(prior_sorted, width,
+ max_width);
+ }
+ }
+
+ g_slist_free(prior_sorted);
+}
+
+#define SBAR_ITEM_REDRAW_NEEDED(_bar, _item, _xpos) \
+ (((_bar)->dirty_xpos != -1 && (_xpos) >= (_bar)->dirty_xpos) || \
+ (_item)->xpos != (_xpos) || (_item)->current_size != (_item)->size)
+
+static void statusbar_calc_item_positions(STATUSBAR_REC *bar)
+{
+ WINDOW_REC *window;
+ WINDOW_REC *old_active_win;
+ GSList *tmp, *right_items;
+ int xpos, rxpos;
+ int max_width;
+
+ old_active_win = active_win;
+ if (bar->parent_window != NULL)
+ active_win = bar->parent_window->active;
+
+ window = bar->parent_window != NULL
+ ? bar->parent_window->active
+ : NULL;
+
+ max_width = window != NULL ? window->width : term_width;
+ statusbar_resize_items(bar, max_width);
+
+ /* left-aligned items */
+ xpos = 0;
+ for (tmp = bar->items; tmp != NULL; tmp = tmp->next) {
+ SBAR_ITEM_REC *rec = tmp->data;
+
+ if (!rec->config->right_alignment &&
+ (rec->size > 0 || rec->current_size > 0)) {
+ if (SBAR_ITEM_REDRAW_NEEDED(bar, rec, xpos)) {
+ /* redraw the item */
+ rec->dirty = TRUE;
+ if (bar->dirty_xpos == -1 ||
+ xpos < bar->dirty_xpos) {
+ irssi_set_dirty();
+ bar->dirty = TRUE;
+ bar->dirty_xpos = xpos;
+ }
+
+ rec->xpos = xpos;
+ }
+ xpos += rec->size;
+ }
+ }
+
+ /* right-aligned items - first copy them to a new list backwards,
+ easier to draw them in right order */
+ right_items = NULL;
+ for (tmp = bar->items; tmp != NULL; tmp = tmp->next) {
+ SBAR_ITEM_REC *rec = tmp->data;
+
+ if (rec->config->right_alignment) {
+ if (rec->size > 0)
+ right_items = g_slist_prepend(right_items, rec);
+ else if (rec->current_size > 0 &&
+ (bar->dirty_xpos == -1 ||
+ rec->xpos < bar->dirty_xpos)) {
+ /* item was hidden - set the dirty position
+ to begin from the item's old xpos */
+ irssi_set_dirty();
+ bar->dirty = TRUE;
+ bar->dirty_xpos = rec->xpos;
+ }
+ }
+ }
+
+ rxpos = max_width;
+ for (tmp = right_items; tmp != NULL; tmp = tmp->next) {
+ SBAR_ITEM_REC *rec = tmp->data;
+
+ rxpos -= rec->size;
+ if (SBAR_ITEM_REDRAW_NEEDED(bar, rec, rxpos)) {
+ rec->dirty = TRUE;
+ if (bar->dirty_xpos == -1 ||
+ rxpos < bar->dirty_xpos) {
+ irssi_set_dirty();
+ bar->dirty = TRUE;
+ bar->dirty_xpos = rxpos;
+ }
+ rec->xpos = rxpos;
+ }
+ }
+ g_slist_free(right_items);
+
+ active_win = old_active_win;
+}
+
+void statusbar_redraw(STATUSBAR_REC *bar, int force)
+{
+ if (statusbar_need_recreate_items)
+ return; /* don't bother yet */
+
+ if (bar != NULL) {
+ if (force) {
+ irssi_set_dirty();
+ bar->dirty = TRUE;
+ bar->dirty_xpos = 0;
+ }
+ statusbar_calc_item_positions(bar);
+ } else if (active_statusbar_group != NULL) {
+ g_slist_foreach(active_statusbar_group->bars,
+ (GFunc) statusbar_redraw,
+ GINT_TO_POINTER(force));
+ }
+}
+
+void statusbar_item_redraw(SBAR_ITEM_REC *item)
+{
+ WINDOW_REC *old_active_win;
+
+ g_return_if_fail(item != NULL);
+
+ old_active_win = active_win;
+ if (item->bar->parent_window != NULL)
+ active_win = item->bar->parent_window->active;
+
+ item->func(item, TRUE);
+
+ item->dirty = TRUE;
+ item->bar->dirty = TRUE;
+ irssi_set_dirty();
+
+ if (item->max_size != item->size) {
+ /* item wants a new size - we'll need to redraw
+ the statusbar to see if this is allowed */
+ statusbar_redraw(item->bar, item->config->right_alignment);
+ }
+
+ active_win = old_active_win;
+}
+
+void statusbar_items_redraw(const char *name)
+{
+ g_slist_foreach(g_hash_table_lookup(named_sbar_items, name),
+ (GFunc) statusbar_item_redraw, NULL);
+}
+
+static void statusbars_recalc_ypos(STATUSBAR_REC *bar)
+{
+ GSList *tmp, *bar_group;
+ int ypos;
+
+ /* get list of statusbars with same type and placement,
+ sorted by position */
+ bar_group = NULL;
+ tmp = bar->config->type == STATUSBAR_TYPE_ROOT ? bar->group->bars :
+ bar->parent_window->statusbars;
+
+ for (; tmp != NULL; tmp = tmp->next) {
+ STATUSBAR_REC *rec = tmp->data;
+
+ if (rec->config->type == bar->config->type &&
+ rec->config->placement == bar->config->placement) {
+ bar_group = g_slist_insert_sorted(bar_group, rec,
+ (GCompareFunc)
+ sbar_cmp_position);
+ }
+ }
+
+ if (bar_group == NULL) {
+ /* we just destroyed the last statusbar in this
+ type/placement group */
+ return;
+ }
+
+ /* get the Y-position for the first statusbar */
+ if (bar->config->type == STATUSBAR_TYPE_ROOT) {
+ ypos = bar->config->placement == STATUSBAR_TOP ? 0 :
+ term_height - g_slist_length(bar_group);
+ } else {
+ ypos = bar->config->placement == STATUSBAR_TOP ?
+ bar->parent_window->first_line :
+ bar->parent_window->last_line -
+ (g_slist_length(bar_group)-1);
+ }
+
+ /* set the Y-positions */
+ while (bar_group != NULL) {
+ bar = bar_group->data;
+
+ if (bar->real_ypos != ypos) {
+ bar->real_ypos = ypos;
+ statusbar_redraw(bar, TRUE);
+ }
+
+ ypos++;
+ bar_group = g_slist_remove(bar_group, bar_group->data);
+ }
+}
+
+static void sig_terminal_resized(void)
+{
+ GSList *tmp;
+
+ for (tmp = active_statusbar_group->bars; tmp != NULL; tmp = tmp->next) {
+ STATUSBAR_REC *bar = tmp->data;
+
+ if (bar->config->type == STATUSBAR_TYPE_ROOT &&
+ bar->config->placement == STATUSBAR_BOTTOM) {
+ statusbars_recalc_ypos(bar);
+ break;
+ }
+ }
+}
+
+static void mainwindow_recalc_ypos(MAIN_WINDOW_REC *window, int placement)
+{
+ GSList *tmp;
+
+ for (tmp = window->statusbars; tmp != NULL; tmp = tmp->next) {
+ STATUSBAR_REC *bar = tmp->data;
+
+ if (bar->config->placement == placement) {
+ statusbars_recalc_ypos(bar);
+ break;
+ }
+ }
+}
+
+static void sig_mainwindow_resized(MAIN_WINDOW_REC *window)
+{
+ GSList *tmp;
+ mainwindow_recalc_ypos(window, STATUSBAR_TOP);
+ mainwindow_recalc_ypos(window, STATUSBAR_BOTTOM);
+ for (tmp = window->statusbars; tmp != NULL; tmp = tmp->next) {
+ STATUSBAR_REC *bar = tmp->data;
+ statusbar_redraw(bar, TRUE);
+ }
+}
+
+STATUSBAR_REC *statusbar_create(STATUSBAR_GROUP_REC *group,
+ STATUSBAR_CONFIG_REC *config,
+ MAIN_WINDOW_REC *parent_window)
+{
+ STATUSBAR_REC *bar;
+ THEME_REC *theme;
+ GSList *tmp;
+ char *name, *value;
+
+ g_return_val_if_fail(group != NULL, NULL);
+ g_return_val_if_fail(config != NULL, NULL);
+ g_return_val_if_fail(config->type != STATUSBAR_TYPE_WINDOW ||
+ parent_window != NULL, NULL);
+
+ bar = g_new0(STATUSBAR_REC, 1);
+ group->bars = g_slist_append(group->bars, bar);
+
+ bar->group = group;
+
+ bar->config = config;
+ bar->parent_window = parent_window;
+
+ irssi_set_dirty();
+ bar->dirty = TRUE;
+ bar->dirty_xpos = 0;
+
+ signal_remove("terminal resized", (SIGNAL_FUNC) sig_terminal_resized);
+ signal_remove("mainwindow resized", (SIGNAL_FUNC) sig_mainwindow_resized);
+ signal_remove("mainwindow moved", (SIGNAL_FUNC) sig_mainwindow_resized);
+
+ if (config->type == STATUSBAR_TYPE_ROOT) {
+ /* top/bottom of the screen */
+ mainwindows_reserve_lines(config->placement == STATUSBAR_TOP,
+ config->placement == STATUSBAR_BOTTOM);
+ theme = current_theme;
+ } else {
+ /* top/bottom of the window */
+ parent_window->statusbars =
+ g_slist_append(parent_window->statusbars, bar);
+ mainwindow_set_statusbar_lines(parent_window,
+ config->placement == STATUSBAR_TOP,
+ config->placement == STATUSBAR_BOTTOM);
+ theme = parent_window != NULL && parent_window->active != NULL &&
+ parent_window->active->theme != NULL ?
+ parent_window->active->theme : current_theme;
+ }
+
+ signal_add("terminal resized", (SIGNAL_FUNC) sig_terminal_resized);
+ signal_add("mainwindow resized", (SIGNAL_FUNC) sig_mainwindow_resized);
+ signal_add("mainwindow moved", (SIGNAL_FUNC) sig_mainwindow_resized);
+
+ /* get background color from sb_background abstract */
+ name = g_strdup_printf("{sb_%s_bg}", config->name);
+ value = theme_format_expand(theme, name);
+ g_free(name);
+
+ if (*value == '\0') {
+ /* try with the statusbar group name */
+ g_free(value);
+
+ name = g_strdup_printf("{sb_%s_bg}", group->name);
+ value = theme_format_expand(theme, name);
+ g_free(name);
+
+ if (*value == '\0') {
+ /* fallback to default statusbar background
+ (also provides backwards compatibility..) */
+ g_free(value);
+ value = theme_format_expand(theme, "{sb_background}");
+ }
+ }
+
+ if (*value == '\0') {
+ g_free(value);
+ value = g_strdup("%8");
+ }
+ bar->color = g_strconcat("%n", value, NULL);
+ g_free(value);
+
+ statusbars_recalc_ypos(bar);
+ signal_emit("statusbar created", 1, bar);
+
+ /* create the items to statusbar */
+ for (tmp = config->items; tmp != NULL; tmp = tmp->next) {
+ SBAR_ITEM_CONFIG_REC *rec = tmp->data;
+
+ statusbar_item_create(bar, rec);
+ }
+ return bar;
+}
+
+void statusbar_destroy(STATUSBAR_REC *bar)
+{
+ int top;
+
+ g_return_if_fail(bar != NULL);
+
+ bar->group->bars = g_slist_remove(bar->group->bars, bar);
+ if (bar->parent_window != NULL) {
+ bar->parent_window->statusbars =
+ g_slist_remove(bar->parent_window->statusbars, bar);
+ }
+
+ signal_emit("statusbar destroyed", 1, bar);
+
+ while (bar->items != NULL)
+ statusbar_item_destroy(bar->items->data);
+
+ g_free(bar->color);
+
+ if (bar->config->type != STATUSBAR_TYPE_WINDOW ||
+ bar->parent_window != NULL)
+ statusbars_recalc_ypos(bar);
+
+ top = bar->config->placement == STATUSBAR_TOP;
+ if (bar->config->type == STATUSBAR_TYPE_ROOT) {
+ /* top/bottom of the screen */
+ mainwindows_reserve_lines(top ? -1 : 0, !top ? -1 : 0);
+ } else if (bar->parent_window != NULL) {
+ /* top/bottom of the window */
+ mainwindow_set_statusbar_lines(bar->parent_window,
+ top ? -1 : 0, !top ? -1 : 0);
+ }
+
+ g_free(bar);
+}
+
+void statusbar_recreate_items(STATUSBAR_REC *bar)
+{
+ GSList *tmp;
+
+ /* destroy */
+ while (bar->items != NULL)
+ statusbar_item_destroy(bar->items->data);
+
+ /* create */
+ for (tmp = bar->config->items; tmp != NULL; tmp = tmp->next) {
+ SBAR_ITEM_CONFIG_REC *rec = tmp->data;
+
+ statusbar_item_create(bar, rec);
+ }
+
+ statusbar_redraw(bar, TRUE);
+}
+
+void statusbars_recreate_items(void)
+{
+ if (active_statusbar_group != NULL) {
+ g_slist_foreach(active_statusbar_group->bars,
+ (GFunc) statusbar_recreate_items, NULL);
+ }
+}
+
+STATUSBAR_REC *statusbar_find(STATUSBAR_GROUP_REC *group, const char *name,
+ MAIN_WINDOW_REC *window)
+{
+ GSList *tmp;
+
+ for (tmp = group->bars; tmp != NULL; tmp = tmp->next) {
+ STATUSBAR_REC *rec = tmp->data;
+
+ if (rec->parent_window == window &&
+ g_strcmp0(rec->config->name, name) == 0)
+ return rec;
+ }
+
+ return NULL;
+}
+
+static const char *statusbar_item_get_value(SBAR_ITEM_REC *item)
+{
+ const char *value;
+
+ value = item->config->value;
+ if (value == NULL) {
+ value = g_hash_table_lookup(sbar_item_defs,
+ item->config->name);
+ }
+
+ return value;
+}
+
+static GString *finalize_string(const char *str, const char *color)
+{
+ GString *out;
+
+ out = g_string_new(color);
+
+ while (*str != '\0') {
+ if ((unsigned char) *str < 32 ||
+ (term_type == TERM_TYPE_8BIT &&
+ (unsigned char) (*str & 0x7f) < 32)) {
+ /* control char */
+ g_string_append_printf(out, "%%8%c%%8",
+ 'A'-1 + (*str & 0x7f));
+ } else if (*str == '%' && str[1] == 'n') {
+ g_string_append(out, color);
+ str++;
+ } else {
+ g_string_append_c(out, *str);
+ }
+
+ str++;
+ }
+
+ return out;
+}
+
+void statusbar_item_default_handler(SBAR_ITEM_REC *item, int get_size_only,
+ const char *str, const char *data,
+ int escape_vars)
+{
+ SERVER_REC *server;
+ WI_ITEM_REC *wiitem;
+ char *tmpstr, *tmpstr2;
+ theme_rm_col reset;
+ int len;
+ strcpy(reset.m, "n");
+
+ if (str == NULL)
+ str = statusbar_item_get_value(item);
+ if (str == NULL || *str == '\0') {
+ item->min_size = item->max_size = 0;
+ return;
+ }
+
+ if (active_win == NULL) {
+ server = NULL;
+ wiitem = NULL;
+ } else {
+ server = active_win->active_server != NULL ?
+ active_win->active_server : active_win->connect_server;
+ wiitem = active_win->active;
+ }
+
+ /* expand templates */
+ tmpstr = theme_format_expand_data(current_theme, &str,
+ reset, reset,
+ NULL, NULL,
+ EXPAND_FLAG_ROOT |
+ EXPAND_FLAG_IGNORE_REPLACES |
+ EXPAND_FLAG_IGNORE_EMPTY);
+ /* expand $variables */
+ tmpstr2 = parse_special_string(tmpstr, server, wiitem, data, NULL,
+ (escape_vars ? PARSE_FLAG_ESCAPE_VARS : 0 ));
+ g_free(tmpstr);
+
+ /* remove color codes (not %formats) */
+ tmpstr = strip_codes(tmpstr2);
+ g_free(tmpstr2);
+
+ if (get_size_only) {
+ item->min_size = item->max_size = format_get_length(tmpstr);
+ } else {
+ GString *out;
+
+ if (item->size < item->min_size) {
+ /* they're forcing us smaller than minimum size.. */
+ len = format_real_length(tmpstr, item->size);
+ tmpstr[len] = '\0';
+ }
+ out = finalize_string(tmpstr, item->bar->color);
+ /* make sure the str is big enough to fill the
+ requested size, so it won't corrupt screen */
+ len = format_get_length(tmpstr);
+ if (len < item->size) {
+ int i;
+
+ len = item->size-len;
+ for (i = 0; i < len; i++)
+ g_string_append_c(out, ' ');
+ }
+
+ gui_printtext(ITEM_WINDOW_REAL_XPOS(item), item->bar->real_ypos, out->str);
+ g_string_free(out, TRUE);
+ }
+ g_free(tmpstr);
+}
+
+static void statusbar_item_default_func(SBAR_ITEM_REC *item, int get_size_only)
+{
+ statusbar_item_default_handler(item, get_size_only, NULL, "", TRUE);
+}
+
+static void statusbar_update_item(void)
+{
+ GSList *items;
+
+ items = g_hash_table_lookup(sbar_signal_items,
+ GINT_TO_POINTER(signal_get_emitted_id()));
+ while (items != NULL) {
+ SBAR_ITEM_REC *item = items->data;
+
+ statusbar_item_redraw(item);
+ items = items->next;
+ }
+}
+
+static void statusbar_update_server(SERVER_REC *server)
+{
+ SERVER_REC *item_server;
+ GSList *items;
+
+ items = g_hash_table_lookup(sbar_signal_items,
+ GINT_TO_POINTER(signal_get_emitted_id()));
+ while (items != NULL) {
+ SBAR_ITEM_REC *item = items->data;
+
+ item_server = item->bar->parent_window != NULL ?
+ item->bar->parent_window->active->active_server :
+ active_win->active_server;
+
+ if (item_server == server)
+ statusbar_item_redraw(item);
+
+ items = items->next;
+ }
+}
+
+static void statusbar_update_window(WINDOW_REC *window)
+{
+ WINDOW_REC *item_window;
+ GSList *items;
+
+ items = g_hash_table_lookup(sbar_signal_items,
+ GINT_TO_POINTER(signal_get_emitted_id()));
+ while (items != NULL) {
+ SBAR_ITEM_REC *item = items->data;
+
+ item_window = item->bar->parent_window != NULL ?
+ item->bar->parent_window->active : active_win;
+
+ if (item_window == window)
+ statusbar_item_redraw(item);
+
+ items = items->next;
+ }
+}
+
+static void statusbar_update_window_item(WI_ITEM_REC *wiitem)
+{
+ WI_ITEM_REC *item_wi;
+ GSList *items;
+
+ items = g_hash_table_lookup(sbar_signal_items,
+ GINT_TO_POINTER(signal_get_emitted_id()));
+ while (items != NULL) {
+ SBAR_ITEM_REC *item = items->data;
+
+ item_wi = item->bar->parent_window != NULL ?
+ item->bar->parent_window->active->active :
+ active_win->active;
+
+ if (item_wi == wiitem)
+ statusbar_item_redraw(item);
+
+ items = items->next;
+ }
+}
+
+static void statusbar_item_default_signals(SBAR_ITEM_REC *item)
+{
+ SIGNAL_FUNC func;
+ GSList *list;
+ const char *value;
+ void *signal_id;
+ int *signals, *pos;
+
+ value = statusbar_item_get_value(item);
+ if (value == NULL)
+ return;
+
+ signals = special_vars_get_signals(value);
+ if (signals == NULL)
+ return;
+
+ for (pos = signals; *pos != -1; pos += 2) {
+ /* update signal -> item mappings */
+ signal_id = GINT_TO_POINTER(*pos);
+ list = g_hash_table_lookup(sbar_signal_items, signal_id);
+ if (list == NULL) {
+ switch (pos[1]) {
+ case EXPANDO_ARG_NONE:
+ func = (SIGNAL_FUNC) statusbar_update_item;
+ break;
+ case EXPANDO_ARG_SERVER:
+ func = (SIGNAL_FUNC) statusbar_update_server;
+ break;
+ case EXPANDO_ARG_WINDOW:
+ func = (SIGNAL_FUNC) statusbar_update_window;
+ break;
+ case EXPANDO_ARG_WINDOW_ITEM:
+ func = (SIGNAL_FUNC) statusbar_update_window_item;
+ break;
+ default:
+ func = NULL;
+ break;
+ }
+ if (func != NULL) {
+ signal_add_full_id(MODULE_NAME,
+ SIGNAL_PRIORITY_DEFAULT,
+ *pos, func, NULL);
+ }
+ }
+
+ if (g_slist_find(list, item) == NULL)
+ list = g_slist_append(list, item);
+ g_hash_table_insert(sbar_signal_items, signal_id, list);
+
+ /* update item -> signal mappings */
+ list = g_hash_table_lookup(sbar_item_signals, item);
+ if (g_slist_find(list, signal_id) == NULL)
+ list = g_slist_append(list, signal_id);
+ g_hash_table_insert(sbar_item_signals, item, list);
+ }
+ g_free(signals);
+}
+
+SBAR_ITEM_REC *statusbar_item_create(STATUSBAR_REC *bar,
+ SBAR_ITEM_CONFIG_REC *config)
+{
+ SBAR_ITEM_REC *rec;
+ GSList *items;
+
+ g_return_val_if_fail(bar != NULL, NULL);
+ g_return_val_if_fail(config != NULL, NULL);
+
+ rec = g_new0(SBAR_ITEM_REC, 1);
+ bar->items = g_slist_append(bar->items, rec);
+
+ rec->bar = bar;
+ rec->config = config;
+
+ rec->func = (STATUSBAR_FUNC) g_hash_table_lookup(sbar_item_funcs,
+ config->name);
+ if (rec->func == NULL)
+ rec->func = statusbar_item_default_func;
+ statusbar_item_default_signals(rec);
+
+ items = g_hash_table_lookup(named_sbar_items, config->name);
+ items = g_slist_append(items, rec);
+ g_hash_table_insert(named_sbar_items, config->name, items);
+
+ irssi_set_dirty();
+ rec->dirty = TRUE;
+ bar->dirty = TRUE;
+
+ signal_emit("statusbar item created", 1, rec);
+ return rec;
+}
+
+static void statusbar_signal_remove(int signal_id)
+{
+ signal_remove_id(signal_id, (SIGNAL_FUNC) statusbar_update_item, NULL);
+ signal_remove_id(signal_id, (SIGNAL_FUNC) statusbar_update_server, NULL);
+ signal_remove_id(signal_id, (SIGNAL_FUNC) statusbar_update_window, NULL);
+ signal_remove_id(signal_id, (SIGNAL_FUNC) statusbar_update_window_item, NULL);
+}
+
+static void statusbar_item_remove_signal(SBAR_ITEM_REC *item, int signal_id)
+{
+ GSList *list;
+
+ /* update signal -> item hash */
+ list = g_hash_table_lookup(sbar_signal_items,
+ GINT_TO_POINTER(signal_id));
+ list = g_slist_remove(list, item);
+ if (list != NULL) {
+ g_hash_table_insert(sbar_signal_items,
+ GINT_TO_POINTER(signal_id), list);
+ } else {
+ g_hash_table_remove(sbar_signal_items,
+ GINT_TO_POINTER(signal_id));
+ statusbar_signal_remove(signal_id);
+ }
+}
+
+void statusbar_item_destroy(SBAR_ITEM_REC *item)
+{
+ GSList *list;
+
+ g_return_if_fail(item != NULL);
+
+ item->bar->items = g_slist_remove(item->bar->items, item);
+
+ list = g_hash_table_lookup(named_sbar_items, item->config->name);
+ list = g_slist_remove(list, item);
+ if (list == NULL)
+ g_hash_table_remove(named_sbar_items, item->config->name);
+ else
+ g_hash_table_insert(named_sbar_items, item->config->name, list);
+
+ signal_emit("statusbar item destroyed", 1, item);
+
+ list = g_hash_table_lookup(sbar_item_signals, item);
+ g_hash_table_remove(sbar_item_signals, item);
+
+ while (list != NULL) {
+ statusbar_item_remove_signal(item, GPOINTER_TO_INT(list->data));
+ list = g_slist_remove(list, list->data);
+ }
+
+ g_free(item);
+}
+
+static MAIN_WINDOW_BORDER_REC *set_border_info(STATUSBAR_REC *bar)
+{
+ MAIN_WINDOW_BORDER_REC *orig_border, *new_border;
+ orig_border = clrtoeol_info;
+ new_border = g_new0(MAIN_WINDOW_BORDER_REC, 1);
+ new_border->window = bar->parent_window != NULL ? bar->parent_window->screen_win : NULL;
+ new_border->color = bar->color;
+ clrtoeol_info = new_border;
+ return orig_border;
+}
+
+static void restore_border_info(MAIN_WINDOW_BORDER_REC *border_info)
+{
+ MAIN_WINDOW_BORDER_REC *old_border;
+ old_border = clrtoeol_info;
+ clrtoeol_info = border_info;
+ g_free(old_border);
+}
+
+static void statusbar_redraw_needed_items(STATUSBAR_REC *bar)
+{
+ WINDOW_REC *old_active_win;
+ GSList *tmp;
+ char *str;
+
+ old_active_win = active_win;
+ if (bar->parent_window != NULL)
+ active_win = bar->parent_window->active;
+
+ if (bar->dirty_xpos >= 0) {
+ MAIN_WINDOW_BORDER_REC *orig_border;
+ orig_border = set_border_info(bar);
+ str = g_strconcat(bar->color, "%>", NULL);
+ gui_printtext(BAR_WINDOW_REAL_DIRTY_XPOS(bar), bar->real_ypos, str);
+ g_free(str);
+ restore_border_info(orig_border);
+ }
+
+ for (tmp = bar->items; tmp != NULL; tmp = tmp->next) {
+ SBAR_ITEM_REC *rec = tmp->data;
+
+ if (rec->dirty ||
+ (bar->dirty_xpos != -1 &&
+ rec->xpos >= bar->dirty_xpos)) {
+ rec->current_size = rec->size;
+ rec->func(rec, FALSE);
+ rec->dirty = FALSE;
+ }
+ }
+
+ active_win = old_active_win;
+}
+
+void statusbar_redraw_dirty(void)
+{
+ GSList *tmp;
+
+ if (statusbar_need_recreate_items) {
+ statusbar_need_recreate_items = FALSE;
+ statusbars_recreate_items();
+ }
+
+ for (tmp = active_statusbar_group->bars; tmp != NULL; tmp = tmp->next) {
+ STATUSBAR_REC *rec = tmp->data;
+
+ if (rec->dirty) {
+ statusbar_redraw_needed_items(rec);
+ rec->dirty = FALSE;
+ rec->dirty_xpos = -1;
+ }
+ }
+}
+
+#define STATUSBAR_IS_VISIBLE(bar, window) \
+ ((bar)->visible == STATUSBAR_VISIBLE_ALWAYS || \
+ (active_mainwin == (window) && \
+ (bar)->visible == STATUSBAR_VISIBLE_ACTIVE) || \
+ (active_mainwin != (window) && \
+ (bar)->visible == STATUSBAR_VISIBLE_INACTIVE))
+
+static void statusbars_remove_unvisible(MAIN_WINDOW_REC *window)
+{
+ GSList *tmp, *next;
+
+ for (tmp = window->statusbars; tmp != NULL; tmp = next) {
+ STATUSBAR_REC *bar = tmp->data;
+
+ next = tmp->next;
+ if (!STATUSBAR_IS_VISIBLE(bar->config, window))
+ statusbar_destroy(bar);
+ }
+}
+
+static void statusbars_add_visible(MAIN_WINDOW_REC *window)
+{
+ STATUSBAR_GROUP_REC *group;
+ STATUSBAR_REC *bar;
+ GSList *tmp;
+
+ group = active_statusbar_group;
+ for (tmp = group->config_bars; tmp != NULL; tmp = tmp->next) {
+ STATUSBAR_CONFIG_REC *config = tmp->data;
+
+ if (config->type == STATUSBAR_TYPE_WINDOW &&
+ STATUSBAR_IS_VISIBLE(config, window) &&
+ statusbar_find(group, config->name, window) == NULL) {
+ bar = statusbar_create(group, config, window);
+ statusbar_redraw(bar, TRUE);
+ }
+ }
+}
+
+static void sig_mainwindow_destroyed(MAIN_WINDOW_REC *window)
+{
+ while (window->statusbars != NULL) {
+ STATUSBAR_REC *bar = window->statusbars->data;
+
+ bar->parent_window->statusbars =
+ g_slist_remove(bar->parent_window->statusbars, bar);
+ bar->parent_window = NULL;
+ statusbar_destroy(bar);
+ }
+}
+
+static void sig_window_changed(void)
+{
+ GSList *tmp;
+
+ for (tmp = mainwindows; tmp != NULL; tmp = tmp->next) {
+ MAIN_WINDOW_REC *rec = tmp->data;
+
+ statusbars_remove_unvisible(rec);
+ statusbars_add_visible(rec);
+ }
+}
+
+static void sig_gui_window_created(WINDOW_REC *window)
+{
+ statusbars_add_visible(WINDOW_MAIN(window));
+}
+
+static void statusbar_item_def_destroy(void *key, void *value)
+{
+ g_free(key);
+ g_free(value);
+}
+
+static void statusbar_signal_item_destroy(void *key, GSList *value)
+{
+ while (value != NULL) {
+ statusbar_signal_remove(GPOINTER_TO_INT(value->data));
+ value->data = g_slist_remove(value, value->data);
+ }
+}
+
+static void statusbar_item_signal_destroy(void *key, GSList *value)
+{
+ g_slist_free(value);
+}
+
+void statusbars_create_window_bars(void)
+{
+ g_slist_foreach(mainwindows, (GFunc) statusbars_add_visible, NULL);
+}
+
+void statusbar_init(void)
+{
+ statusbar_need_recreate_items = FALSE;
+ statusbar_groups = NULL;
+ active_statusbar_group = NULL;
+ sbar_item_defs = g_hash_table_new((GHashFunc) g_str_hash,
+ (GCompareFunc) g_str_equal);
+ sbar_item_funcs = g_hash_table_new((GHashFunc) g_str_hash,
+ (GCompareFunc) g_str_equal);
+ sbar_signal_items = g_hash_table_new((GHashFunc) g_direct_hash,
+ (GCompareFunc) g_direct_equal);
+ sbar_item_signals = g_hash_table_new((GHashFunc) g_direct_hash,
+ (GCompareFunc) g_direct_equal);
+ named_sbar_items = g_hash_table_new((GHashFunc) g_str_hash,
+ (GCompareFunc) g_str_equal);
+
+ signal_add("terminal resized", (SIGNAL_FUNC) sig_terminal_resized);
+ signal_add("mainwindow resized", (SIGNAL_FUNC) sig_mainwindow_resized);
+ signal_add("mainwindow moved", (SIGNAL_FUNC) sig_mainwindow_resized);
+ signal_add("gui window created", (SIGNAL_FUNC) sig_gui_window_created);
+ signal_add("window changed", (SIGNAL_FUNC) sig_window_changed);
+ signal_add("mainwindow destroyed", (SIGNAL_FUNC) sig_mainwindow_destroyed);
+
+ statusbar_items_init();
+ statusbar_config_init(); /* signals need to be before this call */
+}
+
+void statusbar_deinit(void)
+{
+ while (statusbar_groups != NULL)
+ statusbar_group_destroy(statusbar_groups->data);
+
+ g_hash_table_foreach(sbar_item_defs,
+ (GHFunc) statusbar_item_def_destroy, NULL);
+ g_hash_table_destroy(sbar_item_defs);
+
+ g_hash_table_foreach(sbar_item_funcs, (GHFunc) g_free, NULL);
+ g_hash_table_destroy(sbar_item_funcs);
+
+ g_hash_table_foreach(sbar_signal_items,
+ (GHFunc) statusbar_signal_item_destroy, NULL);
+ g_hash_table_destroy(sbar_signal_items);
+ g_hash_table_foreach(sbar_item_signals,
+ (GHFunc) statusbar_item_signal_destroy, NULL);
+ g_hash_table_destroy(sbar_item_signals);
+ g_hash_table_destroy(named_sbar_items);
+
+ signal_remove("terminal resized", (SIGNAL_FUNC) sig_terminal_resized);
+ signal_remove("mainwindow resized", (SIGNAL_FUNC) sig_mainwindow_resized);
+ signal_remove("mainwindow moved", (SIGNAL_FUNC) sig_mainwindow_resized);
+ signal_remove("gui window created", (SIGNAL_FUNC) sig_gui_window_created);
+ signal_remove("window changed", (SIGNAL_FUNC) sig_window_changed);
+ signal_remove("mainwindow destroyed", (SIGNAL_FUNC) sig_mainwindow_destroyed);
+
+ statusbar_items_deinit();
+ statusbar_config_deinit();
+}
diff --git a/src/fe-text/statusbar.h b/src/fe-text/statusbar.h
new file mode 100644
index 0000000..140d355
--- /dev/null
+++ b/src/fe-text/statusbar.h
@@ -0,0 +1,117 @@
+#ifndef IRSSI_FE_TEXT_STATUSBAR_H
+#define IRSSI_FE_TEXT_STATUSBAR_H
+
+#include <irssi/src/fe-text/mainwindows.h>
+#include <irssi/src/fe-text/statusbar-item.h>
+
+#define STATUSBAR_PRIORITY_HIGH 100
+#define STATUSBAR_PRIORITY_NORMAL 0
+#define STATUSBAR_PRIORITY_LOW -100
+
+typedef struct SBAR_ITEM_REC SBAR_ITEM_REC;
+
+/* type */
+#define STATUSBAR_TYPE_ROOT 1
+#define STATUSBAR_TYPE_WINDOW 2
+
+/* placement */
+#define STATUSBAR_TOP 1
+#define STATUSBAR_BOTTOM 2
+
+/* visible */
+#define STATUSBAR_VISIBLE_ALWAYS 1
+#define STATUSBAR_VISIBLE_ACTIVE 2
+#define STATUSBAR_VISIBLE_INACTIVE 3
+
+#define ITEM_WINDOW_REAL_XPOS(item) ( ( (item)->bar->parent_window != NULL ? \
+ (item)->bar->parent_window->first_column + (item)->bar->parent_window->statusbar_columns_left : 0 ) + (item)->xpos )
+
+#define BAR_WINDOW_REAL_DIRTY_XPOS(bar) ( ( (bar)->parent_window != NULL ? \
+ (bar)->parent_window->first_column + (bar)->parent_window->statusbar_columns_left : 0 ) + (bar)->dirty_xpos )
+
+typedef struct {
+ char *name;
+ GSList *config_bars;
+ GSList *bars;
+} STATUSBAR_GROUP_REC;
+
+typedef struct {
+ char *name;
+
+ int type; /* root/window */
+ int placement; /* top/bottom */
+ int position; /* the higher the number, the lower it is in screen */
+ int visible; /* active/inactive/always */
+
+ GSList *items;
+} STATUSBAR_CONFIG_REC;
+
+typedef struct {
+ STATUSBAR_GROUP_REC *group;
+ STATUSBAR_CONFIG_REC *config;
+
+ MAIN_WINDOW_REC *parent_window; /* if config->type == STATUSBAR_TYPE_WINDOW */
+ GSList *items;
+
+ char *color; /* background color */
+ int real_ypos; /* real Y-position in screen at the moment */
+
+ unsigned int dirty:1;
+ int dirty_xpos; /* -1 = only redraw some items, >= 0 = redraw all items after from xpos */
+} STATUSBAR_REC;
+
+typedef struct {
+ char *name;
+ char *value; /* if non-NULL, overrides the default */
+
+ int priority;
+ unsigned int right_alignment:1;
+} SBAR_ITEM_CONFIG_REC;
+
+struct SBAR_ITEM_REC {
+ STATUSBAR_REC *bar;
+ SBAR_ITEM_CONFIG_REC *config;
+ STATUSBAR_FUNC func;
+
+ /* what item wants */
+ int min_size, max_size;
+
+ /* what item gets */
+ int xpos, size;
+
+ int current_size; /* item size currently in screen */
+ unsigned int dirty:1;
+};
+
+extern GSList *statusbar_groups;
+extern STATUSBAR_GROUP_REC *active_statusbar_group;
+
+STATUSBAR_GROUP_REC *statusbar_group_create(const char *name);
+void statusbar_group_destroy(STATUSBAR_GROUP_REC *rec);
+STATUSBAR_GROUP_REC *statusbar_group_find(const char *name);
+
+STATUSBAR_REC *statusbar_create(STATUSBAR_GROUP_REC *group,
+ STATUSBAR_CONFIG_REC *config,
+ MAIN_WINDOW_REC *parent_window);
+void statusbar_destroy(STATUSBAR_REC *bar);
+STATUSBAR_REC *statusbar_find(STATUSBAR_GROUP_REC *group, const char *name,
+ MAIN_WINDOW_REC *window);
+
+SBAR_ITEM_REC *statusbar_item_create(STATUSBAR_REC *bar,
+ SBAR_ITEM_CONFIG_REC *config);
+void statusbar_item_destroy(SBAR_ITEM_REC *item);
+
+/* redraw statusbar, NULL = all */
+void statusbar_redraw(STATUSBAR_REC *bar, int force);
+void statusbar_item_redraw(SBAR_ITEM_REC *item);
+
+void statusbar_recreate_items(STATUSBAR_REC *bar);
+void statusbars_recreate_items(void);
+void statusbars_create_window_bars(void);
+
+void statusbar_redraw_dirty(void);
+
+void statusbar_init(void);
+void statusbar_deinit(void);
+
+#endif
diff --git a/src/fe-text/term-terminfo.c b/src/fe-text/term-terminfo.c
new file mode 100644
index 0000000..9902bc0
--- /dev/null
+++ b/src/fe-text/term-terminfo.c
@@ -0,0 +1,803 @@
+/*
+ term-terminfo.c : irssi
+
+ Copyright (C) 2001 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/fe-text/term.h>
+#include <irssi/src/fe-text/terminfo-core.h>
+#include <irssi/src/fe-common/core/fe-windows.h>
+#include <irssi/src/fe-text/gui-printtext.h>
+#include <irssi/src/core/utf8.h>
+
+#include <signal.h>
+#include <termios.h>
+#include <stdio.h>
+
+#ifdef HAVE_TERM_H
+#include <term.h>
+#else
+/* TODO: This needs arguments, starting with C2X. */
+int tputs();
+#endif
+
+/* returns number of characters in the beginning of the buffer being a
+ a single character, or -1 if more input is needed. The character will be
+ saved in result */
+typedef int (*TERM_INPUT_FUNC)(const unsigned char *buffer, int size,
+ unichar *result);
+
+struct _TERM_WINDOW {
+ /* Terminal to use for window */
+ TERM_REC *term;
+
+ /* Area for window in terminal */
+ int x, y;
+ int width, height;
+};
+
+TERM_WINDOW *root_window;
+
+static char *term_lines_empty; /* 1 if line is entirely empty */
+static int vcmove, vcx, vcy, curs_visible;
+static int crealx, crealy, cforcemove;
+static int curs_x, curs_y;
+
+static unsigned int last_fg, last_bg;
+static int last_attrs;
+
+static GSource *sigcont_source;
+static volatile sig_atomic_t got_sigcont;
+static int freeze_counter;
+
+static TERM_INPUT_FUNC input_func;
+static unsigned char term_inbuf[256];
+static int term_inbuf_pos;
+
+/* SIGCONT handler */
+static void sig_cont(int p)
+{
+ got_sigcont = TRUE;
+}
+
+/* SIGCONT GSource */
+static gboolean sigcont_prepare(GSource *source, gint *timeout)
+{
+ *timeout = -1;
+ return got_sigcont;
+}
+
+static gboolean sigcont_check(GSource *source)
+{
+ return got_sigcont;
+}
+
+static gboolean sigcont_dispatch(GSource *source, GSourceFunc callback, gpointer user_data)
+{
+ got_sigcont = FALSE;
+ if (callback == NULL)
+ return TRUE;
+ return callback(user_data);
+}
+
+static gboolean do_redraw(gpointer unused)
+{
+ terminfo_cont(current_term);
+ irssi_redraw();
+
+ return 1;
+}
+
+static GSourceFuncs sigcont_funcs = {
+ .prepare = sigcont_prepare,
+ .check = sigcont_check,
+ .dispatch = sigcont_dispatch
+};
+
+static void term_atexit(void)
+{
+ if (!quitting && current_term && current_term->TI_rmcup) {
+ /* Unexpected exit, avoid switching out of alternate screen
+ to keep any on-screen errors (like noperl_die()'s) */
+ current_term->TI_rmcup = NULL;
+ }
+
+ term_deinit();
+}
+
+int term_init(void)
+{
+ struct sigaction act;
+ int width, height;
+
+ last_fg = last_bg = -1;
+ last_attrs = 0;
+ vcx = vcy = 0; crealx = crealy = -1;
+ vcmove = FALSE; cforcemove = TRUE;
+ curs_visible = TRUE;
+
+ current_term = terminfo_core_init(stdin, stdout);
+ if (current_term == NULL)
+ return FALSE;
+
+ if (term_get_size(&width, &height)) {
+ current_term->width = width;
+ current_term->height = height;
+ }
+
+ /* grab CONT signal */
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ act.sa_handler = sig_cont;
+ sigaction(SIGCONT, &act, NULL);
+ sigcont_source = g_source_new(&sigcont_funcs, sizeof(GSource));
+ g_source_set_callback(sigcont_source, do_redraw, NULL, NULL);
+ g_source_attach(sigcont_source, NULL);
+
+ curs_x = curs_y = 0;
+ term_width = current_term->width;
+ term_height = current_term->height;
+ root_window = term_window_create(0, 0, term_width, term_height);
+
+ term_lines_empty = g_new0(char, term_height);
+
+ term_set_input_type(TERM_TYPE_8BIT);
+ term_common_init();
+ atexit(term_atexit);
+ return TRUE;
+}
+
+void term_deinit(void)
+{
+ if (current_term != NULL) {
+ signal(SIGCONT, SIG_DFL);
+ g_source_destroy(sigcont_source);
+ g_source_unref(sigcont_source);
+
+ term_common_deinit();
+ terminfo_core_deinit(current_term);
+ current_term = NULL;
+ }
+}
+
+static void term_move_real(void)
+{
+ if (vcx != crealx || vcy != crealy || cforcemove) {
+ if (curs_visible) {
+ terminfo_set_cursor_visible(FALSE);
+ curs_visible = FALSE;
+ }
+
+ if (cforcemove) {
+ crealx = crealy = -1;
+ cforcemove = FALSE;
+ }
+ terminfo_move_relative(crealx, crealy, vcx, vcy);
+ crealx = vcx; crealy = vcy;
+ }
+
+ vcmove = FALSE;
+}
+
+/* Cursor position is unknown - move it immediately to known position */
+static void term_move_reset(int x, int y)
+{
+ if (x >= term_width) x = term_width-1;
+ if (y >= term_height) y = term_height-1;
+
+ vcx = x; vcy = y;
+ cforcemove = TRUE;
+ term_move_real();
+}
+
+/* Resize terminal - if width or height is negative,
+ the new size is unknown and should be figured out somehow */
+void term_resize(int width, int height)
+{
+ if (width < 0 || height < 0) {
+ width = current_term->width;
+ height = current_term->height;
+ }
+
+ if (term_width != width || term_height != height) {
+ term_width = current_term->width = width;
+ term_height = current_term->height = height;
+ term_window_move(root_window, 0, 0, term_width, term_height);
+
+ g_free(term_lines_empty);
+ term_lines_empty = g_new0(char, term_height);
+ }
+
+ term_move_reset(0, 0);
+}
+
+void term_resize_final(int width, int height)
+{
+}
+
+/* Returns TRUE if terminal has colors */
+int term_has_colors(void)
+{
+ return current_term->TI_colors > 0;
+}
+
+/* Force the colors on any way you can */
+void term_force_colors(int set)
+{
+ terminfo_setup_colors(current_term, set);
+}
+
+/* Clear screen */
+void term_clear(void)
+{
+ term_set_color(root_window, ATTR_RESET);
+ terminfo_clear();
+ term_move_reset(0, 0);
+
+ memset(term_lines_empty, 1, term_height);
+}
+
+/* Beep */
+void term_beep(void)
+{
+ terminfo_beep(current_term);
+}
+
+/* Create a new window in terminal */
+TERM_WINDOW *term_window_create(int x, int y, int width, int height)
+{
+ TERM_WINDOW *window;
+
+ window = g_new0(TERM_WINDOW, 1);
+ window->term = current_term;
+ window->x = x; window->y = y;
+ window->width = width; window->height = height;
+ return window;
+}
+
+/* Destroy a terminal window */
+void term_window_destroy(TERM_WINDOW *window)
+{
+ g_free(window);
+}
+
+/* Move/resize a window */
+void term_window_move(TERM_WINDOW *window, int x, int y,
+ int width, int height)
+{
+ window->x = x;
+ window->y = y;
+ window->width = width;
+ window->height = height;
+}
+
+/* Clear window */
+void term_window_clear(TERM_WINDOW *window)
+{
+ int y;
+
+ terminfo_set_normal();
+ if (window->y == 0 && window->height == term_height && window->width == term_width) {
+ term_clear();
+ } else {
+ for (y = 0; y < window->height; y++) {
+ term_move(window, 0, y);
+ term_clrtoeol(window);
+ }
+ }
+}
+
+/* Scroll window up/down */
+void term_window_scroll(TERM_WINDOW *window, int count)
+{
+ int y;
+
+ terminfo_scroll(window->y, window->y+window->height-1, count);
+ term_move_reset(vcx, vcy);
+
+ /* set the newly scrolled area dirty */
+ for (y = 0; (window->y+y) < term_height && y < window->height; y++)
+ term_lines_empty[window->y+y] = FALSE;
+}
+
+inline static int term_putchar(int c)
+{
+ return fputc(c, current_term->out);
+}
+
+static int termctl_set_color_24bit(int bg, unsigned int lc)
+{
+ static char buf[20];
+ const unsigned char color[] = { lc >> 16, lc >> 8, lc };
+
+ if (!term_use_colors24) {
+ if (bg)
+ terminfo_set_bg(color_24bit_256(color));
+ else
+ terminfo_set_fg(color_24bit_256(color));
+ return -1;
+ }
+
+ /* \e[x8;2;...;...;...m */
+ sprintf(buf, "\033[%d8;2;%d;%d;%dm", bg ? 4 : 3, color[0], color[1], color[2]);
+ return tputs(buf, 0, term_putchar);
+}
+
+#define COLOR_RESET UINT_MAX
+#define COLOR_BLACK24 COLOR_RESET - 1
+
+/* Change active color */
+#ifdef TERM_TRUECOLOR
+void term_set_color2(TERM_WINDOW *window, int col, unsigned int fgcol24, unsigned int bgcol24)
+#else
+void term_set_color(TERM_WINDOW *window, int col)
+#endif
+{
+ int set_normal;
+
+ unsigned int fg, bg;
+#ifdef TERM_TRUECOLOR
+ if (col & ATTR_FGCOLOR24) {
+ if (fgcol24)
+ fg = fgcol24 << 8;
+ else
+ fg = COLOR_BLACK24;
+ } else
+#endif
+ fg = (col & FG_MASK);
+
+#ifdef TERM_TRUECOLOR
+ if (col & ATTR_BGCOLOR24) {
+ if (bgcol24)
+ bg = bgcol24 << 8;
+ else
+ bg = COLOR_BLACK24;
+ } else
+#endif
+ bg = ((col & BG_MASK) >> BG_SHIFT);
+
+ if (!term_use_colors && bg > 0)
+ col |= ATTR_REVERSE;
+
+ set_normal = ((col & ATTR_RESETFG) && last_fg != COLOR_RESET) ||
+ ((col & ATTR_RESETBG) && last_bg != COLOR_RESET);
+ if (((last_attrs & ATTR_BOLD) && (col & ATTR_BOLD) == 0) ||
+ ((last_attrs & ATTR_REVERSE) && (col & ATTR_REVERSE) == 0) ||
+ ((last_attrs & ATTR_BLINK) && (col & ATTR_BLINK) == 0)) {
+ /* we'll need to get rid of bold/blink/reverse - this
+ can only be done with setting the default color */
+ set_normal = TRUE;
+ }
+
+ if (set_normal) {
+ last_fg = last_bg = COLOR_RESET;
+ last_attrs = 0;
+ terminfo_set_normal();
+ }
+
+ /* set foreground color */
+ if (fg != last_fg &&
+ (fg != 0 || (col & ATTR_RESETFG) == 0)) {
+ if (term_use_colors) {
+ last_fg = fg;
+ if (fg >> 8)
+ termctl_set_color_24bit(0,
+ last_fg == COLOR_BLACK24 ? 0
+ : last_fg >> 8);
+ else
+ terminfo_set_fg(last_fg);
+ }
+ }
+
+ /* set background color */
+ if (window && window->term->TI_colors &&
+ (term_color256map[bg&0xff]&8) == window->term->TI_colors)
+ col |= ATTR_BLINK;
+ if (col & ATTR_BLINK)
+ current_term->set_blink(current_term);
+
+ if (bg != last_bg &&
+ (bg != 0 || (col & ATTR_RESETBG) == 0)) {
+ if (term_use_colors) {
+ last_bg = bg;
+ if (bg >> 8)
+ termctl_set_color_24bit(1,
+ last_bg == COLOR_BLACK24 ? 0
+ : last_bg >> 8);
+ else
+ terminfo_set_bg(last_bg);
+ }
+ }
+
+ /* reversed text */
+ if (col & ATTR_REVERSE)
+ terminfo_set_reverse();
+
+ /* bold */
+ if (window && window->term->TI_colors &&
+ (term_color256map[fg&0xff]&8) == window->term->TI_colors)
+ col |= ATTR_BOLD;
+ if (col & ATTR_BOLD)
+ terminfo_set_bold();
+
+ /* underline */
+ if (col & ATTR_UNDERLINE) {
+ if ((last_attrs & ATTR_UNDERLINE) == 0)
+ terminfo_set_uline(TRUE);
+ } else if (last_attrs & ATTR_UNDERLINE)
+ terminfo_set_uline(FALSE);
+
+ /* italic */
+ if (col & ATTR_ITALIC) {
+ if ((last_attrs & ATTR_ITALIC) == 0)
+ terminfo_set_italic(TRUE);
+ } else if (last_attrs & ATTR_ITALIC)
+ terminfo_set_italic(FALSE);
+
+ /* update the new attribute settings whilst ignoring color values. */
+ last_attrs = col & ~( BG_MASK | FG_MASK );
+}
+
+void term_move(TERM_WINDOW *window, int x, int y)
+{
+ if (x >= 0 && y >= 0) {
+ vcmove = TRUE;
+ vcx = x+window->x;
+ vcy = y+window->y;
+
+ if (vcx >= term_width)
+ vcx = term_width-1;
+ if (vcy >= term_height)
+ vcy = term_height-1;
+ }
+}
+
+static void term_printed_text(int count)
+{
+ term_lines_empty[vcy] = FALSE;
+
+ /* if we continued writing past the line, wrap to next line.
+ However, next term_move() really shouldn't try to cache
+ the move, otherwise terminals would try to combine the
+ last word in upper line with first word in lower line. */
+ vcx += count;
+ while (vcx >= term_width) {
+ vcx -= term_width;
+ if (vcy < term_height-1) vcy++;
+ if (vcx > 0) term_lines_empty[vcy] = FALSE;
+ }
+
+ crealx += count;
+ if (crealx >= term_width)
+ cforcemove = TRUE;
+}
+
+void term_addch(TERM_WINDOW *window, char chr)
+{
+ if (vcmove) term_move_real();
+
+ /* With UTF-8, move cursor only if this char is either
+ single-byte (8. bit off) or beginning of multibyte
+ (7. bit off) */
+ if (term_type != TERM_TYPE_UTF8 ||
+ (chr & 0x80) == 0 || (chr & 0x40) == 0) {
+ term_printed_text(1);
+ }
+
+ putc(chr, window->term->out);
+}
+
+static void term_addch_utf8(TERM_WINDOW *window, unichar chr)
+{
+ char buf[10];
+ int i, len;
+
+ len = g_unichar_to_utf8(chr, buf);
+ for (i = 0; i < len; i++)
+ putc(buf[i], window->term->out);
+}
+
+void term_add_unichar(TERM_WINDOW *window, unichar chr)
+{
+ if (vcmove) term_move_real();
+
+ switch (term_type) {
+ case TERM_TYPE_UTF8:
+ term_printed_text(unichar_isprint(chr) ? i_wcwidth(chr) : 1);
+ term_addch_utf8(window, chr);
+ break;
+ case TERM_TYPE_BIG5:
+ if (chr > 0xff) {
+ term_printed_text(2);
+ putc((chr >> 8) & 0xff, window->term->out);
+ } else {
+ term_printed_text(1);
+ }
+ putc((chr & 0xff), window->term->out);
+ break;
+ default:
+ term_printed_text(1);
+ putc(chr, window->term->out);
+ break;
+ }
+}
+
+int term_addstr(TERM_WINDOW *window, const char *str)
+{
+ int len, raw_len;
+ unichar tmp;
+ const char *ptr;
+
+ if (vcmove) term_move_real();
+
+ len = 0;
+ raw_len = strlen(str);
+
+ /* The string length depends on the terminal encoding */
+
+ ptr = str;
+
+ if (term_type == TERM_TYPE_UTF8) {
+ while (*ptr != '\0') {
+ tmp = g_utf8_get_char_validated(ptr, -1);
+ /* On utf8 error, treat as single byte and try to
+ continue interpreting rest of string as utf8 */
+ if (tmp == (gunichar)-1 || tmp == (gunichar)-2) {
+ len++;
+ ptr++;
+ } else {
+ len += unichar_isprint(tmp) ? i_wcwidth(tmp) : 1;
+ ptr = g_utf8_next_char(ptr);
+ }
+ }
+ } else
+ len = raw_len;
+
+ term_printed_text(len);
+
+ /* Use strlen() here since we need the number of raw bytes */
+ fwrite(str, 1, raw_len, window->term->out);
+
+ return len;
+}
+
+void term_clrtoeol(TERM_WINDOW *window)
+{
+ if (vcx < window->x) {
+ /* we just wrapped outside of the split, warp the cursor back into the window */
+ vcx += window->x;
+ vcmove = TRUE;
+ }
+ if (window->x + window->width < term_width) {
+ /* we need to fill a vertical split */
+ if (vcmove) term_move_real();
+ terminfo_repeat(' ', window->x + window->width - vcx + 1);
+ terminfo_move(vcx, vcy);
+ term_lines_empty[vcy] = FALSE;
+ } else {
+ /* clrtoeol() doesn't necessarily understand colors */
+ if (last_fg == -1 && last_bg == -1 &&
+ (last_attrs & (ATTR_UNDERLINE|ATTR_REVERSE|ATTR_ITALIC)) == 0) {
+ if (!term_lines_empty[vcy]) {
+ if (vcmove) term_move_real();
+ terminfo_clrtoeol();
+ if (vcx == 0) term_lines_empty[vcy] = TRUE;
+ }
+ } else if (vcx < term_width) {
+ /* we'll need to fill the line ourself. */
+ if (vcmove) term_move_real();
+ terminfo_repeat(' ', term_width-vcx);
+ terminfo_move(vcx, vcy);
+ term_lines_empty[vcy] = FALSE;
+ }
+ }
+}
+
+void term_window_clrtoeol(TERM_WINDOW* window, int ypos)
+{
+ if (ypos >= 0 && window->y + ypos != vcy) {
+ /* the line is already full */
+ return;
+ }
+ term_clrtoeol(window);
+ if (window->x + window->width < term_width) {
+ gui_printtext_window_border(window->x + window->width, window->y + ypos);
+ term_set_color(window, ATTR_RESET);
+ }
+}
+
+void term_window_clrtoeol_abs(TERM_WINDOW* window, int ypos)
+{
+ term_window_clrtoeol(window, ypos - window->y);
+}
+
+void term_move_cursor(int x, int y)
+{
+ curs_x = x;
+ curs_y = y;
+}
+
+void term_refresh(TERM_WINDOW *window)
+{
+ if (freeze_counter > 0)
+ return;
+
+ term_move(root_window, curs_x, curs_y);
+ term_move_real();
+
+ if (!curs_visible) {
+ terminfo_set_cursor_visible(TRUE);
+ curs_visible = TRUE;
+ }
+
+ term_set_color(window, ATTR_RESET);
+ fflush(window != NULL ? window->term->out : current_term->out);
+}
+
+void term_refresh_freeze(void)
+{
+ freeze_counter++;
+}
+
+void term_refresh_thaw(void)
+{
+ if (--freeze_counter == 0)
+ term_refresh(NULL);
+}
+
+void term_stop(void)
+{
+ terminfo_stop(current_term);
+ kill(getpid(), SIGTSTP);
+ /* this call needs to stay here in case the TSTP was ignored,
+ because then we never see a CONT to call the restoration
+ code. On the other hand we also cannot remove the CONT
+ handler because then nothing would restore the screen when
+ Irssi is killed with TSTP/STOP from external. */
+ terminfo_cont(current_term);
+ irssi_redraw();
+}
+
+static int input_utf8(const unsigned char *buffer, int size, unichar *result)
+{
+ unichar c = g_utf8_get_char_validated((char *) buffer, size);
+
+ /* GLib >= 2.63 do not accept Unicode NUL anymore */
+ if (c == (unichar) -2 && *buffer == 0 && size > 0)
+ c = 0;
+
+ switch (c) {
+ case (unichar)-1:
+ /* not UTF8 - fallback to 8bit ascii */
+ *result = *buffer;
+ return 1;
+ case (unichar)-2:
+ /* need more data */
+ return -1;
+ default:
+ *result = c;
+ return g_utf8_skip[*buffer];
+ }
+}
+
+static int input_big5(const unsigned char *buffer, int size, unichar *result)
+{
+ if (is_big5_hi(*buffer)) {
+ /* could be */
+ if (size == 1)
+ return -1;
+
+ if (is_big5_los(buffer[1]) || is_big5_lox(buffer[1])) {
+ *result = buffer[1] + ((int) *buffer << 8);
+ return 2;
+ }
+ }
+
+ *result = *buffer;
+ return 1;
+}
+
+static int input_8bit(const unsigned char *buffer, int size, unichar *result)
+{
+ *result = *buffer;
+ return 1;
+}
+
+void term_set_input_type(int type)
+{
+ switch (type) {
+ case TERM_TYPE_UTF8:
+ input_func = input_utf8;
+ break;
+ case TERM_TYPE_BIG5:
+ input_func = input_big5;
+ break;
+ default:
+ input_func = input_8bit;
+ }
+}
+
+void term_gets(GArray *buffer, int *line_count)
+{
+ int ret, i, char_len;
+
+ /* fread() doesn't work */
+
+ ret = read(fileno(current_term->in),
+ term_inbuf + term_inbuf_pos, sizeof(term_inbuf)-term_inbuf_pos);
+ if (ret == 0) {
+ /* EOF - terminal got lost */
+ ret = -1;
+ } else if (ret == -1 && (errno == EINTR || errno == EAGAIN))
+ ret = 0;
+ if (ret == -1)
+ signal_emit("command quit", 1, "Lost terminal");
+
+ if (ret > 0) {
+ /* convert input to unichars. */
+ term_inbuf_pos += ret;
+ for (i = 0; i < term_inbuf_pos; ) {
+ unichar key;
+ char_len = input_func(term_inbuf+i, term_inbuf_pos-i,
+ &key);
+ if (char_len < 0)
+ break;
+ g_array_append_val(buffer, key);
+ if (key == '\r' || key == '\n')
+ (*line_count)++;
+
+ i += char_len;
+ }
+
+ if (i >= term_inbuf_pos)
+ term_inbuf_pos = 0;
+ else if (i > 0) {
+ memmove(term_inbuf, term_inbuf+i, term_inbuf_pos-i);
+ term_inbuf_pos -= i;
+ }
+ }
+}
+
+static const char* term_env_warning =
+ "You seem to be running Irssi inside %2$s, but the TERM environment variable "
+ "is set to '%1$s', which can cause display glitches.\n"
+ "Consider changing TERM to '%2$s' or '%2$s-256color' instead.";
+
+void term_environment_check(void)
+{
+ const char *term, *sty, *tmux, *multiplexer;
+
+ term = g_getenv("TERM");
+ sty = g_getenv("STY");
+ tmux = g_getenv("TMUX");
+
+ multiplexer = (sty && *sty) ? "screen" :
+ (tmux && *tmux) ? "tmux" : NULL;
+
+ if (!multiplexer) {
+ return;
+ }
+
+ if (term && (g_str_has_prefix(term, "screen") ||
+ g_str_has_prefix(term, "tmux"))) {
+ return;
+ }
+
+ g_warning(term_env_warning, term, multiplexer);
+}
diff --git a/src/fe-text/term.c b/src/fe-text/term.c
new file mode 100644
index 0000000..1d5c2be
--- /dev/null
+++ b/src/fe-text/term.c
@@ -0,0 +1,208 @@
+/*
+ term.c : irssi
+
+ Copyright (C) 2001 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/settings.h>
+
+#include <irssi/src/fe-text/term.h>
+#include <irssi/src/fe-text/mainwindows.h>
+
+#ifdef HAVE_SYS_IOCTL_H
+# include <sys/ioctl.h>
+#endif
+#include <signal.h>
+#include <termios.h>
+
+#define MIN_SCREEN_WIDTH 20
+
+int term_width, term_height;
+
+int term_use_colors;
+int term_use_colors24;
+int term_type;
+
+static int force_colors;
+static int resize_dirty;
+
+int term_get_size(int *width, int *height)
+{
+#ifdef TIOCGWINSZ
+ struct winsize ws;
+
+ /* Get new window size */
+ if (ioctl(0, TIOCGWINSZ, &ws) < 0)
+ return FALSE;
+
+ if (ws.ws_row == 0 && ws.ws_col == 0)
+ return FALSE;
+
+ *width = ws.ws_col;
+ *height = ws.ws_row;
+
+ if (*width < MIN_SCREEN_WIDTH)
+ *width = MIN_SCREEN_WIDTH;
+ if (*height < 1)
+ *height = 1;
+ return TRUE;
+#else
+ return FALSE;
+#endif
+}
+
+/* Resize the terminal if needed */
+void term_resize_dirty(void)
+{
+ int width, height;
+
+ if (!resize_dirty)
+ return;
+
+ resize_dirty = FALSE;
+
+ if (!term_get_size(&width, &height))
+ width = height = -1;
+
+ term_resize(width, height);
+ mainwindows_resize(term_width, term_height);
+ term_resize_final(width, height);
+}
+
+#ifdef SIGWINCH
+static void sig_winch(int p)
+{
+ irssi_set_dirty();
+ resize_dirty = TRUE;
+}
+#endif
+
+static void cmd_resize(void)
+{
+ resize_dirty = TRUE;
+ term_resize_dirty();
+}
+
+static void cmd_redraw(void)
+{
+ irssi_redraw();
+}
+
+int term_color256map[] = {
+ 0, 4, 2, 6, 1, 5, 3, 7, 8,12,10,14, 9,13,11,15,
+ 0, 0, 1, 1, 1, 1, 0, 0, 3, 1, 1, 9, 2, 2, 3, 3, 3, 3,
+ 2, 2, 3, 3, 3, 3, 2, 2, 3, 3, 3,11,10,10, 3, 3,11,11,
+ 0, 0, 5, 1, 1, 9, 0, 8, 8, 8, 9, 9, 2, 8, 8, 8, 9, 9,
+ 2, 8, 8, 8, 9, 9, 2, 8, 8, 3, 3,11,10,10, 3, 3,11,11,
+ 4, 4, 5, 5, 5, 5, 4, 8, 8, 8, 9, 9, 6, 8, 8, 8, 9, 9,
+ 6, 8, 8, 8, 8, 9, 6, 8, 8, 8, 7, 7, 6, 6, 8, 7, 7, 7,
+ 4, 4, 5, 5, 5, 5, 4, 8, 8, 8, 9, 9, 6, 8, 8, 8, 8, 9,
+ 6, 8, 8, 8, 7, 7, 6, 6, 8, 7, 7, 7, 6, 6, 7, 7, 7, 7,
+ 4, 4, 5, 5, 5,13, 4, 8, 8, 5, 5,13, 6, 8, 8, 8, 7, 7,
+ 6, 6, 8, 7, 7, 7, 6, 6, 7, 7, 7, 7,14,14, 7, 7, 7, 7,
+ 12,12, 5, 5,13,13,12,12, 5, 5,13,13, 6, 6, 8, 7, 7, 7,
+ 6, 6, 7, 7, 7, 7,14,14, 7, 7, 7, 7,14,14, 7, 7, 7,15,
+ 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
+ 7, 7, 7, 7, 7, 7, 0 };
+
+static void read_settings(void)
+{
+ const char *str;
+ int old_colors = term_use_colors;
+ int old_colors24 = term_use_colors24;
+ int old_type = term_type;
+
+ /* set terminal type */
+ str = settings_get_str("term_charset");
+ if (g_ascii_strcasecmp(str, "utf-8") == 0)
+ term_type = TERM_TYPE_UTF8;
+ else if (g_ascii_strcasecmp(str, "big5") == 0)
+ term_type = TERM_TYPE_BIG5;
+ else
+ term_type = TERM_TYPE_8BIT;
+
+ if (old_type != term_type)
+ term_set_input_type(term_type);
+
+ /* change color stuff */
+ if (force_colors != settings_get_bool("term_force_colors")) {
+ force_colors = settings_get_bool("term_force_colors");
+ term_force_colors(force_colors);
+ }
+
+ term_use_colors = settings_get_bool("colors") &&
+ (force_colors || term_has_colors());
+
+#ifdef TERM_TRUECOLOR
+ term_use_colors24 = settings_get_bool("colors_ansi_24bit") &&
+ (force_colors || term_has_colors());
+#else
+ term_use_colors24 = FALSE;
+#endif
+
+ if (term_use_colors != old_colors || term_use_colors24 != old_colors24)
+ irssi_redraw();
+}
+
+void term_common_init(void)
+{
+ const char *dummy;
+#ifdef SIGWINCH
+ struct sigaction act;
+#endif
+ settings_add_bool("lookandfeel", "colors", TRUE);
+ settings_add_bool("lookandfeel", "term_force_colors", FALSE);
+ settings_add_bool("lookandfeel", "mirc_blink_fix", FALSE);
+
+ force_colors = FALSE;
+ term_use_colors = term_has_colors() && settings_get_bool("colors");
+#ifdef TERM_TRUECOLOR
+ settings_add_bool("lookandfeel", "colors_ansi_24bit", FALSE);
+ term_use_colors24 = term_has_colors() && settings_get_bool("colors_ansi_24bit");
+#else
+ term_use_colors24 = FALSE;
+#endif
+ read_settings();
+
+ if (g_get_charset(&dummy)) {
+ term_type = TERM_TYPE_UTF8;
+ term_set_input_type(TERM_TYPE_UTF8);
+ }
+
+ signal_add("beep", (SIGNAL_FUNC) term_beep);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+ command_bind("resize", NULL, (SIGNAL_FUNC) cmd_resize);
+ command_bind("redraw", NULL, (SIGNAL_FUNC) cmd_redraw);
+
+#ifdef SIGWINCH
+ sigemptyset (&act.sa_mask);
+ act.sa_flags = 0;
+ act.sa_handler = sig_winch;
+ sigaction(SIGWINCH, &act, NULL);
+#endif
+}
+
+void term_common_deinit(void)
+{
+ command_unbind("resize", (SIGNAL_FUNC) cmd_resize);
+ command_unbind("redraw", (SIGNAL_FUNC) cmd_redraw);
+ signal_remove("beep", (SIGNAL_FUNC) term_beep);
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+}
diff --git a/src/fe-text/term.h b/src/fe-text/term.h
new file mode 100644
index 0000000..7758fea
--- /dev/null
+++ b/src/fe-text/term.h
@@ -0,0 +1,112 @@
+#ifndef IRSSI_FE_TEXT_TERM_H
+#define IRSSI_FE_TEXT_TERM_H
+
+typedef struct _TERM_WINDOW TERM_WINDOW;
+
+#define FG_MASK ( 0x00ff )
+#define BG_MASK ( 0xff00 )
+#define BG_SHIFT 8
+
+/* text attributes */
+#define ATTR_RESETFG ( 0x010000 )
+#define ATTR_RESETBG ( 0x020000 )
+#define ATTR_BOLD ( 0x040000 )
+#define ATTR_BLINK ( 0x080000 )
+#define ATTR_UNDERLINE ( 0x100000 )
+#define ATTR_REVERSE ( 0x200000 )
+#define ATTR_ITALIC ( 0x400000 )
+#define ATTR_FGCOLOR24 ( 0x1000000 )
+#define ATTR_BGCOLOR24 ( 0x2000000 )
+
+#define ATTR_RESET (ATTR_RESETFG|ATTR_RESETBG)
+
+#define ATTR_NOCOLORS (ATTR_UNDERLINE|ATTR_REVERSE|ATTR_BLINK|ATTR_BOLD|ATTR_ITALIC)
+
+/* terminal types */
+#define TERM_TYPE_8BIT 0 /* normal 8bit text */
+#define TERM_TYPE_UTF8 1
+#define TERM_TYPE_BIG5 2
+
+#include <irssi/src/core/utf8.h>
+
+extern TERM_WINDOW *root_window;
+extern int term_width, term_height;
+extern int term_use_colors, term_type;
+extern int term_use_colors24;
+extern int term_color256map[];
+
+/* Initialize / deinitialize terminal */
+int term_init(void);
+void term_deinit(void);
+
+/* Gets the current terminal size, returns TRUE if ok. */
+int term_get_size(int *width, int *height);
+
+/* Resize terminal - if width or height is negative,
+ the new size is unknown and should be figured out somehow */
+void term_resize(int width, int height);
+void term_resize_final(int width, int height);
+/* Resize the terminal if needed */
+void term_resize_dirty(void);
+
+/* Returns TRUE if terminal has colors */
+int term_has_colors(void);
+/* Force the colors on any way you can */
+void term_force_colors(int set);
+
+/* Clear screen */
+void term_clear(void);
+/* Beep */
+void term_beep(void);
+
+/* Create a new window in terminal */
+TERM_WINDOW *term_window_create(int x, int y, int width, int height);
+/* Destroy a terminal window */
+void term_window_destroy(TERM_WINDOW *window);
+
+/* Move/resize window */
+void term_window_move(TERM_WINDOW *window, int x, int y,
+ int width, int height);
+/* Clear window */
+void term_window_clear(TERM_WINDOW *window);
+/* Scroll window up/down */
+void term_window_scroll(TERM_WINDOW *window, int count);
+
+#ifdef TERM_TRUECOLOR
+#define term_set_color(window, col) term_set_color2(window, (col) &~(ATTR_FGCOLOR24|ATTR_BGCOLOR24), UINT_MAX, UINT_MAX)
+void term_set_color2(TERM_WINDOW *window, int col, unsigned int fgcol24, unsigned int bgcol24);
+#else
+#define term_set_color2(window, col, unused1, unused2) term_set_color(window, col)
+void term_set_color(TERM_WINDOW *window, int col);
+#endif
+
+void term_move(TERM_WINDOW *window, int x, int y);
+void term_addch(TERM_WINDOW *window, char chr);
+void term_add_unichar(TERM_WINDOW *window, unichar chr);
+int term_addstr(TERM_WINDOW *window, const char *str);
+void term_clrtoeol(TERM_WINDOW *window);
+void term_window_clrtoeol(TERM_WINDOW* window, int ypos);
+void term_window_clrtoeol_abs(TERM_WINDOW* window, int ypos_abs);
+
+void term_move_cursor(int x, int y);
+
+void term_refresh_freeze(void);
+void term_refresh_thaw(void);
+void term_refresh(TERM_WINDOW *window);
+
+void term_stop(void);
+
+void term_set_appkey_mode(int enable);
+void term_set_bracketed_paste_mode(int enable);
+
+/* keyboard input handling */
+void term_set_input_type(int type);
+void term_gets(GArray *buffer, int *line_count);
+
+/* internal */
+void term_common_init(void);
+void term_common_deinit(void);
+
+void term_environment_check(void);
+
+#endif
diff --git a/src/fe-text/terminfo-core.c b/src/fe-text/terminfo-core.c
new file mode 100644
index 0000000..7c21d50
--- /dev/null
+++ b/src/fe-text/terminfo-core.c
@@ -0,0 +1,740 @@
+#include "module.h"
+#include <irssi/src/core/signals.h>
+#include <irssi/src/fe-text/terminfo-core.h>
+
+#ifndef _POSIX_VDISABLE
+# define _POSIX_VDISABLE 0
+#endif
+
+#define tput(s) tputs(s, 0, term_putchar)
+inline static int term_putchar(int c)
+{
+ return fputc(c, current_term->out);
+}
+
+#ifdef HAVE_TERM_H
+#include <term.h>
+#else
+/* Don't bother including curses.h because of these -
+ they might not even be defined there */
+char *tparm();
+int tputs();
+
+int setupterm();
+char *tigetstr();
+int tigetnum();
+int tigetflag();
+#endif
+
+#define term_getstr(x, buffer) tigetstr(x.ti_name)
+#define term_getnum(x) tigetnum(x.ti_name);
+#define term_getflag(x) tigetflag(x.ti_name);
+
+#define CAP_TYPE_FLAG 0
+#define CAP_TYPE_INT 1
+#define CAP_TYPE_STR 2
+
+typedef struct {
+ const char *ti_name; /* terminfo name */
+ const char *tc_name; /* termcap name */
+ int type;
+ unsigned int offset;
+} TERMINFO_REC;
+
+TERM_REC *current_term;
+
+/* Define only what we might need */
+static TERMINFO_REC tcaps[] = {
+ /* Terminal size */
+ { "cols", "co", CAP_TYPE_INT, G_STRUCT_OFFSET(TERM_REC, width) },
+ { "lines", "li", CAP_TYPE_INT, G_STRUCT_OFFSET(TERM_REC, height) },
+
+ /* Cursor movement */
+ { "smcup", "ti", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_smcup) },
+ { "rmcup", "te", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_rmcup) },
+ { "cup", "cm", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_cup) },
+ { "hpa", "ch", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_hpa) },
+ { "vpa", "vh", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_vpa) },
+ { "cub1", "le", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_cub1) },
+ { "cuf1", "nd", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_cuf1) },
+ { "civis", "vi", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_civis) },
+ { "cnorm", "ve", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_cnorm) },
+
+ /* Scrolling */
+ { "csr", "cs", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_csr) },
+ { "wind", "wi", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_wind) },
+ { "ri", "sr", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_ri) },
+ { "rin", "SR", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_rin) },
+ { "ind", "sf", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_ind) },
+ { "indn", "SF", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_indn) },
+ { "il", "AL", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_il) },
+ { "il1", "al", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_il1) },
+ { "dl", "DL", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_dl) },
+ { "dl1", "dl", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_dl1) },
+
+ /* Clearing screen */
+ { "clear", "cl", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_clear) },
+ { "ed", "cd", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_ed) },
+
+ /* Clearing to end of line */
+ { "el", "ce", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_el) },
+
+ /* Repeating character */
+ { "rep", "rp", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_rep) },
+
+ /* Colors */
+ { "colors", "Co", CAP_TYPE_INT, G_STRUCT_OFFSET(TERM_REC, TI_colors) },
+ { "sgr0", "me", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_sgr0) },
+ { "smul", "us", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_smul) },
+ { "rmul", "ue", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_rmul) },
+ { "smso", "so", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_smso) },
+ { "rmso", "se", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_rmso) },
+ { "sitm", "ZH", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_sitm) },
+ { "ritm", "ZR", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_ritm) },
+ { "bold", "md", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_bold) },
+ { "blink", "mb", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_blink) },
+ { "rev", "mr", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_rev) },
+ { "setaf", "AF", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_setaf) },
+ { "setab", "AB", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_setab) },
+ { "setf", "Sf", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_setf) },
+ { "setb", "Sb", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_setb) },
+
+ /* Beep */
+ { "bel", "bl", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_bel) },
+
+ /* Keyboard-transmit mode */
+ { "smkx", "ks", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_smkx) },
+ { "rmkx", "ke", CAP_TYPE_STR, G_STRUCT_OFFSET(TERM_REC, TI_rmkx) },
+};
+
+/* Move cursor (cursor_address / cup) */
+static void _move_cup(TERM_REC *term, int x, int y)
+{
+ tput(tparm(term->TI_cup, y, x, 0, 0, 0, 0, 0, 0, 0));
+}
+
+/* Move cursor (column_address+row_address / hpa+vpa) */
+static void _move_pa(TERM_REC *term, int x, int y)
+{
+ tput(tparm(term->TI_hpa, x, 0, 0, 0, 0, 0, 0, 0, 0));
+ tput(tparm(term->TI_vpa, y, 0, 0, 0, 0, 0, 0, 0, 0));
+}
+
+/* Move cursor from a known position */
+static void _move_relative(TERM_REC *term, int oldx, int oldy, int x, int y)
+{
+ if (oldx == 0 && x == 0 && y == oldy+1) {
+ /* move to beginning of next line -
+ hope this works everywhere */
+ tput("\r\n");
+ return;
+ }
+
+ if (oldx > 0 && y == oldy) {
+ /* move cursor left/right */
+ if (x == oldx-1 && term->TI_cub1) {
+ tput(tparm(term->TI_cub1, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+ return;
+ }
+ if (x == oldx+1 && y == oldy && term->TI_cuf1) {
+ tput(tparm(term->TI_cuf1, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+ return;
+ }
+ }
+
+ /* fallback to absolute positioning */
+ if (term->TI_cup) {
+ tput(tparm(term->TI_cup, y, x, 0, 0, 0, 0, 0, 0, 0));
+ return;
+ }
+
+ if (oldy != y)
+ tput(tparm(term->TI_vpa, y, 0, 0, 0, 0, 0, 0, 0, 0));
+ if (oldx != x)
+ tput(tparm(term->TI_hpa, x, 0, 0, 0, 0, 0, 0, 0, 0));
+}
+
+/* Set cursor visible/invisible */
+static void _set_cursor_visible(TERM_REC *term, int set)
+{
+ tput(tparm(set ? term->TI_cnorm : term->TI_civis, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+}
+
+#define scroll_region_setup(term, y1, y2) \
+ if ((term)->TI_csr != NULL) \
+ tput(tparm((term)->TI_csr, y1, y2, 0, 0, 0, 0, 0, 0, 0)); \
+ else if ((term)->TI_wind != NULL) \
+ tput(tparm((term)->TI_wind, y1, y2, 0, (term)->width - 1, 0, 0, 0, 0, 0));
+
+/* Scroll (change_scroll_region+parm_rindex+parm_index / csr+rin+indn) */
+static void _scroll_region(TERM_REC *term, int y1, int y2, int count)
+{
+ /* setup the scrolling region to wanted area */
+ scroll_region_setup(term, y1, y2);
+
+ term->move(term, 0, y1);
+ if (count > 0) {
+ term->move(term, 0, y2);
+ tput(tparm(term->TI_indn, count, count, 0, 0, 0, 0, 0, 0, 0));
+ } else if (count < 0) {
+ term->move(term, 0, y1);
+ tput(tparm(term->TI_rin, -count, -count, 0, 0, 0, 0, 0, 0, 0));
+ }
+
+ /* reset the scrolling region to full screen */
+ scroll_region_setup(term, 0, term->height-1);
+}
+
+/* Scroll (change_scroll_region+scroll_reverse+scroll_forward / csr+ri+ind) */
+static void _scroll_region_1(TERM_REC *term, int y1, int y2, int count)
+{
+ int i;
+
+ /* setup the scrolling region to wanted area */
+ scroll_region_setup(term, y1, y2);
+
+ if (count > 0) {
+ term->move(term, 0, y2);
+ for (i = 0; i < count; i++)
+ tput(tparm(term->TI_ind, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+ } else if (count < 0) {
+ term->move(term, 0, y1);
+ for (i = count; i < 0; i++)
+ tput(tparm(term->TI_ri, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+ }
+
+ /* reset the scrolling region to full screen */
+ scroll_region_setup(term, 0, term->height-1);
+}
+
+/* Scroll (parm_insert_line+parm_delete_line / il+dl) */
+static void _scroll_line(TERM_REC *term, int y1, int y2, int count)
+{
+ /* setup the scrolling region to wanted area -
+ this might not necessarily work with il/dl, but at least it
+ looks better if it does */
+ scroll_region_setup(term, y1, y2);
+
+ if (count > 0) {
+ term->move(term, 0, y1);
+ tput(tparm(term->TI_dl, count, count, 0, 0, 0, 0, 0, 0, 0));
+ term->move(term, 0, y2-count+1);
+ tput(tparm(term->TI_il, count, count, 0, 0, 0, 0, 0, 0, 0));
+ } else if (count < 0) {
+ term->move(term, 0, y2+count+1);
+ tput(tparm(term->TI_dl, -count, -count, 0, 0, 0, 0, 0, 0, 0));
+ term->move(term, 0, y1);
+ tput(tparm(term->TI_il, -count, -count, 0, 0, 0, 0, 0, 0, 0));
+ }
+
+ /* reset the scrolling region to full screen */
+ scroll_region_setup(term, 0, term->height-1);
+}
+
+/* Scroll (insert_line+delete_line / il1+dl1) */
+static void _scroll_line_1(TERM_REC *term, int y1, int y2, int count)
+{
+ int i;
+
+ if (count > 0) {
+ term->move(term, 0, y1);
+ for (i = 0; i < count; i++)
+ tput(tparm(term->TI_dl1, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+ term->move(term, 0, y2-count+1);
+ for (i = 0; i < count; i++)
+ tput(tparm(term->TI_il1, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+ } else if (count < 0) {
+ term->move(term, 0, y2+count+1);
+ for (i = count; i < 0; i++)
+ tput(tparm(term->TI_dl1, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+ term->move(term, 0, y1);
+ for (i = count; i < 0; i++)
+ tput(tparm(term->TI_il1, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+ }
+}
+
+/* Clear screen (clear_screen / clear) */
+static void _clear_screen(TERM_REC *term)
+{
+ tput(tparm(term->TI_clear, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+}
+
+/* Clear screen (clr_eos / ed) */
+static void _clear_eos(TERM_REC *term)
+{
+ term->move(term, 0, 0);
+ tput(tparm(term->TI_ed, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+}
+
+/* Clear screen (parm_delete_line / dl) */
+static void _clear_del(TERM_REC *term)
+{
+ term->move(term, 0, 0);
+ tput(tparm(term->TI_dl, term->height, term->height, 0, 0, 0, 0, 0, 0, 0));
+}
+
+/* Clear screen (delete_line / dl1) */
+static void _clear_del_1(TERM_REC *term)
+{
+ int i;
+
+ term->move(term, 0, 0);
+ for (i = 0; i < term->height; i++)
+ tput(tparm(term->TI_dl1, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+}
+
+/* Clear to end of line (clr_eol / el) */
+static void _clrtoeol(TERM_REC *term)
+{
+ tput(tparm(term->TI_el, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+}
+
+/* Repeat character (rep / rp) */
+static void _repeat(TERM_REC *term, char chr, int count)
+{
+ tput(tparm(term->TI_rep, chr, count, 0, 0, 0, 0, 0, 0, 0));
+}
+
+/* Repeat character (manual) */
+static void _repeat_manual(TERM_REC *term, char chr, int count)
+{
+ while (count > 0) {
+ putc(chr, term->out);
+ count--;
+ }
+}
+
+/* Reset all terminal attributes */
+static void _set_normal(TERM_REC *term)
+{
+ tput(tparm(term->TI_normal, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+}
+
+static void _set_blink(TERM_REC *term)
+{
+ tput(tparm(term->TI_blink, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+}
+
+/* Reverse on */
+static void _set_reverse(TERM_REC *term)
+{
+ tput(tparm(term->TI_rev, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+}
+
+/* Bold on */
+static void _set_bold(TERM_REC *term)
+{
+ tput(tparm(term->TI_bold, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+}
+
+/* Underline on/off */
+static void _set_uline(TERM_REC *term, int set)
+{
+ tput(tparm(set ? term->TI_smul : term->TI_rmul, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+}
+
+/* Standout on/off */
+static void _set_standout(TERM_REC *term, int set)
+{
+ tput(tparm(set ? term->TI_smso : term->TI_rmso, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+}
+
+/* Italic on/off */
+static void _set_italic(TERM_REC *term, int set)
+{
+ tput(tparm(set ? term->TI_sitm : term->TI_ritm, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+}
+
+/* Standout on (fallback for reverse) */
+static void _set_standout_on(TERM_REC *term)
+{
+ _set_standout(term, TRUE);
+}
+
+inline static int color256(const TERM_REC *term, const int color) {
+ if (color < term->TI_colors)
+ return color;
+
+ if (color < 16)
+ return color % term->TI_colors;
+
+ if (color < 256)
+ return term_color256map[color] % term->TI_colors;
+
+ return color % term->TI_colors;
+}
+
+/* Change foreground color */
+static void _set_fg(TERM_REC *term, int color)
+{
+ tput(tparm(term->TI_fg[color256(term, color)], 0, 0, 0, 0, 0, 0, 0, 0, 0));
+}
+
+/* Change background color */
+static void _set_bg(TERM_REC *term, int color)
+{
+ tput(tparm(term->TI_bg[color256(term, color)], 0, 0, 0, 0, 0, 0, 0, 0, 0));
+}
+
+/* Beep */
+static void _beep(TERM_REC *term)
+{
+ tput(tparm(term->TI_bel, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+}
+
+static void _ignore(TERM_REC *term)
+{
+}
+
+static void _ignore_parm(TERM_REC *term, int param)
+{
+}
+
+static void terminfo_set_appkey_mode(TERM_REC *term, int set)
+{
+ if (term->TI_smkx && term->TI_rmkx)
+ tput(tparm(set ? term->TI_smkx : term->TI_rmkx, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+}
+
+static void term_dec_set_bracketed_paste_mode(int enable)
+{
+ if (enable)
+ tputs("\e[?2004h", 0, term_putchar);
+ else
+ tputs("\e[?2004l", 0, term_putchar);
+}
+
+static void term_fill_capabilities(TERM_REC *term)
+{
+ int i, ival;
+ char *sval;
+ void *ptr;
+
+ for (i = 0; i < sizeof(tcaps)/sizeof(tcaps[0]); i++) {
+ ptr = G_STRUCT_MEMBER_P(term, tcaps[i].offset);
+
+ switch (tcaps[i].type) {
+ case CAP_TYPE_FLAG:
+ ival = term_getflag(tcaps[i]);
+ *(int *)ptr = ival;
+ break;
+ case CAP_TYPE_INT:
+ ival = term_getnum(tcaps[i]);
+ *(int *)ptr = ival;
+ break;
+ case CAP_TYPE_STR:
+ sval = term_getstr(tcaps[i], tptr);
+ if (sval == (char *) -1)
+ *(char **)ptr = NULL;
+ else
+ *(char **)ptr = sval;
+ break;
+ }
+ }
+}
+
+static void terminfo_colors_deinit(TERM_REC *term)
+{
+ int i;
+
+ if (terminfo_is_colors_set(term)) {
+ for (i = 0; i < term->TI_colors; i++) {
+ g_free(term->TI_fg[i]);
+ g_free(term->TI_bg[i]);
+ }
+
+ g_free_and_null(term->TI_fg);
+ g_free_and_null(term->TI_bg);
+ }
+}
+
+/* Setup colors - if force is set, use ANSI-style colors if
+ terminal capabilities don't contain color codes */
+void terminfo_setup_colors(TERM_REC *term, int force)
+{
+ static const char ansitab[16] = {
+ 0, 4, 2, 6, 1, 5, 3, 7,
+ 8, 12, 10, 14, 9, 13, 11, 15
+ };
+ unsigned int i, color;
+
+ terminfo_colors_deinit(term);
+
+ if (force && term->TI_setf == NULL && term->TI_setaf == NULL)
+ term->TI_colors = 8;
+
+ if ((term->TI_setf || term->TI_setaf || force) &&
+ term->TI_colors > 0) {
+ term->TI_fg = g_new0(char *, term->TI_colors);
+ term->TI_bg = g_new0(char *, term->TI_colors);
+ term->set_fg = _set_fg;
+ term->set_bg = _set_bg;
+ } else {
+ /* no colors */
+ term->TI_colors = 0;
+ term->set_fg = term->set_bg = _ignore_parm;
+ }
+
+ if (term->TI_setaf) {
+ for (i = 0; i < term->TI_colors; i++) {
+ color = i < 16 ? ansitab[i] : i;
+ term->TI_fg[i] =
+ g_strdup(tparm(term->TI_setaf, color, 0, 0, 0, 0, 0, 0, 0, 0));
+ }
+ } else if (term->TI_setf) {
+ for (i = 0; i < term->TI_colors; i++)
+ term->TI_fg[i] = g_strdup(tparm(term->TI_setf, i, 0, 0, 0, 0, 0, 0, 0, 0));
+ } else if (force) {
+ for (i = 0; i < 8; i++)
+ term->TI_fg[i] = g_strdup_printf("\033[%dm", 30+ansitab[i]);
+ }
+
+ if (term->TI_setab) {
+ for (i = 0; i < term->TI_colors; i++) {
+ color = i < 16 ? ansitab[i] : i;
+ term->TI_bg[i] =
+ g_strdup(tparm(term->TI_setab, color, 0, 0, 0, 0, 0, 0, 0, 0));
+ }
+ } else if (term->TI_setb) {
+ for (i = 0; i < term->TI_colors; i++)
+ term->TI_bg[i] = g_strdup(tparm(term->TI_setb, i, 0, 0, 0, 0, 0, 0, 0, 0));
+ } else if (force) {
+ for (i = 0; i < 8; i++)
+ term->TI_bg[i] = g_strdup_printf("\033[%dm", 40+ansitab[i]);
+ }
+}
+
+static void terminfo_input_init0(TERM_REC *term)
+{
+ tcgetattr(fileno(term->in), &term->old_tio);
+ memcpy(&term->tio, &term->old_tio, sizeof(term->tio));
+
+ term->tio.c_lflag &= ~(ICANON | ECHO); /* CBREAK, no ECHO */
+ /* Disable the ICRNL flag to disambiguate ^J and Enter, also disable the
+ * software flow control to leave ^Q and ^S ready to be bound */
+ term->tio.c_iflag &= ~(ICRNL | IXON | IXOFF);
+ term->tio.c_cc[VMIN] = 1; /* read() is satisfied after 1 char */
+ term->tio.c_cc[VTIME] = 0; /* No timer */
+
+ /* Disable INTR, QUIT, VDSUSP and SUSP keys */
+ term->tio.c_cc[VINTR] = _POSIX_VDISABLE;
+ term->tio.c_cc[VQUIT] = _POSIX_VDISABLE;
+#ifdef VDSUSP
+ term->tio.c_cc[VDSUSP] = _POSIX_VDISABLE;
+#endif
+#ifdef VSUSP
+ term->tio.c_cc[VSUSP] = _POSIX_VDISABLE;
+#endif
+
+}
+
+static void terminfo_input_init(TERM_REC *term)
+{
+ tcsetattr(fileno(term->in), TCSADRAIN, &term->tio);
+}
+
+static void terminfo_input_deinit(TERM_REC *term)
+{
+ tcsetattr(fileno(term->in), TCSADRAIN, &term->old_tio);
+}
+
+void terminfo_cont(TERM_REC *term)
+{
+ if (term->TI_smcup)
+ tput(tparm(term->TI_smcup, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+ if (term->appkey_enabled)
+ terminfo_set_appkey_mode(term, TRUE);
+
+ if (term->bracketed_paste_enabled)
+ term_dec_set_bracketed_paste_mode(TRUE);
+
+ terminfo_input_init(term);
+}
+
+void terminfo_stop(TERM_REC *term)
+{
+ /* reset colors */
+ terminfo_set_normal();
+ /* move cursor to bottom of the screen */
+ terminfo_move(0, term->height-1);
+
+ if (term->bracketed_paste_enabled)
+ term_dec_set_bracketed_paste_mode(FALSE);
+
+ /* stop cup-mode */
+ if (term->TI_rmcup)
+ tput(tparm(term->TI_rmcup, 0, 0, 0, 0, 0, 0, 0, 0, 0));
+
+ if (term->appkey_enabled)
+ terminfo_set_appkey_mode(term, FALSE);
+
+ /* reset input settings */
+ terminfo_input_deinit(term);
+ fflush(term->out);
+}
+
+static int term_setup(TERM_REC *term)
+{
+ GString *str;
+ int err;
+ char *term_env;
+
+ term_env = getenv("TERM");
+ if (term_env == NULL) {
+ fprintf(stderr, "TERM environment not set\n");
+ return 0;
+ }
+
+ if (setupterm(term_env, 1, &err) != 0) {
+ fprintf(stderr, "setupterm() failed for TERM=%s: %d\n", term_env, err);
+ return 0;
+ }
+
+ term_fill_capabilities(term);
+
+ /* Cursor movement */
+ if (term->TI_cup)
+ term->move = _move_cup;
+ else if (term->TI_hpa && term->TI_vpa)
+ term->move = _move_pa;
+ else {
+ fprintf(stderr, "Terminal doesn't support cursor movement\n");
+ return 0;
+ }
+ term->move_relative = _move_relative;
+ term->set_cursor_visible = term->TI_civis && term->TI_cnorm ?
+ _set_cursor_visible : _ignore_parm;
+
+ /* Scrolling */
+ if ((term->TI_csr || term->TI_wind) && term->TI_rin && term->TI_indn)
+ term->scroll = _scroll_region;
+ else if (term->TI_il && term->TI_dl)
+ term->scroll = _scroll_line;
+ else if ((term->TI_csr || term->TI_wind) && term->TI_ri && term->TI_ind)
+ term->scroll = _scroll_region_1;
+ else if (term->scroll == NULL && (term->TI_il1 && term->TI_dl1))
+ term->scroll = _scroll_line_1;
+ else if (term->scroll == NULL) {
+ fprintf(stderr, "Terminal doesn't support scrolling\n");
+ return 0;
+ }
+
+ /* Clearing screen */
+ if (term->TI_clear)
+ term->clear = _clear_screen;
+ else if (term->TI_ed)
+ term->clear = _clear_eos;
+ else if (term->TI_dl)
+ term->clear = _clear_del;
+ else if (term->TI_dl1)
+ term->clear = _clear_del_1;
+ else {
+ /* we could do this by line inserts as well, but don't
+ bother - if some terminal has insert line it most probably
+ has delete line as well, if not a regular clear screen */
+ fprintf(stderr, "Terminal doesn't support clearing screen\n");
+ return 0;
+ }
+
+ /* Clearing to end of line */
+ if (term->TI_el)
+ term->clrtoeol = _clrtoeol;
+ else {
+ fprintf(stderr, "Terminal doesn't support clearing to end of line\n");
+ return 0;
+ }
+
+ /* Repeating character */
+ if (term->TI_rep)
+ term->repeat = _repeat;
+ else
+ term->repeat = _repeat_manual;
+
+ /* Bold, underline, standout, reverse, italics */
+ term->set_blink = term->TI_blink ? _set_blink : _ignore;
+ term->set_bold = term->TI_bold ? _set_bold : _ignore;
+ term->set_reverse = term->TI_rev ? _set_reverse :
+ term->TI_smso ? _set_standout_on : _ignore;
+ term->set_uline = term->TI_smul && term->TI_rmul ?
+ _set_uline : _ignore_parm;
+ term->set_standout = term->TI_smso && term->TI_rmso ?
+ _set_standout : _ignore_parm;
+ term->set_italic = term->TI_sitm && term->TI_ritm ?
+ _set_italic : _ignore_parm;
+
+ /* Create a string to set all attributes off */
+ str = g_string_new(NULL);
+ if (term->TI_sgr0)
+ g_string_append(str, term->TI_sgr0);
+ if (term->TI_rmul && (term->TI_sgr0 == NULL || g_strcmp0(term->TI_rmul, term->TI_sgr0) != 0))
+ g_string_append(str, term->TI_rmul);
+ if (term->TI_rmso && (term->TI_sgr0 == NULL || g_strcmp0(term->TI_rmso, term->TI_sgr0) != 0))
+ g_string_append(str, term->TI_rmso);
+ if (term->TI_ritm && (term->TI_sgr0 == NULL || g_strcmp0(term->TI_ritm, term->TI_sgr0) != 0))
+ g_string_append(str, term->TI_ritm);
+ term->TI_normal = str->str;
+ g_string_free(str, FALSE);
+ term->set_normal = _set_normal;
+
+ term->beep = term->TI_bel ? _beep : _ignore;
+
+ terminfo_setup_colors(term, FALSE);
+ terminfo_input_init0(term);
+ terminfo_cont(term);
+ return 1;
+}
+
+void term_set_appkey_mode(int enable)
+{
+ if (current_term->appkey_enabled == enable)
+ return;
+
+ current_term->appkey_enabled = enable;
+ terminfo_set_appkey_mode(current_term, enable);
+}
+
+void term_set_bracketed_paste_mode(int enable)
+{
+ if (current_term->bracketed_paste_enabled == enable)
+ return;
+
+ current_term->bracketed_paste_enabled = enable;
+ term_dec_set_bracketed_paste_mode(enable);
+}
+
+TERM_REC *terminfo_core_init(FILE *in, FILE *out)
+{
+ TERM_REC *old_term, *term;
+
+ old_term = current_term;
+ current_term = term = g_new0(TERM_REC, 1);
+
+ term->in = in;
+ term->out = out;
+
+ if (!term_setup(term)) {
+ g_free(term);
+ term = NULL;
+ }
+
+ current_term = old_term;
+ return term;
+}
+
+void terminfo_core_deinit(TERM_REC *term)
+{
+ TERM_REC *old_term;
+
+ old_term = current_term;
+ current_term = term;
+ term->set_normal(term);
+ current_term = old_term;
+
+ terminfo_stop(term);
+
+ g_free(term->TI_normal);
+ terminfo_colors_deinit(term);
+
+ g_free(term);
+}
diff --git a/src/fe-text/terminfo-core.h b/src/fe-text/terminfo-core.h
new file mode 100644
index 0000000..a077d0b
--- /dev/null
+++ b/src/fe-text/terminfo-core.h
@@ -0,0 +1,113 @@
+#ifndef IRSSI_FE_TEXT_TERMINFO_CORE_H
+#define IRSSI_FE_TEXT_TERMINFO_CORE_H
+
+#include <termios.h>
+
+#define terminfo_move(x, y) current_term->move(current_term, x, y)
+#define terminfo_move_relative(oldx, oldy, x, y) current_term->move_relative(current_term, oldx, oldy, x, y)
+#define terminfo_set_cursor_visible(set) current_term->set_cursor_visible(current_term, set)
+#define terminfo_scroll(y1, y2, count) current_term->scroll(current_term, y1, y2, count)
+#define terminfo_clear() current_term->clear(current_term)
+#define terminfo_clrtoeol() current_term->clrtoeol(current_term)
+#define terminfo_repeat(chr, count) current_term->repeat(current_term, chr, count)
+#define terminfo_set_fg(color) current_term->set_fg(current_term, color)
+#define terminfo_set_bg(color) current_term->set_bg(current_term, color)
+#define terminfo_set_normal() current_term->set_normal(current_term)
+#define terminfo_set_bold() current_term->set_bold(current_term)
+#define terminfo_set_uline(set) current_term->set_uline(current_term, set)
+#define terminfo_set_standout(set) current_term->set_standout(current_term, set)
+#define terminfo_set_reverse() current_term->set_reverse(current_term)
+#define terminfo_set_italic(set) current_term->set_italic(current_term, set)
+#define terminfo_is_colors_set(term) (term->TI_fg != NULL)
+#define terminfo_beep(term) current_term->beep(current_term)
+
+typedef struct _TERM_REC TERM_REC;
+
+struct _TERM_REC {
+ /* Functions */
+ void (*move)(TERM_REC *term, int x, int y);
+ void (*move_relative)(TERM_REC *term, int oldx, int oldy, int x, int y);
+ void (*set_cursor_visible)(TERM_REC *term, int set);
+ void (*scroll)(TERM_REC *term, int y1, int y2, int count);
+
+ void (*clear)(TERM_REC *term);
+ void (*clrtoeol)(TERM_REC *term);
+ void (*repeat)(TERM_REC *term, char chr, int count);
+
+ void (*set_fg)(TERM_REC *term, int color);
+ void (*set_bg)(TERM_REC *term, int color);
+ void (*set_normal)(TERM_REC *term);
+ void (*set_blink)(TERM_REC *term);
+ void (*set_bold)(TERM_REC *term);
+ void (*set_reverse)(TERM_REC *term);
+ void (*set_uline)(TERM_REC *term, int set);
+ void (*set_standout)(TERM_REC *term, int set);
+ void (*set_italic)(TERM_REC *term, int set);
+
+ void (*beep)(TERM_REC *term);
+
+#ifndef HAVE_TERMINFO
+ char buffer1[1024], buffer2[1024];
+#endif
+ FILE *in, *out;
+ struct termios tio, old_tio;
+
+ /* Terminal size */
+ int width, height;
+
+ /* Cursor movement */
+ const char *TI_smcup, *TI_rmcup, *TI_cup;
+ const char *TI_hpa, *TI_vpa, *TI_cub1, *TI_cuf1;
+ const char *TI_civis, *TI_cnorm;
+
+ /* Scrolling */
+ const char *TI_csr, *TI_wind;
+ const char *TI_ri, *TI_rin, *TI_ind, *TI_indn;
+ const char *TI_il, *TI_il1, *TI_dl, *TI_dl1;
+
+ /* Clearing screen */
+ const char *TI_clear, *TI_ed; /* + *TI_dl, *TI_dl1; */
+
+ /* Clearing to end of line */
+ const char *TI_el;
+
+ /* Repeating character */
+ const char *TI_rep;
+
+ /* Colors */
+ int TI_colors; /* numbers of colors in TI_fg[] and TI_bg[] */
+ const char *TI_sgr0; /* turn off all attributes */
+ const char *TI_smul, *TI_rmul; /* underline on/off */
+ const char *TI_smso, *TI_rmso; /* standout on/off */
+ const char *TI_sitm, *TI_ritm; /* italic on/off */
+ const char *TI_bold, *TI_blink, *TI_rev;
+ const char *TI_setaf, *TI_setab, *TI_setf, *TI_setb;
+
+ /* Colors - generated and dynamically allocated */
+ char **TI_fg, **TI_bg, *TI_normal;
+
+ /* Beep */
+ char *TI_bel;
+
+ /* Keyboard-transmit mode */
+ const char *TI_smkx;
+ const char *TI_rmkx;
+
+ /* Terminal mode states */
+ int appkey_enabled;
+ int bracketed_paste_enabled;
+};
+
+extern TERM_REC *current_term;
+
+TERM_REC *terminfo_core_init(FILE *in, FILE *out);
+void terminfo_core_deinit(TERM_REC *term);
+
+/* Setup colors - if force is set, use ANSI-style colors if
+ terminal capabilities don't contain color codes */
+void terminfo_setup_colors(TERM_REC *term, int force);
+
+void terminfo_cont(TERM_REC *term);
+void terminfo_stop(TERM_REC *term);
+
+#endif
diff --git a/src/fe-text/textbuffer-commands.c b/src/fe-text/textbuffer-commands.c
new file mode 100644
index 0000000..3d1d963
--- /dev/null
+++ b/src/fe-text/textbuffer-commands.c
@@ -0,0 +1,481 @@
+/*
+ textbuffer-commands.c : Text buffer handling
+
+ Copyright (C) 1999-2001 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#include "module.h"
+#include <irssi/src/core/commands.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/refstrings.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/fe-text/module-formats.h>
+#include <irssi/src/fe-text/textbuffer-formats.h>
+
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/fe-text/gui-windows.h>
+
+static int activity_hide_window_hidelevel;
+
+/* SYNTAX: CLEAR [-all] [<refnum>] */
+static void cmd_clear(const char *data)
+{
+ WINDOW_REC *window;
+ GHashTable *optlist;
+ char *refnum;
+ void *free_arg;
+ GSList *tmp;
+
+ g_return_if_fail(data != NULL);
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS,
+ "clear", &optlist, &refnum)) return;
+
+ if (g_hash_table_lookup(optlist, "all") != NULL) {
+ /* clear all windows */
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ window = tmp->data;
+ textbuffer_view_clear(WINDOW_GUI(window)->view);
+ }
+ } else if (*refnum != '\0') {
+ /* clear specified window */
+ window = window_find_refnum(atoi(refnum));
+ if (window != NULL)
+ textbuffer_view_clear(WINDOW_GUI(window)->view);
+ } else {
+ /* clear active window */
+ textbuffer_view_clear(WINDOW_GUI(active_win)->view);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+static void cmd_window_scroll(const char *data)
+{
+ GUI_WINDOW_REC *gui;
+
+ gui = WINDOW_GUI(active_win);
+ if (g_ascii_strcasecmp(data, "default") == 0) {
+ gui->use_scroll = FALSE;
+ } else if (g_ascii_strcasecmp(data, "on") == 0) {
+ gui->use_scroll = TRUE;
+ gui->scroll = TRUE;
+ } else if (g_ascii_strcasecmp(data, "off") == 0) {
+ gui->use_scroll = TRUE;
+ gui->scroll = FALSE;
+ } else if (*data != '\0') {
+ printformat(NULL, NULL, MSGLEVEL_CLIENTERROR,
+ TXT_WINDOW_SCROLL_UNKNOWN, data);
+ return;
+ }
+
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_WINDOW_SCROLL, !gui->use_scroll ? "DEFAULT" :
+ gui->scroll ? "ON" : "OFF");
+ textbuffer_view_set_scroll(gui->view, gui->use_scroll ?
+ gui->scroll : settings_get_bool("scroll"));
+}
+
+/* SYNTAX: WINDOW HIDELEVEL [<levels>] */
+static void cmd_window_hidelevel(const char *data)
+{
+ GUI_WINDOW_REC *gui;
+ char *level;
+
+ g_return_if_fail(data != NULL);
+
+ gui = WINDOW_GUI(active_win);
+ textbuffer_view_set_hidden_level(gui->view,
+ combine_level(gui->view->hidden_level, data));
+ textbuffer_view_redraw(gui->view);
+ level = gui->view->hidden_level == 0 ? g_strdup("NONE") :
+ bits2level(gui->view->hidden_level);
+ printformat_window(active_win, MSGLEVEL_CLIENTNOTICE,
+ TXT_WINDOW_HIDELEVEL, level);
+ g_free(level);
+}
+
+static void cmd_scrollback(const char *data, SERVER_REC *server,
+ WI_ITEM_REC *item)
+{
+ command_runsub("scrollback", data, server, item);
+}
+
+/* SYNTAX: SCROLLBACK CLEAR [-all] [<refnum>] */
+static void cmd_scrollback_clear(const char *data)
+{
+ WINDOW_REC *window;
+ GHashTable *optlist;
+ char *refnum;
+ void *free_arg;
+ GSList *tmp;
+
+ g_return_if_fail(data != NULL);
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS,
+ "scrollback clear", &optlist, &refnum)) return;
+
+ if (g_hash_table_lookup(optlist, "all") != NULL) {
+ /* clear all windows */
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ window = tmp->data;
+ textbuffer_view_remove_all_lines(WINDOW_GUI(window)->view);
+ }
+ } else if (*refnum != '\0') {
+ /* clear specified window */
+ window = window_find_refnum(atoi(refnum));
+ if (window != NULL)
+ textbuffer_view_remove_all_lines(WINDOW_GUI(window)->view);
+ } else {
+ /* clear active window */
+ textbuffer_view_remove_all_lines(WINDOW_GUI(active_win)->view);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: SCROLLBACK LEVELCLEAR [-all] [-level <level>] [<refnum>] */
+static void cmd_scrollback_levelclear(const char *data)
+{
+ WINDOW_REC *window;
+ GHashTable *optlist;
+ char *refnum;
+ void *free_arg;
+ GSList *tmp;
+ int level;
+ char *levelarg;
+
+ g_return_if_fail(data != NULL);
+
+ if (!cmd_get_params(data, &free_arg, 1 | PARAM_FLAG_OPTIONS,
+ "scrollback levelclear", &optlist, &refnum)) return;
+
+ levelarg = g_hash_table_lookup(optlist, "level");
+ level = (levelarg == NULL || *levelarg == '\0') ? 0 :
+ level2bits(replace_chars(levelarg, ',', ' '), NULL);
+ if (level == 0) {
+ cmd_params_free(free_arg);
+ return;
+ }
+
+ if (g_hash_table_lookup(optlist, "all") != NULL) {
+ /* clear all windows */
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ window = tmp->data;
+ textbuffer_view_remove_lines_by_level(WINDOW_GUI(window)->view, level);
+ }
+ } else if (*refnum != '\0') {
+ /* clear specified window */
+ window = window_find_refnum(atoi(refnum));
+ if (window != NULL)
+ textbuffer_view_remove_lines_by_level(WINDOW_GUI(window)->view, level);
+ } else {
+ /* clear active window */
+ textbuffer_view_remove_lines_by_level(WINDOW_GUI(active_win)->view, level);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+static void scrollback_goto_line(int linenum)
+{
+ TEXT_BUFFER_VIEW_REC *view;
+
+ view = WINDOW_GUI(active_win)->view;
+ if (view->buffer->lines_count == 0)
+ return;
+
+ textbuffer_view_scroll_line(view, view->buffer->first_line);
+ gui_window_scroll(active_win, linenum);
+}
+
+static void scrollback_goto_time(const char *datearg, const char *timearg)
+{
+ LINE_REC *line;
+ struct tm tm;
+ time_t now, stamp;
+ int day, month;
+
+ /* [dd[.mm] | -<days ago>] hh:mi[:ss] */
+ now = stamp = time(NULL);
+ if (*datearg == '-') {
+ /* -<days ago> */
+ stamp -= atoi(datearg+1) * 3600*24;
+ memcpy(&tm, localtime(&stamp), sizeof(struct tm));
+ } else if (*timearg != '\0') {
+ /* dd[.mm] */
+ memcpy(&tm, localtime(&stamp), sizeof(struct tm));
+
+ day = month = 0;
+ sscanf(datearg, "%d.%d", &day, &month);
+ if (day <= 0) return;
+
+ if (month <= 0) {
+ /* month not given */
+ if (day > tm.tm_mday) {
+ /* last month's day */
+ if (tm.tm_mon > 0)
+ tm.tm_mon--;
+ else {
+ /* last year's day.. */
+ tm.tm_year--;
+ tm.tm_mon = 11;
+ }
+ }
+ } else {
+ month--;
+ if (month > tm.tm_mon)
+ tm.tm_year--;
+ tm.tm_mon = month;
+ }
+
+ tm.tm_mday = day;
+ stamp = mktime(&tm);
+ }
+ else
+ {
+ /* only time given, move it to timearg */
+ timearg = datearg;
+ }
+
+ /* hh:mi[:ss] */
+ memcpy(&tm, localtime(&stamp), sizeof(struct tm));
+ tm.tm_sec = 0;
+ sscanf(timearg, "%d:%d:%d", &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
+ stamp = mktime(&tm);
+
+ if (stamp > now && timearg == datearg) {
+ /* we used /SB GOTO 23:59 or something, we want to jump to
+ previous day's 23:59 time instead of into future. */
+ stamp -= 3600*24;
+ }
+
+ if (stamp > now) {
+ /* we're still looking into future, don't bother checking */
+ return;
+ }
+
+ /* scroll to first line after timestamp */
+ line = textbuffer_view_get_lines(WINDOW_GUI(active_win)->view);
+ for (; line != NULL; line = line->next) {
+ if (line->info.time >= stamp) {
+ gui_window_scroll_line(active_win, line);
+ break;
+ }
+ }
+}
+
+/* SYNTAX: SCROLLBACK GOTO <+|-linecount>|<linenum>|<timestamp> */
+static void cmd_scrollback_goto(const char *data)
+{
+ char *datearg, *timearg;
+ void *free_arg;
+ int lines;
+
+ if (!cmd_get_params(data, &free_arg, 2, &datearg, &timearg))
+ return;
+
+ if (*timearg == '\0' && (*datearg == '-' || *datearg == '+')) {
+ /* go forward/backward n lines */
+ lines = atoi(datearg + (*datearg == '-' ? 0 : 1));
+ gui_window_scroll(active_win, lines);
+ } else if (*timearg == '\0' && is_numeric(datearg, '\0')) {
+ /* go to n'th line. */
+ scrollback_goto_line(atoi(datearg));
+ } else {
+ /* should be timestamp */
+ scrollback_goto_time(datearg, timearg);
+ }
+
+ cmd_params_free(free_arg);
+}
+
+/* SYNTAX: SCROLLBACK HOME */
+static void cmd_scrollback_home(const char *data)
+{
+ TEXT_BUFFER_REC *buffer;
+
+ buffer = WINDOW_GUI(active_win)->view->buffer;
+ if (buffer->lines_count > 0)
+ gui_window_scroll_line(active_win, buffer->first_line);
+}
+
+/* SYNTAX: SCROLLBACK END */
+static void cmd_scrollback_end(const char *data)
+{
+ TEXT_BUFFER_VIEW_REC *view;
+
+ view = WINDOW_GUI(active_win)->view;
+ if (view->bottom_startline == NULL ||
+ (view->bottom_startline == view->startline &&
+ view->bottom_subline == view->subline))
+ return;
+
+ textbuffer_view_scroll_line(view, view->bottom_startline);
+ gui_window_scroll(active_win, view->bottom_subline);
+}
+
+static void cmd_scrollback_status(void)
+{
+ GSList *tmp;
+ int total_lines;
+ size_t window_mem, total_mem;
+
+ total_lines = 0; total_mem = 0;
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *window = tmp->data;
+ int i;
+ LINE_REC *tmp;
+ TEXT_BUFFER_VIEW_REC *view;
+
+ view = WINDOW_GUI(window)->view;
+
+ window_mem = sizeof(TEXT_BUFFER_REC);
+ window_mem += view->buffer->lines_count * sizeof(LINE_REC);
+ for (tmp = view->buffer->cur_line; tmp != NULL; tmp = tmp->prev) {
+ if (tmp->info.text != NULL) {
+ window_mem += sizeof(char) * (strlen(tmp->info.text) + 1);
+ }
+ if (tmp->info.format != NULL) {
+ window_mem += sizeof(TEXT_BUFFER_FORMAT_REC);
+ for (i = 0; i < tmp->info.format->nargs; i++) {
+ if (tmp->info.format->args[i] != NULL) {
+ window_mem +=
+ sizeof(char) *
+ (strlen(tmp->info.format->args[i]) + 1);
+ }
+ }
+ }
+ }
+ total_lines += view->buffer->lines_count;
+ total_mem += window_mem;
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ "Window %d: %d lines, %dkB of data",
+ window->refnum, view->buffer->lines_count,
+ (int)(window_mem / 1024));
+ }
+
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ "Total: %d lines, %dkB of data",
+ total_lines, (int)(total_mem / 1024));
+ {
+ char *tmp = i_refstr_table_size_info();
+ if (tmp != NULL)
+ printtext(NULL, NULL, MSGLEVEL_CLIENTCRAP,
+ "%s", tmp);
+ g_free(tmp);
+ }
+}
+
+/* SYNTAX: SCROLLBACK REDRAW */
+static void cmd_scrollback_redraw(void)
+{
+ GUI_WINDOW_REC *gui;
+
+ gui = WINDOW_GUI(active_win);
+
+ term_refresh_freeze();
+ textbuffer_view_reset_cache(gui->view);
+ textbuffer_view_resize(gui->view, gui->view->width, gui->view->height);
+ gui_window_redraw(active_win);
+ term_refresh_thaw();
+}
+
+static void sig_away_changed(SERVER_REC *server)
+{
+ GSList *tmp;
+
+ if (!server->usermode_away)
+ return;
+
+ for (tmp = windows; tmp != NULL; tmp = tmp->next) {
+ WINDOW_REC *rec = tmp->data;
+
+ textbuffer_view_set_bookmark_bottom(WINDOW_GUI(rec)->view,
+ "lastlog_last_away");
+ }
+}
+
+static void sig_window_hilight_check(TEXT_DEST_REC *dest, char *msg, int *data_level, int *ignore)
+{
+ GUI_WINDOW_REC *gui;
+
+ g_return_if_fail(dest != NULL);
+ g_return_if_fail(ignore != NULL);
+
+ if (*ignore != 0 || !activity_hide_window_hidelevel || dest->window == NULL)
+ return;
+
+ gui = WINDOW_GUI(dest->window);
+
+ if (dest->level & gui->view->hidden_level) {
+ *ignore = TRUE;
+ }
+}
+
+static void read_settings(void)
+{
+ activity_hide_window_hidelevel = settings_get_bool("activity_hide_window_hidelevel");
+}
+
+void textbuffer_commands_init(void)
+{
+ settings_add_bool("lookandfeel", "activity_hide_window_hidelevel", TRUE);
+
+ command_bind("clear", NULL, (SIGNAL_FUNC) cmd_clear);
+ command_bind("window scroll", NULL, (SIGNAL_FUNC) cmd_window_scroll);
+ command_bind("window hidelevel", NULL, (SIGNAL_FUNC) cmd_window_hidelevel);
+ command_bind("scrollback", NULL, (SIGNAL_FUNC) cmd_scrollback);
+ command_bind("scrollback clear", NULL, (SIGNAL_FUNC) cmd_scrollback_clear);
+ command_bind("scrollback levelclear", NULL, (SIGNAL_FUNC) cmd_scrollback_levelclear);
+ command_bind("scrollback goto", NULL, (SIGNAL_FUNC) cmd_scrollback_goto);
+ command_bind("scrollback home", NULL, (SIGNAL_FUNC) cmd_scrollback_home);
+ command_bind("scrollback end", NULL, (SIGNAL_FUNC) cmd_scrollback_end);
+ command_bind("scrollback status", NULL, (SIGNAL_FUNC) cmd_scrollback_status);
+ command_bind("scrollback redraw", NULL, (SIGNAL_FUNC) cmd_scrollback_redraw);
+
+ command_set_options("clear", "all");
+ command_set_options("scrollback clear", "all");
+ command_set_options("scrollback levelclear", "all -level");
+
+ read_settings();
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_add("away mode changed", (SIGNAL_FUNC) sig_away_changed);
+ signal_add("window hilight check", (SIGNAL_FUNC) sig_window_hilight_check);
+}
+
+void textbuffer_commands_deinit(void)
+{
+ command_unbind("clear", (SIGNAL_FUNC) cmd_clear);
+ command_unbind("window scroll", (SIGNAL_FUNC) cmd_window_scroll);
+ command_unbind("window hidelevel", (SIGNAL_FUNC) cmd_window_hidelevel);
+ command_unbind("scrollback", (SIGNAL_FUNC) cmd_scrollback);
+ command_unbind("scrollback clear", (SIGNAL_FUNC) cmd_scrollback_clear);
+ command_unbind("scrollback levelclear", (SIGNAL_FUNC) cmd_scrollback_levelclear);
+ command_unbind("scrollback goto", (SIGNAL_FUNC) cmd_scrollback_goto);
+ command_unbind("scrollback home", (SIGNAL_FUNC) cmd_scrollback_home);
+ command_unbind("scrollback end", (SIGNAL_FUNC) cmd_scrollback_end);
+ command_unbind("scrollback status", (SIGNAL_FUNC) cmd_scrollback_status);
+ command_unbind("scrollback redraw", (SIGNAL_FUNC) cmd_scrollback_redraw);
+
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_remove("away mode changed", (SIGNAL_FUNC) sig_away_changed);
+ signal_remove("window hilight check", (SIGNAL_FUNC) sig_window_hilight_check);
+}
diff --git a/src/fe-text/textbuffer-formats.c b/src/fe-text/textbuffer-formats.c
new file mode 100644
index 0000000..ce104c2
--- /dev/null
+++ b/src/fe-text/textbuffer-formats.c
@@ -0,0 +1,465 @@
+#include "module.h"
+#include <irssi/src/core/expandos.h>
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/misc.h>
+#include <irssi/src/core/refstrings.h>
+#include <irssi/src/core/servers.h>
+#include <irssi/src/core/settings.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/special-vars.h>
+#include <irssi/src/fe-common/core/printtext.h>
+#include <irssi/src/fe-common/core/themes.h>
+#include <irssi/src/fe-text/gui-printtext.h>
+#include <irssi/src/fe-text/gui-windows.h>
+#include <irssi/src/fe-text/textbuffer-formats.h>
+#include <irssi/src/fe-text/textbuffer-view.h>
+
+TEXT_BUFFER_REC *color_buf;
+gboolean scrollback_format;
+gboolean show_server_time;
+int signal_gui_render_line_text;
+
+static void collector_free(GSList **collector)
+{
+ while (*collector) {
+ GSList *next = (*collector)->next->next;
+ i_refstr_release((*collector)->data);
+ g_free((*collector)->next->data);
+ g_slist_free_1((*collector)->next);
+ g_slist_free_1((*collector));
+ *collector = next;
+ }
+}
+
+void textbuffer_format_rec_free(TEXT_BUFFER_FORMAT_REC *rec)
+{
+ int n;
+
+ if (rec == NULL)
+ return;
+ if (rec == LINE_INFO_FORMAT_SET)
+ return;
+
+ i_refstr_release(rec->module);
+ i_refstr_release(rec->format);
+ i_refstr_release(rec->server_tag);
+ i_refstr_release(rec->target);
+ i_refstr_release(rec->nick);
+ i_refstr_release(rec->address);
+ if (rec->nargs >= 1) {
+ i_refstr_release(rec->args[0]);
+ }
+ for (n = 1; n < rec->nargs; n++) {
+ g_free(rec->args[n]);
+ }
+ rec->nargs = 0;
+ g_free(rec->args);
+ collector_free(&rec->expando_cache);
+ g_slice_free(TEXT_BUFFER_FORMAT_REC, rec);
+}
+
+static TEXT_BUFFER_FORMAT_REC *format_rec_new(const char *module, const char *format_tag, int nargs,
+ const char **args)
+{
+ int n;
+ TEXT_BUFFER_FORMAT_REC *ret = g_slice_new0(TEXT_BUFFER_FORMAT_REC);
+ ret->module = i_refstr_intern(module);
+ ret->format = i_refstr_intern(format_tag);
+ ret->nargs = nargs;
+ ret->args = g_new0(char *, nargs);
+ if (nargs >= 1) {
+ ret->args[0] = i_refstr_intern(args[0]);
+ }
+ for (n = 1; n < nargs; n++) {
+ ret->args[n] = g_strdup(args[n]);
+ }
+ return ret;
+}
+
+static void format_rec_set_dest(TEXT_BUFFER_FORMAT_REC *rec, const TEXT_DEST_REC *dest)
+{
+ i_refstr_release(rec->server_tag);
+ i_refstr_release(rec->target);
+ i_refstr_release(rec->nick);
+ i_refstr_release(rec->address);
+ rec->server_tag = i_refstr_intern(dest->server_tag);
+ rec->target = i_refstr_intern(dest->target);
+ rec->nick = i_refstr_intern(dest->nick);
+ rec->address = i_refstr_intern(dest->address);
+ rec->flags = dest->flags & ~PRINT_FLAG_FORMAT;
+}
+
+void textbuffer_meta_rec_free(LINE_INFO_META_REC *rec)
+{
+ if (rec == NULL)
+ return;
+
+ if (rec->hash != NULL)
+ g_hash_table_destroy(rec->hash);
+
+ g_free(rec);
+}
+
+static void meta_hash_create(struct _LINE_INFO_META_REC *meta)
+{
+ if (meta->hash == NULL) {
+ meta->hash = g_hash_table_new_full(g_str_hash, (GEqualFunc) g_str_equal,
+ (GDestroyNotify) i_refstr_release,
+ (GDestroyNotify) g_free);
+ }
+}
+
+static LINE_INFO_META_REC *line_meta_create(GHashTable *meta_hash)
+{
+ struct _LINE_INFO_META_REC *meta;
+ GHashTableIter iter;
+ const char *key;
+ const char *val;
+
+ if (meta_hash == NULL || g_hash_table_size(meta_hash) == 0)
+ return NULL;
+
+ meta = g_new0(struct _LINE_INFO_META_REC, 1);
+
+ g_hash_table_iter_init(&iter, meta_hash);
+ while (g_hash_table_iter_next(&iter, (gpointer *) &key, (gpointer *) &val)) {
+ if (g_strcmp0("time", key) == 0) {
+ GDateTime *time;
+ if ((time = g_date_time_new_from_iso8601(val, NULL)) != NULL) {
+ meta->server_time = g_date_time_to_unix(time);
+ g_date_time_unref(time);
+ }
+ } else {
+ meta_hash_create(meta);
+ g_hash_table_replace(meta->hash, i_refstr_intern(key), g_strdup(val));
+ }
+ }
+
+ return meta;
+}
+
+static LINE_INFO_REC *store_lineinfo_tmp(TEXT_DEST_REC *dest)
+{
+ GUI_WINDOW_REC *gui;
+ TEXT_BUFFER_VIEW_REC *view;
+ TEXT_BUFFER_REC *buffer;
+ LINE_INFO_REC *lineinfo;
+
+ gui = WINDOW_GUI(dest->window);
+ view = gui->view;
+ buffer = view->buffer;
+
+ lineinfo = g_new0(LINE_INFO_REC, 1);
+ lineinfo->level = dest->level;
+ lineinfo->time =
+ (gui->use_insert_after && gui->insert_after_time) ? gui->insert_after_time : time(NULL);
+
+ buffer->cur_info = g_slist_prepend(buffer->cur_info, lineinfo);
+ return lineinfo;
+}
+
+static void free_lineinfo_tmp(WINDOW_REC *window)
+{
+ GUI_WINDOW_REC *gui;
+ TEXT_BUFFER_REC *buffer;
+ LINE_INFO_REC *info;
+
+ gui = WINDOW_GUI(window);
+ buffer = gui->view->buffer;
+
+ if (buffer->cur_info == NULL)
+ return;
+
+ info = buffer->cur_info->data;
+ buffer->cur_info = g_slist_delete_link(buffer->cur_info, buffer->cur_info);
+ textbuffer_line_info_free1(info);
+ g_free(info);
+}
+
+static void sig_print_format(THEME_REC *theme, const char *module, TEXT_DEST_REC *dest,
+ void *formatnump, const char **args)
+{
+ int formatnum;
+ FORMAT_REC *formats;
+ LINE_INFO_REC *info;
+
+ if (!scrollback_format)
+ return;
+
+ if (module == NULL)
+ return;
+
+ info = store_lineinfo_tmp(dest);
+
+ formatnum = GPOINTER_TO_INT(formatnump);
+ formats = g_hash_table_lookup(default_formats, module);
+
+ info->format =
+ format_rec_new(module, formats[formatnum].tag, formats[formatnum].params, args);
+ special_push_collector(&info->format->expando_cache);
+
+ dest->flags |= PRINT_FLAG_FORMAT;
+
+ signal_continue(5, theme, module, dest, formatnump, args);
+
+ special_pop_collector();
+ free_lineinfo_tmp(dest->window);
+}
+
+static void sig_print_noformat(TEXT_DEST_REC *dest, const char *text)
+{
+ LINE_INFO_REC *info;
+
+ if (!scrollback_format)
+ return;
+
+ info = store_lineinfo_tmp(dest);
+
+ info->format = format_rec_new(NULL, NULL, 2, (const char *[]){ NULL, text });
+ special_push_collector(&info->format->expando_cache);
+
+ dest->flags |= PRINT_FLAG_FORMAT;
+
+ signal_continue(2, dest, text);
+
+ special_pop_collector();
+ free_lineinfo_tmp(dest->window);
+}
+
+static GSList *reverse_collector(GSList *a1)
+{
+ GSList *b1, *c1;
+ c1 = NULL;
+ while (a1) {
+ b1 = a1->next->next;
+ a1->next->next = c1;
+
+ c1 = a1;
+ a1 = b1;
+ }
+ return c1;
+}
+
+static void sig_gui_print_text_finished(WINDOW_REC *window, TEXT_DEST_REC *dest)
+{
+ GUI_WINDOW_REC *gui;
+ LINE_REC *insert_after;
+ LINE_INFO_REC *info;
+ TEXT_BUFFER_REC *buffer;
+
+ gui = WINDOW_GUI(window);
+ buffer = gui->view->buffer;
+ insert_after = gui->use_insert_after ? gui->insert_after : buffer->cur_line;
+
+ if (buffer->cur_info == NULL)
+ return;
+
+ info = buffer->cur_info->data;
+
+ if (info->format == NULL)
+ return;
+
+ info->format->expando_cache = reverse_collector(info->format->expando_cache);
+ format_rec_set_dest(info->format, dest);
+
+ info->meta = line_meta_create(dest->meta);
+
+ info->level = dest->level | MSGLEVEL_FORMAT;
+
+ /* the line will be inserted into the view with textbuffer_view_insert_line by
+ gui-printtext.c:view_add_eol */
+ insert_after = textbuffer_insert(buffer, insert_after, (const unsigned char[]){}, 0, info);
+
+ /* the TEXT_BUFFER_FORMAT_REC and meta pointer is now owned by the textbuffer */
+ info->format = LINE_INFO_FORMAT_SET;
+ info->meta = NULL;
+
+ if (gui->use_insert_after)
+ gui->insert_after = insert_after;
+}
+
+static void parse_colors_collector(const WINDOW_REC *window, const void *fgcolor_int,
+ const void *bgcolor_int, const void *flags_int,
+ const char *textpiece, const TEXT_DEST_REC *dest)
+{
+ int fg, bg, flags, attr;
+
+ flags = GPOINTER_TO_INT(flags_int);
+ fg = GPOINTER_TO_INT(fgcolor_int);
+ bg = GPOINTER_TO_INT(bgcolor_int);
+ gui_printtext_get_colors(&flags, &fg, &bg, &attr);
+
+ if (flags & GUI_PRINT_FLAG_NEWLINE) {
+ g_string_append_c(color_buf->cur_text, '\n');
+ }
+ format_gui_flags(color_buf->cur_text, &color_buf->last_fg, &color_buf->last_bg,
+ &color_buf->last_flags, fg, bg, flags);
+
+ g_string_append(color_buf->cur_text, textpiece);
+}
+
+static char *parse_colors(TEXT_DEST_REC *dest, const char *text)
+{
+ char *tmp;
+
+ if (text == NULL)
+ return NULL;
+
+ color_buf = textbuffer_create(NULL);
+ format_send_as_gui_flags(dest, text, (SIGNAL_FUNC) parse_colors_collector);
+ tmp = g_strdup(color_buf->cur_text->str);
+ textbuffer_destroy(color_buf);
+ color_buf = NULL;
+
+ return tmp;
+}
+
+static char *fallback_format(TEXT_BUFFER_FORMAT_REC *format_rec)
+{
+ int i;
+ GString *bs;
+ char *tmp;
+ bs = g_string_new(NULL);
+ g_string_printf(bs, "{%s#%s", format_rec->module, format_rec->format);
+ for (i = 0; i < format_rec->nargs && format_rec->args[i] != NULL; i++) {
+ tmp = g_strescape(format_rec->args[i], "");
+ g_string_append_printf(bs, " \"%s\"", tmp);
+ g_free(tmp);
+ }
+ g_string_append(bs, "}");
+ return g_string_free(bs, FALSE);
+}
+
+char *textbuffer_line_get_text(TEXT_BUFFER_REC *buffer, LINE_REC *line, gboolean raw)
+{
+ TEXT_DEST_REC dest;
+ GUI_WINDOW_REC *gui;
+ char *tmp, *text = NULL;
+
+ g_return_val_if_fail(buffer != NULL, NULL);
+ g_return_val_if_fail(buffer->window != NULL, NULL);
+
+ gui = WINDOW_GUI(buffer->window);
+ if (line == NULL || gui == NULL)
+ return NULL;
+
+ if (line->info.level & MSGLEVEL_FORMAT && line->info.format != NULL) {
+ LINE_REC *curr;
+ THEME_REC *theme;
+ int formatnum;
+ TEXT_BUFFER_FORMAT_REC *format_rec;
+ LINE_INFO_META_REC *meta;
+ char *tmp2;
+
+ curr = line;
+ line = NULL;
+ format_rec = curr->info.format;
+ meta = curr->info.meta;
+
+ format_create_dest_tag(
+ &dest,
+ format_rec->server_tag != NULL ? server_find_tag(format_rec->server_tag) : NULL,
+ format_rec->server_tag, format_rec->target, curr->info.level & ~MSGLEVEL_FORMAT,
+ buffer->window);
+ dest.nick = format_rec->nick;
+ dest.address = format_rec->address;
+ dest.flags = format_rec->flags;
+
+ theme = window_get_theme(dest.window);
+
+ special_fill_cache(format_rec->expando_cache);
+ if (format_rec->format != NULL) {
+ char *arglist[MAX_FORMAT_PARAMS] = { 0 };
+ formatnum = format_find_tag(format_rec->module, format_rec->format);
+ memcpy(arglist, format_rec->args, format_rec->nargs * sizeof(char *));
+ text = format_get_text_theme_charargs(theme, format_rec->module, &dest,
+ formatnum, arglist);
+ if (text == NULL) {
+ text = fallback_format(format_rec);
+ }
+ } else {
+ text = g_strdup(format_rec->args[1]);
+ }
+
+ if (text != NULL && *text != '\0') {
+ GString *str;
+
+ reference_time = curr->info.time;
+ if (show_server_time && meta != NULL && meta->server_time != 0) {
+ current_time = meta->server_time;
+ } else {
+ current_time = curr->info.time;
+ }
+
+ str = g_string_new(text);
+ signal_emit_id(signal_gui_render_line_text, 3, &dest, str, meta);
+ if (g_strcmp0(text, str->str) == 0) {
+ g_string_free(str, TRUE);
+ } else {
+ g_free(text);
+ text = g_string_free(str, FALSE);
+ }
+
+ tmp = format_get_level_tag(theme, &dest);
+ tmp2 = !theme->info_eol ? format_add_linestart(text, tmp) :
+ format_add_lineend(text, tmp);
+ g_free_not_null(tmp);
+ g_free_not_null(text);
+ text = tmp2;
+ tmp = format_get_line_start(theme, &dest, current_time);
+ tmp2 = !theme->info_eol ? format_add_linestart(text, tmp) :
+ format_add_lineend(text, tmp);
+ g_free_not_null(tmp);
+ g_free_not_null(text);
+ text = tmp2;
+ /* str = g_strconcat(text, "\n", NULL); */
+ /* g_free(text); */
+
+ dest.flags |= PRINT_FLAG_FORMAT;
+
+ reference_time = current_time = (time_t) -1;
+ } else if (format_rec->format != NULL) {
+ g_free(text);
+ text = NULL;
+ }
+ special_fill_cache(NULL);
+ } else {
+ format_create_dest(&dest, NULL, NULL, line->info.level, buffer->window);
+ text = g_strdup(line->info.text);
+ }
+
+ if (raw)
+ return text;
+
+ tmp = parse_colors(&dest, text);
+ g_free(text);
+ return tmp;
+}
+
+static void read_settings(void)
+{
+ scrollback_format = settings_get_bool("scrollback_format");
+ show_server_time = settings_get_bool("show_server_time");
+}
+
+void textbuffer_formats_init(void)
+{
+ signal_gui_render_line_text = signal_get_uniq_id("gui render line text");
+
+ settings_add_bool("lookandfeel", "scrollback_format", TRUE);
+ settings_add_bool("lookandfeel", "show_server_time", FALSE);
+
+ read_settings();
+ signal_add("print format", (SIGNAL_FUNC) sig_print_format);
+ signal_add("print noformat", (SIGNAL_FUNC) sig_print_noformat);
+ signal_add_first("gui print text finished", (SIGNAL_FUNC) sig_gui_print_text_finished);
+ signal_add("setup changed", (SIGNAL_FUNC) read_settings);
+}
+
+void textbuffer_formats_deinit(void)
+{
+ signal_remove("setup changed", (SIGNAL_FUNC) read_settings);
+ signal_remove("print format", (SIGNAL_FUNC) sig_print_format);
+ signal_remove("print noformat", (SIGNAL_FUNC) sig_print_noformat);
+ signal_remove("gui print text finished", (SIGNAL_FUNC) sig_gui_print_text_finished);
+}
diff --git a/src/fe-text/textbuffer-formats.h b/src/fe-text/textbuffer-formats.h
new file mode 100644
index 0000000..8658756
--- /dev/null
+++ b/src/fe-text/textbuffer-formats.h
@@ -0,0 +1,26 @@
+#ifndef IRSSI_FE_TEXT_TEXTBUFFER_FORMATS_H
+#define IRSSI_FE_TEXT_TEXTBUFFER_FORMATS_H
+
+#include <irssi/src/fe-text/textbuffer.h>
+#include <irssi/src/fe-common/core/formats.h>
+
+typedef struct _TEXT_BUFFER_FORMAT_REC {
+ char *module;
+ char *format;
+ char *server_tag;
+ char *target;
+ char *nick;
+ char *address;
+ char **args;
+ int nargs;
+ GSList *expando_cache;
+ int flags;
+} TEXT_BUFFER_FORMAT_REC;
+
+void textbuffer_format_rec_free(TEXT_BUFFER_FORMAT_REC *rec);
+void textbuffer_meta_rec_free(LINE_INFO_META_REC *rec);
+char *textbuffer_line_get_text(TEXT_BUFFER_REC *buffer, LINE_REC *line, gboolean raw);
+void textbuffer_formats_init(void);
+void textbuffer_formats_deinit(void);
+
+#endif
diff --git a/src/fe-text/textbuffer-view.c b/src/fe-text/textbuffer-view.c
new file mode 100644
index 0000000..2cc6ce6
--- /dev/null
+++ b/src/fe-text/textbuffer-view.c
@@ -0,0 +1,1546 @@
+/*
+ textbuffer-view.c : Text buffer handling
+
+ Copyright (C) 1999-2001 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#define G_LOG_DOMAIN "TextBufferView"
+
+#include "module.h"
+#include <irssi/src/core/levels.h>
+#include <irssi/src/core/signals.h>
+#include <irssi/src/core/utf8.h>
+#include <irssi/src/fe-common/core/formats.h>
+#include <irssi/src/fe-text/textbuffer-formats.h>
+#include <irssi/src/fe-text/textbuffer-view.h>
+
+typedef struct {
+ char *name;
+ LINE_REC *line;
+} BOOKMARK_REC;
+
+/* how often to scan line cache for lines not accessed for a while (ms) */
+#define LINE_CACHE_CHECK_TIME (5*60*1000)
+/* how long to keep line cache in memory (seconds) */
+#define LINE_CACHE_KEEP_TIME (10*60)
+
+static int linecache_tag;
+static GSList *views;
+
+#define view_is_bottom(view) \
+ ((view)->ypos >= -1 && (view)->ypos < (view)->height)
+
+#define view_get_linecount_hidden(view, line) \
+ textbuffer_view_get_line_cache(view, line)->count
+
+#define view_line_is_hidden(view, line) \
+ (((line)->info.level & (view)->hidden_level) != 0)
+
+#define view_get_linecount(view, line) \
+ (view_line_is_hidden(view, line) ? 0 : view_get_linecount_hidden(view, line))
+
+static GSList *textbuffer_get_views(TEXT_BUFFER_REC *buffer)
+{
+ GSList *tmp, *list;
+
+ for (tmp = views; tmp != NULL; tmp = tmp->next) {
+ TEXT_BUFFER_VIEW_REC *view = tmp->data;
+
+ if (view->buffer == buffer) {
+ list = g_slist_copy(view->siblings);
+ return g_slist_prepend(list, view);
+ }
+ }
+
+ return NULL;
+}
+
+static TEXT_BUFFER_CACHE_REC *
+textbuffer_cache_get(GSList *views, int width)
+{
+ TEXT_BUFFER_CACHE_REC *cache;
+
+ /* check if there's existing cache with correct width */
+ while (views != NULL) {
+ TEXT_BUFFER_VIEW_REC *view = views->data;
+
+ if (view->width == width) {
+ view->cache->refcount++;
+ return view->cache;
+ }
+ views = views->next;
+ }
+
+ /* create new cache */
+ cache = g_new0(TEXT_BUFFER_CACHE_REC, 1);
+ cache->refcount = 1;
+ cache->width = width;
+ cache->line_cache = g_hash_table_new((GHashFunc) g_direct_hash,
+ (GCompareFunc) g_direct_equal);
+ return cache;
+}
+
+static int line_cache_destroy(void *key, LINE_CACHE_REC *cache)
+{
+ g_free(cache->line_text);
+ g_free(cache);
+ return TRUE;
+}
+
+static void textbuffer_cache_destroy(TEXT_BUFFER_CACHE_REC *cache)
+{
+ g_hash_table_foreach(cache->line_cache,
+ (GHFunc) line_cache_destroy, NULL);
+ g_hash_table_destroy(cache->line_cache);
+ g_free(cache);
+}
+
+static void textbuffer_cache_unref(TEXT_BUFFER_CACHE_REC *cache)
+{
+ if (--cache->refcount == 0)
+ textbuffer_cache_destroy(cache);
+}
+
+#define FGATTR (ATTR_NOCOLORS | ATTR_RESETFG | FG_MASK | ATTR_FGCOLOR24)
+#define BGATTR (ATTR_NOCOLORS | ATTR_RESETBG | BG_MASK | ATTR_BGCOLOR24)
+
+#ifdef TERM_TRUECOLOR
+static void unformat_24bit_line_color(const unsigned char **ptr, int off, int *flags, unsigned int *fg, unsigned int *bg)
+{
+ unsigned int color;
+ unsigned char rgbx[4];
+ unsigned int i;
+ for (i = 0; i < 4; ++i) {
+ if ((*ptr)[i + off] == '\0')
+ return;
+ rgbx[i] = (*ptr)[i + off];
+ }
+ rgbx[3] -= 0x20;
+ *ptr += 4;
+ for (i = 0; i < 3; ++i) {
+ if (rgbx[3] & (0x10 << i))
+ rgbx[i] -= 0x20;
+ }
+ color = rgbx[0] << 16 | rgbx[1] << 8 | rgbx[2];
+ if (rgbx[3] & 0x1) {
+ *flags = (*flags & FGATTR) | ATTR_BGCOLOR24;
+ *bg = color;
+ }
+ else {
+ *flags = (*flags & BGATTR) | ATTR_FGCOLOR24;
+ *fg = color;
+ }
+}
+#endif
+
+static inline unichar read_unichar(const unsigned char *data, const unsigned char **next, int *width)
+{
+ unichar chr = g_utf8_get_char_validated((const char *) data, -1);
+
+ if (chr & 0x80000000) {
+ chr = 0xfffd;
+ *next = data + 1;
+ *width = 1;
+ } else {
+ *next = (unsigned char *)g_utf8_next_char(data);
+ *width = unichar_isprint(chr) ? i_wcwidth(chr) : 1;
+ }
+ return chr;
+}
+
+static inline void unformat(const unsigned char **ptr, int *color, unsigned int *fg24,
+ unsigned int *bg24)
+{
+ switch (**ptr) {
+ case FORMAT_STYLE_BLINK:
+ *color ^= ATTR_BLINK;
+ break;
+ case FORMAT_STYLE_UNDERLINE:
+ *color ^= ATTR_UNDERLINE;
+ break;
+ case FORMAT_STYLE_BOLD:
+ *color ^= ATTR_BOLD;
+ break;
+ case FORMAT_STYLE_REVERSE:
+ *color ^= ATTR_REVERSE;
+ break;
+ case FORMAT_STYLE_ITALIC:
+ *color ^= ATTR_ITALIC;
+ break;
+ case FORMAT_STYLE_MONOSPACE:
+ /* *color ^= ATTR_MONOSPACE; */
+ break;
+ case FORMAT_STYLE_DEFAULTS:
+ *color = ATTR_RESET;
+ break;
+ case FORMAT_STYLE_CLRTOEOL:
+ break;
+#define SET_COLOR_EXT_FG_BITS(base, pc) \
+ *color &= ~ATTR_FGCOLOR24; \
+ *color = (*color & BGATTR) | (base + *pc - FORMAT_COLOR_NOCHANGE)
+#define SET_COLOR_EXT_BG_BITS(base, pc) \
+ *color &= ~ATTR_BGCOLOR24; \
+ *color = (*color & FGATTR) | ((base + *pc - FORMAT_COLOR_NOCHANGE) << BG_SHIFT)
+ case FORMAT_COLOR_EXT1:
+ SET_COLOR_EXT_FG_BITS(0x10, ++*ptr);
+ break;
+ case FORMAT_COLOR_EXT1_BG:
+ SET_COLOR_EXT_BG_BITS(0x10, ++*ptr);
+ break;
+ case FORMAT_COLOR_EXT2:
+ SET_COLOR_EXT_FG_BITS(0x60, ++*ptr);
+ break;
+ case FORMAT_COLOR_EXT2_BG:
+ SET_COLOR_EXT_BG_BITS(0x60, ++*ptr);
+ break;
+ case FORMAT_COLOR_EXT3:
+ SET_COLOR_EXT_FG_BITS(0xb0, ++*ptr);
+ break;
+ case FORMAT_COLOR_EXT3_BG:
+ SET_COLOR_EXT_BG_BITS(0xb0, ++*ptr);
+ break;
+#undef SET_COLOR_EXT_BG_BITS
+#undef SET_COLOR_EXT_FG_BITS
+#ifdef TERM_TRUECOLOR
+ case FORMAT_COLOR_24:
+ unformat_24bit_line_color(ptr, 1, color, fg24, bg24);
+ break;
+#endif
+ default:
+ if (**ptr != FORMAT_COLOR_NOCHANGE) {
+ if (**ptr == (unsigned char) 0xff) {
+ *color = (*color & BGATTR) | ATTR_RESETFG;
+ } else {
+ *color = (*color & BGATTR) | (((unsigned char) **ptr - '0') & 0xf);
+ }
+ }
+ if ((*ptr)[1] == '\0')
+ break;
+
+ (*ptr)++;
+ if (**ptr != FORMAT_COLOR_NOCHANGE) {
+ if (**ptr == (unsigned char) 0xff) {
+ *color = (*color & FGATTR) | ATTR_RESETBG;
+ } else {
+ *color = (*color & FGATTR) |
+ ((((unsigned char) **ptr - '0') & 0xf) << BG_SHIFT);
+ }
+ }
+ }
+ if (**ptr == '\0')
+ return;
+
+ (*ptr)++;
+}
+
+#define NEXT_CHAR_OR_BREAK(p) \
+ (p)++; \
+ if (*(p) == '\0') \
+ break
+
+static LINE_CACHE_REC *
+view_update_line_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
+{
+ INDENT_FUNC indent_func;
+ LINE_CACHE_REC *rec;
+ LINE_CACHE_SUB_REC *sub;
+ GSList *lines;
+ char *line_text;
+ const unsigned char *ptr, *next_ptr, *last_space_ptr;
+ int xpos, pos, indent_pos, last_space, last_color, color, linecount;
+ unsigned int last_bg24, last_fg24, bg24, fg24;
+ int char_width;
+
+ color = ATTR_RESETFG | ATTR_RESETBG;
+ xpos = 0; indent_pos = view->default_indent;
+ last_space = last_color = 0; last_space_ptr = NULL; sub = NULL;
+ bg24 = fg24 = last_bg24 = last_fg24 = UINT_MAX;
+
+ indent_func = view->default_indent_func;
+ linecount = 1;
+ lines = NULL;
+
+ line_text = textbuffer_line_get_text(view->buffer, line, FALSE);
+ if (line_text != NULL) {
+ for (ptr = (unsigned char *) line_text;;) {
+ if (*ptr == '\0')
+ break;
+
+ if (*ptr == '\n') {
+ /* newline */
+ xpos = 0;
+ last_space = 0;
+
+ sub = g_new0(LINE_CACHE_SUB_REC, 1);
+
+ sub->start = ++ptr;
+ sub->color = color;
+#ifdef TERM_TRUECOLOR
+ sub->fg24 = fg24;
+ sub->bg24 = bg24;
+#endif
+
+ lines = g_slist_append(lines, sub);
+ linecount++;
+
+ continue;
+ }
+
+ if (*ptr == 4) {
+ /* format */
+ NEXT_CHAR_OR_BREAK(ptr);
+
+ if (*ptr == FORMAT_STYLE_INDENT) {
+ /* set indentation position here - don't do
+ it if we're too close to right border */
+ if (xpos < view->width - 5)
+ indent_pos = xpos;
+ ptr++;
+ } else {
+ unformat(&ptr, &color, &fg24, &bg24);
+ }
+ continue;
+ }
+
+ if (!view->utf8) {
+ /* MH */
+ if (term_type != TERM_TYPE_BIG5 || ptr[1] == '\0' ||
+ !is_big5(ptr[0], ptr[1]))
+ char_width = 1;
+ else
+ char_width = 2;
+ next_ptr = ptr + char_width;
+ } else {
+ read_unichar(ptr, &next_ptr, &char_width);
+ }
+
+ if (xpos + char_width > view->width && sub != NULL &&
+ (last_space <= indent_pos || last_space <= 10) &&
+ view->longword_noindent) {
+ /* long word, remove the indentation from this line */
+ xpos -= sub->indent;
+ sub->indent = 0;
+ sub->indent_func = NULL;
+ }
+
+ if (xpos + char_width > view->width) {
+ xpos =
+ indent_func == NULL ? indent_pos : indent_func(view, line, -1);
+
+ sub = g_new0(LINE_CACHE_SUB_REC, 1);
+ if (last_space > indent_pos && last_space > 10) {
+ /* go back to last space */
+ color = last_color;
+ fg24 = last_fg24;
+ bg24 = last_bg24;
+ ptr = last_space_ptr;
+ while (*ptr == ' ')
+ ptr++;
+ } else if (view->longword_noindent) {
+ /* long word, no indentation in next line */
+ xpos = 0;
+ sub->continues = TRUE;
+ }
+
+ sub->start = ptr;
+ sub->indent = xpos;
+ sub->indent_func = indent_func;
+ sub->color = color;
+#ifdef TERM_TRUECOLOR
+ sub->fg24 = fg24;
+ sub->bg24 = bg24;
+#endif
+
+ lines = g_slist_append(lines, sub);
+ linecount++;
+
+ last_space = 0;
+ continue;
+ }
+
+ if (view->break_wide && char_width > 1) {
+ last_space = xpos;
+ last_space_ptr = next_ptr;
+ last_color = color;
+ last_fg24 = fg24;
+ last_bg24 = bg24;
+ } else if (*ptr == ' ') {
+ last_space = xpos;
+ last_space_ptr = ptr;
+ last_color = color;
+ last_fg24 = fg24;
+ last_bg24 = bg24;
+ }
+
+ xpos += char_width;
+ ptr = next_ptr;
+ }
+ }
+
+ rec = g_malloc(sizeof(LINE_CACHE_REC)-sizeof(LINE_CACHE_SUB_REC) +
+ sizeof(LINE_CACHE_SUB_REC) * (linecount-1));
+ rec->last_access = time(NULL);
+ if (line_text == NULL) {
+ linecount = 0;
+ }
+ rec->count = linecount;
+ rec->line_text = line_text;
+
+ if (rec->count > 1) {
+ for (pos = 0; lines != NULL; pos++) {
+ LINE_CACHE_SUB_REC *data = lines->data;
+
+ memcpy(&rec->lines[pos], data, sizeof(LINE_CACHE_SUB_REC));
+
+ lines = g_slist_remove(lines, data);
+ g_free(data);
+ }
+ }
+
+ g_hash_table_insert(view->cache->line_cache, line, rec);
+ return rec;
+}
+
+static void view_remove_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
+ unsigned char update_counter)
+{
+ LINE_CACHE_REC *cache;
+
+ if (view->cache->update_counter == update_counter)
+ return;
+ view->cache->update_counter = update_counter;
+
+ cache = g_hash_table_lookup(view->cache->line_cache, line);
+ if (cache != NULL) {
+ line_cache_destroy(NULL, cache);
+ g_hash_table_remove(view->cache->line_cache, line);
+ }
+}
+
+static void view_update_cache(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
+ unsigned char update_counter)
+{
+ view_remove_cache(view, line, update_counter);
+
+ if (view->buffer->cur_line == line)
+ view_get_linecount(view, line);
+}
+
+void textbuffer_view_reset_cache(TEXT_BUFFER_VIEW_REC *view)
+{
+ GSList *tmp;
+
+ /* destroy line caches - note that you can't do simultaneously
+ unrefs + cache_get()s or it will keep using the old caches */
+ textbuffer_cache_unref(view->cache);
+ g_slist_foreach(view->siblings, (GFunc) textbuffer_cache_unref, NULL);
+
+ view->cache = textbuffer_cache_get(view->siblings, view->width);
+ for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
+ TEXT_BUFFER_VIEW_REC *rec = tmp->data;
+
+ rec->cache = textbuffer_cache_get(rec->siblings, rec->width);
+ }
+}
+
+static int view_line_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
+ int subline, int ypos, int max)
+{
+ INDENT_FUNC indent_func;
+ LINE_CACHE_REC *cache;
+ const unsigned char *text, *end, *text_newline;
+ unichar chr;
+ int xpos, color, drawcount, first, need_move, need_clrtoeol, char_width;
+ unsigned int fg24, bg24;
+ fg24 = bg24 = UINT_MAX;
+
+ if (view->dirty) /* don't bother drawing anything - redraw is coming */
+ return 0;
+
+ cache = textbuffer_view_get_line_cache(view, line);
+ if (subline >= cache->count)
+ return 0;
+
+ color = ATTR_RESET;
+ need_move = TRUE; need_clrtoeol = FALSE;
+ xpos = drawcount = 0; first = TRUE;
+ text_newline = text =
+ subline == 0 ? (unsigned char *) cache->line_text : cache->lines[subline - 1].start;
+
+ for (;;) {
+ if (text == text_newline) {
+ if (need_clrtoeol && xpos < view->width + (view->width == term_width ? 0 : 1)) {
+ term_set_color(view->window, ATTR_RESET);
+ term_window_clrtoeol(view->window, ypos);
+ }
+
+ if (first)
+ first = FALSE;
+ else {
+ ypos++;
+ if (--max == 0)
+ break;
+ }
+
+ if (subline > 0) {
+ /* continuing previous line - indent it */
+ indent_func = cache->lines[subline-1].indent_func;
+ if (indent_func == NULL || cache->lines[subline-1].continues)
+ xpos = cache->lines[subline-1].indent;
+ color = cache->lines[subline-1].color;
+#ifdef TERM_TRUECOLOR
+ fg24 = cache->lines[subline-1].fg24;
+ bg24 = cache->lines[subline-1].bg24;
+#endif
+ } else {
+ indent_func = NULL;
+ }
+
+ if (xpos == 0 && (indent_func == NULL || cache->lines[subline-1].continues))
+ need_clrtoeol = TRUE;
+ else {
+ /* line was indented - need to clear the
+ indented area first */
+ term_set_color(view->window, ATTR_RESET);
+ term_move(view->window, 0, ypos);
+ term_window_clrtoeol(view->window, ypos);
+
+ if (indent_func != NULL)
+ xpos = indent_func(view, line, ypos);
+ }
+
+ if (need_move || xpos > 0)
+ term_move(view->window, xpos, ypos);
+
+ term_set_color2(view->window, color, fg24, bg24);
+
+ if (subline == cache->count-1) {
+ text_newline = NULL;
+ need_move = FALSE;
+ } else {
+ /* get the beginning of the next subline */
+ text_newline = cache->lines[subline].start;
+ if (view->width == term_width) {
+ /* ensure that links / long words are not broken */
+ need_move = !cache->lines[subline].continues;
+ } else {
+ /* we cannot use the need_move
+ optimisation unless the split spans
+ the whole width */
+ need_move = TRUE;
+ }
+ }
+ drawcount++;
+ subline++;
+ }
+
+ if (*text == '\n') {
+ /* newline */
+ NEXT_CHAR_OR_BREAK(text);
+ continue;
+ }
+
+ if (*text == 0) {
+ break;
+ }
+
+ if (*text == 4) {
+ /* format */
+ NEXT_CHAR_OR_BREAK(text);
+
+ if (*text == FORMAT_STYLE_INDENT) {
+ /* ??? */
+ NEXT_CHAR_OR_BREAK(text);
+ } else {
+ unformat(&text, &color, &fg24, &bg24);
+ term_set_color2(view->window, color, fg24, bg24);
+ if (*text == 0)
+ break;
+ }
+ continue;
+ }
+
+ if (view->utf8) {
+ chr = read_unichar(text, &end, &char_width);
+ } else {
+ chr = *text;
+ end = text;
+ if (term_type == TERM_TYPE_BIG5 &&
+ is_big5(end[0], end[1]))
+ char_width = 2;
+ else
+ char_width = 1;
+ end += char_width;
+ }
+
+ xpos += char_width;
+ if (xpos <= view->width) {
+ if (unichar_isprint(chr)) {
+ if (view->utf8)
+ term_add_unichar(view->window, chr);
+ else
+ for (; text < end; text++)
+ term_addch(view->window, *text);
+ } else {
+ /* low-ascii */
+ term_set_color(view->window, ATTR_RESET|ATTR_REVERSE);
+ term_addch(view->window, (chr & 127)+'A'-1);
+ term_set_color2(view->window, color, fg24, bg24);
+ }
+ }
+ text = end;
+ }
+
+ if (need_clrtoeol && xpos < view->width + (view->width == term_width ? 0 : 1)) {
+ term_set_color(view->window, ATTR_RESET);
+ term_window_clrtoeol(view->window, ypos);
+ }
+
+ return drawcount;
+}
+
+/* Recalculate view's bottom line information - try to keep the
+ original if possible */
+static void textbuffer_view_init_bottom(TEXT_BUFFER_VIEW_REC *view)
+{
+ LINE_REC *line;
+ int linecount, total;
+
+ if (view->empty_linecount == 0) {
+ /* no empty lines in screen, no need to try to keep
+ the old bottom startline */
+ view->bottom_startline = NULL;
+ }
+
+ total = 0;
+ line = textbuffer_line_last(view->buffer);
+ for (; line != NULL; line = line->prev) {
+ if (view_line_is_hidden(view, line))
+ continue;
+
+ linecount = view_get_linecount(view, line);
+ if (line == view->bottom_startline) {
+ /* keep the old one, make sure that subline is ok */
+ if (view->bottom_subline > linecount)
+ view->bottom_subline = linecount;
+ view->empty_linecount = view->height - total -
+ (linecount-view->bottom_subline);
+ return;
+ }
+
+ total += linecount;
+ if (total >= view->height) {
+ view->bottom_startline = line;
+ view->bottom_subline = total - view->height;
+ view->empty_linecount = 0;
+ return;
+ }
+ }
+
+ /* not enough lines so we must be at the beginning of the buffer */
+ view->bottom_startline = view->buffer->first_line;
+ view->bottom_subline = 0;
+ view->empty_linecount = view->height - total;
+}
+
+static void textbuffer_view_init_ypos(TEXT_BUFFER_VIEW_REC *view)
+{
+ LINE_REC *line;
+
+ g_return_if_fail(view != NULL);
+
+ view->ypos = -view->subline-1;
+ for (line = view->startline; line != NULL; line = line->next)
+ view->ypos += view_get_linecount(view, line);
+}
+
+/* Create new view. */
+TEXT_BUFFER_VIEW_REC *textbuffer_view_create(TEXT_BUFFER_REC *buffer,
+ int width, int height,
+ int scroll, int utf8)
+{
+ TEXT_BUFFER_VIEW_REC *view;
+
+ g_return_val_if_fail(buffer != NULL, NULL);
+ g_return_val_if_fail(width > 0, NULL);
+
+ view = g_new0(TEXT_BUFFER_VIEW_REC, 1);
+ view->buffer = buffer;
+ view->siblings = textbuffer_get_views(buffer);
+
+ view->width = width;
+ view->height = height;
+ view->scroll = scroll;
+ view->utf8 = utf8;
+
+ view->cache = textbuffer_cache_get(view->siblings, width);
+ textbuffer_view_init_bottom(view);
+
+ view->startline = view->bottom_startline;
+ view->subline = view->bottom_subline;
+ view->bottom = TRUE;
+
+ view->hidden_level = 0;
+
+ textbuffer_view_init_ypos(view);
+
+ view->bookmarks = g_hash_table_new((GHashFunc) g_str_hash,
+ (GCompareFunc) g_str_equal);
+
+ views = g_slist_append(views, view);
+ return view;
+}
+
+/* Destroy the view. */
+void textbuffer_view_destroy(TEXT_BUFFER_VIEW_REC *view)
+{
+ GSList *tmp;
+
+ g_return_if_fail(view != NULL);
+
+ views = g_slist_remove(views, view);
+
+ if (view->siblings == NULL) {
+ /* last view for textbuffer, destroy */
+ textbuffer_destroy(view->buffer);
+ } else {
+ /* remove ourself from siblings lists */
+ for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
+ TEXT_BUFFER_VIEW_REC *rec = tmp->data;
+
+ rec->siblings = g_slist_remove(rec->siblings, view);
+ }
+ g_slist_free(view->siblings);
+ }
+
+ g_hash_table_foreach(view->bookmarks, (GHFunc) g_free, NULL);
+ g_hash_table_destroy(view->bookmarks);
+
+ textbuffer_cache_unref(view->cache);
+ g_free(view);
+}
+
+/* Change the default indent position */
+void textbuffer_view_set_default_indent(TEXT_BUFFER_VIEW_REC *view,
+ int default_indent,
+ int longword_noindent,
+ INDENT_FUNC indent_func)
+{
+ if (default_indent != -1)
+ view->default_indent = default_indent;
+ if (longword_noindent != -1)
+ view->longword_noindent = longword_noindent;
+
+ view->default_indent_func = indent_func;
+}
+
+/* Enable breaking of wide chars */
+void textbuffer_view_set_break_wide(TEXT_BUFFER_VIEW_REC *view,
+ gboolean break_wide)
+{
+ if (view->break_wide != break_wide) {
+ view->break_wide = break_wide;
+ textbuffer_view_reset_cache(view);
+ }
+}
+
+static void view_unregister_indent_func(TEXT_BUFFER_VIEW_REC *view,
+ INDENT_FUNC indent_func)
+{
+ if (view->default_indent_func == indent_func)
+ view->default_indent_func = NULL;
+
+ /* recreate cache so it won't contain references
+ to the indent function */
+ textbuffer_view_reset_cache(view);
+}
+
+void textbuffer_views_unregister_indent_func(INDENT_FUNC indent_func)
+{
+ g_slist_foreach(views, (GFunc) view_unregister_indent_func,
+ (void *) indent_func);
+}
+
+void textbuffer_view_set_scroll(TEXT_BUFFER_VIEW_REC *view, int scroll)
+{
+ view->scroll = scroll;
+}
+
+void textbuffer_view_set_utf8(TEXT_BUFFER_VIEW_REC *view, int utf8)
+{
+ view->utf8 = utf8;
+}
+
+static int view_get_linecount_all(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
+{
+ int linecount;
+
+ linecount = 0;
+ while (line != NULL) {
+ linecount += view_get_linecount(view, line);
+ line = line->next;
+ }
+
+ return linecount;
+}
+
+static void view_draw(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
+ int subline, int ypos, int lines, int fill_bottom)
+{
+ int linecount;
+
+ if (view->dirty) /* don't bother drawing anything - redraw is coming */
+ return;
+
+ while (line != NULL && lines > 0) {
+ if (!view_line_is_hidden(view, line)) {
+ linecount = view_line_draw(view, line, subline, ypos, lines);
+ ypos += linecount; lines -= linecount;
+ }
+
+ subline = 0;
+ line = line->next;
+ }
+
+ if (fill_bottom) {
+ /* clear the rest of the view */
+ term_set_color(view->window, ATTR_RESET);
+ while (lines > 0) {
+ term_move(view->window, 0, ypos);
+ term_window_clrtoeol(view->window, ypos);
+ ypos++; lines--;
+ }
+ }
+}
+
+#define view_draw_top(view, lines, fill_bottom) \
+ view_draw(view, (view)->startline, (view)->subline, \
+ 0, lines, fill_bottom)
+
+static void view_draw_bottom(TEXT_BUFFER_VIEW_REC *view, int lines)
+{
+ LINE_REC *line;
+ int ypos, maxline, subline, linecount;
+
+ maxline = view->height-lines;
+ line = view->startline; ypos = -view->subline; subline = 0;
+ while (line != NULL && ypos < maxline) {
+ linecount = view_get_linecount(view, line);
+ ypos += linecount;
+ if (ypos > maxline) {
+ subline = maxline-(ypos-linecount);
+ break;
+ }
+ line = line->next;
+ }
+
+ view_draw(view, line, subline, maxline, lines, TRUE);
+}
+
+/* lines: this pointer is scrolled by scrollcount screen lines
+ subline: this pointer contains the subline position
+ scrollcount: the number of lines to scroll down (negative: up)
+ draw_nonclean: whether to redraw the screen now
+
+ Returns number of lines actually scrolled */
+static int view_scroll(TEXT_BUFFER_VIEW_REC *view, LINE_REC **lines,
+ int *subline, int scrollcount, int draw_nonclean)
+{
+ int linecount, realcount, scroll_visible;
+
+ if (*lines == NULL)
+ return 0;
+
+ /* scroll down */
+ scroll_visible = lines == &view->startline;
+
+ realcount = -*subline;
+ scrollcount += *subline;
+ *subline = 0;
+ while (scrollcount > 0) {
+ linecount = view_get_linecount(view, *lines);
+
+ if ((scroll_visible && *lines == view->bottom_startline) &&
+ (scrollcount >= view->bottom_subline)) {
+ *subline = view->bottom_subline;
+ realcount += view->bottom_subline;
+ scrollcount = 0;
+ break;
+ }
+
+ realcount += linecount;
+ scrollcount -= linecount;
+ if (scrollcount < 0) {
+ realcount += scrollcount;
+ *subline = linecount+scrollcount;
+ scrollcount = 0;
+ break;
+ }
+
+ if ((*lines)->next == NULL)
+ break;
+
+ *lines = (*lines)->next;
+ }
+
+ /* scroll up */
+ while (scrollcount < 0 && (*lines)->prev != NULL) {
+ *lines = (*lines)->prev;
+ linecount = view_get_linecount(view, *lines);
+
+ realcount -= linecount;
+ scrollcount += linecount;
+ if (scrollcount > 0) {
+ realcount += scrollcount;
+ *subline = scrollcount;
+ break;
+ }
+ }
+
+ if (scroll_visible && realcount != 0 && view->window != NULL) {
+ if (realcount <= -view->height || realcount >= view->height) {
+ /* scrolled more than screenful, redraw the
+ whole view */
+ textbuffer_view_redraw(view);
+ } else {
+ if (view->width == term_width) {
+ /* we can try to use vt100 scroll regions */
+ term_set_color(view->window, ATTR_RESET);
+ term_window_scroll(view->window, realcount);
+
+ if (draw_nonclean) {
+ if (realcount < 0)
+ view_draw_top(view, -realcount, TRUE);
+ else
+ view_draw_bottom(view, realcount);
+ }
+
+ term_refresh(view->window);
+ } else {
+ /* do not bother with vt400 scroll
+ rectangles for now, redraw the
+ whole view */
+ view->dirty = TRUE;
+ irssi_set_dirty();
+ }
+ }
+ }
+
+ return realcount >= 0 ? realcount : -realcount;
+}
+
+/* Resize the view. */
+void textbuffer_view_resize(TEXT_BUFFER_VIEW_REC *view, int width, int height)
+{
+ int linecount;
+
+ g_return_if_fail(view != NULL);
+ g_return_if_fail(width > 0);
+
+ if (view->width != width) {
+ /* line cache needs to be recreated */
+ textbuffer_cache_unref(view->cache);
+ view->cache = textbuffer_cache_get(view->siblings, width);
+ }
+
+ view->width = width > 10 ? width : 10;
+ view->height = height > 1 ? height : 1;
+
+ if (view->buffer->first_line == NULL) {
+ view->empty_linecount = height;
+ return;
+ }
+
+ textbuffer_view_init_bottom(view);
+
+ /* check that we didn't scroll lower than bottom startline.. */
+ if (textbuffer_line_exists_after(view->bottom_startline->next,
+ view->startline)) {
+ view->startline = view->bottom_startline;
+ view->subline = view->bottom_subline;
+ } else if (view->startline == view->bottom_startline &&
+ view->subline > view->bottom_subline) {
+ view->subline = view->bottom_subline;
+ } else if (view->startline != NULL) {
+ /* make sure the subline is still in allowed range */
+ linecount = view_get_linecount(view, view->startline);
+ if (view->subline > linecount)
+ view->subline = linecount;
+ } else {
+ /* we don't have a startline. still under construction? */
+ view->subline = 0;
+ }
+
+ textbuffer_view_init_ypos(view);
+ if (view->bottom && !view_is_bottom(view)) {
+ /* we scrolled to far up, need to get down. go right over
+ the empty lines if there's any */
+ view->startline = view->bottom_startline;
+ view->subline = view->bottom_subline;
+ if (view->empty_linecount > 0) {
+ view_scroll(view, &view->startline, &view->subline,
+ -view->empty_linecount, FALSE);
+ }
+ textbuffer_view_init_ypos(view);
+ }
+
+ view->bottom = view_is_bottom(view);
+ if (view->bottom) {
+ /* check if we left empty space at the bottom.. */
+ linecount = view_get_linecount_all(view, view->startline) -
+ view->subline;
+ if (view->empty_linecount < view->height-linecount)
+ view->empty_linecount = view->height-linecount;
+ view->more_text = FALSE;
+ }
+
+ view->dirty = TRUE;
+}
+
+/* Clear the view, don't actually remove any lines from buffer. */
+void textbuffer_view_clear(TEXT_BUFFER_VIEW_REC *view)
+{
+ g_return_if_fail(view != NULL);
+
+ view->ypos = -1;
+ view->bottom_startline = view->startline =
+ textbuffer_line_last(view->buffer);
+ view->bottom_subline = view->subline =
+ view->buffer->cur_line == NULL ? 0 :
+ view_get_linecount(view, view->buffer->cur_line);
+ view->empty_linecount = view->height;
+ view->bottom = TRUE;
+ view->more_text = FALSE;
+
+ textbuffer_view_redraw(view);
+}
+
+/* Scroll the view up/down */
+void textbuffer_view_scroll(TEXT_BUFFER_VIEW_REC *view, int lines)
+{
+ int count, ypos;
+
+ g_return_if_fail(view != NULL);
+
+ count = view_scroll(view, &view->startline, &view->subline, lines, TRUE);
+
+ ypos = view->ypos + (lines < 0 ? count : -count);
+ textbuffer_view_init_ypos(view);
+ if (ypos != view->ypos)
+ textbuffer_view_resize(view, view->width, view->height);
+
+ view->bottom = view_is_bottom(view);
+ if (view->bottom) view->more_text = FALSE;
+
+ if (view->window != NULL)
+ term_refresh(view->window);
+}
+
+/* Scroll to specified line */
+void textbuffer_view_scroll_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
+{
+ g_return_if_fail(view != NULL);
+
+ if (textbuffer_line_exists_after(view->bottom_startline->next, line)) {
+ view->startline = view->bottom_startline;
+ view->subline = view->bottom_subline;
+ } else {
+ view->startline = line;
+ view->subline = 0;
+ }
+
+ textbuffer_view_init_ypos(view);
+ view->bottom = view_is_bottom(view);
+ if (view->bottom) view->more_text = FALSE;
+
+ textbuffer_view_redraw(view);
+}
+
+/* Return line cache */
+LINE_CACHE_REC *textbuffer_view_get_line_cache(TEXT_BUFFER_VIEW_REC *view,
+ LINE_REC *line)
+{
+ LINE_CACHE_REC *cache;
+
+ g_assert(view != NULL);
+ g_assert(line != NULL);
+
+ cache = g_hash_table_lookup(view->cache->line_cache, line);
+ if (cache == NULL)
+ cache = view_update_line_cache(view, line);
+ else
+ cache->last_access = time(NULL);
+
+ return cache;
+}
+
+static void view_insert_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
+{
+ int linecount, ypos, subline;
+
+ if (!view->bottom)
+ view->more_text = TRUE;
+
+ if (view->bottom_startline == NULL) {
+ view->startline = view->bottom_startline =
+ view->buffer->first_line;
+ }
+
+ if (view->buffer->cur_line != line &&
+ !textbuffer_line_exists_after(view->bottom_startline, line))
+ return;
+
+ linecount = view_get_linecount(view, line);
+ view->ypos += linecount;
+ if (view->empty_linecount > 0) {
+ view->empty_linecount -= linecount;
+ if (view->empty_linecount >= 0)
+ linecount = 0;
+ else {
+ linecount = -view->empty_linecount;
+ view->empty_linecount = 0;
+ }
+ }
+
+ if (linecount > 0) {
+ view_scroll(view, &view->bottom_startline,
+ &view->bottom_subline, linecount, FALSE);
+ }
+
+ if (view->bottom) {
+ if (view->scroll && view->ypos >= view->height) {
+ linecount = view->ypos-view->height+1;
+ view_scroll(view, &view->startline,
+ &view->subline, linecount, FALSE);
+ view->ypos -= linecount;
+ } else {
+ view->bottom = view_is_bottom(view);
+ }
+
+ if (view->window != NULL && !view_line_is_hidden(view, line)) {
+ ypos = view->ypos+1 - view_get_linecount(view, line);
+ if (ypos >= 0)
+ subline = 0;
+ else {
+ subline = -ypos;
+ ypos = 0;
+ }
+ if (ypos < view->height) {
+ view_line_draw(view, line, subline, ypos,
+ view->height - ypos);
+ }
+ }
+ }
+
+ if (view->window != NULL && !view_line_is_hidden(view, line))
+ term_refresh(view->window);
+}
+
+/* Update some line in the buffer which has been modified using
+ textbuffer_append() or textbuffer_insert(). */
+void textbuffer_view_insert_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
+{
+ GSList *tmp;
+ unsigned char update_counter;
+
+ g_return_if_fail(view != NULL);
+ g_return_if_fail(line != NULL);
+
+ if (!view->buffer->last_eol)
+ return;
+
+ update_counter = view->cache->update_counter+1;
+ view_update_cache(view, line, update_counter);
+ view_insert_line(view, line);
+
+ for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
+ TEXT_BUFFER_VIEW_REC *rec = tmp->data;
+
+ view_update_cache(rec, line, update_counter);
+ view_insert_line(rec, line);
+ }
+}
+
+typedef struct {
+ LINE_REC *remove_line;
+ GSList *remove_list;
+} BOOKMARK_FIND_REC;
+
+static void bookmark_check_remove(char *key, LINE_REC *line,
+ BOOKMARK_FIND_REC *rec)
+{
+ if (line == rec->remove_line)
+ rec->remove_list = g_slist_append(rec->remove_list, key);
+}
+
+static void view_bookmarks_check(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
+{
+ BOOKMARK_FIND_REC rec;
+ GSList *tmp;
+
+ rec.remove_line = line;
+ rec.remove_list = NULL;
+ g_hash_table_foreach(view->bookmarks,
+ (GHFunc) bookmark_check_remove, &rec);
+
+ if (rec.remove_list != NULL) {
+ for (tmp = rec.remove_list; tmp != NULL; tmp = tmp->next) {
+ g_hash_table_remove(view->bookmarks, tmp->data);
+ g_free(tmp->data);
+ }
+ g_slist_free(rec.remove_list);
+ }
+}
+
+/* Return number of real lines `lines' list takes -
+ stops counting when the height reaches the view height */
+static int view_get_lines_height(TEXT_BUFFER_VIEW_REC *view,
+ LINE_REC *line, int subline,
+ LINE_REC *skip_line)
+{
+ int height, linecount;
+
+ height = -subline;
+ while (line != NULL && height < view->height) {
+ if (line != skip_line) {
+ linecount = view_get_linecount(view, line);
+ height += linecount;
+ }
+ line = line->next;
+ }
+
+ return height < view->height ? height : view->height;
+}
+
+/* line: line to remove
+ linecount: linecount of that line, to be offset when the line was in/below view
+
+ scroll the window maintaining the startline while removing line
+ if startline is removed, make the previous line the new startline
+*/
+static void view_remove_line_update_startline(TEXT_BUFFER_VIEW_REC *view,
+ LINE_REC *line, int linecount)
+{
+ int scroll;
+
+ if (view->startline == line) {
+ view->startline = view->startline->prev != NULL ?
+ view->startline->prev : view->startline->next;
+ view->subline = 0;
+ } else {
+ scroll = view->height -
+ view_get_lines_height(view, view->startline,
+ view->subline, line);
+ if (scroll > 0) {
+ view_scroll(view, &view->startline,
+ &view->subline, -scroll, FALSE);
+ }
+ }
+
+ /* FIXME: this is slow and unnecessary, but it's easy and
+ really works :) */
+ textbuffer_view_init_ypos(view);
+ if (textbuffer_line_exists_after(view->startline, line))
+ view->ypos -= linecount;
+}
+
+static void view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line,
+ int linecount)
+{
+ int realcount;
+
+ view_bookmarks_check(view, line);
+
+ if (view->buffer->cur_line == line) {
+ /* the last line is being removed */
+ LINE_REC *prevline;
+
+ prevline = view->buffer->first_line == line ?
+ NULL :
+ textbuffer_line_last(view->buffer)->prev;
+ if (prevline != NULL)
+ view_get_linecount(view, prevline);
+ }
+
+ /* first line in the buffer - this is the most commonly
+ removed line.. */
+ if (view->buffer->first_line == line) {
+ if (view->bottom_startline == line) {
+ /* very small scrollback.. */
+ view->bottom_startline = view->bottom_startline->next;
+ view->bottom_subline = 0;
+ }
+
+ if (view->startline == line) {
+ /* removing the first line in screen */
+ int is_last = view->startline->next == NULL;
+
+ realcount = view_scroll(view, &view->startline,
+ &view->subline,
+ linecount, FALSE);
+ view->ypos -= realcount;
+ view->empty_linecount += linecount-realcount;
+ if (is_last == 1)
+ view->startline = NULL;
+ }
+
+ if (view->startline == line) {
+ view->startline = line->next;
+ view->subline = 0;
+ }
+ } else {
+ if (textbuffer_line_exists_after(view->bottom_startline,
+ line)) {
+ realcount = view_scroll(view, &view->bottom_startline,
+ &view->bottom_subline,
+ -linecount, FALSE);
+ view->empty_linecount += linecount-realcount;
+ }
+
+ if (view->bottom_startline == line) {
+ view->bottom_startline = view->bottom_startline->next;
+ view->bottom_subline = 0;
+ }
+
+ if (textbuffer_line_exists_after(view->startline,
+ line)) {
+ view_remove_line_update_startline(view, line,
+ linecount);
+ }
+ }
+
+ view->bottom = view_is_bottom(view);
+ if (view->bottom) view->more_text = FALSE;
+ if (view->window != NULL)
+ term_refresh(view->window);
+}
+
+/* Remove one line from buffer. */
+void textbuffer_view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line)
+{
+ GSList *tmp;
+ unsigned char update_counter;
+ int linecount;
+
+ g_return_if_fail(view != NULL);
+ g_return_if_fail(line != NULL);
+
+ signal_emit("gui textbuffer line removed", 3, view, line, line->prev);
+
+ linecount = view_get_linecount(view, line);
+ update_counter = view->cache->update_counter+1;
+
+ view_remove_line(view, line, linecount);
+ view_remove_cache(view, line, update_counter);
+
+ for (tmp = view->siblings; tmp != NULL; tmp = tmp->next) {
+ TEXT_BUFFER_VIEW_REC *rec = tmp->data;
+
+ view_remove_line(rec, line, linecount);
+ view_remove_cache(rec, line, update_counter);
+ }
+
+ textbuffer_remove(view->buffer, line);
+ if (view->bottom_startline == NULL) {
+ /* We may have removed the bottom_startline, make sure
+ that scroll doesn't get stuck */
+ textbuffer_view_init_bottom(view);
+ }
+}
+
+void textbuffer_view_remove_lines_by_level(TEXT_BUFFER_VIEW_REC *view, int level)
+{
+ LINE_REC *line, *next;
+
+ term_refresh_freeze();
+ line = textbuffer_view_get_lines(view);
+
+ while (line != NULL) {
+ next = line->next;
+
+ if (line->info.level & level)
+ textbuffer_view_remove_line(view, line);
+ line = next;
+ }
+ textbuffer_view_redraw(view);
+ term_refresh_thaw();
+}
+
+/* Remove all lines from buffer. */
+void textbuffer_view_remove_all_lines(TEXT_BUFFER_VIEW_REC *view)
+{
+ g_return_if_fail(view != NULL);
+
+ textbuffer_remove_all_lines(view->buffer);
+
+ g_hash_table_foreach(view->bookmarks, (GHFunc) g_free, NULL);
+ g_hash_table_remove_all(view->bookmarks);
+
+ textbuffer_view_reset_cache(view);
+ textbuffer_view_clear(view);
+ g_slist_foreach(view->siblings, (GFunc) textbuffer_view_clear, NULL);
+}
+
+/* Set a bookmark in view */
+void textbuffer_view_set_bookmark(TEXT_BUFFER_VIEW_REC *view,
+ const char *name, LINE_REC *line)
+{
+ gpointer key, value;
+
+ g_return_if_fail(view != NULL);
+ g_return_if_fail(name != NULL);
+
+ if (g_hash_table_lookup_extended(view->bookmarks, name,
+ &key, &value)) {
+ g_hash_table_remove(view->bookmarks, key);
+ g_free(key);
+ }
+
+ g_hash_table_insert(view->bookmarks, g_strdup(name), line);
+}
+
+/* Set a bookmark in view to the bottom line */
+void textbuffer_view_set_bookmark_bottom(TEXT_BUFFER_VIEW_REC *view,
+ const char *name)
+{
+ LINE_REC *line;
+
+ g_return_if_fail(view != NULL);
+ g_return_if_fail(name != NULL);
+
+ if (view->bottom_startline != NULL) {
+ line = textbuffer_line_last(view->buffer);
+ textbuffer_view_set_bookmark(view, name, line);
+ }
+}
+
+/* Return the line for bookmark */
+LINE_REC *textbuffer_view_get_bookmark(TEXT_BUFFER_VIEW_REC *view,
+ const char *name)
+{
+ g_return_val_if_fail(view != NULL, NULL);
+ g_return_val_if_fail(name != NULL, NULL);
+
+ return g_hash_table_lookup(view->bookmarks, name);
+}
+
+void textbuffer_view_set_hidden_level(TEXT_BUFFER_VIEW_REC *view, int level)
+{
+ g_return_if_fail(view != NULL);
+
+ if (view->hidden_level != level) {
+ if (view->empty_linecount > 0 && view->startline != NULL) {
+ int old_height, new_height;
+ LINE_REC *hidden_start;
+
+ hidden_start = view->startline;
+ while (hidden_start->prev != NULL && view_line_is_hidden(view, hidden_start->prev)) {
+ hidden_start = hidden_start->prev;
+ }
+
+ old_height = view_get_lines_height(view, hidden_start, view->subline, NULL);
+ view->hidden_level = level;
+ new_height = view_get_lines_height(view, hidden_start, view->subline, NULL);
+
+ view->empty_linecount -= new_height - old_height;
+
+ if (view->empty_linecount < 0)
+ view->empty_linecount = 0;
+ else if (view->empty_linecount > view->height)
+ view->empty_linecount = view->height;
+ } else {
+ view->hidden_level = level;
+ }
+ textbuffer_view_resize(view, view->width, view->height);
+ }
+}
+
+/* Specify window where the changes in view should be drawn,
+ NULL disables it. */
+void textbuffer_view_set_window(TEXT_BUFFER_VIEW_REC *view,
+ TERM_WINDOW *window)
+{
+ g_return_if_fail(view != NULL);
+
+ if (view->window != window) {
+ view->window = window;
+ if (window != NULL) {
+ textbuffer_view_resize(view, view->width, view->height);
+ view->dirty = TRUE;
+ }
+ }
+}
+
+/* Redraw a view to window */
+void textbuffer_view_redraw(TEXT_BUFFER_VIEW_REC *view)
+{
+ g_return_if_fail(view != NULL);
+
+ if (view->window != NULL) {
+ view->dirty = FALSE;
+ view_draw_top(view, view->height, TRUE);
+ term_refresh(view->window);
+ }
+}
+
+static int line_cache_check_remove(void *key, LINE_CACHE_REC *cache,
+ time_t *now)
+{
+ if (cache->last_access+LINE_CACHE_KEEP_TIME > *now)
+ return FALSE;
+
+ line_cache_destroy(NULL, cache);
+ return TRUE;
+}
+
+static int sig_check_linecache(void)
+{
+ GSList *tmp, *caches;
+ time_t now;
+
+ now = time(NULL);
+ caches = NULL;
+ for (tmp = views; tmp != NULL; tmp = tmp->next) {
+ TEXT_BUFFER_VIEW_REC *rec = tmp->data;
+
+ if (rec->window != NULL) {
+ /* keep visible lines mapped */
+ view_get_lines_height(rec, rec->startline, rec->subline, NULL);
+ }
+
+ if (g_slist_find(caches, rec->cache) != NULL)
+ continue;
+
+ caches = g_slist_append(caches, rec->cache);
+ g_hash_table_foreach_remove(rec->cache->line_cache,
+ (GHRFunc) line_cache_check_remove,
+ &now);
+ }
+
+ g_slist_free(caches);
+ return 1;
+}
+
+void textbuffer_view_init(void)
+{
+ linecache_tag = g_timeout_add(LINE_CACHE_CHECK_TIME, (GSourceFunc) sig_check_linecache, NULL);
+}
+
+void textbuffer_view_deinit(void)
+{
+ g_source_remove(linecache_tag);
+}
diff --git a/src/fe-text/textbuffer-view.h b/src/fe-text/textbuffer-view.h
new file mode 100644
index 0000000..4e5ed82
--- /dev/null
+++ b/src/fe-text/textbuffer-view.h
@@ -0,0 +1,172 @@
+#ifndef IRSSI_FE_TEXT_TEXTBUFFER_VIEW_H
+#define IRSSI_FE_TEXT_TEXTBUFFER_VIEW_H
+
+#include <irssi/src/fe-text/textbuffer.h>
+#include <irssi/src/fe-text/term.h>
+
+typedef struct _TEXT_BUFFER_VIEW_REC TEXT_BUFFER_VIEW_REC;
+
+/* if ypos == -1, don't print anything, just return the indent size */
+typedef int (*INDENT_FUNC) (TEXT_BUFFER_VIEW_REC *view,
+ LINE_REC *line, int ypos);
+
+typedef struct {
+ const unsigned char *start;
+ int indent;
+ INDENT_FUNC indent_func;
+ int color;
+#ifdef TERM_TRUECOLOR
+ unsigned int fg24, bg24;
+#endif
+
+ /* first word in line belong to the end of the last word in
+ previous line */
+ unsigned int continues:1;
+} LINE_CACHE_SUB_REC;
+
+typedef struct {
+ time_t last_access;
+
+ char *line_text;
+ int count; /* number of real lines */
+
+ /* variable sized array, actually. starts from the second line,
+ so size of it is count-1 */
+ LINE_CACHE_SUB_REC lines[1];
+} LINE_CACHE_REC;
+
+typedef struct {
+ int refcount;
+ int width;
+
+ GHashTable *line_cache;
+
+ /* should contain the same value for each cache that uses the
+ same buffer */
+ unsigned char update_counter;
+ /* number of real lines used by the last line in buffer */
+ int last_linecount;
+} TEXT_BUFFER_CACHE_REC;
+
+struct _TEXT_BUFFER_VIEW_REC {
+ TEXT_BUFFER_REC *buffer;
+ /* other views that use the same buffer */
+ GSList *siblings;
+
+ TERM_WINDOW *window;
+ int width, height;
+
+ int default_indent;
+ INDENT_FUNC default_indent_func;
+
+ TEXT_BUFFER_CACHE_REC *cache;
+ /* cursor position - visible area is 0..height-1 */
+ int ypos;
+
+ /* line at the top of the screen */
+ LINE_REC *startline;
+ /* number of "real lines" to skip from `startline' */
+ int subline;
+
+ /* marks the bottom of the text buffer */
+ LINE_REC *bottom_startline;
+ int bottom_subline;
+
+ /* Bookmarks to the lines in the buffer - removed automatically
+ when the line gets removed from buffer */
+ GHashTable *bookmarks;
+
+ /* these levels should be hidden */
+ int hidden_level;
+ /* how many empty lines are in screen. a screenful when started
+ or used /CLEAR */
+ int empty_linecount;
+
+ unsigned int longword_noindent:1;
+ /* scroll down automatically when at bottom */
+ unsigned int scroll:1;
+ /* use UTF8 in this view */
+ unsigned int utf8:1;
+ /* Break wide chars in this view */
+ unsigned int break_wide:1;
+ /* window is at the bottom of the text buffer */
+ unsigned int bottom:1;
+ /* if !bottom - new text has been printed since we were at bottom */
+ unsigned int more_text:1;
+ /* Window needs a redraw */
+ unsigned int dirty:1;
+};
+
+/* Create new view. */
+TEXT_BUFFER_VIEW_REC *textbuffer_view_create(TEXT_BUFFER_REC *buffer,
+ int width, int height,
+ int scroll, int utf8);
+/* Destroy the view. */
+void textbuffer_view_destroy(TEXT_BUFFER_VIEW_REC *view);
+/* Change the default indent position */
+void textbuffer_view_set_default_indent(TEXT_BUFFER_VIEW_REC *view,
+ int default_indent,
+ int longword_noindent,
+ INDENT_FUNC indent_func);
+void textbuffer_views_unregister_indent_func(INDENT_FUNC indent_func);
+void textbuffer_view_set_break_wide(TEXT_BUFFER_VIEW_REC *view,
+ gboolean break_wide);
+
+void textbuffer_view_set_scroll(TEXT_BUFFER_VIEW_REC *view, int scroll);
+void textbuffer_view_set_utf8(TEXT_BUFFER_VIEW_REC *view, int utf8);
+
+/* Resize the view. */
+void textbuffer_view_resize(TEXT_BUFFER_VIEW_REC *view, int width, int height);
+/* Clear the view, don't actually remove any lines from buffer. */
+void textbuffer_view_clear(TEXT_BUFFER_VIEW_REC *view);
+
+#define textbuffer_view_get_lines(view) \
+ ((view)->buffer->first_line)
+
+/* Scroll the view up/down */
+void textbuffer_view_scroll(TEXT_BUFFER_VIEW_REC *view, int lines);
+/* Scroll to specified line */
+void textbuffer_view_scroll_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line);
+/* Return line cache */
+LINE_CACHE_REC *textbuffer_view_get_line_cache(TEXT_BUFFER_VIEW_REC *view,
+ LINE_REC *line);
+/* Reset the whole line cache */
+void textbuffer_view_reset_cache(TEXT_BUFFER_VIEW_REC *view);
+
+/*
+ Functions for manipulating the text buffer, using these commands update
+ all views that use the buffer.
+*/
+
+/* Update some line in the buffer which has been modified using
+ textbuffer_append() or textbuffer_insert(). */
+void textbuffer_view_insert_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line);
+/* Remove one line from buffer. */
+void textbuffer_view_remove_line(TEXT_BUFFER_VIEW_REC *view, LINE_REC *line);
+/* Remove all lines from buffer. */
+void textbuffer_view_remove_all_lines(TEXT_BUFFER_VIEW_REC *view);
+void textbuffer_view_remove_lines_by_level(TEXT_BUFFER_VIEW_REC *view, int level);
+
+/* Set a bookmark in view */
+void textbuffer_view_set_bookmark(TEXT_BUFFER_VIEW_REC *view,
+ const char *name, LINE_REC *line);
+/* Set a bookmark in view to the bottom line */
+void textbuffer_view_set_bookmark_bottom(TEXT_BUFFER_VIEW_REC *view,
+ const char *name);
+/* Return the line for bookmark */
+LINE_REC *textbuffer_view_get_bookmark(TEXT_BUFFER_VIEW_REC *view,
+ const char *name);
+/* Set hidden level for view */
+void textbuffer_view_set_hidden_level(TEXT_BUFFER_VIEW_REC *view, int level);
+
+/* Specify window where the changes in view should be drawn,
+ NULL disables it. */
+void textbuffer_view_set_window(TEXT_BUFFER_VIEW_REC *view,
+ TERM_WINDOW *window);
+/* Redraw the view */
+void textbuffer_view_redraw(TEXT_BUFFER_VIEW_REC *view);
+
+void textbuffer_view_init(void);
+void textbuffer_view_deinit(void);
+
+#endif
diff --git a/src/fe-text/textbuffer.c b/src/fe-text/textbuffer.c
new file mode 100644
index 0000000..8a206f2
--- /dev/null
+++ b/src/fe-text/textbuffer.c
@@ -0,0 +1,345 @@
+/*
+ textbuffer.c : Text buffer handling
+
+ Copyright (C) 1999-2001 Timo Sirainen
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+#define G_LOG_DOMAIN "TextBuffer"
+
+#include "module.h"
+#include <irssi/src/core/misc.h>
+#include <irssi/src/fe-common/core/formats.h>
+#include <irssi/src/core/utf8.h>
+#include <irssi/src/core/iregex.h>
+
+#include <irssi/src/fe-text/textbuffer-formats.h>
+#include <irssi/src/fe-text/textbuffer.h>
+
+#define TEXT_CHUNK_USABLE_SIZE (LINE_TEXT_CHUNK_SIZE-2-(int)sizeof(char*))
+
+TEXT_BUFFER_REC *textbuffer_create(WINDOW_REC *window)
+{
+ TEXT_BUFFER_REC *buffer;
+
+ buffer = g_slice_new0(TEXT_BUFFER_REC);
+ buffer->window = window;
+ buffer->last_eol = TRUE;
+ buffer->last_fg = -1;
+ buffer->last_bg = -1;
+ buffer->cur_text = g_string_sized_new(TEXT_CHUNK_USABLE_SIZE);
+ return buffer;
+}
+
+void textbuffer_destroy(TEXT_BUFFER_REC *buffer)
+{
+ GSList *tmp;
+
+ g_return_if_fail(buffer != NULL);
+
+ textbuffer_remove_all_lines(buffer);
+ g_string_free(buffer->cur_text, TRUE);
+ for (tmp = buffer->cur_info; tmp != NULL; tmp = tmp->next) {
+ LINE_INFO_REC *info = buffer->cur_info->data;
+ textbuffer_line_info_free1(info);
+ g_free(info);
+ }
+ g_slist_free(buffer->cur_info);
+
+ buffer->window = NULL;
+ g_slice_free(TEXT_BUFFER_REC, buffer);
+}
+
+void textbuffer_line_info_free1(LINE_INFO_REC *info)
+{
+ textbuffer_format_rec_free(info->format);
+ textbuffer_meta_rec_free(info->meta);
+ g_free(info->text);
+}
+
+static void text_chunk_append(TEXT_BUFFER_REC *buffer,
+ const unsigned char *data, int len)
+{
+ if (len == 0)
+ return;
+
+ /* g_string_append_len(buffer->cur_text, (const char *)data, len); */
+ g_string_append(buffer->cur_text, (const char *) data);
+}
+
+static LINE_REC *textbuffer_line_create(TEXT_BUFFER_REC *buffer)
+{
+ LINE_REC *rec;
+
+ rec = g_slice_new0(LINE_REC);
+ return rec;
+}
+
+static LINE_REC *textbuffer_line_insert(TEXT_BUFFER_REC *buffer,
+ LINE_REC *prev)
+{
+ LINE_REC *line;
+
+ line = textbuffer_line_create(buffer);
+ line->prev = prev;
+ if (prev == NULL) {
+ line->next = buffer->first_line;
+ if (buffer->first_line != NULL)
+ buffer->first_line->prev = line;
+ buffer->first_line = line;
+ } else {
+ line->next = prev->next;
+ if (line->next != NULL)
+ line->next->prev = line;
+ prev->next = line;
+ }
+
+ if (prev == buffer->cur_line)
+ buffer->cur_line = line;
+ buffer->lines_count++;
+
+ return line;
+}
+
+LINE_REC *textbuffer_line_last(TEXT_BUFFER_REC *buffer)
+{
+ return buffer->cur_line;
+}
+
+/* returns TRUE if `search' comes on or after `line' in the buffer */
+int textbuffer_line_exists_after(LINE_REC *line, LINE_REC *search)
+{
+ while (line != NULL) {
+ if (line == search)
+ return TRUE;
+ line = line->next;
+ }
+ return FALSE;
+}
+
+void textbuffer_line_add_colors(TEXT_BUFFER_REC *buffer, LINE_REC **line,
+ int fg, int bg, int flags)
+{
+ GString *out = g_string_new(NULL);
+ format_gui_flags(out, &buffer->last_fg, &buffer->last_bg, &buffer->last_flags, fg, bg,
+ flags);
+
+ if (*(out->str) != '\0') {
+ *line =
+ textbuffer_insert(buffer, *line, (unsigned char *) out->str, out->len, NULL);
+ }
+ g_string_free(out, TRUE);
+}
+
+LINE_REC *textbuffer_append(TEXT_BUFFER_REC *buffer,
+ const unsigned char *data, int len,
+ LINE_INFO_REC *info)
+{
+ return textbuffer_insert(buffer, buffer->cur_line, data, len, info);
+}
+
+LINE_REC *textbuffer_insert(TEXT_BUFFER_REC *buffer, LINE_REC *insert_after,
+ const unsigned char *data, int len,
+ LINE_INFO_REC *info)
+{
+ LINE_REC *line;
+
+ g_return_val_if_fail(buffer != NULL, NULL);
+ g_return_val_if_fail(data != NULL, NULL);
+
+ line = !buffer->last_eol ? insert_after :
+ textbuffer_line_insert(buffer, insert_after);
+
+ if (info != NULL)
+ memcpy(&line->info, info, sizeof(line->info));
+
+ text_chunk_append(buffer, data, len);
+
+ buffer->last_eol = len >= 2 &&
+ data[len-2] == 0 && data[len-1] == LINE_CMD_EOL;
+
+ if (buffer->last_eol) {
+ if (!line->info.format) {
+ line->info.text = g_strdup(buffer->cur_text->str);
+ g_string_truncate(buffer->cur_text, 0);
+ }
+
+ buffer->last_fg = -1;
+ buffer->last_bg = -1;
+ buffer->last_flags = 0;
+ }
+
+ return line;
+}
+
+void textbuffer_remove(TEXT_BUFFER_REC *buffer, LINE_REC *line)
+{
+ g_return_if_fail(buffer != NULL);
+ g_return_if_fail(line != NULL);
+
+ if (buffer->first_line == line)
+ buffer->first_line = line->next;
+ if (line->prev != NULL)
+ line->prev->next = line->next;
+ if (line->next != NULL)
+ line->next->prev = line->prev;
+
+ if (buffer->cur_line == line) {
+ buffer->cur_line = line->prev;
+ }
+
+ line->prev = line->next = NULL;
+
+ buffer->lines_count--;
+ textbuffer_line_info_free1(&line->info);
+ g_slice_free(LINE_REC, line);
+}
+
+/* Removes all lines from buffer */
+void textbuffer_remove_all_lines(TEXT_BUFFER_REC *buffer)
+{
+ LINE_REC *line;
+
+ g_return_if_fail(buffer != NULL);
+
+ while (buffer->first_line != NULL) {
+ line = buffer->first_line->next;
+ textbuffer_line_info_free1(&buffer->first_line->info);
+ g_slice_free(LINE_REC, buffer->first_line);
+ buffer->first_line = line;
+ }
+ buffer->lines_count = 0;
+
+ buffer->cur_line = NULL;
+ g_string_truncate(buffer->cur_text, 0);
+
+ buffer->last_eol = TRUE;
+}
+
+void textbuffer_line2text(TEXT_BUFFER_REC *buffer, LINE_REC *line, int coloring, GString *str)
+{
+ char *ptr, *tmp;
+
+ g_return_if_fail(line != NULL);
+ g_return_if_fail(str != NULL);
+
+ g_string_truncate(str, 0);
+
+ if ((ptr = textbuffer_line_get_text(buffer, line, coloring == COLORING_RAW)) != NULL) {
+ if (coloring == COLORING_STRIP) {
+ tmp = ptr;
+ ptr = strip_codes(tmp);
+ g_free(tmp);
+ } else if (coloring == COLORING_UNEXPAND) {
+ tmp = ptr;
+ ptr = format_string_unexpand(tmp, 0);
+ g_free(tmp);
+ }
+ g_string_append(str, ptr);
+ g_free(ptr);
+ }
+}
+
+GList *textbuffer_find_text(TEXT_BUFFER_REC *buffer, LINE_REC *startline,
+ int level, int nolevel, const char *text,
+ int before, int after,
+ int regexp, int fullword, int case_sensitive)
+{
+ Regex *preg;
+ LINE_REC *line, *pre_line;
+ GList *matches;
+ GString *str;
+ int i, match_after, line_matched;
+ char * (*match_func)(const char *, const char *);
+
+ g_return_val_if_fail(buffer != NULL, NULL);
+ g_return_val_if_fail(text != NULL, NULL);
+
+ preg = NULL;
+
+ if (regexp) {
+ preg = i_regex_new(text, case_sensitive ? 0 : G_REGEX_CASELESS, 0, NULL);
+
+ if (preg == NULL)
+ return NULL;
+ }
+
+ matches = NULL; match_after = 0;
+ str = g_string_new(NULL);
+
+ line = startline != NULL ? startline : buffer->first_line;
+
+ if (fullword)
+ match_func = case_sensitive ? strstr_full : stristr_full;
+ else
+ match_func = case_sensitive ? strstr : stristr;
+
+ for (; line != NULL; line = line->next) {
+ line_matched = (line->info.level & level) != 0 &&
+ (line->info.level & nolevel) == 0;
+
+ if (*text != '\0') {
+ textbuffer_line2text(buffer, line, 0, str);
+
+ if (line_matched) {
+ line_matched = regexp ?
+ i_regex_match(preg, str->str, 0, NULL)
+ : match_func(str->str, text) != NULL;
+ }
+ }
+
+ if (line_matched) {
+ /* add the -before lines */
+ pre_line = line;
+ for (i = 0; i < before; i++) {
+ if (pre_line->prev == NULL ||
+ g_list_nth_data(matches, 0) == pre_line->prev ||
+ g_list_nth_data(matches, 1) == pre_line->prev)
+ break;
+ pre_line = pre_line->prev;
+ }
+
+ for (; pre_line != line; pre_line = pre_line->next)
+ matches = g_list_prepend(matches, pre_line);
+
+ match_after = after;
+ }
+
+ if (line_matched || match_after > 0) {
+ /* matched */
+ matches = g_list_prepend(matches, line);
+
+ if ((!line_matched && --match_after == 0) ||
+ (line_matched && match_after == 0 && before > 0))
+ matches = g_list_prepend(matches, NULL);
+ }
+ }
+
+ matches = g_list_reverse(matches);
+
+ if (preg != NULL)
+ i_regex_unref(preg);
+ g_string_free(str, TRUE);
+ return matches;
+}
+
+void textbuffer_init(void)
+{
+}
+
+void textbuffer_deinit(void)
+{
+}
diff --git a/src/fe-text/textbuffer.h b/src/fe-text/textbuffer.h
new file mode 100644
index 0000000..6c72a56
--- /dev/null
+++ b/src/fe-text/textbuffer.h
@@ -0,0 +1,106 @@
+#ifndef IRSSI_FE_TEXT_TEXTBUFFER_H
+#define IRSSI_FE_TEXT_TEXTBUFFER_H
+
+/* Make sure TEXT_CHUNK_REC is not slightly more than a page, as that
+ wastes a lot of memory. */
+#define LINE_TEXT_CHUNK_SIZE (16384 - 16)
+
+#define LINE_INFO_FORMAT_SET (void *) 0x1
+
+enum {
+ LINE_CMD_EOL=0x80, /* line ends here */
+};
+
+enum {
+ COLORING_STRIP = 0,
+ COLORING_EXPAND = 1,
+ COLORING_UNEXPAND = 2,
+ COLORING_RAW = 3,
+};
+
+struct _TEXT_BUFFER_FORMAT_REC;
+
+typedef struct {
+ int level;
+ time_t time;
+ char *text;
+ struct _LINE_INFO_META_REC *meta;
+ struct _TEXT_BUFFER_FORMAT_REC *format;
+} LINE_INFO_REC;
+
+typedef struct _LINE_REC {
+ struct _LINE_REC *prev, *next;
+ /* Text in the line. \0 means that the next char will be a
+ color or command.
+
+ If the 7th bit is set, the lowest 7 bits are the command
+ (see LINE_CMD_xxxx). Otherwise they specify a color change:
+
+ Bit:
+ 5 - Setting a background color
+ 4 - Use "default terminal color"
+ 0-3 - Color
+
+ DO NOT ADD BLACK WITH \0\0 - this will break things. Use
+ LINE_CMD_COLOR0 instead. */
+ LINE_INFO_REC info;
+} LINE_REC;
+
+typedef struct {
+ unsigned char buffer[LINE_TEXT_CHUNK_SIZE];
+ int pos;
+ int refcount;
+} TEXT_CHUNK_REC;
+
+typedef struct {
+ WINDOW_REC *window;
+
+ LINE_REC *first_line;
+ int lines_count;
+
+ LINE_REC *cur_line;
+ GString *cur_text;
+ GSList *cur_info;
+
+ int last_fg;
+ int last_bg;
+ int last_flags;
+ unsigned int last_eol:1;
+} TEXT_BUFFER_REC;
+
+/* Create new buffer */
+TEXT_BUFFER_REC *textbuffer_create(WINDOW_REC *window);
+/* Destroy the buffer */
+void textbuffer_destroy(TEXT_BUFFER_REC *buffer);
+
+LINE_REC *textbuffer_line_last(TEXT_BUFFER_REC *buffer);
+int textbuffer_line_exists_after(LINE_REC *line, LINE_REC *search);
+
+void textbuffer_line_add_colors(TEXT_BUFFER_REC *buffer, LINE_REC **line,
+ int fg, int bg, int flags);
+
+/* Append text to buffer. When \0<EOL> is found at the END OF DATA, a new
+ line is created. You must send the EOL command before you can do anything
+ else with the buffer. */
+LINE_REC *textbuffer_append(TEXT_BUFFER_REC *buffer,
+ const unsigned char *data, int len,
+ LINE_INFO_REC *info);
+LINE_REC *textbuffer_insert(TEXT_BUFFER_REC *buffer, LINE_REC *insert_after,
+ const unsigned char *data, int len,
+ LINE_INFO_REC *info);
+
+void textbuffer_remove(TEXT_BUFFER_REC *buffer, LINE_REC *line);
+/* Removes all lines from buffer, ignoring reference counters */
+void textbuffer_remove_all_lines(TEXT_BUFFER_REC *buffer);
+void textbuffer_line_info_free1(LINE_INFO_REC *info);
+
+void textbuffer_line2text(TEXT_BUFFER_REC *buffer, LINE_REC *line, int coloring, GString *str);
+GList *textbuffer_find_text(TEXT_BUFFER_REC *buffer, LINE_REC *startline,
+ int level, int nolevel, const char *text,
+ int before, int after,
+ int regexp, int fullword, int case_sensitive);
+
+void textbuffer_init(void);
+void textbuffer_deinit(void);
+
+#endif