diff options
Diffstat (limited to '')
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, ¶ms)) + 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 |